diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 02e50474..a393cc70 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1664,6 +1664,7 @@ Blocks factor into voxel pathfinding Debugging pathfinding code New AI behaviors - Will explore for resources if local ones aren't available +Async pathfinding diff --git a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiAI.java b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiAI.java index 0f35094e..5e1c50e5 100644 --- a/src/main/java/electrosphere/client/ui/menu/debug/ImGuiAI.java +++ b/src/main/java/electrosphere/client/ui/menu/debug/ImGuiAI.java @@ -77,11 +77,13 @@ public class ImGuiAI { if(ImGui.collapsingHeader("Statuses")){ for(AI ai : Globals.aiManager.getAIList()){ + ImGui.indent(); if(ImGui.collapsingHeader(ai.getParent().getId() + " - " + ai.getStatus())){ if(ImGui.button("Draw current pathing")){ throw new Error("Unsupported currently!"); } } + ImGui.unindent(); } } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 34e87306..ff3a9485 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -728,6 +728,7 @@ public class Globals { Globals.clientSynchronizationManager = new ClientSynchronizationManager(); Globals.server = null; Globals.serverSynchronizationManager = new ServerSynchronizationManager(); + Globals.aiManager.shutdown(); if(Globals.realmManager != null){ Globals.realmManager.reset(); } @@ -739,6 +740,7 @@ public class Globals { */ public static void resetGlobals(){ Globals.unloadScene(); + Globals.aiManager.shutdown(); // //Actual globals to destroy Globals.assetManager = null; @@ -757,6 +759,7 @@ public class Globals { Globals.clientSynchronizationManager = null; Globals.server = null; Globals.serverSynchronizationManager = null; + Globals.aiManager = null; Globals.playerManager = null; Globals.javaPID = null; Globals.RENDER_FLAG_RENDER_SHADOW_MAP = true; diff --git a/src/main/java/electrosphere/server/ai/AIManager.java b/src/main/java/electrosphere/server/ai/AIManager.java index d3e2ef51..80064c0f 100644 --- a/src/main/java/electrosphere/server/ai/AIManager.java +++ b/src/main/java/electrosphere/server/ai/AIManager.java @@ -10,6 +10,7 @@ import electrosphere.entity.Entity; import electrosphere.game.data.creature.type.ai.AITreeData; import electrosphere.logger.LoggerInterface; import electrosphere.server.ai.services.NearbyEntityService; +import electrosphere.server.ai.services.PathfindingService; import electrosphere.server.ai.services.TimerService; /** @@ -47,6 +48,11 @@ public class AIManager { */ NearbyEntityService nearbyEntityService = new NearbyEntityService(); + /** + * Service for performing pathfinding + */ + PathfindingService pathfindingService = new PathfindingService(); + /** * The random of the ai */ @@ -156,6 +162,14 @@ public class AIManager { return nearbyEntityService; } + /** + * Gets the pathfinding service + * @return The pathfinding service + */ + public PathfindingService getPathfindingService(){ + return this.pathfindingService; + } + /** * Gets the ai manager's random * @return The random @@ -163,5 +177,14 @@ public class AIManager { public Random getRandom(){ return random; } + + /** + * Shuts down the ai manager + */ + public void shutdown(){ + this.pathfindingService.shutdown(); + this.nearbyEntityService.shutdown(); + this.timerService.shutdown(); + } } diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/PathfindingNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/PathfindingNode.java index 130c4ea4..a91fc970 100644 --- a/src/main/java/electrosphere/server/ai/nodes/plan/PathfindingNode.java +++ b/src/main/java/electrosphere/server/ai/nodes/plan/PathfindingNode.java @@ -1,7 +1,5 @@ package electrosphere.server.ai.nodes.plan; -import java.util.List; - import org.joml.Vector3d; import electrosphere.engine.Globals; @@ -52,7 +50,7 @@ public class PathfindingNode implements AITreeNode { //make sure that the solved pathfinding data is for the point we want if(PathfindingNode.hasPathfindingData(blackboard)){ PathingProgressiveData pathingProgressiveData = PathfindingNode.getPathfindingData(blackboard); - Vector3d actualPoint = pathingProgressiveData.getPoints().get(pathingProgressiveData.getPoints().size() - 1); + Vector3d actualPoint = pathingProgressiveData.getGoal(); Object targetRaw = blackboard.get(this.targetEntityKey); Vector3d targetPos = null; if(targetRaw == null){ @@ -95,9 +93,7 @@ public class PathfindingNode implements AITreeNode { Vector3d entityPos = EntityUtils.getPosition(entity); - List path = pathfindingManager.findPath(entityPos, targetPos); - path.add(targetPos); - PathingProgressiveData pathingProgressiveData = new PathingProgressiveData(path); + PathingProgressiveData pathingProgressiveData = pathfindingManager.findPathAsync(entityPos, targetPos); PathfindingNode.setPathfindingData(blackboard, pathingProgressiveData); } @@ -105,9 +101,15 @@ public class PathfindingNode implements AITreeNode { throw new Error("Failed to find path! Unhandled"); } + //check if the path has been found + PathingProgressiveData pathingProgressiveData = PathfindingNode.getPathfindingData(blackboard); + if(!pathingProgressiveData.isReady()){ + return AITreeNodeResult.RUNNING; + } + + Vector3d entityPos = EntityUtils.getPosition(entity); - PathingProgressiveData pathingProgressiveData = PathfindingNode.getPathfindingData(blackboard); Vector3d currentPathPos = null; if(pathingProgressiveData.getCurrentPoint() < pathingProgressiveData.getPoints().size()){ currentPathPos = pathingProgressiveData.getPoints().get(pathingProgressiveData.getCurrentPoint()); diff --git a/src/main/java/electrosphere/server/ai/services/AIService.java b/src/main/java/electrosphere/server/ai/services/AIService.java index 3e6f3ff4..637dc9b8 100644 --- a/src/main/java/electrosphere/server/ai/services/AIService.java +++ b/src/main/java/electrosphere/server/ai/services/AIService.java @@ -10,4 +10,9 @@ public interface AIService { */ public void exec(); + /** + * Shuts down the service + */ + public void shutdown(); + } diff --git a/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java b/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java index 7036a377..ca723d72 100644 --- a/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java +++ b/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java @@ -61,4 +61,8 @@ public class NearbyEntityService implements AIService { return blackboard.has(BlackboardKeys.NEARBY_ENTITIES); } + @Override + public void shutdown() { + } + } diff --git a/src/main/java/electrosphere/server/ai/services/PathfindingService.java b/src/main/java/electrosphere/server/ai/services/PathfindingService.java new file mode 100644 index 00000000..19db186a --- /dev/null +++ b/src/main/java/electrosphere/server/ai/services/PathfindingService.java @@ -0,0 +1,58 @@ +package electrosphere.server.ai.services; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.joml.Vector3d; + +import electrosphere.server.datacell.interfaces.VoxelCellManager; +import electrosphere.server.pathfinding.recast.PathingProgressiveData; +import electrosphere.server.pathfinding.voxel.VoxelPathfinder; + +/** + * Service for performing pathfinding + */ +public class PathfindingService implements AIService { + + /** + * The executor service + */ + ExecutorService executorService; + + /** + * Queues a pathfinding job + * @param start The start point + * @param end The end point + * @param pathfinder The pathfinder object + * @param voxelCellManager The voxel cell manager + * @return The object that will eventually hold the pathfinding data + */ + public PathingProgressiveData queuePathfinding(Vector3d start, Vector3d end, VoxelPathfinder pathfinder, VoxelCellManager voxelCellManager){ + PathingProgressiveData rVal = new PathingProgressiveData(end); + if(executorService == null){ + executorService = Executors.newFixedThreadPool(2); + } + executorService.submit(() -> { + List points = pathfinder.findPath(voxelCellManager, start, end, VoxelPathfinder.DEFAULT_MAX_COST); + points.add(end); + rVal.setPoints(points); + rVal.setReady(true); + }); + return rVal; + } + + @Override + public void exec() { + //No synchronous logic required yet + } + + @Override + public void shutdown() { + if(executorService != null){ + executorService.shutdownNow(); + } + executorService = null; + } + +} diff --git a/src/main/java/electrosphere/server/ai/services/TimerService.java b/src/main/java/electrosphere/server/ai/services/TimerService.java index 82cd2a54..d7dc5c7f 100644 --- a/src/main/java/electrosphere/server/ai/services/TimerService.java +++ b/src/main/java/electrosphere/server/ai/services/TimerService.java @@ -93,5 +93,9 @@ public class TimerService implements AIService { } timerMap.put(timerId,frameCount); } + + @Override + public void shutdown() { + } } diff --git a/src/main/java/electrosphere/server/ai/trees/creature/MoveToTree.java b/src/main/java/electrosphere/server/ai/trees/creature/MoveToTree.java index bc3abee1..7f8cd401 100644 --- a/src/main/java/electrosphere/server/ai/trees/creature/MoveToTree.java +++ b/src/main/java/electrosphere/server/ai/trees/creature/MoveToTree.java @@ -10,6 +10,7 @@ import electrosphere.server.ai.nodes.checks.spatial.TargetRangeCheckNode; import electrosphere.server.ai.nodes.meta.DataDeleteNode; import electrosphere.server.ai.nodes.meta.collections.SelectorNode; import electrosphere.server.ai.nodes.meta.collections.SequenceNode; +import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; import electrosphere.server.ai.nodes.meta.decorators.RunnerNode; import electrosphere.server.ai.nodes.meta.decorators.SucceederNode; import electrosphere.server.ai.nodes.plan.PathfindingNode; @@ -47,7 +48,9 @@ public class MoveToTree { //not in range of target, keep moving towards it new SequenceNode( + new PublishStatusNode("Thinking about pathing"), PathfindingNode.createPathEntity(targetKey), + new PublishStatusNode("Moving"), new FaceTargetNode(BlackboardKeys.PATHFINDING_POINT), new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD)) ) @@ -76,7 +79,9 @@ public class MoveToTree { //not in range of target, keep moving towards it new SequenceNode( + new PublishStatusNode("Thinking about pathing"), PathfindingNode.createPathEntity(targetKey), + new PublishStatusNode("Moving"), new FaceTargetNode(BlackboardKeys.PATHFINDING_POINT), new RunnerNode(new MoveStartNode(MovementRelativeFacing.FORWARD)) ) diff --git a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java index 72e0a62a..015cf83a 100644 --- a/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java +++ b/src/main/java/electrosphere/server/datacell/gridded/GriddedDataCellManager.java @@ -37,6 +37,7 @@ import electrosphere.server.datacell.interfaces.VoxelCellManager; import electrosphere.server.datacell.physics.PhysicsDataCell; import electrosphere.server.entity.ServerContentManager; import electrosphere.server.entity.serialization.ContentSerialization; +import electrosphere.server.pathfinding.recast.PathingProgressiveData; import electrosphere.server.pathfinding.voxel.VoxelPathfinder; import electrosphere.server.physics.block.manager.ServerBlockManager; import electrosphere.server.physics.fluid.manager.ServerFluidChunk; @@ -1140,4 +1141,9 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager return serverTerrainManager.getChunk(worldX, worldY, worldZ, ServerChunkCache.STRIDE_FULL_RES); } + @Override + public PathingProgressiveData findPathAsync(Vector3d start, Vector3d end) { + return Globals.aiManager.getPathfindingService().queuePathfinding(start, end, this.pathfinder, this); + } + } diff --git a/src/main/java/electrosphere/server/datacell/interfaces/PathfindingManager.java b/src/main/java/electrosphere/server/datacell/interfaces/PathfindingManager.java index 73a4f60f..dfc2673a 100644 --- a/src/main/java/electrosphere/server/datacell/interfaces/PathfindingManager.java +++ b/src/main/java/electrosphere/server/datacell/interfaces/PathfindingManager.java @@ -4,6 +4,8 @@ import java.util.List; import org.joml.Vector3d; +import electrosphere.server.pathfinding.recast.PathingProgressiveData; + /** * Performs pathfinding */ @@ -17,4 +19,12 @@ public interface PathfindingManager { */ public List findPath(Vector3d start, Vector3d end); + /** + * Solves a path + * @param start The start point + * @param end The end point + * @return The path if it exists, null otherwise + */ + public PathingProgressiveData findPathAsync(Vector3d start, Vector3d end); + } diff --git a/src/main/java/electrosphere/server/pathfinding/recast/PathingProgressiveData.java b/src/main/java/electrosphere/server/pathfinding/recast/PathingProgressiveData.java index 0e4b3e64..907b2457 100644 --- a/src/main/java/electrosphere/server/pathfinding/recast/PathingProgressiveData.java +++ b/src/main/java/electrosphere/server/pathfinding/recast/PathingProgressiveData.java @@ -20,11 +20,22 @@ public class PathingProgressiveData { int currentPoint; /** - * Constructor - * @param points The points for the path + * The goal position */ - public PathingProgressiveData(List points){ - this.points = points; + Vector3d goal; + + /** + * Tracks whether this data is ready to be used or not + */ + boolean ready = false; + + + /** + * Constructor + * @param goal The goal point + */ + public PathingProgressiveData(Vector3d goal){ + this.goal = goal; this.currentPoint = 0; } @@ -60,6 +71,37 @@ public class PathingProgressiveData { this.currentPoint = currentPoint; } + /** + * Gets the goal point + * @return The goal point + */ + public Vector3d getGoal() { + return goal; + } + + /** + * Sets the goal point + * @param goal The goal point + */ + public void setGoal(Vector3d goal) { + this.goal = goal; + } + + /** + * Gets whether this data is ready or not + * @return true if it is ready, false otherwise + */ + public boolean isReady() { + return ready; + } + + /** + * Sets the ready status of this data + * @param ready true if the data is ready, false otherwise + */ + public void setReady(boolean ready) { + this.ready = ready; + } }