From 1cffb3b390e8da317d2b1b2ecdfa5b20200aaea6 Mon Sep 17 00:00:00 2001 From: Avryell Date: Tue, 14 Apr 2026 16:09:06 +0200 Subject: [PATCH 1/9] [ADDED] ServiceManger System - IPhysicsService Interface - ServiceManager - methods in VxPhysicsWorld --- .../core/physics/world/VxPhysicsWorld.java | 96 +++++- .../core/services/IPhysicsService.java | 71 +++++ .../core/services/ServiceManager.java | 292 ++++++++++++++++++ 3 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java create mode 100644 common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index beced416..e341fd17 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -13,6 +13,8 @@ import net.xmx.velthoric.core.constraint.manager.VxConstraintManager; import net.xmx.velthoric.core.physics.VxPhysicsBootstrap; import net.xmx.velthoric.core.ragdoll.VxRagdollManager; +import net.xmx.velthoric.core.services.IPhysicsService; +import net.xmx.velthoric.core.services.ServiceManager; import net.xmx.velthoric.core.terrain.VxTerrainSystem; import net.xmx.velthoric.init.VxMainClass; import net.xmx.velthoric.util.VxFrameTimer; @@ -24,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; +import java.util.function.Supplier; /** * Manages the entire physics simulation for a single Minecraft dimension. @@ -50,7 +53,7 @@ public final class VxPhysicsWorld implements Runnable, Executor { private static final float speculativeContactDistance = 0.02f; private static final float baumgarteFactor = 0.2f; private static final float penetrationSlop = 0.001f; - private static final float timeBeforeSleep = 1.0f; + private static final float timeBeforeSleep = 0.2f; private static final float pointVelocitySleepThreshold = 0.005f; private static final float gravityY = -9.81f; private static final int tempAllocatorSize = 64 * 1024 * 1024; // 64MB @@ -62,6 +65,8 @@ public final class VxPhysicsWorld implements Runnable, Executor { private final VxTerrainSystem terrainSystem; private final VxRagdollManager ragdollManager; + private final ServiceManager serviceManager; + private final VxFrameTimer physicsFrameTimer = new VxFrameTimer(); private PhysicsSystem physicsSystem; @@ -81,6 +86,8 @@ private VxPhysicsWorld(ServerLevel level) { this.constraintManager = new VxConstraintManager(this.bodyManager); this.terrainSystem = new VxTerrainSystem(this, this.level); this.ragdollManager = new VxRagdollManager(this); + + this.serviceManager = new ServiceManager(this,level); } public static VxPhysicsWorld getOrCreate(ServerLevel level) { @@ -117,6 +124,8 @@ private void initializeAndStart() { this.constraintManager.initialize(); this.terrainSystem.initialize(); + this.serviceManager.initialize(); + this.isRunning = true; String threadName = "Velthoric Physics Thread - " + dimensionKey.location().getPath().replace('/', '_'); this.physicsThreadExecutor = new Thread(this, threadName); @@ -199,15 +208,18 @@ private void updatePhysicsLoop(float deltaTime) { public void onPrePhysicsTick() { this.bodyManager.onPrePhysicsTick(this); + this.serviceManager.onPrePhysicsTick(this); } public void onPhysicsTick() { this.bodyManager.onPhysicsTick(this); + this.serviceManager.onPhysicsTick(this); } public void onGameTick(ServerLevel level) { this.bodyManager.onGameTick(level); + this.serviceManager.onGameTick(level); } private void processCommandQueue() { @@ -262,6 +274,9 @@ private void shutdownInternalSystems() { if (this.bodyManager != null) { this.bodyManager.shutdown(); } + if (this.serviceManager != null) { + this.serviceManager.shutdown(); + } } private void cleanupJolt() { @@ -356,6 +371,85 @@ public static VxRagdollManager getRagdollManager(ResourceKey dimensionKey return world != null ? world.getRagdollManager() : null; } + /** + * This method allow you to get a Service for later Use + * @param dimensionKey is for the dimension you are checking + * @param clazz the class of the service you are getting + * @return the service you are asking for + * @param any class that extends {@link IPhysicsService} + * + * @author LOLAtom + */ + @Nullable + public static T getService(ResourceKey dimensionKey,Class clazz) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getService(clazz) : null; + } + + /** + * This method allow you to register a Service to integrate your own systems to Velthorics PhysicsWorld + * @param dimensionKey is for the dimension you are checking + * @param service is for the service you are registering + * @return the service you registered + * @param any class that extends {@link IPhysicsService} + * + * @author LOLAtom + */ + @Nullable + public static T registerService(ResourceKey dimensionKey,T service) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.registerService(service) : null; + } + + @Nullable + public static T getServiceOrDefault(ResourceKey dimensionKey, Class clazz, T defaultValue) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getServiceOrDefault(clazz,defaultValue) : null; + } + + @Nullable + public static T getServiceOrCreate(ResourceKey dimensionKey,Class clazz, Supplier creator) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getServiceOrCreate(clazz,creator) : null; + } + + /** + * Check if level has a certain service + * @param dimensionKey is for the dimension you are checking + * @param clazz is the class of the service you are checking presence of + * @return if yes or not a service is currently present in asked level + * + * @author LOLAtom + */ + public static boolean hasService(ResourceKey dimensionKey,Class clazz) { + VxPhysicsWorld world = get(dimensionKey); + return world != null && world.hasService(clazz); + } + + public ServiceManager getServiceManager() { + return this.serviceManager; + } + + public T getService(Class clazz) { + return this.serviceManager.getService(clazz); + } + + public T registerService(T service) { + return this.serviceManager.registerService(service); + } + + public T getServiceOrDefault(Class clazz, T defaultValue) { + return this.serviceManager.getServiceOrDefault(clazz, defaultValue); + } + + public T getServiceOrCreate(Class clazz, Supplier creator) { + return this.serviceManager.getServiceOrCreate(clazz, creator); + } + + public boolean hasService(Class clazz) { + return this.serviceManager.hasService(clazz); + } + public static Collection getAll() { return Collections.unmodifiableCollection(worlds.values()); } diff --git a/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java new file mode 100644 index 00000000..31bbec47 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java @@ -0,0 +1,71 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.services; + +import net.minecraft.server.level.ServerLevel; +import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; + +/** + * Base interface for optional physics subsystem services. + *

+ * Services registered with {@link ServiceManager} must implement this interface. + * Core managers (bodies, constraints, etc.) should remain as direct fields + * in VxPhysicsWorld for maximum performance. + * + * @author LOLAtom + */ +public interface IPhysicsService { + + /** + * Returns a unique identifier for this service type. + * Used for debugging and logging. + * + * @return The service identification string. + */ + String getIdentification(); + + /** + * Called when the physics world is initializing. + * Use this to set up resources, register events, etc. + *

+ * Called from: Server thread, during {@code VxPhysicsWorld.initializeAndStart()} + */ + void initialize(); + + /** + * Called when the physics world is shutting down. + * Use this to clean up resources, unregister events, etc. + *

+ * Called from: Server thread, during {@code VxPhysicsWorld.shutdown()} + */ + void shutdown(); + + /** + * Optional: Called before each physics simulation step. + *

+ * Called from: Physics thread, at 60Hz + * + * @implSpec Default implementation does nothing. + */ + default void onPrePhysicsTick(VxPhysicsWorld world) {} + + /** + * Optional: Called after each physics simulation step. + *

+ * Called from: Physics thread, at 60Hz + * + * @implSpec Default implementation does nothing. + */ + default void onPhysicsTick(VxPhysicsWorld world) {} + + /** + * Optional: Called on game tick (server thread, not physics thread). + *

+ * Called from: Server thread, at 20Hz (Minecraft tick rate) + * + * @implSpec Default implementation does nothing. + */ + default void onGameTick(ServerLevel level) {} +} \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java new file mode 100644 index 00000000..22258d6a --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java @@ -0,0 +1,292 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.services; + + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.server.level.ServerLevel; +import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; +import net.xmx.velthoric.init.VxMainClass; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +/** + * High-performance service registry for optional physics subsystems. + * + * @author LOLAtom + */ +public class ServiceManager { + + private volatile Object2ObjectOpenHashMap, IPhysicsService> services; + + private final VxPhysicsWorld physicsWorld; + private final ServerLevel level; + + public ServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { + this.physicsWorld = physicsWorld; + this.level = level; + } + + /** + * Registers a service instance. + *

+ * Performance: O(n) where n = current service count (copy-on-write) + *
Thread-safe: yes (synchronized + volatile publish) + *
When to call: During server initialization, not in tick loops + * + * @param service The service to register. + * @param The service type. + * @return The same service instance (for chaining). + */ + public T registerService(T service) { + synchronized (this) { + Object2ObjectOpenHashMap, IPhysicsService> current = services; + Object2ObjectOpenHashMap, IPhysicsService> newMap; + + if (current == null) { + newMap = new Object2ObjectOpenHashMap<>(8, 0.75f); + } else { + newMap = new Object2ObjectOpenHashMap<>(current); + } + + @SuppressWarnings("unchecked") + Class serviceClass = (Class) service.getClass(); + IPhysicsService existing = newMap.put(serviceClass, service); + + if (existing != null) { + VxMainClass.LOGGER.warn("Service {} was already registered, replacing", service.getIdentification()); + } + + this.services = newMap; + + VxMainClass.LOGGER.debug("Registered service: {} (total: {})", + service.getIdentification(), newMap.size()); + return service; + } + } + + /** + * Gets a service by its class type. + *

+ * Performance: ~3-5ns average (volatile read + FastUtil get) + *
Thread-safe: yes (lock-free volatile read) + *
Type-safe: yes (generics + cast) + * + * @param clazz The service class to look up. + * @param The service type. + * @return The service instance, or null if not registered. + */ + @Nullable + @SuppressWarnings("unchecked") + public T getService(Class clazz) { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + + if (map == null) { + return null; + } + IPhysicsService service = map.get(clazz); + + if (service == null) { + return null; + } + return (T) service; + } + + /** + * Gets a service with a default fallback. + *

+ * Avoids null checks at call sites. Useful for optional features. + * + * @param clazz The service class to look up. + * @param defaultValue Value to return if service not found. + * @param The service type. + * @return The service instance, or defaultValue if not registered. + */ + public T getServiceOrDefault(Class clazz, T defaultValue) { + T service = getService(clazz); + return service != null ? service : defaultValue; + } + + /** + * Gets a service, creating it lazily if not present. + *

+ * Useful for services that are expensive to create and may not be needed. + * Note: The supplier is called with the write lock held. + * + * @param clazz The service class to look up. + * @param creator Supplier to create the service if not found. + * @param The service type. + * @return The existing or newly created service instance. + */ + public T getServiceOrCreate(Class clazz, Supplier creator) { + T service = getService(clazz); + if (service != null) { + return service; + } + + // Create and register in one atomic operation + synchronized (this) { + service = getService(clazz); + if (service != null) { + return service; + } + + service = creator.get(); + return registerService(service); + } + } + + /** + * Checks if a service is registered. + *

+ * Performance: ~3ns (volatile read + containsKey) + * + * @param clazz The service class to check. + * @return true if the service is registered. + */ + public boolean hasService(Class clazz) { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + return map != null && map.containsKey(clazz); + } + + /** + * Initializes all registered services. + *

+ * Called during {@code VxPhysicsWorld.initializeAndStart()}. + * Services are initialized after core managers (bodies, constraints, etc.) + * so they can safely depend on core functionality. + */ + public void initialize() { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + VxMainClass.LOGGER.debug("Initializing {} registered services", map.size()); + + for (IPhysicsService service : map.values()) { + try { + service.initialize(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to initialize service: {}", + service.getIdentification(), e); + } + } + } + + /** + * Shuts down all registered services. + *

+ * Called during {@code VxPhysicsWorld.shutdown()}. + * Services are shut down BEFORE core managers so they can clean up + * while their dependencies are still available. + */ + public void shutdown() { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + VxMainClass.LOGGER.debug("Shutting down {} registered services", map.size()); + + // Shutdown in reverse registration order (LIFO) for dependency safety + // Services registered later may depend on services registered earlier + IPhysicsService[] servicesArray = map.values().toArray(new IPhysicsService[0]); + for (int i = servicesArray.length - 1; i >= 0; i--) { + try { + servicesArray[i].shutdown(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to shutdown service: {}", + servicesArray[i].getIdentification(), e); + } + } + + // Clear the map reference (volatile write) + synchronized (this) { + this.services = null; + } + } + + /** + * Calls {@link IPhysicsService#onPrePhysicsTick(VxPhysicsWorld)} on all services. + *

+ * Must be called from: Physics thread + *
Frequency: 60Hz (fixed timestep) + *
Performance: O(n) where n = service count (typically < 20) + */ + public void onPrePhysicsTick(VxPhysicsWorld world) { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IPhysicsService service : map.values()) { + try { + service.onPrePhysicsTick(world); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onPrePhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * Calls {@link IPhysicsService#onPhysicsTick(VxPhysicsWorld)} on all services. + *

+ * Must be called from: Physics thread + *
Frequency: 60Hz (fixed timestep) + */ + public void onPhysicsTick(VxPhysicsWorld world) { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IPhysicsService service : map.values()) { + try { + service.onPhysicsTick(world); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onPhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * Calls {@link IPhysicsService#onGameTick(ServerLevel)} on all services. + *

+ * Called from: Server thread (not physics thread) + *
Frequency: 20Hz (Minecraft tick rate) + */ + public void onGameTick(ServerLevel level) { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IPhysicsService service : map.values()) { + try { + service.onGameTick(level); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onGameTick(): {}", + service.getIdentification(), e); + } + } + } + + public VxPhysicsWorld getPhysicsWorld() { + return physicsWorld; + } + + public ServerLevel getLevel() { + return level; + } + + public int getServiceCount() { + Object2ObjectOpenHashMap, IPhysicsService> map = services; + return map != null ? map.size() : 0; + } +} \ No newline at end of file From d89786944a1558e906a7d6eb107ea5dd65d6b753 Mon Sep 17 00:00:00 2001 From: Avryell Date: Tue, 14 Apr 2026 16:27:38 +0200 Subject: [PATCH 2/9] [ADDED] ServiceManger System - IPhysicsService Interface - ServiceManager - methods in VxPhysicsWorld --- .../net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index e341fd17..00054ad3 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -53,7 +53,7 @@ public final class VxPhysicsWorld implements Runnable, Executor { private static final float speculativeContactDistance = 0.02f; private static final float baumgarteFactor = 0.2f; private static final float penetrationSlop = 0.001f; - private static final float timeBeforeSleep = 0.2f; + private static final float timeBeforeSleep = 1f; private static final float pointVelocitySleepThreshold = 0.005f; private static final float gravityY = -9.81f; private static final int tempAllocatorSize = 64 * 1024 * 1024; // 64MB From 33cdd40059ef659d1ff03e14e692ecedfe9099d4 Mon Sep 17 00:00:00 2001 From: Avryell Date: Tue, 14 Apr 2026 16:09:06 +0200 Subject: [PATCH 3/9] feat: Implement ServiceManager system for optional physics subsystems --- .../core/physics/world/VxPhysicsWorld.java | 89 ++++++ .../core/service/IVxPhysicsService.java | 74 +++++ .../core/service/VxServiceManager.java | 291 ++++++++++++++++++ 3 files changed, 454 insertions(+) create mode 100644 common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java create mode 100644 common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index beced416..dd121fe1 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -13,6 +13,8 @@ import net.xmx.velthoric.core.constraint.manager.VxConstraintManager; import net.xmx.velthoric.core.physics.VxPhysicsBootstrap; import net.xmx.velthoric.core.ragdoll.VxRagdollManager; +import net.xmx.velthoric.core.service.IVxPhysicsService; +import net.xmx.velthoric.core.service.VxServiceManager; import net.xmx.velthoric.core.terrain.VxTerrainSystem; import net.xmx.velthoric.init.VxMainClass; import net.xmx.velthoric.util.VxFrameTimer; @@ -24,6 +26,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; +import java.util.function.Supplier; /** * Manages the entire physics simulation for a single Minecraft dimension. @@ -33,6 +36,7 @@ * provides a thread-safe command queue for interacting with the simulation. * * @author xI-Mx-Ix + * @author LOLAtom */ public final class VxPhysicsWorld implements Runnable, Executor { private static final int SIMULATION_HZ = 60; @@ -62,6 +66,8 @@ public final class VxPhysicsWorld implements Runnable, Executor { private final VxTerrainSystem terrainSystem; private final VxRagdollManager ragdollManager; + private final VxServiceManager serviceManager; + private final VxFrameTimer physicsFrameTimer = new VxFrameTimer(); private PhysicsSystem physicsSystem; @@ -81,6 +87,8 @@ private VxPhysicsWorld(ServerLevel level) { this.constraintManager = new VxConstraintManager(this.bodyManager); this.terrainSystem = new VxTerrainSystem(this, this.level); this.ragdollManager = new VxRagdollManager(this); + + this.serviceManager = new VxServiceManager(this,level); } public static VxPhysicsWorld getOrCreate(ServerLevel level) { @@ -117,6 +125,8 @@ private void initializeAndStart() { this.constraintManager.initialize(); this.terrainSystem.initialize(); + this.serviceManager.initialize(); + this.isRunning = true; String threadName = "Velthoric Physics Thread - " + dimensionKey.location().getPath().replace('/', '_'); this.physicsThreadExecutor = new Thread(this, threadName); @@ -199,15 +209,18 @@ private void updatePhysicsLoop(float deltaTime) { public void onPrePhysicsTick() { this.bodyManager.onPrePhysicsTick(this); + this.serviceManager.onPrePhysicsTick(this); } public void onPhysicsTick() { this.bodyManager.onPhysicsTick(this); + this.serviceManager.onPhysicsTick(this); } public void onGameTick(ServerLevel level) { this.bodyManager.onGameTick(level); + this.serviceManager.onGameTick(level); } private void processCommandQueue() { @@ -262,6 +275,9 @@ private void shutdownInternalSystems() { if (this.bodyManager != null) { this.bodyManager.shutdown(); } + if (this.serviceManager != null) { + this.serviceManager.shutdown(); + } } private void cleanupJolt() { @@ -356,6 +372,79 @@ public static VxRagdollManager getRagdollManager(ResourceKey dimensionKey return world != null ? world.getRagdollManager() : null; } + /** + * This method allow you to get a Service for later Use + * @param dimensionKey is for the dimension you are checking + * @param clazz the class of the service you are getting + * @return the service you are asking for + * @param any class that extends {@link IVxPhysicsService} + */ + @Nullable + public static T getService(ResourceKey dimensionKey, Class clazz) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getService(clazz) : null; + } + + /** + * This method allow you to register a Service to integrate your own systems to Velthorics PhysicsWorld + * @param dimensionKey is for the dimension you are checking + * @param service is for the service you are registering + * @return the service you registered + * @param any class that extends {@link IVxPhysicsService} + */ + @Nullable + public static T registerService(ResourceKey dimensionKey, T service) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.registerService(service) : null; + } + + @Nullable + public static T getServiceOrDefault(ResourceKey dimensionKey, Class clazz, T defaultValue) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getServiceOrDefault(clazz,defaultValue) : null; + } + + @Nullable + public static T getServiceOrCreate(ResourceKey dimensionKey, Class clazz, Supplier creator) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getServiceOrCreate(clazz,creator) : null; + } + + /** + * Check if level has a certain service + * @param dimensionKey is for the dimension you are checking + * @param clazz is the class of the service you are checking presence of + * @return if yes or not a service is currently present in asked level + */ + public static boolean hasService(ResourceKey dimensionKey,Class clazz) { + VxPhysicsWorld world = get(dimensionKey); + return world != null && world.hasService(clazz); + } + + public VxServiceManager getServiceManager() { + return this.serviceManager; + } + + public T getService(Class clazz) { + return this.serviceManager.getService(clazz); + } + + public T registerService(T service) { + return this.serviceManager.registerService(service); + } + + public T getServiceOrDefault(Class clazz, T defaultValue) { + return this.serviceManager.getServiceOrDefault(clazz, defaultValue); + } + + public T getServiceOrCreate(Class clazz, Supplier creator) { + return this.serviceManager.getServiceOrCreate(clazz, creator); + } + + public boolean hasService(Class clazz) { + return this.serviceManager.hasService(clazz); + } + public static Collection getAll() { return Collections.unmodifiableCollection(worlds.values()); } diff --git a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java new file mode 100644 index 00000000..f8b7a591 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -0,0 +1,74 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.service; + +import net.minecraft.server.level.ServerLevel; +import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; + +/** + * Base interface for optional physics subsystem services. + *

+ * Services registered with {@link VxServiceManager} must implement this interface. + * Core managers (bodies, constraints, etc.) should remain as direct fields + * in VxPhysicsWorld for maximum performance. + * + * @author LOLAtom + */ +public interface IVxPhysicsService { + + /** + * Returns a unique identifier for this service type. + * Used for debugging and logging. + * + * @return The service identification string. + */ + String getIdentification(); + + /** + * Called when the physics world is initializing. + * Use this to set up resources, register events, etc. + *

+ * Called from: Server thread, during {@code VxPhysicsWorld.initializeAndStart()} + */ + void initialize(); + + /** + * Called when the physics world is shutting down. + * Use this to clean up resources, unregister events, etc. + *

+ * Called from: Server thread, during {@code VxPhysicsWorld.shutdown()} + */ + void shutdown(); + + /** + * Optional: Called before each physics simulation step. + *

+ * Called from: Physics thread, at 60Hz + * + * @implSpec Default implementation does nothing. + */ + default void onPrePhysicsTick(VxPhysicsWorld world) { + } + + /** + * Optional: Called after each physics simulation step. + *

+ * Called from: Physics thread, at 60Hz + * + * @implSpec Default implementation does nothing. + */ + default void onPhysicsTick(VxPhysicsWorld world) { + } + + /** + * Optional: Called on game tick (server thread, not physics thread). + *

+ * Called from: Server thread, at 20Hz (Minecraft tick rate) + * + * @implSpec Default implementation does nothing. + */ + default void onGameTick(ServerLevel level) { + } +} \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java new file mode 100644 index 00000000..dc92c3e2 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -0,0 +1,291 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.service; + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.server.level.ServerLevel; +import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; +import net.xmx.velthoric.init.VxMainClass; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Supplier; + +/** + * High-performance service registry for optional physics subsystems. + * + * @author LOLAtom + */ +public class VxServiceManager { + + private volatile Object2ObjectOpenHashMap, IVxPhysicsService> services; + + private final VxPhysicsWorld physicsWorld; + private final ServerLevel level; + + public VxServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { + this.physicsWorld = physicsWorld; + this.level = level; + } + + /** + * Registers a service instance. + *

+ * Performance: O(n) where n = current service count (copy-on-write) + *
Thread-safe: yes (synchronized + volatile publish) + *
When to call: During server initialization, not in tick loops + * + * @param service The service to register. + * @param The service type. + * @return The same service instance (for chaining). + */ + public T registerService(T service) { + synchronized (this) { + Object2ObjectOpenHashMap, IVxPhysicsService> current = services; + Object2ObjectOpenHashMap, IVxPhysicsService> newMap; + + if (current == null) { + newMap = new Object2ObjectOpenHashMap<>(8, 0.75f); + } else { + newMap = new Object2ObjectOpenHashMap<>(current); + } + + @SuppressWarnings("unchecked") + Class serviceClass = (Class) service.getClass(); + IVxPhysicsService existing = newMap.put(serviceClass, service); + + if (existing != null) { + VxMainClass.LOGGER.warn("Service {} was already registered, replacing", service.getIdentification()); + } + + this.services = newMap; + + VxMainClass.LOGGER.debug("Registered service: {} (total: {})", + service.getIdentification(), newMap.size()); + return service; + } + } + + /** + * Gets a service by its class type. + *

+ * Performance: ~3-5ns average (volatile read + FastUtil get) + *
Thread-safe: yes (lock-free volatile read) + *
Type-safe: yes (generics + cast) + * + * @param clazz The service class to look up. + * @param The service type. + * @return The service instance, or null if not registered. + */ + @Nullable + @SuppressWarnings("unchecked") + public T getService(Class clazz) { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + + if (map == null) { + return null; + } + IVxPhysicsService service = map.get(clazz); + + if (service == null) { + return null; + } + return (T) service; + } + + /** + * Gets a service with a default fallback. + *

+ * Avoids null checks at call sites. Useful for optional features. + * + * @param clazz The service class to look up. + * @param defaultValue Value to return if service not found. + * @param The service type. + * @return The service instance, or defaultValue if not registered. + */ + public T getServiceOrDefault(Class clazz, T defaultValue) { + T service = getService(clazz); + return service != null ? service : defaultValue; + } + + /** + * Gets a service, creating it lazily if not present. + *

+ * Useful for services that are expensive to create and may not be needed. + * Note: The supplier is called with the write lock held. + * + * @param clazz The service class to look up. + * @param creator Supplier to create the service if not found. + * @param The service type. + * @return The existing or newly created service instance. + */ + public T getServiceOrCreate(Class clazz, Supplier creator) { + T service = getService(clazz); + if (service != null) { + return service; + } + + // Create and register in one atomic operation + synchronized (this) { + service = getService(clazz); + if (service != null) { + return service; + } + + service = creator.get(); + return registerService(service); + } + } + + /** + * Checks if a service is registered. + *

+ * Performance: ~3ns (volatile read + containsKey) + * + * @param clazz The service class to check. + * @return true if the service is registered. + */ + public boolean hasService(Class clazz) { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + return map != null && map.containsKey(clazz); + } + + /** + * Initializes all registered services. + *

+ * Called during {@code VxPhysicsWorld.initializeAndStart()}. + * Services are initialized after core managers (bodies, constraints, etc.) + * so they can safely depend on core functionality. + */ + public void initialize() { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + VxMainClass.LOGGER.debug("Initializing {} registered services", map.size()); + + for (IVxPhysicsService service : map.values()) { + try { + service.initialize(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to initialize service: {}", + service.getIdentification(), e); + } + } + } + + /** + * Shuts down all registered services. + *

+ * Called during {@code VxPhysicsWorld.shutdown()}. + * Services are shut down BEFORE core managers so they can clean up + * while their dependencies are still available. + */ + public void shutdown() { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + VxMainClass.LOGGER.debug("Shutting down {} registered services", map.size()); + + // Shutdown in reverse registration order (LIFO) for dependency safety + // Services registered later may depend on services registered earlier + IVxPhysicsService[] servicesArray = map.values().toArray(new IVxPhysicsService[0]); + for (int i = servicesArray.length - 1; i >= 0; i--) { + try { + servicesArray[i].shutdown(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to shutdown service: {}", + servicesArray[i].getIdentification(), e); + } + } + + // Clear the map reference (volatile write) + synchronized (this) { + this.services = null; + } + } + + /** + * Calls {@link IVxPhysicsService#onPrePhysicsTick(VxPhysicsWorld)} on all services. + *

+ * Must be called from: Physics thread + *
Frequency: 60Hz (fixed timestep) + *
Performance: O(n) where n = service count (typically < 20) + */ + public void onPrePhysicsTick(VxPhysicsWorld world) { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IVxPhysicsService service : map.values()) { + try { + service.onPrePhysicsTick(world); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onPrePhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * Calls {@link IVxPhysicsService#onPhysicsTick(VxPhysicsWorld)} on all services. + *

+ * Must be called from: Physics thread + *
Frequency: 60Hz (fixed timestep) + */ + public void onPhysicsTick(VxPhysicsWorld world) { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IVxPhysicsService service : map.values()) { + try { + service.onPhysicsTick(world); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onPhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * Calls {@link IVxPhysicsService#onGameTick(ServerLevel)} on all services. + *

+ * Called from: Server thread (not physics thread) + *
Frequency: 20Hz (Minecraft tick rate) + */ + public void onGameTick(ServerLevel level) { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + if (map == null || map.isEmpty()) { + return; + } + + for (IVxPhysicsService service : map.values()) { + try { + service.onGameTick(level); + } catch (Exception e) { + VxMainClass.LOGGER.error("Error in service.onGameTick(): {}", + service.getIdentification(), e); + } + } + } + + public VxPhysicsWorld getPhysicsWorld() { + return physicsWorld; + } + + public ServerLevel getLevel() { + return level; + } + + public int getServiceCount() { + Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + return map != null ? map.size() : 0; + } +} \ No newline at end of file From 26feb95c21640bb7016f681f6a7a8d3ebe0ef617 Mon Sep 17 00:00:00 2001 From: xI-Mx-Ix Date: Tue, 14 Apr 2026 17:27:18 +0200 Subject: [PATCH 4/9] docs: Standardized and expanded Javadoc --- .../core/physics/world/VxPhysicsWorld.java | 39 ++++++++------ .../core/service/IVxPhysicsService.java | 54 ++++++++++++------- .../core/service/VxServiceManager.java | 38 ++++++++++++- 3 files changed, 93 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index dd121fe1..3dc463a0 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -373,11 +373,12 @@ public static VxRagdollManager getRagdollManager(ResourceKey dimensionKey } /** - * This method allow you to get a Service for later Use - * @param dimensionKey is for the dimension you are checking - * @param clazz the class of the service you are getting - * @return the service you are asking for - * @param any class that extends {@link IVxPhysicsService} + * Retrieves a specific physics service for the given dimension. + * + * @param dimensionKey The registry key of the dimension. + * @param clazz The class type of the service to retrieve. + * @param The service type. + * @return The service instance, or null if not registered or if the world is missing. */ @Nullable public static T getService(ResourceKey dimensionKey, Class clazz) { @@ -386,11 +387,13 @@ public static T getService(ResourceKey dime } /** - * This method allow you to register a Service to integrate your own systems to Velthorics PhysicsWorld - * @param dimensionKey is for the dimension you are checking - * @param service is for the service you are registering - * @return the service you registered - * @param any class that extends {@link IVxPhysicsService} + * Registers a physics service for a specific dimension. + * This allows integrating custom subsystems into the physics world lifecycle. + * + * @param dimensionKey The registry key of the dimension. + * @param service The service instance to register. + * @param The service type. + * @return The registered service instance, or null if the world is missing. */ @Nullable public static T registerService(ResourceKey dimensionKey, T service) { @@ -401,22 +404,23 @@ public static T registerService(ResourceKey @Nullable public static T getServiceOrDefault(ResourceKey dimensionKey, Class clazz, T defaultValue) { VxPhysicsWorld world = get(dimensionKey); - return world != null ? world.getServiceOrDefault(clazz,defaultValue) : null; + return world != null ? world.getServiceOrDefault(clazz, defaultValue) : null; } @Nullable public static T getServiceOrCreate(ResourceKey dimensionKey, Class clazz, Supplier creator) { VxPhysicsWorld world = get(dimensionKey); - return world != null ? world.getServiceOrCreate(clazz,creator) : null; + return world != null ? world.getServiceOrCreate(clazz, creator) : null; } /** - * Check if level has a certain service - * @param dimensionKey is for the dimension you are checking - * @param clazz is the class of the service you are checking presence of - * @return if yes or not a service is currently present in asked level + * Checks if a specific dimension has a registered physics service. + * + * @param dimensionKey The registry key of the dimension. + * @param clazz The service class to check. + * @return True if the service is present, false otherwise. */ - public static boolean hasService(ResourceKey dimensionKey,Class clazz) { + public static boolean hasService(ResourceKey dimensionKey, Class clazz) { VxPhysicsWorld world = get(dimensionKey); return world != null && world.hasService(clazz); } @@ -425,6 +429,7 @@ public VxServiceManager getServiceManager() { return this.serviceManager; } + @Nullable public T getService(Class clazz) { return this.serviceManager.getService(clazz); } diff --git a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java index f8b7a591..c98187f0 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -11,63 +11,77 @@ * Base interface for optional physics subsystem services. *

* Services registered with {@link VxServiceManager} must implement this interface. - * Core managers (bodies, constraints, etc.) should remain as direct fields - * in VxPhysicsWorld for maximum performance. + * These subsystems allow for extending the physics engine's functionality with global systems + * (e.g., custom spatial querying, specialized debugging subsystems, or third-party integrations) + * in a modular fashion. + *

+ * Note: For logic that applies to specific physics bodies (like buoyancy, aerodynamics, or engines), + * use the {@code VxBehavior} system instead. + *

+ * Performance Note: Core managers (bodies, constraints, etc.) remain as direct fields in + * {@link VxPhysicsWorld} for maximum cache efficiency and reduced pointer indirection. High-frequency + * logic within these services should prioritize SoA data access where possible. * - * @author LOLAtom + * @author xI-Mx-Ix */ public interface IVxPhysicsService { /** * Returns a unique identifier for this service type. - * Used for debugging and logging. + * This identifier is used for debugging, logging, and potentially networking. * - * @return The service identification string. + * @return The unique service identification string. */ String getIdentification(); /** - * Called when the physics world is initializing. - * Use this to set up resources, register events, etc. + * Called during the initialization phase of the physics world. + * Use this to allocate resources, register listeners, or perform initial state setup. *

- * Called from: Server thread, during {@code VxPhysicsWorld.initializeAndStart()} + * Threading: Called from the Main Server Thread. */ void initialize(); /** - * Called when the physics world is shutting down. - * Use this to clean up resources, unregister events, etc. + * Called during the shutdown sequence of the physics world. + * Implementations must release all resources and unregister any hooks to prevent memory leaks. *

- * Called from: Server thread, during {@code VxPhysicsWorld.shutdown()} + * Threading: Called from the Main Server Thread. */ void shutdown(); /** - * Optional: Called before each physics simulation step. + * Optional: Invoked at the start of each physics simulation frame. + * Useful for applying forces or modifying body states before Jolt performs the integration step. *

- * Called from: Physics thread, at 60Hz + * Threading: Called from the Physics Thread (High Frequency: 60Hz default). * - * @implSpec Default implementation does nothing. + * @param world The physics world instance being simulated. + * @implSpec The default implementation is a no-op. */ default void onPrePhysicsTick(VxPhysicsWorld world) { } /** - * Optional: Called after each physics simulation step. + * Optional: Invoked after each physics simulation frame has completed. + * Useful for post-processing results, updating custom spatial indexes, or triggering collision callbacks. *

- * Called from: Physics thread, at 60Hz + * Threading: Called from the Physics Thread (High Frequency: 60Hz default). * - * @implSpec Default implementation does nothing. + * @param world The physics world instance that was just simulated. + * @implSpec The default implementation is a no-op. */ default void onPhysicsTick(VxPhysicsWorld world) { } /** - * Optional: Called on game tick (server thread, not physics thread). + * Optional: Invoked once per Minecraft game tick. + * Useful for synchronization between the physics world and the Minecraft level (e.g., entity spawning, block updates). *

- * Called from: Server thread, at 20Hz (Minecraft tick rate) + * Threading: Called from the Main Server Thread (Frequency: 20Hz). * - * @implSpec Default implementation does nothing. + * @param level The Minecraft server level associated with the physics world. + * @implSpec The default implementation is a no-op. */ default void onGameTick(ServerLevel level) { } diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java index dc92c3e2..06e1237c 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -14,16 +14,43 @@ /** * High-performance service registry for optional physics subsystems. + *

+ * This manager provides a thread-safe, lock-free (for reads) registry for {@link IVxPhysicsService} implementations. + * It allows decoupled systems to interact with the physics world without being hard-coded into the core managers. + * Responsibilities include: + *

    + *
  • Service Discovery: Efficient lookups for registered physics services via class-based keys.
  • + *
  • Lifecycle Management: Coordinating initialization and shutdown sequences for all registered subsystems.
  • + *
  • Event Dispatching: Routing physics and game thread ticks to active services.
  • + *
  • Concurrency: Ensuring safe access to services across multiple threads using a copy-on-write registry.
  • + *
* - * @author LOLAtom + * @author xI-Mx-Ix */ public class VxServiceManager { + /** + * Map of registered service classes to their singleton instances. + * Uses a copy-on-write strategy with a volatile reference for O(1) lock-free reads. + */ private volatile Object2ObjectOpenHashMap, IVxPhysicsService> services; + /** + * The physics world instance these services are associated with. + */ private final VxPhysicsWorld physicsWorld; + + /** + * The Minecraft server level this manager operates within. + */ private final ServerLevel level; + /** + * Constructs a new service manager for the specified world and level. + * + * @param physicsWorld The physics world instance this manager belongs to. + * @param level The Minecraft server level. + */ public VxServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { this.physicsWorld = physicsWorld; this.level = level; @@ -276,14 +303,23 @@ public void onGameTick(ServerLevel level) { } } + /** + * @return The physics world instance managed by this manager. + */ public VxPhysicsWorld getPhysicsWorld() { return physicsWorld; } + /** + * @return The Minecraft server level this manager is associated with. + */ public ServerLevel getLevel() { return level; } + /** + * @return The total number of currently registered services. + */ public int getServiceCount() { Object2ObjectOpenHashMap, IVxPhysicsService> map = services; return map != null ? map.size() : 0; From 3e9b1b84227d672af138aebad5566842a58a4218 Mon Sep 17 00:00:00 2001 From: xI-Mx-Ix Date: Tue, 14 Apr 2026 17:43:05 +0200 Subject: [PATCH 5/9] refactor: Make all methods in IVxPhysicsService default --- .../xmx/velthoric/core/service/IVxPhysicsService.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java index c98187f0..a4ed7c2a 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -32,7 +32,9 @@ public interface IVxPhysicsService { * * @return The unique service identification string. */ - String getIdentification(); + default String getIdentification() { + return ""; + } /** * Called during the initialization phase of the physics world. @@ -40,7 +42,8 @@ public interface IVxPhysicsService { *

* Threading: Called from the Main Server Thread. */ - void initialize(); + default void initialize() { + } /** * Called during the shutdown sequence of the physics world. @@ -48,7 +51,8 @@ public interface IVxPhysicsService { *

* Threading: Called from the Main Server Thread. */ - void shutdown(); + default void shutdown() { + } /** * Optional: Invoked at the start of each physics simulation frame. From 92ad4c5faccfeb4db578ac985bc7b0f525f25488 Mon Sep 17 00:00:00 2001 From: xI-Mx-Ix Date: Tue, 14 Apr 2026 17:46:31 +0200 Subject: [PATCH 6/9] refactor: Convert ragdoll system to IVxPhysicsService and remove legacy accessors --- .../velthoric/command/test/SpawnRagdollTest.java | 2 +- .../core/physics/world/VxPhysicsWorld.java | 14 ++------------ .../velthoric/core/ragdoll/VxRagdollManager.java | 10 +++++++++- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/net/xmx/velthoric/command/test/SpawnRagdollTest.java b/common/src/main/java/net/xmx/velthoric/command/test/SpawnRagdollTest.java index 1f52bef8..9ae5f421 100644 --- a/common/src/main/java/net/xmx/velthoric/command/test/SpawnRagdollTest.java +++ b/common/src/main/java/net/xmx/velthoric/command/test/SpawnRagdollTest.java @@ -64,7 +64,7 @@ private int execute(CommandContext context) throws CommandSy } // Access the ragdoll manager and request the creation of a humanoid ragdoll. - VxRagdollManager ragdollManager = world.getRagdollManager(); + VxRagdollManager ragdollManager = world.getService(VxRagdollManager.class); if (ragdollManager != null) { // Convert Minecraft's Vec3 to Jolt's RVec3 for the physics engine. RVec3 joltSpawnPos = new RVec3((float) spawnPos.x, (float) spawnPos.y, (float) spawnPos.z); diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index 3dc463a0..0a83e4b2 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -64,7 +64,6 @@ public final class VxPhysicsWorld implements Runnable, Executor { private final VxServerBodyManager bodyManager; private final VxConstraintManager constraintManager; private final VxTerrainSystem terrainSystem; - private final VxRagdollManager ragdollManager; private final VxServiceManager serviceManager; @@ -86,9 +85,9 @@ private VxPhysicsWorld(ServerLevel level) { this.bodyManager = new VxServerBodyManager(this); this.constraintManager = new VxConstraintManager(this.bodyManager); this.terrainSystem = new VxTerrainSystem(this, this.level); - this.ragdollManager = new VxRagdollManager(this); - this.serviceManager = new VxServiceManager(this,level); + this.serviceManager = new VxServiceManager(this, level); + this.serviceManager.registerService(new VxRagdollManager(this)); } public static VxPhysicsWorld getOrCreate(ServerLevel level) { @@ -319,10 +318,6 @@ public VxTerrainSystem getTerrainSystem() { return this.terrainSystem; } - public VxRagdollManager getRagdollManager() { - return this.ragdollManager; - } - public ServerLevel getLevel() { return this.level; } @@ -366,11 +361,6 @@ public static VxTerrainSystem getTerrainSystem(ResourceKey dimensionKey) return world != null ? world.getTerrainSystem() : null; } - @Nullable - public static VxRagdollManager getRagdollManager(ResourceKey dimensionKey) { - VxPhysicsWorld world = get(dimensionKey); - return world != null ? world.getRagdollManager() : null; - } /** * Retrieves a specific physics service for the given dimension. diff --git a/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java b/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java index f8a90b78..8e3c5fde 100644 --- a/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java @@ -17,6 +17,7 @@ import net.xmx.velthoric.core.constraint.manager.VxConstraintManager; import net.xmx.velthoric.core.ragdoll.body.VxBodyPartRigidBody; import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; +import net.xmx.velthoric.core.service.IVxPhysicsService; import org.jetbrains.annotations.Nullable; import java.util.EnumMap; @@ -26,10 +27,12 @@ * Manages the creation and lifecycle of ragdolls within a physics world. * This class handles spawning the individual rigid bodies for each body part * and connecting them with appropriate constraints to simulate ragdoll physics. + *

+ * This system is registered as an {@link IVxPhysicsService} within the physics world. * * @author xI-Mx-Ix */ -public class VxRagdollManager { +public class VxRagdollManager implements IVxPhysicsService { private final VxPhysicsWorld world; private final VxServerBodyManager bodyManager; @@ -41,6 +44,11 @@ public VxRagdollManager(VxPhysicsWorld world) { this.constraintManager = world.getConstraintManager(); } + @Override + public String getIdentification() { + return "RagdollManager"; + } + /** * Creates a humanoid ragdoll based on the state of a living entity at a specific world position. * The ragdoll is spawned with the entity's Yaw orientation but remains upright (no Pitch), From c3006e6d41a8fbe2d08540af342bdaf4b167c64c Mon Sep 17 00:00:00 2001 From: xI-Mx-Ix Date: Tue, 14 Apr 2026 17:50:16 +0200 Subject: [PATCH 7/9] docs: Restore author tags --- .../java/net/xmx/velthoric/core/service/IVxPhysicsService.java | 1 + .../java/net/xmx/velthoric/core/service/VxServiceManager.java | 1 + 2 files changed, 2 insertions(+) diff --git a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java index a4ed7c2a..84ae124e 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -22,6 +22,7 @@ * {@link VxPhysicsWorld} for maximum cache efficiency and reduced pointer indirection. High-frequency * logic within these services should prioritize SoA data access where possible. * + * @author LOLAtom * @author xI-Mx-Ix */ public interface IVxPhysicsService { diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java index 06e1237c..87f60452 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -25,6 +25,7 @@ *

  • Concurrency: Ensuring safe access to services across multiple threads using a copy-on-write registry.
  • * * + * @author LOLAtom * @author xI-Mx-Ix */ public class VxServiceManager { From 94b6a1fbd013e0da304e00e8e5c87bac08d124be Mon Sep 17 00:00:00 2001 From: xI-Mx-Ix Date: Tue, 14 Apr 2026 20:25:54 +0200 Subject: [PATCH 8/9] feat: Implement high-performance Global Slot service architecture and modularize ragdoll system --- .../core/physics/world/VxPhysicsWorld.java | 6 +- .../core/service/VxServiceManager.java | 239 ++++++++++-------- .../core/service/VxServiceSlots.java | 55 ++++ 3 files changed, 190 insertions(+), 110 deletions(-) create mode 100644 common/src/main/java/net/xmx/velthoric/core/service/VxServiceSlots.java diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index 0a83e4b2..e9b28b5f 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -79,6 +79,11 @@ public final class VxPhysicsWorld implements Runnable, Executor { private float timeAccumulator = 0.0f; private long lastTimeNanos = 0L; + static { + // Register built-in Velthoric services + VxServiceManager.registerFactory(VxRagdollManager::new); + } + private VxPhysicsWorld(ServerLevel level) { this.level = level; this.dimensionKey = level.dimension(); @@ -87,7 +92,6 @@ private VxPhysicsWorld(ServerLevel level) { this.terrainSystem = new VxTerrainSystem(this, this.level); this.serviceManager = new VxServiceManager(this, level); - this.serviceManager.registerService(new VxRagdollManager(this)); } public static VxPhysicsWorld getOrCreate(ServerLevel level) { diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java index 87f60452..a3e4d068 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -4,26 +4,34 @@ */ package net.xmx.velthoric.core.service; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.server.level.ServerLevel; import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; import net.xmx.velthoric.init.VxMainClass; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; import java.util.function.Supplier; /** - * High-performance service registry for optional physics subsystems. + * Ultra-high-performance service registry for optional physics subsystems. + *

    + * This manager provides a thread-safe, lock-free (for reads) registry using a "Global Slot" + * architecture. Each service type is assigned a unique, immutable integer ID (slot) at runtime + * via {@link VxServiceSlots}. Services are stored in a fixed-index array, allowing lookups + * to achieve performance equivalent to direct Java field access. *

    - * This manager provides a thread-safe, lock-free (for reads) registry for {@link IVxPhysicsService} implementations. - * It allows decoupled systems to interact with the physics world without being hard-coded into the core managers. * Responsibilities include: *

      - *
    • Service Discovery: Efficient lookups for registered physics services via class-based keys.
    • - *
    • Lifecycle Management: Coordinating initialization and shutdown sequences for all registered subsystems.
    • - *
    • Event Dispatching: Routing physics and game thread ticks to active services.
    • - *
    • Concurrency: Ensuring safe access to services across multiple threads using a copy-on-write registry.
    • + *
    • Instant Discovery: Direct array-index lookups via globally unique slot IDs.
    • + *
    • Lifecycle Management: Coordinating initialization and shutdown sequences for all modular subsystems.
    • + *
    • Zero-Overhead Ticking: High-frequency event dispatching using cached compact arrays to avoid allocations.
    • + *
    • Concurrency: Atomic service publishing using copy-on-write array semantics for thread-safe access.
    • *
    + *

    + * Performance Note: Retrieval and iteration costs are minimized to sub-nanosecond levels, + * making this system suitable for high-frequency physics logic. * * @author LOLAtom * @author xI-Mx-Ix @@ -31,10 +39,22 @@ public class VxServiceManager { /** - * Map of registered service classes to their singleton instances. - * Uses a copy-on-write strategy with a volatile reference for O(1) lock-free reads. + * Primary storage for registered services, indexed by their unique global slot ID. + * Uses a volatile reference for thread-safe, lock-free reads. + */ + private volatile IVxPhysicsService[] slots; + + /** + * Cached compact array of registered services used for high-performance iteration. + * This avoids iterating over empty indices in the {@link #slots} array during tick loops. */ - private volatile Object2ObjectOpenHashMap, IVxPhysicsService> services; + private volatile IVxPhysicsService[] active; + + /** + * Global registry of factories used to automatically instantiate services for every new world. + * Synchronized during access to ensure thread-safe registration. + */ + private static final List> globalFactories = new ArrayList<>(); /** * The physics world instance these services are associated with. @@ -55,77 +75,104 @@ public class VxServiceManager { public VxServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { this.physicsWorld = physicsWorld; this.level = level; + this.slots = new IVxPhysicsService[Math.max(8, VxServiceSlots.getTotalSlots() + 4)]; + this.active = new IVxPhysicsService[0]; + + // Automatically bootstrap services from the global factory registry + synchronized (globalFactories) { + for (Function factory : globalFactories) { + this.registerService(factory.apply(this.physicsWorld)); + } + } + } + + /** + * Registers a global service factory. + *

    + * Every {@link VxPhysicsWorld} created after this call will automatically + * instantiate and register the service provided by this factory. + * + * @param factory The factory to register. + */ + public static void registerFactory(Function factory) { + synchronized (globalFactories) { + globalFactories.add(factory); + } } /** - * Registers a service instance. + * Registers a service instance into its designated global slot. *

    - * Performance: O(n) where n = current service count (copy-on-write) - *
    Thread-safe: yes (synchronized + volatile publish) - *
    When to call: During server initialization, not in tick loops + * Performance: O(n) during registration (copy-on-write expansion) but O(1) for lookups. + *
    Thread-safety: Fully thread-safe via synchronization and volatile array publication. * - * @param service The service to register. + * @param service The service instance to register. * @param The service type. * @return The same service instance (for chaining). */ public T registerService(T service) { + int id = VxServiceSlots.get(service.getClass()); + synchronized (this) { - Object2ObjectOpenHashMap, IVxPhysicsService> current = services; - Object2ObjectOpenHashMap, IVxPhysicsService> newMap; + IVxPhysicsService[] currentSlots = this.slots; + int totalRequired = Math.max(id + 1, VxServiceSlots.getTotalSlots()); + + // Expand array if the allocated slot is out of bounds + if (id >= currentSlots.length || totalRequired > currentSlots.length) { + IVxPhysicsService[] newSlots = new IVxPhysicsService[totalRequired + 4]; + System.arraycopy(currentSlots, 0, newSlots, 0, currentSlots.length); + currentSlots = newSlots; + } - if (current == null) { - newMap = new Object2ObjectOpenHashMap<>(8, 0.75f); - } else { - newMap = new Object2ObjectOpenHashMap<>(current); + if (currentSlots[id] != null) { + VxMainClass.LOGGER.warn("Service slot {} was already occupied by {}, replacing with {}", + id, currentSlots[id].getIdentification(), service.getIdentification()); } - @SuppressWarnings("unchecked") - Class serviceClass = (Class) service.getClass(); - IVxPhysicsService existing = newMap.put(serviceClass, service); + currentSlots[id] = service; + this.slots = currentSlots; // Volatile publish - if (existing != null) { - VxMainClass.LOGGER.warn("Service {} was already registered, replacing", service.getIdentification()); + // Rebuild the compact active array for ticks + List activeList = new ArrayList<>(); + for (IVxPhysicsService s : currentSlots) { + if (s != null) { + activeList.add(s); + } } + this.active = activeList.toArray(new IVxPhysicsService[0]); - this.services = newMap; + VxMainClass.LOGGER.debug("Registered service: {} in slot {} (total active: {})", + service.getIdentification(), id, activeList.size()); - VxMainClass.LOGGER.debug("Registered service: {} (total: {})", - service.getIdentification(), newMap.size()); return service; } } /** - * Gets a service by its class type. + * Retrieves a service by its class type using its unique global slot ID. *

    - * Performance: ~3-5ns average (volatile read + FastUtil get) - *
    Thread-safe: yes (lock-free volatile read) - *
    Type-safe: yes (generics + cast) + * Performance: ~0.5-1.0ns (equivalent to a direct field access via 1 array lookup). + *
    Memory Semantics: Performs a single volatile array read. * * @param clazz The service class to look up. * @param The service type. - * @return The service instance, or null if not registered. + * @return The service instance, or null if not registered for this world. */ @Nullable @SuppressWarnings("unchecked") public T getService(Class clazz) { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; + int id = VxServiceSlots.get(clazz); + IVxPhysicsService[] currentSlots = this.slots; - if (map == null) { + if (id >= currentSlots.length) { return null; } - IVxPhysicsService service = map.get(clazz); - if (service == null) { - return null; - } - return (T) service; + return (T) currentSlots[id]; } /** * Gets a service with a default fallback. - *

    - * Avoids null checks at call sites. Useful for optional features. * * @param clazz The service class to look up. * @param defaultValue Value to return if service not found. @@ -139,9 +186,6 @@ public T getServiceOrDefault(Class clazz, T def /** * Gets a service, creating it lazily if not present. - *

    - * Useful for services that are expensive to create and may not be needed. - * Note: The supplier is called with the write lock held. * * @param clazz The service class to look up. * @param creator Supplier to create the service if not found. @@ -154,47 +198,40 @@ public T getServiceOrCreate(Class clazz, Suppli return service; } - // Create and register in one atomic operation synchronized (this) { service = getService(clazz); if (service != null) { return service; } - service = creator.get(); - return registerService(service); + return registerService(creator.get()); } } /** * Checks if a service is registered. - *

    - * Performance: ~3ns (volatile read + containsKey) * * @param clazz The service class to check. * @return true if the service is registered. */ public boolean hasService(Class clazz) { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - return map != null && map.containsKey(clazz); + int id = VxServiceSlots.get(clazz); + IVxPhysicsService[] currentSlots = this.slots; + return id < currentSlots.length && currentSlots[id] != null; } /** * Initializes all registered services. *

    - * Called during {@code VxPhysicsWorld.initializeAndStart()}. - * Services are initialized after core managers (bodies, constraints, etc.) - * so they can safely depend on core functionality. + * Threading: Called from the Main Server Thread during world startup. */ public void initialize() { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } + IVxPhysicsService[] services = this.active; + if (services.length == 0) return; - VxMainClass.LOGGER.debug("Initializing {} registered services", map.size()); + VxMainClass.LOGGER.debug("Initializing {} registered services", services.length); - for (IVxPhysicsService service : map.values()) { + for (IVxPhysicsService service : services) { try { service.initialize(); } catch (Exception e) { @@ -207,50 +244,41 @@ public void initialize() { /** * Shuts down all registered services. *

    - * Called during {@code VxPhysicsWorld.shutdown()}. - * Services are shut down BEFORE core managers so they can clean up - * while their dependencies are still available. + * Services are shut down in reverse registration order to satisfy potential dependencies. + * Threading: Called from the Main Server Thread during world shutdown. */ public void shutdown() { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } + IVxPhysicsService[] services = this.active; + if (services.length == 0) return; - VxMainClass.LOGGER.debug("Shutting down {} registered services", map.size()); + VxMainClass.LOGGER.debug("Shutting down {} registered services", services.length); - // Shutdown in reverse registration order (LIFO) for dependency safety - // Services registered later may depend on services registered earlier - IVxPhysicsService[] servicesArray = map.values().toArray(new IVxPhysicsService[0]); - for (int i = servicesArray.length - 1; i >= 0; i--) { + // Shutdown in reverse registration order if needed, or just sequence + for (int i = services.length - 1; i >= 0; i--) { try { - servicesArray[i].shutdown(); + services[i].shutdown(); } catch (Exception e) { VxMainClass.LOGGER.error("Failed to shutdown service: {}", - servicesArray[i].getIdentification(), e); + services[i].getIdentification(), e); } } - // Clear the map reference (volatile write) synchronized (this) { - this.services = null; + this.slots = new IVxPhysicsService[0]; + this.active = new IVxPhysicsService[0]; } } /** - * Calls {@link IVxPhysicsService#onPrePhysicsTick(VxPhysicsWorld)} on all services. + * High-performance physics pre-tick for all registered services. *

    - * Must be called from: Physics thread - *
    Frequency: 60Hz (fixed timestep) - *
    Performance: O(n) where n = service count (typically < 20) + * Threading: Called from the Physics Thread (Frequency: 60Hz). + * + * @param world The current physics world instance. */ public void onPrePhysicsTick(VxPhysicsWorld world) { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IVxPhysicsService service : map.values()) { + IVxPhysicsService[] services = this.active; + for (IVxPhysicsService service : services) { try { service.onPrePhysicsTick(world); } catch (Exception e) { @@ -261,18 +289,15 @@ public void onPrePhysicsTick(VxPhysicsWorld world) { } /** - * Calls {@link IVxPhysicsService#onPhysicsTick(VxPhysicsWorld)} on all services. + * High-performance physics tick for all registered services. *

    - * Must be called from: Physics thread - *
    Frequency: 60Hz (fixed timestep) + * Threading: Called from the Physics Thread (Frequency: 60Hz). + * + * @param world The current physics world instance. */ public void onPhysicsTick(VxPhysicsWorld world) { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IVxPhysicsService service : map.values()) { + IVxPhysicsService[] services = this.active; + for (IVxPhysicsService service : services) { try { service.onPhysicsTick(world); } catch (Exception e) { @@ -283,18 +308,15 @@ public void onPhysicsTick(VxPhysicsWorld world) { } /** - * Calls {@link IVxPhysicsService#onGameTick(ServerLevel)} on all services. + * High-performance game tick for all registered services. *

    - * Called from: Server thread (not physics thread) - *
    Frequency: 20Hz (Minecraft tick rate) + * Threading: Called from the Main Server Thread (Frequency: 20Hz). + * + * @param level The current Minecraft server level. */ public void onGameTick(ServerLevel level) { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IVxPhysicsService service : map.values()) { + IVxPhysicsService[] services = this.active; + for (IVxPhysicsService service : services) { try { service.onGameTick(level); } catch (Exception e) { @@ -322,7 +344,6 @@ public ServerLevel getLevel() { * @return The total number of currently registered services. */ public int getServiceCount() { - Object2ObjectOpenHashMap, IVxPhysicsService> map = services; - return map != null ? map.size() : 0; + return active.length; } } \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceSlots.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceSlots.java new file mode 100644 index 00000000..d6ee2f48 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceSlots.java @@ -0,0 +1,55 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.service; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Global registry for service slot allocation. + *

    + * This class assigns a unique, immutable integer ID (slot) to every service type + * encountered during the application lifecycle. These IDs are then used by the + * {@link VxServiceManager} to store and retrieve services in a fixed-index array, + * achieving performance comparable to direct field access. + * + * @author xI-Mx-Ix + */ +public final class VxServiceSlots { + + private static final AtomicInteger nextSlot = new AtomicInteger(0); + + /** + * Map of service classes to their unique slot IDs. + * Uses Java's highly-optimized {@link ClassValue} for O(1) type-to-ID resolution. + */ + private static final ClassValue slots = new ClassValue<>() { + @Override + protected Integer computeValue(Class type) { + return nextSlot.getAndIncrement(); + } + }; + + /** + * Retrieves the unique slot ID for a given service class. + * If no ID has been assigned yet, a new one is allocated atomically. + * + * @param clazz The service class to resolve. + * @return The unique slot ID. + */ + public static int get(Class clazz) { + return slots.get(clazz); + } + + /** + * @return The total number of slots allocated across the entire system. + */ + public static int getTotalSlots() { + return nextSlot.get(); + } + + private VxServiceSlots() { + // Prevent instantiation + } +} \ No newline at end of file From 3e4fda73778a33b491939c636fbc82fad370cb4a Mon Sep 17 00:00:00 2001 From: Avryell Date: Wed, 15 Apr 2026 13:39:22 +0200 Subject: [PATCH 9/9] [ADDED] VxServiceDependency Annotation - IVxPhysicsService now contain bit check to know which tick methods it uses - IVxPhysicsService are now sorted by Dependency amount - VxServiceManager will now sort Services --- .../core/physics/world/VxPhysicsWorld.java | 11 + .../core/ragdoll/VxRagdollManager.java | 5 + .../core/service/IVxPhysicsService.java | 63 +++- .../core/service/VxServiceDependency.java | 26 ++ .../core/service/VxServiceManager.java | 252 +++++++++++++-- .../core/services/IPhysicsService.java | 71 ----- .../core/services/ServiceManager.java | 292 ------------------ 7 files changed, 314 insertions(+), 406 deletions(-) create mode 100644 common/src/main/java/net/xmx/velthoric/core/service/VxServiceDependency.java delete mode 100644 common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java delete mode 100644 common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java diff --git a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java index e9b28b5f..fa6a3663 100644 --- a/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java +++ b/common/src/main/java/net/xmx/velthoric/core/physics/world/VxPhysicsWorld.java @@ -407,6 +407,12 @@ public static T getServiceOrCreate(ResourceKey T[] registerServices(ResourceKey dimensionKey, T... services) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? this.serviceManager.registerServices(services) : null; + } + /** * Checks if a specific dimension has a registered physics service. * @@ -432,6 +438,11 @@ public T registerService(T service) { return this.serviceManager.registerService(service); } + @SafeVarargs + public final T[] registerServices(T... services) { + return this.serviceManager.registerServices(services); + } + public T getServiceOrDefault(Class clazz, T defaultValue) { return this.serviceManager.getServiceOrDefault(clazz, defaultValue); } diff --git a/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java b/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java index 8e3c5fde..f7120988 100644 --- a/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/ragdoll/VxRagdollManager.java @@ -49,6 +49,11 @@ public String getIdentification() { return "RagdollManager"; } + @Override + public int getCapabilities() { + return 0; + } + /** * Creates a humanoid ragdoll based on the state of a living entity at a specific world position. * The ragdoll is spawned with the entity's Yaw orientation but remains upright (no Pitch), diff --git a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java index 84ae124e..213376e6 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -27,14 +27,53 @@ */ public interface IVxPhysicsService { + /** Service implements {@link #onPrePhysicsTick} + * bit for onPrePhysicsTick to be called or not + **/ + int CAP_PRE_TICK = 1 << 0; + + /** Service implements {@link #onPhysicsTick} + * bit for onPhysicsTick to be called or not + **/ + int CAP_PHYSICS_TICK = 1 << 1; + + /** Service implements {@link #onGameTick} + * bit for onGameTick to be called or not + **/ + int CAP_GAME_TICK = 1 << 2; + + //TODO:Add more (if needed) and make it a long instead of an int if you ever see the use to + + /** + * All Capabilities Enabled, allow for : + * {@link #onPrePhysicsTick} + * {@link #onPhysicsTick} + * {@link #onGameTick} + * to all be called + **/ + int CAP_ALL = CAP_PRE_TICK | CAP_PHYSICS_TICK | CAP_GAME_TICK; + + /** * Returns a unique identifier for this service type. - * This identifier is used for debugging, logging, and potentially networking. + * Used for debugging, logging, and networking. * * @return The unique service identification string. */ default String getIdentification() { - return ""; + return this.getClass().getSimpleName(); + } + + /** + * Returns a bitmask of which tick phases this service implements. + *

    + * Override to skip unused phases and reduce virtual dispatch overhead. + * Default: all phases enabled (for backwards compatibility). + * + * @return Bitmask of {@code CAP_} flags. + */ + default int getCapabilities() { + return CAP_ALL; } /** @@ -42,52 +81,52 @@ default String getIdentification() { * Use this to allocate resources, register listeners, or perform initial state setup. *

    * Threading: Called from the Main Server Thread. + *
    Performance: May allocate; called once per world lifetime. */ - default void initialize() { - } + default void initialize() {} /** * Called during the shutdown sequence of the physics world. * Implementations must release all resources and unregister any hooks to prevent memory leaks. *

    * Threading: Called from the Main Server Thread. + *
    Performance: May allocate; called once per world lifetime. */ - default void shutdown() { - } + default void shutdown() {} /** * Optional: Invoked at the start of each physics simulation frame. * Useful for applying forces or modifying body states before Jolt performs the integration step. *

    * Threading: Called from the Physics Thread (High Frequency: 60Hz default). + *
    Performance Contract: MUST NOT allocate memory. * * @param world The physics world instance being simulated. * @implSpec The default implementation is a no-op. */ - default void onPrePhysicsTick(VxPhysicsWorld world) { - } + default void onPrePhysicsTick(VxPhysicsWorld world) {} /** * Optional: Invoked after each physics simulation frame has completed. * Useful for post-processing results, updating custom spatial indexes, or triggering collision callbacks. *

    * Threading: Called from the Physics Thread (High Frequency: 60Hz default). + *
    Performance Contract: MUST NOT allocate memory. * * @param world The physics world instance that was just simulated. * @implSpec The default implementation is a no-op. */ - default void onPhysicsTick(VxPhysicsWorld world) { - } + default void onPhysicsTick(VxPhysicsWorld world) {} /** * Optional: Invoked once per Minecraft game tick. * Useful for synchronization between the physics world and the Minecraft level (e.g., entity spawning, block updates). *

    * Threading: Called from the Main Server Thread (Frequency: 20Hz). + *
    Performance: May allocate; lower frequency than physics ticks. * * @param level The Minecraft server level associated with the physics world. * @implSpec The default implementation is a no-op. */ - default void onGameTick(ServerLevel level) { - } + default void onGameTick(ServerLevel level) {} } \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceDependency.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceDependency.java new file mode 100644 index 00000000..488cf6f8 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceDependency.java @@ -0,0 +1,26 @@ +/* + * This file is part of Velthoric. + * Licensed under LGPL 3.0. + */ +package net.xmx.velthoric.core.service; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation allow for Services to be dependent of other services + * @author LOLAtom + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface VxServiceDependency { + /** + * value represent what Other service the current annotated service depends on + * Example : + * /@ServiceDependency(RagdollManager.class, ExplosiveManager.class) + * /public class DestroyedBodyManager implements IVxPhysicsService + */ + Class[] value(); +} \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java index a3e4d068..0635e8ec 100644 --- a/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -9,8 +9,7 @@ import net.xmx.velthoric.init.VxMainClass; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.function.Function; import java.util.function.Supplier; @@ -75,6 +74,8 @@ public class VxServiceManager { public VxServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { this.physicsWorld = physicsWorld; this.level = level; + + // Pre-allocate slots array with reasonable initial capacity this.slots = new IVxPhysicsService[Math.max(8, VxServiceSlots.getTotalSlots() + 4)]; this.active = new IVxPhysicsService[0]; @@ -111,47 +112,74 @@ public static void registerFactory(Function f * @return The same service instance (for chaining). */ public T registerService(T service) { - int id = VxServiceSlots.get(service.getClass()); + return registerServices(service)[0]; + } + + /** + * Registers multiple services in a single atomic operation. + *

    + * Rebuilds the active array only once, regardless of service count. + *

    + * Performance: O(1) active-array rebuild instead of O(n) for n services. + *
    Thread-safety: Fully thread-safe via synchronization. + * + * @param services Varargs of services to register. + * @param Service type (wildcard for mixed types). + * @return Array of registered services (same instances, for chaining). + */ + @SafeVarargs + public final T[] registerServices(T... services) { + if (services == null || services.length == 0) { + return services; + } synchronized (this) { IVxPhysicsService[] currentSlots = this.slots; - int totalRequired = Math.max(id + 1, VxServiceSlots.getTotalSlots()); + int maxId = -1; // max slot id - // Expand array if the allocated slot is out of bounds - if (id >= currentSlots.length || totalRequired > currentSlots.length) { + for (IVxPhysicsService service : services) { + if (service == null) continue; + int id = VxServiceSlots.get(service.getClass()); + maxId = Math.max(maxId, id); // make max slot larger the more service are added + } + + int totalRequired = Math.max(maxId + 1, VxServiceSlots.getTotalSlots()); // gets the total requirement of slots + if (totalRequired > currentSlots.length) { IVxPhysicsService[] newSlots = new IVxPhysicsService[totalRequired + 4]; System.arraycopy(currentSlots, 0, newSlots, 0, currentSlots.length); currentSlots = newSlots; } - if (currentSlots[id] != null) { - VxMainClass.LOGGER.warn("Service slot {} was already occupied by {}, replacing with {}", - id, currentSlots[id].getIdentification(), service.getIdentification()); + for (IVxPhysicsService service : services) { + if (service == null) continue; + int id = VxServiceSlots.get(service.getClass()); + + if (currentSlots[id] != null) { // In case a service slot was already occupied by another + VxMainClass.LOGGER.warn("Service slot {} was already occupied by {}, replacing with {}", + id, currentSlots[id].getIdentification(), service.getIdentification()); + } + currentSlots[id] = service; } - currentSlots[id] = service; - this.slots = currentSlots; // Volatile publish + this.slots = currentSlots; // volatile write - // Rebuild the compact active array for ticks - List activeList = new ArrayList<>(); + List activeList = new ArrayList<>(currentSlots.length); for (IVxPhysicsService s : currentSlots) { - if (s != null) { - activeList.add(s); - } + if (s != null) activeList.add(s); } this.active = activeList.toArray(new IVxPhysicsService[0]); - VxMainClass.LOGGER.debug("Registered service: {} in slot {} (total active: {})", - service.getIdentification(), id, activeList.size()); + VxMainClass.LOGGER.debug("Registered {} services (total active: {})", + services.length, this.active.length); - return service; + return services; } } /** * Retrieves a service by its class type using its unique global slot ID. *

    - * Performance: ~0.5-1.0ns (equivalent to a direct field access via 1 array lookup). + * Performance: ~0.3ns (equivalent to direct field access via 1 array lookup). *
    Memory Semantics: Performs a single volatile array read. * * @param clazz The service class to look up. @@ -221,17 +249,58 @@ public boolean hasService(Class clazz) { } /** - * Initializes all registered services. + * Unregisters a service by class type. + *

    + * Note: The slot ID is not reclaimed (ClassValue semantics). + * The array slot will be set to null, and the active array rebuilt. + * + * @param clazz The service class to unregister. + * @param The service type. + * @return The unregistered service, or null if not found. + */ + @Nullable + public T unregisterService(Class clazz) { + int id = VxServiceSlots.get(clazz); + + synchronized (this) { + IVxPhysicsService[] currentSlots = this.slots; + if (id >= currentSlots.length || currentSlots[id] == null) { + return null; + } + + @SuppressWarnings("unchecked") + T removed = (T) currentSlots[id]; + currentSlots[id] = null; + this.slots = currentSlots; // volatile write + + List activeList = new ArrayList<>(currentSlots.length); + for (IVxPhysicsService s : currentSlots) { + if (s != null) activeList.add(s); + } + this.active = activeList.toArray(new IVxPhysicsService[0]); + + VxMainClass.LOGGER.debug("Unregistered service: {} (total active: {})", + removed != null ? removed.getIdentification() : "null", this.active.length); + + return removed; + } + } + + /** + * Initializes all registered services in dependency order. *

    * Threading: Called from the Main Server Thread during world startup. + *
    Order: Dependencies initialized first (topological sort). */ public void initialize() { IVxPhysicsService[] services = this.active; if (services.length == 0) return; - VxMainClass.LOGGER.debug("Initializing {} registered services", services.length); + List sorted = sort(Arrays.asList(services)); - for (IVxPhysicsService service : services) { + VxMainClass.LOGGER.debug("Initializing {} registered services", sorted.size()); + + for (IVxPhysicsService service : sorted) { try { service.initialize(); } catch (Exception e) { @@ -242,24 +311,25 @@ public void initialize() { } /** - * Shuts down all registered services. + * Shuts down all registered services in reverse dependency order. *

    - * Services are shut down in reverse registration order to satisfy potential dependencies. + * Services are shut down BEFORE their dependencies to satisfy cleanup requirements. * Threading: Called from the Main Server Thread during world shutdown. */ public void shutdown() { IVxPhysicsService[] services = this.active; if (services.length == 0) return; - VxMainClass.LOGGER.debug("Shutting down {} registered services", services.length); + List sorted = sort(Arrays.asList(services)); //Sorted service to not shutdown dependencies first + + VxMainClass.LOGGER.debug("Shutting down {} registered services", sorted.size()); - // Shutdown in reverse registration order if needed, or just sequence - for (int i = services.length - 1; i >= 0; i--) { + for (int i = sorted.size() - 1; i >= 0; i--) { try { - services[i].shutdown(); + sorted.get(i).shutdown(); } catch (Exception e) { VxMainClass.LOGGER.error("Failed to shutdown service: {}", - services[i].getIdentification(), e); + sorted.get(i).getIdentification(), e); } } @@ -269,16 +339,89 @@ public void shutdown() { } } + /** + * Performs sort of services by their {@link VxServiceDependency} annotations. + * + * @param services List of services to sort. + * @return Services in dependency order (dependencies first). + */ + private List sort(List services) { // Algorithm implementation for sorting + Map> graph = new HashMap<>(); // services by size of dependency amount + Map, IVxPhysicsService> classToService = new HashMap<>(); + + for (IVxPhysicsService service : services) { + classToService.put(service.getClass(), service); + graph.put(service, new HashSet<>()); + } + + for (IVxPhysicsService service : services) { + VxServiceDependency annotation = service.getClass().getAnnotation(VxServiceDependency.class); + if (annotation != null) { + for (Class depClass : annotation.value()) { // check for dependency for later sorting + IVxPhysicsService dep = classToService.get(depClass); + if (dep != null) { + graph.get(service).add(dep); + } + } + } + } + + // Sorting by who's a dependency and who is a dependent after + List result = new ArrayList<>(services.size()); + Map inDegree = new HashMap<>(); + + for (IVxPhysicsService service : services) { + inDegree.put(service, graph.get(service).size()); + } + + Queue queue = new ArrayDeque<>(); + for (IVxPhysicsService service : services) { + if (inDegree.get(service) == 0) { + queue.offer(service); + } + } + + while (!queue.isEmpty()) { + IVxPhysicsService current = queue.poll(); + result.add(current); + + for (IVxPhysicsService other : services) { + if (graph.get(other).contains(current)) { + int newDegree = inDegree.get(other) - 1; + inDegree.put(other, newDegree); + if (newDegree == 0) { + queue.offer(other); + } + } + } + } + + //if the result size isn't the same as the asked one, then it warns saying it will be using a fallback + if (result.size() != services.size()) { + VxMainClass.LOGGER.warn("Services Couldn't match due to Looping Dependency, Fallback sorting instead."); + return new ArrayList<>(services); + } + + return result; + } + /** * High-performance physics pre-tick for all registered services. *

    * Threading: Called from the Physics Thread (Frequency: 60Hz). + *
    Performance: Skips services without CAP_PRE_TICK capability. * * @param world The current physics world instance. */ public void onPrePhysicsTick(VxPhysicsWorld world) { IVxPhysicsService[] services = this.active; + if (services.length == 0) return; + for (IVxPhysicsService service : services) { + // Skip if service doesn't implement this phase + if ((service.getCapabilities() & IVxPhysicsService.CAP_PRE_TICK) == 0) { // check if the servvice contains pre physics tick CAP + continue; + } try { service.onPrePhysicsTick(world); } catch (Exception e) { @@ -292,12 +435,19 @@ public void onPrePhysicsTick(VxPhysicsWorld world) { * High-performance physics tick for all registered services. *

    * Threading: Called from the Physics Thread (Frequency: 60Hz). + *
    Performance: Skips services without CAP_PHYSICS_TICK capability. * * @param world The current physics world instance. */ public void onPhysicsTick(VxPhysicsWorld world) { IVxPhysicsService[] services = this.active; + if (services.length == 0) return; + for (IVxPhysicsService service : services) { + // Skip if service doesn't implement this phase + if ((service.getCapabilities() & IVxPhysicsService.CAP_PHYSICS_TICK) == 0) { // check if the servvice contains physics tick CAP + continue; + } try { service.onPhysicsTick(world); } catch (Exception e) { @@ -311,12 +461,19 @@ public void onPhysicsTick(VxPhysicsWorld world) { * High-performance game tick for all registered services. *

    * Threading: Called from the Main Server Thread (Frequency: 20Hz). + *
    Performance: Skips services without CAP_GAME_TICK capability. * * @param level The current Minecraft server level. */ public void onGameTick(ServerLevel level) { IVxPhysicsService[] services = this.active; + if (services.length == 0) return; + for (IVxPhysicsService service : services) { + // Skip if service doesn't implement this phase + if ((service.getCapabilities() & IVxPhysicsService.CAP_GAME_TICK) == 0) { // check if the servvice contains game tick CAP + continue; + } try { service.onGameTick(level); } catch (Exception e) { @@ -326,13 +483,45 @@ public void onGameTick(ServerLevel level) { } } + /** + * Returns the slots array for direct read access. + *

    + * Warning: Do not modify the returned array. Cache the reference only within a single method. + *

    + * Usage: For ultra-hot paths where even method call overhead matters: + *

    +     *   private static final int MY_SLOT = VxServiceSlots.get(MyService.class);
    +     *
    +     *   public void tick(VxPhysicsWorld world) {
    +     *       IVxPhysicsService[] slots = world.getServiceRegistry().getSlotsForRead();
    +     *       MyService service = (MyService) slots[MY_SLOT];
    +     *       if (service != null) service.doWork();
    +     *   }
    +     * 
    + * + * @return The current slots array (volatile read). + */ + public IVxPhysicsService[] getSlotsForRead() { + return this.slots; + } + + /** + * Returns the compact active array for iteration. + *

    + * Warning: Do not modify the returned array. For internal tick loops only. + * + * @return The current active services array (volatile read). + */ + public IVxPhysicsService[] getActiveForRead() { + return this.active; + } + /** * @return The physics world instance managed by this manager. */ public VxPhysicsWorld getPhysicsWorld() { return physicsWorld; } - /** * @return The Minecraft server level this manager is associated with. */ @@ -346,4 +535,5 @@ public ServerLevel getLevel() { public int getServiceCount() { return active.length; } + } \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java b/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java deleted file mode 100644 index 31bbec47..00000000 --- a/common/src/main/java/net/xmx/velthoric/core/services/IPhysicsService.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of Velthoric. - * Licensed under LGPL 3.0. - */ -package net.xmx.velthoric.core.services; - -import net.minecraft.server.level.ServerLevel; -import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; - -/** - * Base interface for optional physics subsystem services. - *

    - * Services registered with {@link ServiceManager} must implement this interface. - * Core managers (bodies, constraints, etc.) should remain as direct fields - * in VxPhysicsWorld for maximum performance. - * - * @author LOLAtom - */ -public interface IPhysicsService { - - /** - * Returns a unique identifier for this service type. - * Used for debugging and logging. - * - * @return The service identification string. - */ - String getIdentification(); - - /** - * Called when the physics world is initializing. - * Use this to set up resources, register events, etc. - *

    - * Called from: Server thread, during {@code VxPhysicsWorld.initializeAndStart()} - */ - void initialize(); - - /** - * Called when the physics world is shutting down. - * Use this to clean up resources, unregister events, etc. - *

    - * Called from: Server thread, during {@code VxPhysicsWorld.shutdown()} - */ - void shutdown(); - - /** - * Optional: Called before each physics simulation step. - *

    - * Called from: Physics thread, at 60Hz - * - * @implSpec Default implementation does nothing. - */ - default void onPrePhysicsTick(VxPhysicsWorld world) {} - - /** - * Optional: Called after each physics simulation step. - *

    - * Called from: Physics thread, at 60Hz - * - * @implSpec Default implementation does nothing. - */ - default void onPhysicsTick(VxPhysicsWorld world) {} - - /** - * Optional: Called on game tick (server thread, not physics thread). - *

    - * Called from: Server thread, at 20Hz (Minecraft tick rate) - * - * @implSpec Default implementation does nothing. - */ - default void onGameTick(ServerLevel level) {} -} \ No newline at end of file diff --git a/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java b/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java deleted file mode 100644 index 22258d6a..00000000 --- a/common/src/main/java/net/xmx/velthoric/core/services/ServiceManager.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * This file is part of Velthoric. - * Licensed under LGPL 3.0. - */ -package net.xmx.velthoric.core.services; - - -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.server.level.ServerLevel; -import net.xmx.velthoric.core.physics.world.VxPhysicsWorld; -import net.xmx.velthoric.init.VxMainClass; -import org.jetbrains.annotations.Nullable; - -import java.util.function.Supplier; - -/** - * High-performance service registry for optional physics subsystems. - * - * @author LOLAtom - */ -public class ServiceManager { - - private volatile Object2ObjectOpenHashMap, IPhysicsService> services; - - private final VxPhysicsWorld physicsWorld; - private final ServerLevel level; - - public ServiceManager(VxPhysicsWorld physicsWorld, ServerLevel level) { - this.physicsWorld = physicsWorld; - this.level = level; - } - - /** - * Registers a service instance. - *

    - * Performance: O(n) where n = current service count (copy-on-write) - *
    Thread-safe: yes (synchronized + volatile publish) - *
    When to call: During server initialization, not in tick loops - * - * @param service The service to register. - * @param The service type. - * @return The same service instance (for chaining). - */ - public T registerService(T service) { - synchronized (this) { - Object2ObjectOpenHashMap, IPhysicsService> current = services; - Object2ObjectOpenHashMap, IPhysicsService> newMap; - - if (current == null) { - newMap = new Object2ObjectOpenHashMap<>(8, 0.75f); - } else { - newMap = new Object2ObjectOpenHashMap<>(current); - } - - @SuppressWarnings("unchecked") - Class serviceClass = (Class) service.getClass(); - IPhysicsService existing = newMap.put(serviceClass, service); - - if (existing != null) { - VxMainClass.LOGGER.warn("Service {} was already registered, replacing", service.getIdentification()); - } - - this.services = newMap; - - VxMainClass.LOGGER.debug("Registered service: {} (total: {})", - service.getIdentification(), newMap.size()); - return service; - } - } - - /** - * Gets a service by its class type. - *

    - * Performance: ~3-5ns average (volatile read + FastUtil get) - *
    Thread-safe: yes (lock-free volatile read) - *
    Type-safe: yes (generics + cast) - * - * @param clazz The service class to look up. - * @param The service type. - * @return The service instance, or null if not registered. - */ - @Nullable - @SuppressWarnings("unchecked") - public T getService(Class clazz) { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - - if (map == null) { - return null; - } - IPhysicsService service = map.get(clazz); - - if (service == null) { - return null; - } - return (T) service; - } - - /** - * Gets a service with a default fallback. - *

    - * Avoids null checks at call sites. Useful for optional features. - * - * @param clazz The service class to look up. - * @param defaultValue Value to return if service not found. - * @param The service type. - * @return The service instance, or defaultValue if not registered. - */ - public T getServiceOrDefault(Class clazz, T defaultValue) { - T service = getService(clazz); - return service != null ? service : defaultValue; - } - - /** - * Gets a service, creating it lazily if not present. - *

    - * Useful for services that are expensive to create and may not be needed. - * Note: The supplier is called with the write lock held. - * - * @param clazz The service class to look up. - * @param creator Supplier to create the service if not found. - * @param The service type. - * @return The existing or newly created service instance. - */ - public T getServiceOrCreate(Class clazz, Supplier creator) { - T service = getService(clazz); - if (service != null) { - return service; - } - - // Create and register in one atomic operation - synchronized (this) { - service = getService(clazz); - if (service != null) { - return service; - } - - service = creator.get(); - return registerService(service); - } - } - - /** - * Checks if a service is registered. - *

    - * Performance: ~3ns (volatile read + containsKey) - * - * @param clazz The service class to check. - * @return true if the service is registered. - */ - public boolean hasService(Class clazz) { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - return map != null && map.containsKey(clazz); - } - - /** - * Initializes all registered services. - *

    - * Called during {@code VxPhysicsWorld.initializeAndStart()}. - * Services are initialized after core managers (bodies, constraints, etc.) - * so they can safely depend on core functionality. - */ - public void initialize() { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - VxMainClass.LOGGER.debug("Initializing {} registered services", map.size()); - - for (IPhysicsService service : map.values()) { - try { - service.initialize(); - } catch (Exception e) { - VxMainClass.LOGGER.error("Failed to initialize service: {}", - service.getIdentification(), e); - } - } - } - - /** - * Shuts down all registered services. - *

    - * Called during {@code VxPhysicsWorld.shutdown()}. - * Services are shut down BEFORE core managers so they can clean up - * while their dependencies are still available. - */ - public void shutdown() { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - VxMainClass.LOGGER.debug("Shutting down {} registered services", map.size()); - - // Shutdown in reverse registration order (LIFO) for dependency safety - // Services registered later may depend on services registered earlier - IPhysicsService[] servicesArray = map.values().toArray(new IPhysicsService[0]); - for (int i = servicesArray.length - 1; i >= 0; i--) { - try { - servicesArray[i].shutdown(); - } catch (Exception e) { - VxMainClass.LOGGER.error("Failed to shutdown service: {}", - servicesArray[i].getIdentification(), e); - } - } - - // Clear the map reference (volatile write) - synchronized (this) { - this.services = null; - } - } - - /** - * Calls {@link IPhysicsService#onPrePhysicsTick(VxPhysicsWorld)} on all services. - *

    - * Must be called from: Physics thread - *
    Frequency: 60Hz (fixed timestep) - *
    Performance: O(n) where n = service count (typically < 20) - */ - public void onPrePhysicsTick(VxPhysicsWorld world) { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IPhysicsService service : map.values()) { - try { - service.onPrePhysicsTick(world); - } catch (Exception e) { - VxMainClass.LOGGER.error("Error in service.onPrePhysicsTick(): {}", - service.getIdentification(), e); - } - } - } - - /** - * Calls {@link IPhysicsService#onPhysicsTick(VxPhysicsWorld)} on all services. - *

    - * Must be called from: Physics thread - *
    Frequency: 60Hz (fixed timestep) - */ - public void onPhysicsTick(VxPhysicsWorld world) { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IPhysicsService service : map.values()) { - try { - service.onPhysicsTick(world); - } catch (Exception e) { - VxMainClass.LOGGER.error("Error in service.onPhysicsTick(): {}", - service.getIdentification(), e); - } - } - } - - /** - * Calls {@link IPhysicsService#onGameTick(ServerLevel)} on all services. - *

    - * Called from: Server thread (not physics thread) - *
    Frequency: 20Hz (Minecraft tick rate) - */ - public void onGameTick(ServerLevel level) { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - if (map == null || map.isEmpty()) { - return; - } - - for (IPhysicsService service : map.values()) { - try { - service.onGameTick(level); - } catch (Exception e) { - VxMainClass.LOGGER.error("Error in service.onGameTick(): {}", - service.getIdentification(), e); - } - } - } - - public VxPhysicsWorld getPhysicsWorld() { - return physicsWorld; - } - - public ServerLevel getLevel() { - return level; - } - - public int getServiceCount() { - Object2ObjectOpenHashMap, IPhysicsService> map = services; - return map != null ? map.size() : 0; - } -} \ No newline at end of file