Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ private int execute(CommandContext<CommandSourceStack> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -262,6 +278,9 @@ private void shutdownInternalSystems() {
if (this.bodyManager != null) {
this.bodyManager.shutdown();
}
if (this.serviceManager != null) {
this.serviceManager.shutdown();
}
}

private void cleanupJolt() {
Expand Down Expand Up @@ -303,10 +322,6 @@ public VxTerrainSystem getTerrainSystem() {
return this.terrainSystem;
}

public VxRagdollManager getRagdollManager() {
return this.ragdollManager;
}

public ServerLevel getLevel() {
return this.level;
}
Expand Down Expand Up @@ -350,10 +365,94 @@ public static VxTerrainSystem getTerrainSystem(ResourceKey<Level> 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 <T> The service type.
* @return The service instance, or null if not registered or if the world is missing.
*/
@Nullable
public static <T extends IVxPhysicsService> T getService(ResourceKey<Level> dimensionKey, Class<T> 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 <T> The service type.
* @return The registered service instance, or null if the world is missing.
*/
@Nullable
public static <T extends IVxPhysicsService> T registerService(ResourceKey<Level> dimensionKey, T service) {
VxPhysicsWorld world = get(dimensionKey);
return world != null ? world.registerService(service) : null;
}

@Nullable
public static VxRagdollManager getRagdollManager(ResourceKey<Level> dimensionKey) {
public static <T extends IVxPhysicsService> T getServiceOrDefault(ResourceKey<Level> dimensionKey, Class<T> clazz, T defaultValue) {
VxPhysicsWorld world = get(dimensionKey);
return world != null ? world.getServiceOrDefault(clazz, defaultValue) : null;
}

@Nullable
public static <T extends IVxPhysicsService> T getServiceOrCreate(ResourceKey<Level> dimensionKey, Class<T> clazz, Supplier<T> creator) {
VxPhysicsWorld world = get(dimensionKey);
return world != null ? world.getServiceOrCreate(clazz, creator) : null;
}

@SafeVarargs
public final <T extends IVxPhysicsService> T[] registerServices(ResourceKey<Level> 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<Level> dimensionKey, Class<? extends IVxPhysicsService> 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 extends IVxPhysicsService> T getService(Class<T> clazz) {
return this.serviceManager.getService(clazz);
}

public <T extends IVxPhysicsService> T registerService(T service) {
return this.serviceManager.registerService(service);
}

@SafeVarargs
public final <T extends IVxPhysicsService> T[] registerServices(T... services) {
return this.serviceManager.registerServices(services);
}

public <T extends IVxPhysicsService> T getServiceOrDefault(Class<T> clazz, T defaultValue) {
return this.serviceManager.getServiceOrDefault(clazz, defaultValue);
}

public <T extends IVxPhysicsService> T getServiceOrCreate(Class<T> clazz, Supplier<T> creator) {
return this.serviceManager.getServiceOrCreate(clazz, creator);
}

public boolean hasService(Class<? extends IVxPhysicsService> clazz) {
return this.serviceManager.hasService(clazz);
}

public static Collection<VxPhysicsWorld> getAll() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
* <p>
* 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;
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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.
* <p>
* <b>Note:</b> For logic that applies to specific physics bodies (like buoyancy, aerodynamics, or engines),
* use the {@code VxBehavior} system instead.
* <p>
* <b>Performance Note:</b> 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.
* <p>
* 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.
* <p>
* <b>Threading:</b> Called from the Main Server Thread.
* <br><b>Performance:</b> 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.
* <p>
* <b>Threading:</b> Called from the Main Server Thread.
* <br><b>Performance:</b> 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.
* <p>
* <b>Threading:</b> Called from the Physics Thread (High Frequency: 60Hz default).
* <br><b>Performance Contract:</b> 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.
* <p>
* <b>Threading:</b> Called from the Physics Thread (High Frequency: 60Hz default).
* <br><b>Performance Contract:</b> 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).
* <p>
* <b>Threading:</b> Called from the Main Server Thread (Frequency: 20Hz).
* <br><b>Performance:</b> 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) {}
}
Loading