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 beced416..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 @@ -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; @@ -60,7 +64,8 @@ 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; private final VxFrameTimer physicsFrameTimer = new VxFrameTimer(); @@ -74,13 +79,19 @@ 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(); 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); } public static VxPhysicsWorld getOrCreate(ServerLevel level) { @@ -117,6 +128,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 +212,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 +278,9 @@ private void shutdownInternalSystems() { if (this.bodyManager != null) { this.bodyManager.shutdown(); } + if (this.serviceManager != null) { + this.serviceManager.shutdown(); + } } private void cleanupJolt() { @@ -303,10 +322,6 @@ public VxTerrainSystem getTerrainSystem() { return this.terrainSystem; } - public VxRagdollManager getRagdollManager() { - return this.ragdollManager; - } - public ServerLevel getLevel() { return this.level; } @@ -350,10 +365,94 @@ public static VxTerrainSystem getTerrainSystem(ResourceKey dimensionKey) return world != null ? world.getTerrainSystem() : null; } + + /** + * 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) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.getService(clazz) : null; + } + + /** + * 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) { + VxPhysicsWorld world = get(dimensionKey); + return world != null ? world.registerService(service) : null; + } + @Nullable - public static VxRagdollManager getRagdollManager(ResourceKey dimensionKey) { + 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; + } + + @SafeVarargs + public final 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. + * + * @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) { VxPhysicsWorld world = get(dimensionKey); - return world != null ? world.getRagdollManager() : null; + return world != null && world.hasService(clazz); + } + + public VxServiceManager getServiceManager() { + return this.serviceManager; + } + + @Nullable + public T getService(Class clazz) { + return this.serviceManager.getService(clazz); + } + + 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); + } + + 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() { 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..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 @@ -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,16 @@ public VxRagdollManager(VxPhysicsWorld world) { this.constraintManager = world.getConstraintManager(); } + @Override + 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 new file mode 100644 index 00000000..213376e6 --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/IVxPhysicsService.java @@ -0,0 +1,132 @@ +/* + * 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. + * 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 { + + /** 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. + * Used for debugging, logging, and networking. + * + * @return The unique service identification string. + */ + default String getIdentification() { + 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; + } + + /** + * Called during the initialization phase of the physics world. + * 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() {} + + /** + * 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() {} + + /** + * 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) {} + + /** + * 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) {} + + /** + * 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) {} +} \ 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 new file mode 100644 index 00000000..0635e8ec --- /dev/null +++ b/common/src/main/java/net/xmx/velthoric/core/service/VxServiceManager.java @@ -0,0 +1,539 @@ +/* + * 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; +import net.xmx.velthoric.init.VxMainClass; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 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. + *

+ * Responsibilities include: + *

    + *
  • 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 + */ +public class VxServiceManager { + + /** + * 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 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. + */ + 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; + + // Pre-allocate slots array with reasonable initial capacity + 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 into its designated global slot. + *

+ * 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 instance to register. + * @param The service type. + * @return The same service instance (for chaining). + */ + public T registerService(T service) { + 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 maxId = -1; // max slot id + + 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; + } + + 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; + } + + 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("Registered {} services (total active: {})", + services.length, this.active.length); + + return services; + } + } + + /** + * Retrieves a service by its class type using its unique global slot ID. + *

+ * 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. + * @param The service type. + * @return The service instance, or null if not registered for this world. + */ + @Nullable + @SuppressWarnings("unchecked") + public T getService(Class clazz) { + int id = VxServiceSlots.get(clazz); + IVxPhysicsService[] currentSlots = this.slots; + + if (id >= currentSlots.length) { + return null; + } + + return (T) currentSlots[id]; + } + + /** + * Gets a service with a default fallback. + * + * @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. + * + * @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; + } + + synchronized (this) { + service = getService(clazz); + if (service != null) { + return service; + } + + return registerService(creator.get()); + } + } + + /** + * Checks if a service is registered. + * + * @param clazz The service class to check. + * @return true if the service is registered. + */ + public boolean hasService(Class clazz) { + int id = VxServiceSlots.get(clazz); + IVxPhysicsService[] currentSlots = this.slots; + return id < currentSlots.length && currentSlots[id] != null; + } + + /** + * 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; + + List sorted = sort(Arrays.asList(services)); + + VxMainClass.LOGGER.debug("Initializing {} registered services", sorted.size()); + + for (IVxPhysicsService service : sorted) { + try { + service.initialize(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to initialize service: {}", + service.getIdentification(), e); + } + } + } + + /** + * Shuts down all registered services in reverse dependency order. + *

+ * 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; + + List sorted = sort(Arrays.asList(services)); //Sorted service to not shutdown dependencies first + + VxMainClass.LOGGER.debug("Shutting down {} registered services", sorted.size()); + + for (int i = sorted.size() - 1; i >= 0; i--) { + try { + sorted.get(i).shutdown(); + } catch (Exception e) { + VxMainClass.LOGGER.error("Failed to shutdown service: {}", + sorted.get(i).getIdentification(), e); + } + } + + synchronized (this) { + this.slots = new IVxPhysicsService[0]; + this.active = new IVxPhysicsService[0]; + } + } + + /** + * 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) { + VxMainClass.LOGGER.error("Error in service.onPrePhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * 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) { + VxMainClass.LOGGER.error("Error in service.onPhysicsTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * 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) { + VxMainClass.LOGGER.error("Error in service.onGameTick(): {}", + service.getIdentification(), e); + } + } + } + + /** + * 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. + */ + public ServerLevel getLevel() { + return level; + } + + /** + * @return The total number of currently registered services. + */ + public int getServiceCount() { + 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