From a78931205f7e6835c2e4d4760ac66979a9e3b3d2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 01:41:18 +0300 Subject: [PATCH] feat: async pathfinding --- src/graphics/render/WorldRenderer.cpp | 68 +++++------ src/io/settings_io.cpp | 3 + src/logic/LevelController.cpp | 4 + .../scripting/lua/libs/libpathfinding.cpp | 39 ++++++- src/settings.hpp | 6 + src/voxels/Pathfinding.cpp | 109 +++++++++--------- src/voxels/Pathfinding.hpp | 51 ++++---- 7 files changed, 168 insertions(+), 112 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 149f5766..8db4f4c7 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -274,6 +274,39 @@ void WorldRenderer::renderLines( } } +static void draw_route( + LinesRenderer& lines, const voxels::Agent& agent +) { + const auto& route = agent.route; + if (!route.found) + return; + + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines.pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } +} + void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, @@ -389,40 +422,7 @@ void WorldRenderer::renderFrame( // In-world lines if (debug) { for (const auto& [_, agent] : level.pathfinding->getAgents()) { - const auto& route = agent.route; - if (!route.found) - continue; - for (const auto& blocked : route.visited) { - lines->pushLine( - glm::vec3(blocked) + glm::vec3(0.5f), - glm::vec3(blocked) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 0, 0, 1) - ); - } - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); - - if (i == 1) { - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } - - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines->pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } + draw_route(*lines, agent); } } diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index cdbf567e..b69be64c 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -83,6 +83,9 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("language", &settings.ui.language); builder.add("world-preview-size", &settings.ui.worldPreviewSize); + builder.section("pathfinding"); + builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent); + builder.section("debug"); builder.add("generator-test-mode", &settings.debug.generatorTestMode); builder.add("do-write-lights", &settings.debug.doWriteLights); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index f1aca628..18225ea0 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -11,6 +11,7 @@ #include "objects/Player.hpp" #include "physics/Hitbox.hpp" #include "voxels/Chunks.hpp" +#include "voxels/Pathfinding.hpp" #include "scripting/scripting.hpp" #include "lighting/Lighting.hpp" #include "settings.hpp" @@ -69,6 +70,9 @@ LevelController::LevelController( } void LevelController::update(float delta, bool pause) { + level->pathfinding->performAllAsync( + settings.pathfinding.stepsPerAsyncAgent.get() + ); for (const auto& [_, player] : *level->players) { if (player->isSuspended()) { continue; diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index e66b1f2a..4d2374a1 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -31,8 +31,10 @@ static int l_make_route(lua::State* L) { if (auto agent = get_agent(L)) { auto start = lua::tovec3(L, 2); auto target = lua::tovec3(L, 3); - auto route = level->pathfinding->perform(*agent, start, target); - agent->route = route; + agent->state = {}; + agent->start = start; + agent->target = target; + auto route = level->pathfinding->perform(*agent); if (!route.found) { return 0; } @@ -46,6 +48,37 @@ static int l_make_route(lua::State* L) { return 0; } +static int l_make_route_async(lua::State* L) { + if (auto agent = get_agent(L)) { + auto start = lua::tovec3(L, 2); + auto target = lua::tovec3(L, 3); + agent->state = {}; + agent->start = start; + agent->target = target; + level->pathfinding->perform(*agent, 0); + } + return 0; +} + +static int l_pull_route(lua::State* L) { + if (auto agent = get_agent(L)) { + auto& route = agent->route; + if (!agent->state.finished) { + return 0; + } + if (!route.found) { + return lua::createtable(L, 0, 0); + } + lua::createtable(L, route.nodes.size(), 0); + for (int i = 0; i < route.nodes.size(); i++) { + lua::pushvec3(L, route.nodes[i].pos); + lua::rawseti(L, i + 1); + } + return 1; + } + return 0; +} + static int l_set_max_visited_blocks(lua::State* L) { if (auto agent = get_agent(L)) { agent->maxVisitedBlocks = lua::tointeger(L, 2); @@ -58,6 +91,8 @@ const luaL_Reg pathfindinglib[] = { {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, + {"make_route_async", lua::wrap}, + {"pull_route", lua::wrap}, {"set_max_visited", lua::wrap}, {NULL, NULL} }; diff --git a/src/settings.hpp b/src/settings.hpp index d4c21688..d51079a2 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -85,6 +85,11 @@ struct GraphicsSettings { IntegerSetting denseRenderDistance {56, 0, 10'000}; }; +struct PathfindingSettings { + /// @brief Max visited blocks by an agent per async tick + IntegerSetting stepsPerAsyncAgent {256, 1, 2048}; +}; + struct DebugSettings { /// @brief Turns off chunks saving/loading FlagSetting generatorTestMode {false}; @@ -109,4 +114,5 @@ struct EngineSettings { DebugSettings debug; UiSettings ui; NetworkSettings network; + PathfindingSettings pathfinding; }; diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 829bbde8..b39fe99e 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -1,8 +1,5 @@ #include "Pathfinding.hpp" -#include -#include - #include "world/Level.hpp" #include "voxels/GlobalChunks.hpp" #include "voxels/Chunk.hpp" @@ -13,19 +10,6 @@ inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2) using namespace voxels; -struct Node { - glm::ivec3 pos; - glm::ivec3 parent; - float gScore; - float fScore; -}; - -struct NodeLess { - bool operator()(const Node& l, const Node& r) const { - return l.fScore > r.fScore; - } -}; - static float heuristic(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } @@ -79,50 +63,70 @@ int Pathfinding::createAgent() { return id; } -Route Pathfinding::perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end -) { +void Pathfinding::performAllAsync(int stepsPerAgent) { + for (auto& [id, agent] : agents) { + if (agent.state.finished) { + continue; + } + perform(agent, stepsPerAgent); + } +} + +Route Pathfinding::perform(Agent& agent, int maxVisited) { using namespace blocks_agent; Route route {}; - std::priority_queue, NodeLess> queue; - queue.push({start, {}, 0, heuristic(start, end)}); - - std::unordered_set blocked; - std::unordered_map parents; + State state = std::move(agent.state); + if (state.queue.empty()) { + state.queue.push({agent.start, {}, 0, heuristic(agent.start, agent.target)}); + } const auto& chunks = *level.chunks; int height = std::max(agent.height, 1); - glm::ivec3 nearest = start; - float minHScore = heuristic(start, end); + if (state.nearest == glm::ivec3(0)) { + state.nearest = agent.start; + state.minHScore = heuristic(agent.start, agent.target); + } + int visited = -1; - while (!queue.empty()) { - if (blocked.size() == agent.maxVisitedBlocks) { + while (!state.queue.empty()) { + if (state.blocked.size() == agent.maxVisitedBlocks) { if (agent.mayBeIncomplete) { - restore_route(route, nearest, parents); - route.nodes.push_back({start}); + restore_route(route, state.nearest, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } break; } - auto node = queue.top(); - queue.pop(); + visited++; + if (visited == maxVisited) { + state.finished = false; + agent.state = std::move(state); + return {}; + } - if (node.pos.x == end.x && - glm::abs((node.pos.y - end.y) / height) == 0 && - node.pos.z == end.z) { - restore_route(route, node.pos, parents); - route.nodes.push_back({start}); + auto node = state.queue.top(); + state.queue.pop(); + + if (node.pos.x == agent.target.x && + glm::abs((node.pos.y - agent.target.y) / height) == 0 && + node.pos.z == agent.target.z) { + restore_route(route, node.pos, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } - blocked.emplace(node.pos); + state.blocked.emplace(node.pos); glm::ivec2 neighbors[8] { {0, 1}, {1, 0}, {0, -1}, {-1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, @@ -139,7 +143,7 @@ Route Pathfinding::perform( } pos.y = surface; auto point = pos + glm::ivec3(offset.x, 0, offset.y); - if (blocked.find(point) != blocked.end()) { + if (state.blocked.find(point) != state.blocked.end()) { continue; } @@ -153,22 +157,23 @@ Route Pathfinding::perform( int score = glm::abs(node.pos.y - pos.y) * 10; float sum = glm::abs(offset.x) + glm::abs(offset.y); float gScore = - node.gScore + glm::max(sum, SQRT2) * 0.5f + sum * 0.5f + score; - const auto& found = parents.find(point); - if (found == parents.end()) { - float hScore = heuristic(point, end); - if (hScore < minHScore) { - minHScore = hScore; - nearest = point; + node.gScore + sum + score; + const auto& found = state.parents.find(point); + if (found == state.parents.end()) { + float hScore = heuristic(point, agent.target); + if (hScore < state.minHScore) { + state.minHScore = hScore; + state.nearest = point; } - float fScore = gScore + hScore; + float fScore = gScore * 0.75f + hScore; Node nNode {point, node.pos, gScore, fScore}; - parents[point] = node; - queue.push(nNode); + state.parents[point] = node; + state.queue.push(nNode); } } } - return route; + agent.state = std::move(state); + return {}; } Agent* Pathfinding::getAgent(int id) { diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index 4b0b0008..c89841e5 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -21,7 +22,28 @@ namespace voxels { struct Route { bool found; std::vector nodes; - std::unordered_set visited; + }; + + struct Node { + glm::ivec3 pos; + glm::ivec3 parent; + float gScore; + float fScore; + }; + + struct NodeLess { + bool operator()(const Node& l, const Node& r) const { + return l.fScore > r.fScore; + } + }; + + struct State { + std::priority_queue, NodeLess> queue; + std::unordered_set blocked; + std::unordered_map parents; + glm::ivec3 nearest; + float minHScore; + bool finished = true; }; struct Agent { @@ -32,26 +54,7 @@ namespace voxels { glm::ivec3 start; glm::ivec3 target; Route route; - }; - - struct Map { - int width; - int height; - std::unique_ptr map; - - Map(int width, int height) - : width(width), - height(height), - map(std::make_unique(width * height)) { - } - - uint8_t& operator[](int i) { - return map[i]; - } - - const uint8_t& operator[](int i) const { - return map[i]; - } + State state {}; }; class Pathfinding { @@ -60,9 +63,9 @@ namespace voxels { int createAgent(); - Route perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end - ); + void performAllAsync(int stepsPerAgent); + + Route perform(Agent& agent, int maxVisited = -1); Agent* getAgent(int id);