载入中...
搜索中...
未找到
CommandQueue.cpp
浏览该文件的文档.
2
4
5#include <simplesquirrel/simplesquirrel.hpp>
6
7#include <algorithm>
8#include <charconv>
9#include <cmath>
10#include <functional>
11#include <iomanip>
12#include <limits>
13#include <set>
14#include <sstream>
15#include <type_traits>
16
17namespace eve::orders {
18namespace {
19
20constexpr size_t noIndex = static_cast<size_t>(-1);
21constexpr std::string_view snapshotSchema = "orders.command_queue";
22constexpr std::int64_t snapshotVersion = 1;
23
25struct ScriptCommandQueue {
26 explicit ScriptCommandQueue(CommandQueueHandleRef value) : reference(value) {}
28};
29
30template <class T>
31bool readObjectMember(const eve::Value::Object& object, std::string_view key, T& result) {
32 const auto it = object.find(std::string(key));
33 if (it == object.end()) return false;
34 if constexpr (std::is_same_v<T, std::string>) {
35 if (!it->second.isString()) return false;
36 result = it->second.asString();
37 return true;
38 } else if constexpr (std::is_same_v<T, std::int64_t>) {
39 if (!it->second.isInt64()) return false;
40 result = it->second.asInt();
41 return true;
42 } else if constexpr (std::is_same_v<T, double>) {
43 if (!it->second.isDouble() && !it->second.isInt64()) return false;
44 result = it->second.isDouble() ? it->second.asDouble() : static_cast<double>(it->second.asInt());
45 return std::isfinite(result);
46 } else {
47 static_assert(std::is_same_v<T, void>, "unsupported snapshot member type");
48 }
49}
50
51bool readCounter(const eve::Value::Object& object, std::string_view key, std::uint64_t& result) {
52 std::string encoded;
53 if (!readObjectMember(object, key, encoded) || encoded.empty()) return false;
54 const auto* first = encoded.data();
55 const auto* last = first + encoded.size();
56 std::uint64_t value = 0;
57 const auto parsed = std::from_chars(first, last, value);
58 if (parsed.ec != std::errc{} || parsed.ptr != last) return false;
59 result = value;
60 return true;
61}
62
63bool parseOrderState(std::string_view name, OrderState& state) {
64 if (name == "queued")
66 else if (name == "active")
68 else if (name == "completed")
70 else if (name == "failed")
72 else if (name == "cancelled")
74 else
75 return false;
76 return true;
77}
78
79bool readOrderState(const eve::Value::Object& object, std::string_view key, OrderState& result) {
80 std::string name;
81 return readObjectMember(object, key, name) && parseOrderState(name, result);
82}
83
84bool readIndex(const eve::Value& value, size_t limit, size_t& result) {
85 if (!value.isInt64() || value.asInt() < 0) return false;
86 const auto index = static_cast<std::uint64_t>(value.asInt());
87 if (index >= limit || index > std::numeric_limits<size_t>::max()) return false;
88 result = static_cast<size_t>(index);
89 return true;
90}
91
92bool generatedOrderId(std::string_view id, std::uint64_t& number) {
93 constexpr std::string_view prefix = "order-";
94 if (!id.starts_with(prefix) || id.size() == prefix.size()) return false;
95 const auto digits = id.substr(prefix.size());
96 for (const char digit : digits)
97 if (digit < '0' || digit > '9') return false;
98 const auto parsed = std::from_chars(digits.data(), digits.data() + digits.size(), number);
99 return parsed.ec == std::errc{} && parsed.ptr == digits.data() + digits.size() && number != 0;
100}
101
102} // namespace
103
104void OrderPayload::setString(const std::string& key, const std::string& value) {
105 if (!key.empty()) values_[key] = eve::Value(value);
106}
107
108void OrderPayload::setNumber(const std::string& key, double value) {
109 if (key.empty() || !std::isfinite(value)) return;
110 values_[key] = eve::Value(value);
111}
112
113void OrderPayload::setBool(const std::string& key, bool value) {
114 if (!key.empty()) values_[key] = eve::Value(value);
115}
116
117void OrderPayload::setNull(const std::string& key) {
118 if (!key.empty()) values_[key] = eve::Value();
119}
120
121eve::Result<void> OrderPayload::setJson(const std::string& key, std::string_view json) {
122 auto parsed = eve::Value::fromJson(json);
123 if (!parsed) return eve::Result<void>::failure(*parsed.error());
124 return set(key, std::move(parsed).takeValue());
125}
126
128 if (key.empty())
130 eve::DiagnosticCode::InvalidArgument, "order payload key must not be empty", "key", {}, "orders"));
131 values_[key] = std::move(value);
133}
134
135bool OrderPayload::has(const std::string& key) const { return values_.contains(key); }
136
138 if (key.empty())
140 eve::DiagnosticCode::InvalidArgument, "order payload key must not be empty", "key", {}, "orders"));
141 return eve::Result<bool>::success(values_.erase(key) != 0);
142}
143
144void OrderPayload::clear() { values_.clear(); }
145
146std::string OrderPayload::getJson(const std::string& key) const {
147 const auto it = values_.find(key);
148 if (it == values_.end()) return {};
149 auto json = it->second.toJson();
150 return json ? std::move(json).takeValue() : std::string{};
151}
152
153std::string OrderPayload::toJson() const {
154 auto json = eve::Value(values_).toJson();
155 return json ? std::move(json).takeValue() : std::string{};
156}
157
158std::string stateName(OrderState state) {
159 switch (state) {
160 case OrderState::Queued: return "queued";
161 case OrderState::Active: return "active";
162 case OrderState::Completed: return "completed";
163 case OrderState::Failed: return "failed";
164 case OrderState::Cancelled: return "cancelled";
165 }
166 return "unknown";
167}
168
169std::string CommandQueue::create(const std::string& kind, int priority, double timeoutSeconds, bool activateNow) {
170 if (kind.empty()) return {};
171 Order order;
172 std::ostringstream id;
173 id << "order-" << std::setw(16) << std::setfill('0') << nextId_++;
174 order.id = id.str();
175 order.kind = kind;
176 order.priority = priority;
177 order.timeoutSeconds = std::max(0.0, timeoutSeconds);
178 orders_.push_back(std::move(order));
179 const size_t index = orders_.size() - 1;
180 events_.push_back(
181 {{nextSequence_++, {}}, orders_[index].id, orders_[index].kind, OrderState::Queued, OrderState::Queued});
182 if (activateNow) {
183 active_ = index;
184 transition(orders_[index], OrderState::Active, {});
185 } else {
186 queued_.push_back(index);
187 std::stable_sort(queued_.begin(), queued_.end(),
188 [this](size_t a, size_t b) { return orders_[a].priority > orders_[b].priority; });
189 if (active_ == noIndex) activateNext();
190 }
191 return orders_[index].id;
192}
193
194std::string CommandQueue::appendUnchecked(const std::string& kind, int priority, double timeoutSeconds) {
195 return create(kind, priority, timeoutSeconds, false);
196}
197
198std::string CommandQueue::replaceUnchecked(const std::string& kind, int priority, double timeoutSeconds) {
199 if (kind.empty()) return {};
200 cancelUnfinished("replaced");
201 return create(kind, priority, timeoutSeconds, true);
202}
203
204std::string CommandQueue::interruptUnchecked(const std::string& kind, int priority, double timeoutSeconds) {
205 if (kind.empty()) return {};
206 if (active_ != noIndex && priority < orders_[active_].priority) return {};
207 if (active_ != noIndex) transition(orders_[active_], OrderState::Cancelled, "interrupted");
208 active_ = noIndex;
209 return create(kind, priority, timeoutSeconds, true);
210}
211
212bool CommandQueue::transition(Order& order, OrderState state, const std::string& reason) {
213 if (order.state == OrderState::Completed || order.state == OrderState::Failed ||
214 order.state == OrderState::Cancelled || order.state == state) {
215 return false;
216 }
217 const OrderState from = order.state;
218 order.state = state;
219 order.reason = reason;
220 events_.push_back({{nextSequence_++, reason}, order.id, order.kind, from, state});
221 return true;
222}
223
224void CommandQueue::activateNext() {
225 if (active_ != noIndex || queued_.empty()) return;
226 active_ = queued_.front();
227 queued_.erase(queued_.begin());
228 transition(orders_[active_], OrderState::Active, {});
229}
230
231bool CommandQueue::completeUnchecked(const std::string& id) {
232 if (active_ == noIndex || orders_[active_].id != id) return false;
233 if (!transition(orders_[active_], OrderState::Completed, {})) return false;
234 active_ = noIndex;
235 activateNext();
236 return true;
237}
238
239bool CommandQueue::failUnchecked(const std::string& id, const std::string& reason) {
240 if (active_ == noIndex || orders_[active_].id != id) return false;
241 if (!transition(orders_[active_], OrderState::Failed, reason)) return false;
242 active_ = noIndex;
243 activateNext();
244 return true;
245}
246
247bool CommandQueue::cancelUnchecked(const std::string& id, const std::string& reason) {
248 if (active_ != noIndex && orders_[active_].id == id) {
249 if (!transition(orders_[active_], OrderState::Cancelled, reason)) return false;
250 active_ = noIndex;
251 activateNext();
252 return true;
253 }
254 const auto it =
255 std::find_if(queued_.begin(), queued_.end(), [this, &id](size_t index) { return orders_[index].id == id; });
256 if (it == queued_.end()) return false;
257 const size_t index = *it;
258 queued_.erase(it);
259 return transition(orders_[index], OrderState::Cancelled, reason);
260}
261
262void CommandQueue::updateUnchecked(double dtSeconds) {
263 if (active_ == noIndex || dtSeconds <= 0.0) return;
264 Order& order = orders_[active_];
265 order.elapsedSeconds += dtSeconds;
266 if (order.timeoutSeconds > 0.0 && order.elapsedSeconds >= order.timeoutSeconds) {
267 if (!failUnchecked(order.id, "timeout")) return;
268 }
269}
270
271eve::Result<std::string> CommandQueue::append(const std::string& kind, int priority, double timeoutSeconds) {
272 if (kind.empty())
274 eve::DiagnosticCode::InvalidArgument, "order kind must not be empty", "kind", {}, "orders"));
275 if (!std::isfinite(timeoutSeconds) || timeoutSeconds < 0.0)
277 "order timeout must be finite and non-negative",
278 "timeoutSeconds", {}, "orders"));
279 const std::string id = appendUnchecked(kind, priority, timeoutSeconds);
280 if (id.empty())
282 eve::DiagnosticCode::Failed, "order append did not produce an identity", {}, {}, "orders"));
284}
285
286eve::Result<std::string> CommandQueue::replace(const std::string& kind, int priority, double timeoutSeconds) {
287 if (kind.empty())
289 eve::DiagnosticCode::InvalidArgument, "order kind must not be empty", "kind", {}, "orders"));
290 if (!std::isfinite(timeoutSeconds) || timeoutSeconds < 0.0)
292 "order timeout must be finite and non-negative",
293 "timeoutSeconds", {}, "orders"));
294 const std::string id = replaceUnchecked(kind, priority, timeoutSeconds);
295 if (id.empty())
297 eve::DiagnosticCode::Failed, "order replacement did not produce an identity", {}, {}, "orders"));
299}
300
301eve::Result<std::string> CommandQueue::interrupt(const std::string& kind, int priority, double timeoutSeconds) {
302 if (kind.empty())
304 eve::DiagnosticCode::InvalidArgument, "order kind must not be empty", "kind", {}, "orders"));
305 if (!std::isfinite(timeoutSeconds) || timeoutSeconds < 0.0)
307 "order timeout must be finite and non-negative",
308 "timeoutSeconds", {}, "orders"));
309 if (active_ != noIndex && priority < orders_[active_].priority)
311 eve::DiagnosticCode::Conflict, "order priority cannot preempt the active order", "priority", {}, "orders"));
312 const std::string id = interruptUnchecked(kind, priority, timeoutSeconds);
313 if (id.empty())
315 eve::DiagnosticCode::Failed, "order interrupt did not produce an identity", {}, {}, "orders"));
317}
318
320 if (id.empty())
322 "order id must not be empty", "id", {}, "orders"));
323 if (!completeUnchecked(id))
325 eve::DiagnosticCode::Conflict, "order is not the active unfinished order", "id", {}, "orders"));
327}
328
329eve::Result<void> CommandQueue::fail(const std::string& id, const std::string& reason) {
330 if (id.empty())
332 "order id must not be empty", "id", {}, "orders"));
333 if (!failUnchecked(id, reason))
335 eve::DiagnosticCode::Conflict, "order is not the active unfinished order", "id", {}, "orders"));
337}
338
339eve::Result<void> CommandQueue::cancel(const std::string& id, const std::string& reason) {
340 if (id.empty())
342 "order id must not be empty", "id", {}, "orders"));
343 if (!cancelUnchecked(id, reason))
345 eve::DiagnosticCode::NotFound, "order id is not active or queued", "id", {}, "orders"));
347}
348
350 if (!std::isfinite(dtSeconds) || dtSeconds < 0.0)
352 "order update delta must be finite and non-negative",
353 "dtSeconds", {}, "orders"));
354 const bool hadActiveOrder = active_ != noIndex;
355 updateUnchecked(dtSeconds);
357 eve::Status::success(dtSeconds == 0.0 || !hadActiveOrder ? eve::StatusCode::NoOp : eve::StatusCode::Applied));
358}
359
360void CommandQueue::cancelUnfinished(const std::string& reason) {
361 if (active_ != noIndex) transition(orders_[active_], OrderState::Cancelled, reason);
362 active_ = noIndex;
363 for (size_t index : queued_) transition(orders_[index], OrderState::Cancelled, reason);
364 queued_.clear();
365}
366
368 orders_.clear();
369 queued_.clear();
370 events_.clear();
371 active_ = noIndex;
372 nextId_ = 1;
373 nextSequence_ = 1;
374}
375
377 return active_ == noIndex ? eve::OptionalRef<Order>{} : std::ref(orders_[active_]);
378}
379
381 return active_ == noIndex ? eve::OptionalRef<const Order>{} : std::cref(orders_[active_]);
382}
383
385 const auto it = std::find_if(orders_.begin(), orders_.end(), [&id](const Order& order) { return order.id == id; });
386 return it == orders_.end() ? eve::OptionalRef<Order>{} : std::ref(*it);
387}
388
390 const auto it = std::find_if(orders_.begin(), orders_.end(), [&id](const Order& order) { return order.id == id; });
391 return it == orders_.end() ? eve::OptionalRef<const Order>{} : std::cref(*it);
392}
393
394int CommandQueue::queuedCount() const { return static_cast<int>(queued_.size()); }
395
396int CommandQueue::orderCount() const { return static_cast<int>(orders_.size()); }
397
399 if (index < 0 || static_cast<size_t>(index) >= orders_.size()) return {};
400 return std::ref(orders_[static_cast<size_t>(index)]);
401}
402
404 if (index < 0 || static_cast<size_t>(index) >= orders_.size()) return {};
405 return std::cref(orders_[static_cast<size_t>(index)]);
406}
407
408int CommandQueue::eventCount() const { return static_cast<int>(events_.size()); }
409
411 if (index < 0 || static_cast<size_t>(index) >= events_.size()) return {};
412 return std::ref(events_[static_cast<size_t>(index)]);
413}
414
416 if (index < 0 || static_cast<size_t>(index) >= events_.size()) return {};
417 return std::cref(events_[static_cast<size_t>(index)]);
418}
419
420void CommandQueue::clearEvents() { events_.clear(); }
421
424 root.emplace("active", active_ == noIndex ? eve::Value() : eve::Value(static_cast<std::int64_t>(active_)));
425 root.emplace("nextId", eve::Value(std::to_string(nextId_)));
426 root.emplace("nextSequence", eve::Value(std::to_string(nextSequence_)));
427 root.emplace("schema", eve::Value(std::string(snapshotSchema)));
428 root.emplace("version", eve::Value(snapshotVersion));
429
431 queued.reserve(queued_.size());
432 for (const size_t index : queued_) {
433 if (index >= orders_.size() || index > static_cast<size_t>(std::numeric_limits<std::int64_t>::max()))
435 "queue contains an invalid queued index",
436 "queued", {}, "orders.snapshot"));
437 queued.emplace_back(static_cast<std::int64_t>(index));
438 }
439 root.emplace("queued", eve::Value(std::move(queued)));
440
441 eve::Value::Array orders;
442 orders.reserve(orders_.size());
443 for (const auto& order : orders_) {
444 eve::Value::Object encoded;
445 encoded.emplace("elapsedSeconds", eve::Value(order.elapsedSeconds));
446 encoded.emplace("id", eve::Value(order.id));
447 encoded.emplace("kind", eve::Value(order.kind));
448 encoded.emplace("payload", eve::Value(order.payload.values_));
449 encoded.emplace("priority", eve::Value(order.priority));
450 encoded.emplace("reason", eve::Value(order.reason));
451 encoded.emplace("state", eve::Value(stateName(order.state)));
452 encoded.emplace("timeoutSeconds", eve::Value(order.timeoutSeconds));
453 orders.emplace_back(std::move(encoded));
454 }
455 root.emplace("orders", eve::Value(std::move(orders)));
456
458 events.reserve(events_.size());
459 for (const auto& event : events_) {
460 eve::Value::Object encoded;
461 encoded.emplace("from", eve::Value(stateName(event.from)));
462 encoded.emplace("kind", eve::Value(event.kind));
463 encoded.emplace("orderId", eve::Value(event.orderId));
464 encoded.emplace("reason", eve::Value(event.reason));
465 encoded.emplace("sequence", eve::Value(std::to_string(event.sequence)));
466 encoded.emplace("to", eve::Value(stateName(event.to)));
467 events.emplace_back(std::move(encoded));
468 }
469 root.emplace("events", eve::Value(std::move(events)));
470
471 auto encoded = eve::Value(std::move(root)).toJson();
472 if (!encoded.ok()) return eve::Result<std::string>::failure(encoded.status());
473 return eve::Result<std::string>::success(std::move(encoded).takeValue());
474}
475
477 auto parsed = eve::Value::fromJson(json);
478 if (!parsed.ok()) return eve::Result<void>::failure(parsed.status());
479 eve::Value snapshotValue = std::move(parsed).takeValue();
480 const auto* root = snapshotValue.getIf<eve::Value::Object>();
481 if (!root)
483 eve::DiagnosticCode::ParseError, "command queue snapshot must be an object", {}, {}, "orders.snapshot"));
484
485 std::string schema;
486 std::int64_t version = 0;
487 if (!readObjectMember(*root, "schema", schema) || schema != snapshotSchema ||
488 !readObjectMember(*root, "version", version) || version != snapshotVersion)
490 "unsupported command queue schema or version",
491 "schema", {}, "orders.snapshot"));
492
493 const auto ordersIt = root->find("orders");
494 const auto queuedIt = root->find("queued");
495 const auto activeIt = root->find("active");
496 const auto eventsIt = root->find("events");
497 if (ordersIt == root->end() || queuedIt == root->end() || activeIt == root->end() || eventsIt == root->end() ||
498 !ordersIt->second.isArray() || !queuedIt->second.isArray() || !eventsIt->second.isArray())
500 "command queue snapshot is missing required arrays",
501 {}, {}, "orders.snapshot"));
502
503 CommandQueue candidate;
504 if (!readCounter(*root, "nextId", candidate.nextId_) ||
505 !readCounter(*root, "nextSequence", candidate.nextSequence_) || candidate.nextId_ == 0 ||
506 candidate.nextSequence_ == 0)
508 "command queue snapshot has invalid counters",
509 "counters", {}, "orders.snapshot"));
510
511 const auto& encodedOrders = *ordersIt->second.getIf<eve::Value::Array>();
512 std::set<std::string> ids;
513 std::uint64_t largestId = 0;
514 for (const auto& encoded : encodedOrders) {
515 const auto* object = encoded.getIf<eve::Value::Object>();
516 if (!object)
518 eve::DiagnosticCode::ParseError, "order entry must be an object", "orders", {}, "orders.snapshot"));
519
520 Order order;
521 std::int64_t priority = 0;
522 if (!readObjectMember(*object, "id", order.id) || !readObjectMember(*object, "kind", order.kind) ||
523 !readObjectMember(*object, "priority", priority) || !readOrderState(*object, "state", order.state) ||
524 !readObjectMember(*object, "timeoutSeconds", order.timeoutSeconds) ||
525 !readObjectMember(*object, "elapsedSeconds", order.elapsedSeconds) ||
526 !readObjectMember(*object, "reason", order.reason))
528 eve::DiagnosticCode::ParseError, "order entry has an invalid field", "orders", {}, "orders.snapshot"));
529 const auto payloadIt = object->find("payload");
530 if (payloadIt == object->end() || !payloadIt->second.isObject())
532 "order payload must be an object",
533 "orders.payload", {}, "orders.snapshot"));
534 if (priority < std::numeric_limits<int>::min() || priority > std::numeric_limits<int>::max() ||
535 order.id.empty() || order.kind.empty() || order.timeoutSeconds < 0.0 || order.elapsedSeconds < 0.0 ||
536 !ids.insert(order.id).second)
538 eve::DiagnosticCode::ParseError, "order entry violates an invariant", "orders", {}, "orders.snapshot"));
539
540 std::uint64_t idNumber = 0;
541 if (!generatedOrderId(order.id, idNumber) || idNumber >= candidate.nextId_)
543 "order id does not match the queue counter",
544 "orders.id", {}, "orders.snapshot"));
545 largestId = std::max(largestId, idNumber);
546 order.priority = static_cast<int>(priority);
547 order.payload.values_ = *payloadIt->second.getIf<eve::Value::Object>();
548 candidate.orders_.push_back(std::move(order));
549 }
550 if (!candidate.orders_.empty() && largestId >= candidate.nextId_)
552 "nextId must follow every restored order", "nextId",
553 {}, "orders.snapshot"));
554
555 std::vector<bool> queuedMembership(candidate.orders_.size(), false);
556 const auto& encodedQueued = *queuedIt->second.getIf<eve::Value::Array>();
557 for (const auto& encodedIndex : encodedQueued) {
558 size_t index = 0;
559 if (!readIndex(encodedIndex, candidate.orders_.size(), index) || queuedMembership[index] ||
560 candidate.orders_[index].state != OrderState::Queued)
562 "queued membership violates an invariant",
563 "queued", {}, "orders.snapshot"));
564 if (!candidate.queued_.empty()) {
565 const size_t previous = candidate.queued_.back();
566 if (candidate.orders_[previous].priority < candidate.orders_[index].priority ||
567 (candidate.orders_[previous].priority == candidate.orders_[index].priority && previous > index))
569 "queued order is not deterministically sorted",
570 "queued", {}, "orders.snapshot"));
571 }
572 queuedMembership[index] = true;
573 candidate.queued_.push_back(index);
574 }
575
576 if (activeIt->second.isNull()) {
577 candidate.active_ = noIndex;
578 } else if (!readIndex(activeIt->second, candidate.orders_.size(), candidate.active_) ||
579 candidate.orders_[candidate.active_].state != OrderState::Active) {
581 eve::DiagnosticCode::ParseError, "active order violates an invariant", "active", {}, "orders.snapshot"));
582 }
583 for (size_t index = 0; index < candidate.orders_.size(); ++index) {
584 const auto state = candidate.orders_[index].state;
585 if ((state == OrderState::Queued) != queuedMembership[index] ||
586 (state == OrderState::Active) != (candidate.active_ == index))
588 "order state and queue membership disagree",
589 "orders", {}, "orders.snapshot"));
590 }
591
592 const auto& encodedEvents = *eventsIt->second.getIf<eve::Value::Array>();
593 std::uint64_t previousSequence = 0;
594 for (const auto& encoded : encodedEvents) {
595 const auto* object = encoded.getIf<eve::Value::Object>();
596 if (!object)
598 eve::DiagnosticCode::ParseError, "event entry must be an object", "events", {}, "orders.snapshot"));
599 OrderEvent event;
600 std::string from;
601 std::string to;
602 if (!readCounter(*object, "sequence", event.sequence) || !readObjectMember(*object, "orderId", event.orderId) ||
603 !readObjectMember(*object, "kind", event.kind) || !readObjectMember(*object, "from", from) ||
604 !readObjectMember(*object, "to", to) || !readObjectMember(*object, "reason", event.reason) ||
605 !parseOrderState(from, event.from) || !parseOrderState(to, event.to) || event.sequence == 0 ||
606 event.sequence <= previousSequence || event.sequence >= candidate.nextSequence_ || event.kind.empty() ||
607 !ids.contains(event.orderId))
609 eve::DiagnosticCode::ParseError, "event entry violates an invariant", "events", {}, "orders.snapshot"));
610 previousSequence = event.sequence;
611 candidate.events_.push_back(std::move(event));
612 }
613
614 *this = std::move(candidate);
616}
617
619 Orders* module = Orders::create();
620 return module->queues_.emplace(std::make_unique<CommandQueue>());
621}
622
624 Orders* module = ModuleManager::getInstance<Orders>("Orders");
625 if (!module) return {};
626 return module->queues_.resolve(reference);
627}
628
630 Orders* module = ModuleManager::getInstance<Orders>("Orders");
631 if (!module)
633 eve::DiagnosticCode::StaleHandle, "Orders module is no longer loaded", "queue", {}, "orders"));
634 return module->queues_.erase(reference);
635}
636
638 if (!reference.isValid()) return false;
639 Orders* module = ModuleManager::getInstance<Orders>("Orders");
640 return !module || module->queues_.isStale(reference);
641}
642
644
645void Orders::expose(ssq::Table& table) {
646 const HSQUIRRELVM vm = table.getHandle();
647 auto payload = table.addClass<OrderPayload>(
648 "OrderPayload", std::function<OrderPayload*()>([]() -> OrderPayload* { return nullptr; }), false);
649 payload.addFunc("setString", &OrderPayload::setString);
650 payload.addFunc("setNumber", &OrderPayload::setNumber);
651 payload.addFunc("setBool", &OrderPayload::setBool);
652 payload.addFunc("setNull", &OrderPayload::setNull);
653 payload.addFunc("setJson", [vm](OrderPayload* value, const std::string& key, const std::string& json) {
654 if (!value)
657 "order payload must not be null", "payload", {}, "orders")));
658 return eve::script::projectResult(vm, value->setJson(key, json));
659 });
660 payload.addFunc("has", &OrderPayload::has);
661 payload.addFunc("erase", [vm](OrderPayload* value, const std::string& key) {
662 if (!value)
664 vm,
666 eve::DiagnosticCode::InvalidArgument, "order payload must not be null", "payload", {}, "orders")),
667 [](bool removed) { return eve::Value(removed); });
668 return eve::script::projectResult(vm, value->erase(key), [](bool removed) { return eve::Value(removed); });
669 });
670 payload.addFunc("clear", &OrderPayload::clear);
671 payload.addFunc("getJson", &OrderPayload::getJson);
672 payload.addFunc("toJson", &OrderPayload::toJson);
673
674 auto order = table.addClass<Order>("Order", std::function<Order*()>([]() -> Order* { return nullptr; }), false);
675 order.addFunc("getId", [](Order* value) { return value ? value->id : std::string{}; });
676 order.addFunc("getKind", [](Order* value) { return value ? value->kind : std::string{}; });
677 order.addFunc("getPriority", [](Order* value) { return value ? value->priority : 0; });
678 order.addFunc("getState", [](Order* value) { return value ? stateName(value->state) : std::string{}; });
679 order.addFunc("getTimeout", [](Order* value) { return value ? static_cast<float>(value->timeoutSeconds) : 0.0f; });
680 order.addFunc("getElapsed", [](Order* value) { return value ? static_cast<float>(value->elapsedSeconds) : 0.0f; });
681 order.addFunc("getReason", [](Order* value) { return value ? value->reason : std::string{}; });
682 order.addFunc("getPayload", [](Order* value) -> OrderPayload* { return value ? &value->payload : nullptr; });
683
684 auto event = table.addClass<OrderEvent>(
685 "OrderEvent", std::function<OrderEvent*()>([]() -> OrderEvent* { return nullptr; }), false);
686 event.addFunc("getSequence",
687 [](OrderEvent* value) { return value ? static_cast<int64_t>(value->sequence) : int64_t{0}; });
688 event.addFunc("getOrderId", [](OrderEvent* value) { return value ? value->orderId : std::string{}; });
689 event.addFunc("getKind", [](OrderEvent* value) { return value ? value->kind : std::string{}; });
690 event.addFunc("getFrom", [](OrderEvent* value) { return value ? stateName(value->from) : std::string{}; });
691 event.addFunc("getTo", [](OrderEvent* value) { return value ? stateName(value->to) : std::string{}; });
692 event.addFunc("getReason", [](OrderEvent* value) { return value ? value->reason : std::string{}; });
693
694 auto queue = table.addClass<CommandQueue>(
695 "CommandQueue", std::function<CommandQueue*()>([]() -> CommandQueue* { return nullptr; }), false);
696 queue.addFunc("append", [vm](CommandQueue* value, const std::string& kind, int priority, float timeout) {
697 if (!value)
699 vm,
701 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")),
702 [](std::string&& id) { return eve::Value(std::move(id)); });
703 return eve::script::projectResult(vm, value->append(kind, priority, timeout),
704 [](std::string&& id) { return eve::Value(std::move(id)); });
705 });
706 queue.addFunc("replace", [vm](CommandQueue* value, const std::string& kind, int priority, float timeout) {
707 if (!value)
709 vm,
711 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")),
712 [](std::string&& id) { return eve::Value(std::move(id)); });
713 return eve::script::projectResult(vm, value->replace(kind, priority, timeout),
714 [](std::string&& id) { return eve::Value(std::move(id)); });
715 });
716 queue.addFunc("interrupt", [vm](CommandQueue* value, const std::string& kind, int priority, float timeout) {
717 if (!value)
719 vm,
721 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")),
722 [](std::string&& id) { return eve::Value(std::move(id)); });
723 return eve::script::projectResult(vm, value->interrupt(kind, priority, timeout),
724 [](std::string&& id) { return eve::Value(std::move(id)); });
725 });
726 queue.addFunc("complete", [vm](CommandQueue* value, const std::string& id) {
727 if (!value)
730 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")));
731 return eve::script::projectResult(vm, value->complete(id));
732 });
733 queue.addFunc("fail", [vm](CommandQueue* value, const std::string& id, const std::string& reason) {
734 if (!value)
737 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")));
738 return eve::script::projectResult(vm, value->fail(id, reason));
739 });
740 queue.addFunc("cancel", [vm](CommandQueue* value, const std::string& id, const std::string& reason) {
741 if (!value)
744 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")));
745 return eve::script::projectResult(vm, value->cancel(id, reason));
746 });
747 queue.addFunc("update", [vm](CommandQueue* value, float dt) {
748 if (!value)
751 eve::DiagnosticCode::InvalidArgument, "order queue must not be null", "queue", {}, "orders")));
752 return eve::script::projectResult(vm, value->update(dt));
753 });
754 queue.addFunc("clear", &CommandQueue::clear);
755 queue.addFunc("current", [](CommandQueue* value) -> Order* {
756 if (!value) return nullptr;
757 auto result = value->current();
758 return result ? &result->get() : nullptr;
759 });
760 queue.addFunc("find", [](CommandQueue* value, const std::string& id) -> Order* {
761 if (!value) return nullptr;
762 auto result = value->find(id);
763 return result ? &result->get() : nullptr;
764 });
765 queue.addFunc("queuedCount", &CommandQueue::queuedCount);
766 queue.addFunc("orderCount", &CommandQueue::orderCount);
767 queue.addFunc("orderAt", [](CommandQueue* value, int index) -> Order* {
768 if (!value) return nullptr;
769 auto result = value->orderAt(index);
770 return result ? &result->get() : nullptr;
771 });
772 queue.addFunc("eventCount", &CommandQueue::eventCount);
773 queue.addFunc("eventAt", [](CommandQueue* value, int index) -> OrderEvent* {
774 if (!value) return nullptr;
775 auto result = value->eventAt(index);
776 return result ? &result->get() : nullptr;
777 });
778 queue.addFunc("clearEvents", &CommandQueue::clearEvents);
779
780 // Canonical script ownership path. The returned proxy is an Owned script
781 // object (Squirrel release hook); its queue is addressed only by a
782 // generation-qualified RuntimeHandleRef and resolves as Borrowed per call.
783 auto ownedQueue = table.addClass<ScriptCommandQueue>(
784 "OwnedOrderQueue", std::function<ScriptCommandQueue*()>([]() -> ScriptCommandQueue* { return nullptr; }),
785 false);
786 ownedQueue.addFunc("ownership", [](ScriptCommandQueue*) {
788 });
789 ownedQueue.addFunc("handle",
790 [](ScriptCommandQueue* value) { return value ? value->reference.packed() : std::uint64_t{0}; });
791 ownedQueue.addFunc("isStale",
792 [](ScriptCommandQueue* value) { return !value || Orders::isStale(value->reference); });
793 ownedQueue.addFunc("release", [vm](ScriptCommandQueue* value) {
794 if (!value)
797 "owned order queue proxy must not be null",
798 "queue", {}, "orders")));
799 auto result = Orders::release(value->reference);
800 // Retain the coordinates after release so the same script object can
801 // report a stale handle and a second release returns StaleHandle.
802 return eve::script::projectResult(vm, std::move(result));
803 });
804 ownedQueue.addFunc("append", [vm](ScriptCommandQueue* value, const std::string& kind, int priority, float timeout) {
805 if (!value)
809 "owned order queue proxy must not be null", "queue", {}, "orders")),
810 [](std::string&& id) { return eve::Value(std::move(id)); });
811 auto queueView = Orders::resolve(value->reference);
812 if (!queueView.isBound())
814 vm,
816 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")),
817 [](std::string&& id) { return eve::Value(std::move(id)); });
818 return eve::script::projectResult(vm, queueView->append(kind, priority, timeout),
819 [](std::string&& id) { return eve::Value(std::move(id)); });
820 });
821 ownedQueue.addFunc("replace", [vm](ScriptCommandQueue* value, const std::string& kind, int priority, float timeout) {
822 if (!value)
826 "owned order queue proxy must not be null", "queue", {}, "orders")),
827 [](std::string&& id) { return eve::Value(std::move(id)); });
828 auto queueView = Orders::resolve(value->reference);
829 if (!queueView.isBound())
831 vm,
833 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")),
834 [](std::string&& id) { return eve::Value(std::move(id)); });
835 return eve::script::projectResult(vm, queueView->replace(kind, priority, timeout),
836 [](std::string&& id) { return eve::Value(std::move(id)); });
837 });
838 ownedQueue.addFunc("interrupt", [vm](ScriptCommandQueue* value, const std::string& kind, int priority,
839 float timeout) {
840 if (!value)
844 "owned order queue proxy must not be null", "queue", {}, "orders")),
845 [](std::string&& id) { return eve::Value(std::move(id)); });
846 auto queueView = Orders::resolve(value->reference);
847 if (!queueView.isBound())
849 vm,
851 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")),
852 [](std::string&& id) { return eve::Value(std::move(id)); });
853 return eve::script::projectResult(vm, queueView->interrupt(kind, priority, timeout),
854 [](std::string&& id) { return eve::Value(std::move(id)); });
855 });
856 ownedQueue.addFunc("fail", [vm](ScriptCommandQueue* value, const std::string& id, const std::string& reason) {
857 if (!value)
860 "owned order queue proxy must not be null",
861 "queue", {}, "orders")));
862 auto queueView = Orders::resolve(value->reference);
863 if (!queueView.isBound())
866 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")));
867 return eve::script::projectResult(vm, queueView->fail(id, reason));
868 });
869 ownedQueue.addFunc("cancel", [vm](ScriptCommandQueue* value, const std::string& id, const std::string& reason) {
870 if (!value)
873 "owned order queue proxy must not be null",
874 "queue", {}, "orders")));
875 auto queueView = Orders::resolve(value->reference);
876 if (!queueView.isBound())
879 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")));
880 return eve::script::projectResult(vm, queueView->cancel(id, reason));
881 });
882 ownedQueue.addFunc("current", [](ScriptCommandQueue* value) -> Order* {
883 if (!value) return nullptr;
884 auto queueView = Orders::resolve(value->reference);
885 if (!queueView.isBound()) return nullptr;
886 auto current = queueView->current();
887 return current ? &current->get() : nullptr;
888 });
889 ownedQueue.addFunc("find", [](ScriptCommandQueue* value, const std::string& id) -> Order* {
890 if (!value) return nullptr;
891 auto queueView = Orders::resolve(value->reference);
892 if (!queueView.isBound()) return nullptr;
893 auto order = queueView->find(id);
894 return order ? &order->get() : nullptr;
895 });
896 ownedQueue.addFunc("complete", [vm](ScriptCommandQueue* value, const std::string& id) {
897 if (!value)
900 "owned order queue proxy must not be null",
901 "queue", {}, "orders")));
902 auto queueView = Orders::resolve(value->reference);
903 if (!queueView.isBound())
906 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")));
907 return eve::script::projectResult(vm, queueView->complete(id));
908 });
909 ownedQueue.addFunc("snapshot", [vm](ScriptCommandQueue* value) {
910 if (!value)
914 "owned order queue proxy must not be null", "queue", {}, "orders")),
915 [](std::string&& text) { return eve::Value(std::move(text)); });
916 auto queueView = Orders::resolve(value->reference);
917 if (!queueView.isBound())
919 vm,
921 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")),
922 [](std::string&& text) { return eve::Value(std::move(text)); });
923 return eve::script::projectResult(vm, queueView->snapshot(),
924 [](std::string&& text) { return eve::Value(std::move(text)); });
925 });
926 ownedQueue.addFunc("restore", [vm](ScriptCommandQueue* value, const std::string& json) {
927 if (!value)
930 "owned order queue proxy must not be null",
931 "queue", {}, "orders")));
932 auto queueView = Orders::resolve(value->reference);
933 if (!queueView.isBound())
936 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")));
937 return eve::script::projectResult(vm, queueView->restore(json));
938 });
939 ownedQueue.addFunc("update", [vm](ScriptCommandQueue* value, float dt) {
940 if (!value)
943 "owned order queue proxy must not be null",
944 "queue", {}, "orders")));
945 auto queueView = Orders::resolve(value->reference);
946 if (!queueView.isBound())
949 eve::DiagnosticCode::StaleHandle, "owned order queue handle is stale", "queue", {}, "orders")));
950 return eve::script::projectResult(vm, queueView->update(dt));
951 });
952 ownedQueue.addFunc("clear", [](ScriptCommandQueue* value) {
953 if (!value) return;
954 auto queueView = Orders::resolve(value->reference);
955 if (queueView.isBound()) queueView->clear();
956 });
957
958 auto cls = table.addClass(name, Orders::create, false);
959 expose(cls);
960}
961
962void Orders::expose(ssq::Class& cls) {
963 cls.addFunc("getName", &Orders::getName);
964 cls.addFunc("newQueueOwned", [vm = cls.getHandle()](Orders*) -> ssq::Table {
965 auto reference = Orders::newQueueHandle();
966 if (!reference) {
967 return eve::script::projectStatusResult(vm, reference.status());
968 }
969 const auto ref = std::move(reference).takeValue();
970 auto object =
971 eve::script::makeOwnedSquirrelInstance<ScriptCommandQueue>(vm, std::make_unique<ScriptCommandQueue>(ref));
972 if (!object) {
973 object.ignore("failed to create owned order queue proxy");
974 Orders::release(ref).ignore("rollback failed owned order queue allocation");
976 }
977 ssq::Object owned = std::move(object).takeValue();
979 result.set("value", owned);
980 return result;
981 });
982}
983
984} // namespace eve::orders
double value
Value::Object payload
int root
Definition AnimSmr.cpp:119
std::string from
ActiveSource owned
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
int priority
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::uint32_t key
wgpu::PopErrorScopeStatus status
glm::uvec4 ids
std::int32_t first
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
TokenKind kind
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< std::int32_t > order
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
graphics::Canvas * previous
size_t queued
Definition OnnxGpgpu.cpp:60
std::string id
Definition PlayHost.cpp:108
double number
int removed
double current
std::string string
The single Squirrel projection for common Result, Status and Value.
Battle::Events events
Json object
int limit
Definition TreeMesh.cpp:164
float size
Definition TreeMesh.cpp:156
uint32_t index
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
virtual std::string getName() const =0
Returns the name.
void ignore(std::string_view reason={}) const noexcept
Explicitly discard this result after documenting the reason.
Definition Result.h:537
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
Result< std::string > toJson() const
Serialize this value as deterministic compact JSON.
Definition Value.cpp:67
static Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Generic priority order queue with no gameplay or entity semantics.
void clear()
Resets the queue, retained history, ids, and event sequences.
eve::Result< void > restore(std::string_view json)
Restores a command queue transactionally from a JSON snapshot.
eve::OptionalRef< Order > find(const std::string &id)
Returns an order by stable id, including terminal history.
eve::Result< void > fail(const std::string &id, const std::string &reason)
Fails the active command.
eve::OptionalRef< OrderEvent > eventAt(int index)
Returns an event by sequence order, or null for an invalid index.
eve::Result< std::string > snapshot() const
Serializes the complete command queue as deterministic JSON.
eve::OptionalRef< Order > current()
Returns the active order, or a null observation when the queue is idle.
eve::Result< void > complete(const std::string &id)
Completes the active command.
eve::Result< std::string > interrupt(const std::string &kind, int priority=0, double timeoutSeconds=0.0)
Preempts the active command when priority permits.
int orderCount() const
Returns the total retained order count.
eve::Result< std::string > append(const std::string &kind, int priority=0, double timeoutSeconds=0.0)
Appends a command and returns its stable identity or validation failure.
eve::Result< void > update(double dtSeconds)
Checked simulation update.
eve::OptionalRef< Order > orderAt(int index)
Returns an order in creation order, or null for an invalid index.
eve::Result< std::string > replace(const std::string &kind, int priority=0, double timeoutSeconds=0.0)
Cancels unfinished commands and starts a replacement.
int eventCount() const
Returns the number of retained deterministic events.
int queuedCount() const
Returns the number of queued orders, excluding the active order.
void clearEvents()
Clears retained events without resetting their sequence counter.
eve::Result< void > cancel(const std::string &id, const std::string &reason="cancelled")
Cancels an active or queued command.
JSON-compatible key/value payload carried by an order.
eve::Result< bool > erase(const std::string &key)
Removes key and reports whether it existed.
void setBool(const std::string &key, bool value)
Sets a JSON boolean value.
void setNumber(const std::string &key, double value)
Sets a JSON number value.
bool has(const std::string &key) const
Returns true when the payload contains key.
void clear()
Removes every field.
eve::Result< void > setJson(const std::string &key, std::string_view json)
Parses and stores one unchecked JSON value.
void setNull(const std::string &key)
Sets a JSON null value.
eve::Result< void > set(const std::string &key, eve::Value value)
Stores an already validated canonical value.
std::string toJson() const
Serializes the complete payload as a deterministic JSON object.
void setString(const std::string &key, const std::string &value)
Sets a JSON string value.
std::string getJson(const std::string &key) const
Returns the canonical JSON fragment for key, or an empty string.
Script module factory for generic CommandQueue objects.
static eve::Result< CommandQueueHandleRef > newQueueHandle()
Allocates a queue and returns its generation-qualified ownership reference.
static bool isStale(CommandQueueHandleRef reference) noexcept
Reports whether a queue reference is invalid for the current module.
static eve::script::Borrowed< CommandQueue > resolve(CommandQueueHandleRef reference) noexcept
Resolves a live queue as a non-owning observation.
Orders()=default
Constructs a Orders.
static eve::Result< void > release(CommandQueueHandleRef reference)
Releases a queue owned by the Orders module.
Non-owning C++ observation with no implicit lifetime extension.
Result< std::uint32_t > readIndex(const Accessor &accessor, std::uint32_t element)
Read one bounded unsigned scalar index.
OrderState
Lifecycle state of a generic order.
eve::script::RuntimeHandleRef< CommandQueueHandleTag > CommandQueueHandleRef
Generation-qualified reference to a module-owned order queue.
std::string stateName(OrderState state)
Returns the stable lowercase name of a state.
std::unordered_map< std::string, SkillDefinition > & table()
Definition Skill.cpp:65
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
const char * objectSemanticName(ObjectSemantic semantic) noexcept
Returns the stable name used by diagnostics and documentation.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
std::optional< std::reference_wrapper< T > > OptionalRef
Optional borrowed reference; it does not extend T's lifetime.
Definition BorrowedRef.h:24
Deterministically sequenced order lifecycle event.
One entity-agnostic command and its lifecycle metadata.
Handle plus owner-lifetime epoch for a non-ECS registry object.