载入中...
搜索中...
未找到
RTSTraffic.cpp
浏览该文件的文档.
1#include "map/Pathfinder.h"
2#include "rts/RTS.h"
4
5#include <algorithm>
6#include <array>
7#include <limits>
8#include <map>
9#include <set>
10
11namespace eve::rts {
12namespace {
13using Cell = std::pair<int, int>;
14constexpr std::array<Cell, 4> directions{{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}};
15
16std::optional<Cell> cellAt(WorldPosition point, const NavigationGrid& grid) {
17 const double x = (static_cast<double>(point.x) - grid.originX) / grid.cellSize;
18 const double y = (static_cast<double>(point.y) - grid.originY) / grid.cellSize;
19 constexpr double limit = static_cast<double>(std::numeric_limits<int>::max()) - 4096.0;
20 if (!std::isfinite(x) || !std::isfinite(y) || std::abs(x) > limit || std::abs(y) > limit) return std::nullopt;
21 return Cell{static_cast<int>(std::lround(x)), static_cast<int>(std::lround(y))};
22}
23
24bool isNarrow(const map::Pathfinder& pathfinder, Cell cell) {
25 if (!pathfinder.isWalkable(cell.first, cell.second)) return false;
26 int exits = 0;
27 for (const auto& [dx, dy] : directions)
28 if (pathfinder.isWalkable(cell.first + dx, cell.second + dy)) ++exits;
29 return exits <= 2;
30}
31
32struct Corridor {
33 std::set<Cell> cells;
34 std::set<Cell> exits;
35};
36
37Result<Corridor> discoverCorridor(const map::Pathfinder& pathfinder, Cell seed) {
38 Corridor result;
39 result.cells.insert(seed);
40 std::vector<Cell> pending{seed};
41 for (std::size_t i = 0; i < pending.size(); ++i) {
42 const auto cell = pending[i];
43 int narrowNeighbors = 0;
44 for (const auto& [dx, dy] : directions) {
45 const Cell next{cell.first + dx, cell.second + dy};
46 if (!pathfinder.isWalkable(next.first, next.second)) continue;
47 if (!isNarrow(pathfinder, next)) {
48 result.exits.insert(next);
49 continue;
50 }
51 ++narrowNeighbors;
52 if (result.cells.contains(next)) continue;
53 if (result.cells.size() >= 1024)
56 "RTS corridor exceeds the 1024-cell reservation budget", "traffic.corridor"));
57 result.cells.insert(next);
58 pending.push_back(next);
59 }
60 if (narrowNeighbors <= 1) result.exits.insert(cell);
61 }
62 return Result<Corridor>::success(std::move(result));
63}
64
65std::optional<WorldPosition> evacuationStep(const map::Pathfinder& pathfinder, const NavigationGrid& grid,
66 const Corridor& corridor, Cell exit, Cell current, WorldPosition position,
67 float radius, std::set<Cell>& parking) {
68 if (corridor.cells.contains(exit)) return std::nullopt;
69 const auto world = [&](Cell cell) {
70 return WorldPosition{grid.originX + static_cast<float>(cell.first) * grid.cellSize,
71 grid.originY + static_cast<float>(cell.second) * grid.cellSize};
72 };
73 std::optional<Cell> bay;
74 for (const auto& [dx, dy] : directions) {
75 if (!corridor.cells.contains({exit.first - dx, exit.second - dy})) continue;
76 for (int forward = 0; forward < 3 && !bay; ++forward) {
77 for (int side = 1; side <= 3 && !bay; ++side) {
78 for (int sign : {-1, 1}) {
79 const Cell candidate{exit.first + dx * forward - dy * side * sign,
80 exit.second + dy * forward + dx * side * sign};
81 if (!parking.contains(candidate) && !corridor.cells.contains(candidate) &&
82 systems_internal::isFormationSegmentClear(pathfinder, grid, world(exit), world(candidate),
83 radius)) {
84 bay = candidate;
85 break;
86 }
87 }
88 }
89 }
90 }
91 if (!bay) return std::nullopt;
92 parking.insert(*bay);
93 WorldPosition target = world(*bay);
94 if (corridor.cells.contains(current)) {
95 std::map<Cell, Cell> towardExit;
96 towardExit.emplace(exit, exit);
97 std::vector<Cell> pending{exit};
98 for (std::size_t i = 0; i < pending.size() && !towardExit.contains(current); ++i) {
99 for (const auto& [dx, dy] : directions) {
100 const Cell next{pending[i].first + dx, pending[i].second + dy};
101 if (corridor.cells.contains(next) && towardExit.emplace(next, pending[i]).second)
102 pending.push_back(next);
103 }
104 }
105 const auto next = towardExit.find(current);
106 if (next == towardExit.end()) return std::nullopt;
107 target = world(next->second);
108 }
111 if (!systems_internal::isFormationSegmentClear(pathfinder, grid, position, target, radius)) return std::nullopt;
112 }
113 if (std::hypot(static_cast<double>(target.x) - position.x, static_cast<double>(target.y) - position.y) < 1e-4)
114 return std::nullopt;
115 return target;
116}
117} // namespace
118
120 if (!std::isfinite(grid.cellSize) || grid.cellSize <= 0.f || !std::isfinite(grid.originX) ||
121 !std::isfinite(grid.originY))
124 "RTS traffic grid requires finite origin and positive cell size", "traffic.grid"));
125 struct Candidate {
126 Unit::Navigation* navigation;
128 int priority;
129 Cell current;
130 Cell exit;
131 Cell next;
132 bool occupant;
134 float radius;
135 };
136 std::map<Cell, Corridor> corridors;
137 std::map<Cell, Cell> owners;
138 std::map<Cell, std::vector<Candidate>> contenders;
139 std::vector<Unit::Navigation*> recovering;
140 std::size_t processed = 0;
141 auto view =
142 ecs::View<Unit, Unit::Identity, Unit::Motion, Unit::Navigation, Unit::Orders, Unit::Containment, Unit::Crowd>();
143 for (auto it = view.begin(); it != view.end(); ++it) {
144 auto [identity, motion, navigation, orders, containment, crowd] = *it;
145 if (!identity)
147 DiagnosticCode::InvariantViolation, "RTS traffic candidate has no identity", "unit.identity"));
148 navigation->trafficWaiting = false;
149 if (navigation->trafficRecoveryTarget) recovering.push_back(navigation);
150 navigation->trafficRecoveryTarget.reset();
151 if (containment->container.isBound()) continue;
152 auto currentOrder = systems_internal::readCurrent(orders->values);
153 if (!currentOrder) return Result<std::size_t>::failure(currentOrder.status());
154 const auto& order = currentOrder.value();
155 if (!order || !systems_internal::isMovementOrder(order->kind) || navigation->unreachable) continue;
156 const bool hasPath = navigation->plannedOrderId == order->id;
157 const auto current = cellAt({motion->x, motion->y}, grid);
158 const auto goal = cellAt(hasPath ? navigation->plannedGoal : order->target, grid);
159 if (!current || !goal)
161 "RTS traffic coordinates exceed the grid index range",
162 "traffic.position"));
163 ++processed;
164 std::optional<Cell> seed;
165 if (isNarrow(pathfinder, *current)) seed = current;
166 const std::size_t begin = hasPath ? navigation->waypointIndex : navigation->waypoints.size();
167 for (std::size_t index = begin; !seed && index < navigation->waypoints.size() && index - begin < 3; ++index) {
168 const auto cell = cellAt(navigation->waypoints[index], grid);
169 if (cell && isNarrow(pathfinder, *cell)) seed = cell;
170 }
171 if (!seed) continue;
172 auto owner = owners.find(*seed);
173 if (owner == owners.end()) {
174 auto discovered = discoverCorridor(pathfinder, *seed);
175 if (!discovered) return Result<std::size_t>::failure(discovered.status());
176 auto corridor = std::move(discovered).takeValue();
177 const Cell key = *corridor.cells.begin();
178 for (auto cell : corridor.cells) owners.emplace(cell, key);
179 corridors.emplace(key, std::move(corridor));
180 owner = owners.find(*seed);
181 }
182 const Cell key = owner->second;
183 const auto& corridor = corridors.at(key);
184 Cell exit = *goal;
185 if (!corridor.exits.empty()) {
186 exit = *std::min_element(corridor.exits.begin(), corridor.exits.end(), [&](Cell left, Cell right) {
187 const auto distance = [&](Cell cell) {
188 return std::hypot(static_cast<double>(cell.first) - goal->first,
189 static_cast<double>(cell.second) - goal->second);
190 };
191 const double a = distance(left), b = distance(right);
192 return a != b ? a < b : left < right;
193 });
194 }
195 Cell next = *goal;
196 if (begin < navigation->waypoints.size()) {
197 const auto point = cellAt(navigation->waypoints[begin], grid);
198 if (point) next = *point;
199 }
200 contenders[key].push_back({navigation,
201 identity->subject,
202 navigation->movementPriority,
203 *current,
204 exit,
205 next,
206 corridor.cells.contains(*current),
207 {motion->x, motion->y},
208 crowd->radius});
209 }
210 for (auto& [key, values] : contenders) {
211 std::sort(values.begin(), values.end(), [](const Candidate& left, const Candidate& right) {
212 if (left.occupant != right.occupant) return left.occupant;
213 if (left.priority != right.priority) return left.priority > right.priority;
214 return left.subject.format() < right.subject.format();
215 });
216 const Cell direction = values.front().exit;
217 std::set<Cell> reserved;
218 std::set<Cell> parking;
219 for (auto& value : values) {
220 const bool sameDirection = value.exit == direction;
221 const bool narrowNext = isNarrow(pathfinder, value.next);
222 value.navigation->trafficWaiting = !sameDirection || (narrowNext && !reserved.insert(value.next).second);
223 if (!sameDirection)
224 value.navigation->trafficRecoveryTarget =
225 evacuationStep(pathfinder, grid, corridors.at(key), direction, value.current, value.position,
226 value.radius, parking);
227 }
228 }
229 for (auto* navigation : recovering) {
230 if (navigation->trafficRecoveryTarget) continue;
231 navigation->plannedOrderId.clear();
232 navigation->trafficWaiting = true;
233 }
234 return Result<std::size_t>::success(processed,
236}
237void RTS::setNavigationProvider(map::Pathfinder* pathfinder, NavigationGrid grid,
238 NavigationEvent unreachable) noexcept {
239 auto* currentTable = ecs::current();
240 for (const auto& handle : units_) {
241 if (handle.table != currentTable) continue;
242 auto* unit = dynamic_cast<Unit*>(ecs::try_get(handle));
243 if (!unit || !unit->navigation()->trafficRecoveryTarget) continue;
244 unit->navigation()->trafficRecoveryTarget.reset();
245 unit->navigation()->trafficWaiting = false;
246 unit->navigation()->plannedOrderId.clear();
247 }
248 pathfinder_ = pathfinder;
249 navigationGrid_ = grid;
250 navigationEvent_ = std::move(unreachable);
251}
252
253} // namespace eve::rts
LogicalId target
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
int subject
Definition AnimSmr.cpp:163
std::vector< QuestEvent > pending
int priority
std::map< std::string, Var > values
std::uint32_t key
HexVec3 left
HexVec3 right
std::int32_t second
std::int32_t first
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
std::vector< std::int32_t > order
size_t directions
Definition OnnxLstm.cpp:29
World3D * world
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
float radius
std::uint32_t seed
Definition PointSet.cpp:807
PrimitiveHandle handle
float begin
std::set< Cell > exits
RTS module owner and phase-one composition profile entry point.
glm::mat4 view
RoadLaneDirection direction
double current
float dy
float dx
Cell cell
TacticalUnit * unit
ecs::EntityHandle side
int limit
Definition TreeMesh.cpp:164
uint32_t index
glm::vec3 point
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Strong, domain-neutral reference to a runtime subject.
Definition SubjectRef.h:26
Pathfinding facade. Single-agent: A*. Group (same goal): Flow Field + follow. Grid/topology internals...
Definition Pathfinder.h:20
static Result< std::size_t > step(const map::Pathfinder &pathfinder, const NavigationGrid &grid)
Reserve corridor direction, then next narrow cells, by occupancy, priority and stable identity.
RTS unit domain root.
Definition RTSTypes.h:495
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
Definition Learning.h:65
@ Cell
A cell was removed; the out-parameter holds it.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
constexpr uint32_t Corridor
Definition Semantic.h:15
Result< std::optional< OrderRecord > > readCurrent(OrderComponent &orders)
EVENGINE_API_DOMAINS bool isFormationSegmentClear(const map::Pathfinder &pathfinder, const NavigationGrid &grid, WorldPosition from, WorldPosition to, float radius)
std::function< void(Unit &, const OrderRecord &)> NavigationEvent
Notification emitted once when a newly planned order has no canonical map route.
Definition RTSSystems.h:270
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
World/grid conversion used when an RTS composition consumes a canonical map Pathfinder.
Definition RTSSystems.h:263
RTS projection of a canonical map path; the map provider remains the grid authority.
Definition RTSTypes.h:569
Two-dimensional deterministic RTS world position.
Definition RTSTypes.h:48