tess 1.0.0
Performance-first tile and path simulation substrate
Loading...
Searching...
No Matches
local_coordination.h
1#pragma once
2
3#include <tess/core/shape.h>
4
5#include <algorithm>
6#include <cstddef>
7#include <cstdint>
8#include <functional>
9#include <limits>
10#include <span>
11#include <vector>
12
13namespace tess {
14
17 std::uint64_t agent = 0;
18 Coord3 from{};
19 std::uint32_t priority = 0;
20 std::size_t option_offset = 0;
21 std::size_t option_count = 0;
22};
23
26 Coord3 to{};
27 std::int32_t preference = 0;
28};
29
31enum class LocalMoveDecisionStatus : std::uint8_t {
32 Wait,
33 Reserved,
34};
35
38 std::uint64_t agent = 0;
39 Coord3 from{};
40 Coord3 to{};
41 std::int32_t preference = 0;
42 LocalMoveDecisionStatus status = LocalMoveDecisionStatus::Wait;
43};
44
47 Coord3 coord{};
48 std::uint32_t demand = 0;
49 std::uint32_t reserved = 0;
50};
51
53enum class LocalCoordinationStatus : std::uint8_t {
54 Complete,
55 Partial,
56 InvalidInput,
57};
58
61 LocalCoordinationStatus status = LocalCoordinationStatus::Complete;
62 std::size_t reserved_count = 0;
63 std::span<const LocalMoveDecision> decisions;
64 std::span<const LocalCongestion> congestion;
65};
66
69 public:
70 void reserve(std::size_t request_count, std::size_t option_count) {
71 request_order_.reserve(request_count);
72 option_owned_.reserve(option_count);
73 option_feasible_.reserve(option_count);
74 demand_coords_.reserve(option_count);
75 congestion_.reserve(option_count);
76 claimed_.reserve(request_count);
77 decisions_.reserve(request_count);
78 }
79
80 private:
81 template <typename CanEnterFn>
82 friend auto resolve_local_moves(std::span<const LocalMoveRequest> requests,
83 std::span<const LocalMoveOption> options,
84 CanEnterFn&& can_enter,
87
88 std::vector<std::size_t> request_order_;
89 std::vector<std::uint8_t> option_owned_;
90 std::vector<std::uint8_t> option_feasible_;
91 std::vector<Coord3> demand_coords_;
92 std::vector<LocalCongestion> congestion_;
93 std::vector<Coord3> claimed_;
94 std::vector<LocalMoveDecision> decisions_;
95};
96
97namespace detail {
98
99[[nodiscard]] inline auto local_coord_less(Coord3 lhs, Coord3 rhs) noexcept
100 -> bool {
101 if (lhs.x != rhs.x) {
102 return lhs.x < rhs.x;
103 }
104 if (lhs.y != rhs.y) {
105 return lhs.y < rhs.y;
106 }
107 return lhs.z < rhs.z;
108}
109
110} // namespace detail
111
118template <typename CanEnterFn>
119[[nodiscard]] auto resolve_local_moves(
120 std::span<const LocalMoveRequest> requests,
121 std::span<const LocalMoveOption> options, CanEnterFn&& can_enter,
123 scratch.request_order_.clear();
124 scratch.option_owned_.assign(options.size(), 0);
125 scratch.option_feasible_.assign(options.size(), 0);
126 scratch.demand_coords_.clear();
127 scratch.congestion_.clear();
128 scratch.claimed_.clear();
129 scratch.decisions_.clear();
130
131 scratch.request_order_.resize(requests.size());
132 for (std::size_t i = 0; i < requests.size(); ++i) {
133 const auto& request = requests[i];
134 if (request.option_offset > options.size() ||
135 request.option_count > options.size() - request.option_offset) {
136 return {LocalCoordinationStatus::InvalidInput, 0, {}, {}};
137 }
138 scratch.request_order_[i] = i;
139 for (std::size_t j = 0; j < request.option_count; ++j) {
140 const auto option_index = request.option_offset + j;
141 if (scratch.option_owned_[option_index] != 0) {
142 return {LocalCoordinationStatus::InvalidInput, 0, {}, {}};
143 }
144 scratch.option_owned_[option_index] = 1;
145 }
146 }
147
148 std::sort(scratch.request_order_.begin(), scratch.request_order_.end(),
149 [&](std::size_t lhs, std::size_t rhs) {
150 return requests[lhs].agent < requests[rhs].agent;
151 });
152 for (std::size_t i = 1; i < scratch.request_order_.size(); ++i) {
153 if (requests[scratch.request_order_[i - 1]].agent ==
154 requests[scratch.request_order_[i]].agent) {
155 return {LocalCoordinationStatus::InvalidInput, 0, {}, {}};
156 }
157 }
158
159 scratch.decisions_.resize(requests.size());
160 for (std::size_t i = 0; i < requests.size(); ++i) {
161 const auto& request = requests[i];
162 scratch.decisions_[i] = LocalMoveDecision{
163 request.agent,
164 request.from,
165 request.from,
166 0,
167 LocalMoveDecisionStatus::Wait,
168 };
169 for (std::size_t j = 0; j < request.option_count; ++j) {
170 const auto option_index = request.option_offset + j;
171 const auto& option = options[option_index];
172 if (!static_cast<bool>(std::invoke(can_enter, request, option))) {
173 continue;
174 }
175 scratch.option_feasible_[option_index] = 1;
176 auto duplicate = false;
177 for (std::size_t previous = 0; previous < j; ++previous) {
178 const auto previous_index = request.option_offset + previous;
179 if (scratch.option_feasible_[previous_index] != 0 &&
180 options[previous_index].to == option.to) {
181 duplicate = true;
182 break;
183 }
184 }
185 if (!duplicate) {
186 scratch.demand_coords_.push_back(option.to);
187 }
188 }
189 }
190
191 std::sort(scratch.demand_coords_.begin(), scratch.demand_coords_.end(),
192 detail::local_coord_less);
193 for (const auto coord : scratch.demand_coords_) {
194 if (scratch.congestion_.empty() ||
195 scratch.congestion_.back().coord != coord) {
196 scratch.congestion_.push_back(LocalCongestion{coord, 1, 0});
197 } else if (scratch.congestion_.back().demand !=
198 std::numeric_limits<std::uint32_t>::max()) {
199 ++scratch.congestion_.back().demand;
200 }
201 }
202
203 std::sort(scratch.request_order_.begin(), scratch.request_order_.end(),
204 [&](std::size_t lhs, std::size_t rhs) {
205 if (requests[lhs].priority != requests[rhs].priority) {
206 return requests[lhs].priority > requests[rhs].priority;
207 }
208 return requests[lhs].agent < requests[rhs].agent;
209 });
210
211 auto reserved_count = std::size_t{};
212 for (const auto request_index : scratch.request_order_) {
213 const auto& request = requests[request_index];
214 auto best = options.size();
215 for (std::size_t j = 0; j < request.option_count; ++j) {
216 const auto option_index = request.option_offset + j;
217 const auto& option = options[option_index];
218 if (scratch.option_feasible_[option_index] == 0 ||
219 std::binary_search(scratch.claimed_.begin(), scratch.claimed_.end(),
220 option.to, detail::local_coord_less)) {
221 continue;
222 }
223 if (best == options.size() ||
224 option.preference < options[best].preference ||
225 (option.preference == options[best].preference &&
226 detail::local_coord_less(option.to, options[best].to))) {
227 best = option_index;
228 }
229 }
230 if (best == options.size()) {
231 continue;
232 }
233 const auto& chosen = options[best];
234 const auto claimed =
235 std::lower_bound(scratch.claimed_.begin(), scratch.claimed_.end(),
236 chosen.to, detail::local_coord_less);
237 scratch.claimed_.insert(claimed, chosen.to);
238 scratch.decisions_[request_index] = LocalMoveDecision{
239 request.agent,
240 request.from,
241 chosen.to,
242 chosen.preference,
243 LocalMoveDecisionStatus::Reserved,
244 };
245 const auto congestion =
246 std::lower_bound(scratch.congestion_.begin(), scratch.congestion_.end(),
247 chosen.to, [](const LocalCongestion& lhs, Coord3 rhs) {
248 return detail::local_coord_less(lhs.coord, rhs);
249 });
250 if (congestion != scratch.congestion_.end() &&
251 congestion->coord == chosen.to &&
252 congestion->reserved != std::numeric_limits<std::uint32_t>::max()) {
253 ++congestion->reserved;
254 }
255 ++reserved_count;
256 }
257
258 const auto status = reserved_count == requests.size()
259 ? LocalCoordinationStatus::Complete
260 : LocalCoordinationStatus::Partial;
261 return {status, reserved_count,
262 std::span<const LocalMoveDecision>{scratch.decisions_},
263 std::span<const LocalCongestion>{scratch.congestion_}};
264}
265
266} // namespace tess
Reusable storage for deterministic local move coordination.
Definition local_coordination.h:68
friend auto resolve_local_moves(std::span< const LocalMoveRequest > requests, std::span< const LocalMoveOption > options, CanEnterFn &&can_enter, LocalCoordinationScratch &scratch) -> LocalCoordinationResult
Definition local_coordination.h:119
Definition shape.h:46
Feasible demand and accepted reservations for one destination.
Definition local_coordination.h:46
Scratch-borrowing result from resolve_local_moves.
Definition local_coordination.h:60
One local-resolution decision aligned with its input request.
Definition local_coordination.h:37
One caller-generated local destination; lower preference wins.
Definition local_coordination.h:25
One mover and its range of caller-ranked local destination options.
Definition local_coordination.h:16