From 25fd3aa93cebcb6675d4dcc41e07ab5d8a089bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lancelot=20de=20Ferri=C3=A8re?= Date: Sat, 7 Mar 2026 11:23:43 +0100 Subject: [PATCH] Small optimisation for VertexPathfinder edge handling. Previously, edges where bundled collected first then sorted in 4 AA and 1 unaligned bucket. We can separate the unaligned edges right away, which is a little faster. Also make sure Vertex::pred is initialized. --- .../simulation2/helpers/VertexPathfinder.cpp | 61 +++++++++++-------- source/simulation2/helpers/VertexPathfinder.h | 8 +-- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/source/simulation2/helpers/VertexPathfinder.cpp b/source/simulation2/helpers/VertexPathfinder.cpp index 9a507a5651..8522091f32 100644 --- a/source/simulation2/helpers/VertexPathfinder.cpp +++ b/source/simulation2/helpers/VertexPathfinder.cpp @@ -273,7 +273,7 @@ typedef PriorityQueueHeap VertexPriorityQueue; * navcells (for impassable terrain). * Navcells i0 <= i <= i1, j0 <= j <= j1 will be considered. */ -static void AddTerrainEdges(std::vector& edges, std::vector& vertexes, +static void AddTerrainEdges(std::vector& edgesAligned, std::vector& edgesUnaligned, std::vector& vertexes, int i0, int j0, int i1, int j1, pass_class_t passClass, const Grid& grid) { @@ -368,7 +368,10 @@ static void AddTerrainEdges(std::vector& edges, std::vector& verte CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(Pathfinding::NAVCELL_SIZE); CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(Pathfinding::NAVCELL_SIZE); - edges.emplace_back(v0, v1); + if (v0.X == v1.X || v0.Y == v1.Y) + edgesAligned.emplace_back(v0, v1); + else + edgesUnaligned.emplace_back(v0, v1); ia = segmentsR[n]; ib = ia + 1; @@ -390,7 +393,10 @@ static void AddTerrainEdges(std::vector& edges, std::vector& verte CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(Pathfinding::NAVCELL_SIZE); CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(Pathfinding::NAVCELL_SIZE); - edges.emplace_back(v0, v1); + if (v0.X == v1.X || v0.Y == v1.Y) + edgesAligned.emplace_back(v0, v1); + else + edgesUnaligned.emplace_back(v0, v1); ia = segmentsL[n]; ib = ia + 1; @@ -428,7 +434,10 @@ static void AddTerrainEdges(std::vector& edges, std::vector& verte CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(Pathfinding::NAVCELL_SIZE); CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(Pathfinding::NAVCELL_SIZE); - edges.emplace_back(v0, v1); + if (v0.X == v1.X || v0.Y == v1.Y) + edgesAligned.emplace_back(v0, v1); + else + edgesUnaligned.emplace_back(v0, v1); ja = segmentsU[n]; jb = ja + 1; @@ -450,7 +459,10 @@ static void AddTerrainEdges(std::vector& edges, std::vector& verte CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(Pathfinding::NAVCELL_SIZE); CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(Pathfinding::NAVCELL_SIZE); - edges.emplace_back(v0, v1); + if (v0.X == v1.X || v0.Y == v1.Y) + edgesAligned.emplace_back(v0, v1); + else + edgesUnaligned.emplace_back(v0, v1); ja = segmentsD[n]; jb = ja + 1; @@ -461,9 +473,8 @@ static void AddTerrainEdges(std::vector& edges, std::vector& verte } static void SplitAAEdges(const CFixedVector2D& a, - const std::vector& edges, + const std::vector& edgesAligned, const std::vector& squares, - std::vector& edgesUnaligned, std::vector& edgesLeft, std::vector& edgesRight, std::vector& edgesBottom, std::vector& edgesTop) { @@ -479,7 +490,8 @@ static void SplitAAEdges(const CFixedVector2D& a, edgesTop.emplace_back(square.p1, square.p0.X); } - for (const Edge& edge : edges) + // Process aligned edges + for (const Edge& edge : edgesAligned) { if (edge.p0.X == edge.p1.X) { @@ -511,8 +523,6 @@ static void SplitAAEdges(const CFixedVector2D& a, edgesTop.emplace_back(edge.p0, edge.p1.X); } } - else - edgesUnaligned.push_back(edge); } } @@ -576,10 +586,10 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, // Add domain edges // (Inside-out square, so edges are in reverse from the usual direction.) - m_Edges.emplace_back(CFixedVector2D(rangeXMin, rangeZMin), CFixedVector2D(rangeXMin, rangeZMax)); - m_Edges.emplace_back(CFixedVector2D(rangeXMin, rangeZMax), CFixedVector2D(rangeXMax, rangeZMax)); - m_Edges.emplace_back(CFixedVector2D(rangeXMax, rangeZMax), CFixedVector2D(rangeXMax, rangeZMin)); - m_Edges.emplace_back(CFixedVector2D(rangeXMax, rangeZMin), CFixedVector2D(rangeXMin, rangeZMin)); + m_EdgesAligned.emplace_back(CFixedVector2D(rangeXMin, rangeZMin), CFixedVector2D(rangeXMin, rangeZMax)); + m_EdgesAligned.emplace_back(CFixedVector2D(rangeXMin, rangeZMax), CFixedVector2D(rangeXMax, rangeZMax)); + m_EdgesAligned.emplace_back(CFixedVector2D(rangeXMax, rangeZMax), CFixedVector2D(rangeXMax, rangeZMin)); + m_EdgesAligned.emplace_back(CFixedVector2D(rangeXMax, rangeZMin), CFixedVector2D(rangeXMin, rangeZMin)); // Add the start point to the graph @@ -687,10 +697,11 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, m_EdgeSquares.emplace_back(ev1, ev3); else { - m_Edges.emplace_back(ev0, ev1); - m_Edges.emplace_back(ev1, ev2); - m_Edges.emplace_back(ev2, ev3); - m_Edges.emplace_back(ev3, ev0); + // For non-axis-aligned edges, add them to unaligned collection + m_EdgesUnaligned.emplace_back(ev0, ev1); + m_EdgesUnaligned.emplace_back(ev1, ev2); + m_EdgesUnaligned.emplace_back(ev2, ev3); + m_EdgesUnaligned.emplace_back(ev3, ev0); } } @@ -700,7 +711,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, u16 i0, j0, i1, j1; Pathfinding::NearestNavcell(rangeXMin, rangeZMin, i0, j0, m_GridSize, m_GridSize); Pathfinding::NearestNavcell(rangeXMax, rangeZMax, i1, j1, m_GridSize, m_GridSize); - AddTerrainEdges(m_Edges, m_Vertexes, i0, j0, i1, j1, request.passClass, *m_TerrainOnlyGrid); + AddTerrainEdges(m_EdgesAligned, m_EdgesUnaligned, m_Vertexes, i0, j0, i1, j1, request.passClass, *m_TerrainOnlyGrid); } // Clip out vertices that are inside an edgeSquare (i.e. trivially unreachable) @@ -725,7 +736,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, ENSURE(m_Vertexes.size() < 65536); // We store array indexes as u16. - g_VertexPathfinderDebugOverlay.DebugRenderGraph(cmpObstructionManager->GetSimContext(), m_Vertexes, m_Edges, m_EdgeSquares); + g_VertexPathfinderDebugOverlay.DebugRenderGraph(cmpObstructionManager->GetSimContext(), m_Vertexes, m_EdgesAligned, m_EdgeSquares); // Do an A* search over the vertex/visibility graph: @@ -738,7 +749,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, // The path found will be at most this many times worse than the optimal path, which is likely OK. fixed heuristicWeight = fixed::FromInt(1); // If we have a lot of edges to check, relax the constraint more. - if (m_Edges.size() > 100 || m_EdgeSquares.size() > 100) + if (m_EdgesAligned.size() + m_EdgesUnaligned.size() > 100 || m_EdgeSquares.size() > 100) heuristicWeight = heuristicWeight.MulDiv(fixed::FromInt(5), fixed::FromInt(3)); else heuristicWeight = heuristicWeight.MulDiv(fixed::FromInt(4), fixed::FromInt(3)); @@ -776,12 +787,11 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, if (m_EdgeSquares.size() > 8) std::partial_sort(m_EdgeSquares.begin(), m_EdgeSquares.begin() + 8, m_EdgeSquares.end(), SquareSort(m_Vertexes[curr.id].p)); - m_EdgesUnaligned.clear(); m_EdgesLeft.clear(); m_EdgesRight.clear(); m_EdgesBottom.clear(); m_EdgesTop.clear(); - SplitAAEdges(m_Vertexes[curr.id].p, m_Edges, m_EdgeSquares, m_EdgesUnaligned, m_EdgesLeft, m_EdgesRight, m_EdgesBottom, m_EdgesTop); + SplitAAEdges(m_Vertexes[curr.id].p, m_EdgesAligned, m_EdgeSquares, m_EdgesLeft, m_EdgesRight, m_EdgesBottom, m_EdgesTop); // Do the same partial sort for unaligned edges, which helps in e.g. forests. // (Higher values because here we need ot account for all 4 edges, @@ -888,12 +898,11 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, for (u16 id = idBest; id != START_VERTEX_ID; id = m_Vertexes[id].pred) path.m_Waypoints.emplace_back(Waypoint{ m_Vertexes[id].p.X, m_Vertexes[id].p.Y }); - - m_Edges.clear(); + m_EdgesAligned.clear(); + m_EdgesUnaligned.clear(); m_EdgeSquares.clear(); m_Vertexes.clear(); - m_EdgesUnaligned.clear(); m_EdgesLeft.clear(); m_EdgesRight.clear(); m_EdgesBottom.clear(); diff --git a/source/simulation2/helpers/VertexPathfinder.h b/source/simulation2/helpers/VertexPathfinder.h index 89946280d5..95800dc9c8 100644 --- a/source/simulation2/helpers/VertexPathfinder.h +++ b/source/simulation2/helpers/VertexPathfinder.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2025 Wildfire Games. +/* Copyright (C) 2026 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -46,7 +46,7 @@ struct Vertex CFixedVector2D p; fixed g, h; - u16 pred; + u16 pred = 0; u8 status; u8 quadInward : 4; // the quadrant which is inside the shape (or NONE) u8 quadOutward : 4; // the quadrants of the next point on the path which this vertex must be in, given 'pred' @@ -110,7 +110,6 @@ private: // These vectors are expensive to recreate on every call, so we cache them here. // They are made mutable to allow using them in the otherwise const ComputeShortPath. - mutable std::vector m_EdgesUnaligned; mutable std::vector m_EdgesLeft; mutable std::vector m_EdgesRight; mutable std::vector m_EdgesBottom; @@ -121,7 +120,8 @@ private: mutable std::vector m_Vertexes; // List of collision edges - paths must never cross these. // (Edges are one-sided so intersections are fine in one direction, but not the other direction.) - mutable std::vector m_Edges; + mutable std::vector m_EdgesAligned; + mutable std::vector m_EdgesUnaligned; mutable std::vector m_EdgeSquares; // Axis-aligned squares; equivalent to 4 edges. };