载入中...
搜索中...
未找到
RTSProductionAction.cpp
浏览该文件的文档.
2#include "rts/RTSTypes.h"
3
4#include <exception>
5#include <memory>
6#include <optional>
7#include <utility>
8#include <vector>
9
10namespace eve::rts {
11namespace {
12
13class OrderParticipant final : public transaction::ITransactionParticipant {
14public:
15 OrderParticipant(orders::CommandQueue& queue, std::string kind, int priority, double timeout, std::string product,
16 std::string owner)
17 : queue_(queue),
18 kind_(std::move(kind)),
19 priority_(priority),
20 timeout_(timeout),
21 product_(std::move(product)),
22 owner_(std::move(owner)) {}
23
24 [[nodiscard]] std::string_view name() const noexcept override { return "rts-build-order"; }
25
26 [[nodiscard]] eve::Result<void> prepare(const transaction::TransactionContext&) override {
27 if (prepared_ || committed_)
28 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS order participant is already in flight", "orders"));
29 try {
30 before_ = queue_;
31 staged_ = queue_;
32 } catch (const std::exception& exception) {
33 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, std::string("failed to stage RTS order: ") + exception.what(), "orders"));
34 }
35 auto id = staged_->append(kind_, priority_, timeout_);
36 if (!id) return eve::Result<void>::failure(id.status());
37 orderId_ = std::move(id).takeValue();
38 auto order = staged_->find(orderId_);
39 if (!order) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvariantViolation, "staged RTS order disappeared", "orders"));
40 order->get().payload.setString("owner", owner_);
41 order->get().payload.setString("product", product_);
42 prepared_ = true;
44 }
45
46 [[nodiscard]] eve::Result<void> commit(const transaction::TransactionContext&) override {
47 if (!prepared_ || committed_)
48 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS order participant has no prepared stage", "orders"));
49 try {
50 queue_ = *staged_;
51 } catch (const std::exception& exception) {
52 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, std::string("failed to publish RTS order: ") + exception.what(), "orders"));
53 }
54 committed_ = true;
56 }
57
58 [[nodiscard]] eve::Result<void> rollback(const transaction::TransactionContext&) override {
59 if (committed_)
60 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "committed RTS order requires compensation", "orders"));
61 staged_.reset();
62 prepared_ = false;
64 }
65
66 [[nodiscard]] eve::Result<void> compensate(const transaction::TransactionContext&) override {
67 if (!committed_)
68 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS order has no committed state to compensate", "orders"));
69 try {
70 queue_ = *before_;
71 } catch (const std::exception& exception) {
72 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Failed, std::string("failed to compensate RTS order: ") + exception.what(), "orders"));
73 }
74 staged_.reset();
75 before_.reset();
76 prepared_ = false;
77 committed_ = false;
79 }
80
81 [[nodiscard]] const std::string& orderId() const noexcept { return orderId_; }
82
83private:
84 orders::CommandQueue& queue_;
85 std::string kind_;
86 int priority_ = 0;
87 double timeout_ = 0.0;
88 std::string product_;
89 std::string owner_;
90 std::optional<orders::CommandQueue> before_;
91 std::optional<orders::CommandQueue> staged_;
92 std::string orderId_;
93 bool prepared_ = false;
94 bool committed_ = false;
95};
96
97class ProductionParticipant final : public transaction::ITransactionParticipant {
98public:
99 ProductionParticipant(production::WorkQueue& queue, std::string owner, std::string kind, std::string product,
101 eve::Value reservation, Duration duration, int priority, bool settlementRequired)
102 : queue_(queue),
103 owner_(std::move(owner)),
104 kind_(std::move(kind)),
105 product_(std::move(product)),
106 context_(std::move(context)),
107 definition_(std::move(definition)),
108 reservation_(std::move(reservation)),
109 duration_(duration),
110 priority_(priority),
111 settlementRequired_(settlementRequired) {}
112
113 [[nodiscard]] std::string_view name() const noexcept override { return "rts-production-queue"; }
114
115 [[nodiscard]] eve::Result<void> prepare(const transaction::TransactionContext&) override {
116 if (prepared_ || committed_)
117 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS production participant is already in flight", "production"));
118 auto before = queue_.snapshot();
119 if (!before) return eve::Result<void>::failure(before.status());
120 beforeJson_ = std::move(before).takeValue();
121 staged_ = std::make_unique<production::WorkQueue>();
122 auto restored = staged_->restore(beforeJson_);
123 if (!restored) return restored;
124 production::ProductionRequest request;
125 request.owner = owner_;
126 request.kind = kind_;
127 request.product = product_;
128 request.context = context_;
129 request.definition = definition_;
130 request.reservation = reservation_;
131 request.duration = duration_;
132 request.priority = priority_;
133 request.settlementRequired = settlementRequired_;
134 auto task = staged_->enqueue(std::move(request));
135 if (!task) return eve::Result<void>::failure(task.status());
136 taskId_ = std::move(task).takeValue();
137 auto after = staged_->snapshot();
138 if (!after) return eve::Result<void>::failure(after.status());
139 afterJson_ = std::move(after).takeValue();
140 prepared_ = true;
142 }
143
144 [[nodiscard]] eve::Result<void> commit(const transaction::TransactionContext&) override {
145 if (!prepared_ || committed_)
146 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS production participant has no prepared stage", "production"));
147 auto current = queue_.snapshot();
148 if (!current) return eve::Result<void>::failure(current.status());
149 if (current.value() != beforeJson_)
150 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "RTS production queue changed while transaction was staged", "production"));
151 auto restored = queue_.restore(afterJson_);
152 if (!restored) return restored;
153 committed_ = true;
155 }
156
157 [[nodiscard]] eve::Result<void> rollback(const transaction::TransactionContext&) override {
158 if (committed_)
159 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "committed production task requires compensation", "production"));
160 staged_.reset();
161 prepared_ = false;
163 }
164
165 [[nodiscard]] eve::Result<void> compensate(const transaction::TransactionContext&) override {
166 if (!committed_)
167 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "production queue has no committed task to compensate", "production"));
168 auto current = queue_.snapshot();
169 if (!current) return eve::Result<void>::failure(current.status());
170 if (current.value() != afterJson_)
171 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::StaleHandle, "RTS production queue changed before compensation", "production"));
172 auto restored = queue_.restore(beforeJson_);
173 if (!restored) return restored;
174 staged_.reset();
175 beforeJson_.clear();
176 afterJson_.clear();
177 prepared_ = false;
178 committed_ = false;
180 }
181
182 [[nodiscard]] const std::string& taskId() const noexcept { return taskId_; }
183
184private:
185 production::WorkQueue& queue_;
186 std::string owner_;
187 std::string kind_;
188 std::string product_;
189 eve::Value context_;
191 eve::Value reservation_;
192 Duration duration_;
193 int priority_ = 0;
194 bool settlementRequired_ = false;
195 std::unique_ptr<production::WorkQueue> staged_;
196 std::string beforeJson_;
197 std::string afterJson_;
198 std::string taskId_;
199 bool prepared_ = false;
200 bool committed_ = false;
201};
202
203class ActionParticipant final : public transaction::ITransactionParticipant {
204public:
205 ActionParticipant(action::ActionRuntime& runtime, action::ActionDefinition definition,
206 action::ActionRequest request, SimulationTick tick, Duration delta)
207 : runtime_(runtime),
208 definition_(std::move(definition)),
209 request_(std::move(request)),
210 tick_(tick),
211 delta_(delta) {}
212
213 [[nodiscard]] std::string_view name() const noexcept override { return "rts-build-action"; }
214
215 [[nodiscard]] eve::Result<void> prepare(const transaction::TransactionContext&) override {
216 if (execution_)
217 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS Action participant is already in flight", "action"));
218 auto submitted = runtime_.submit(std::move(definition_), std::move(request_));
219 if (!submitted) return eve::Result<void>::failure(submitted.status());
220 execution_ = std::move(submitted).takeValue();
221 prepared_ = true;
223 }
224
225 [[nodiscard]] eve::Result<void> commit(const transaction::TransactionContext&) override {
226 if (!prepared_ || committed_ || !execution_)
227 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "RTS Action participant has no prepared execution", "action"));
228 auto advanced = runtime_.advance(*execution_, tick_, delta_);
229 if (!advanced) return eve::Result<void>::failure(advanced.status());
230 std::move(advanced).takeValue();
231 committed_ = true;
233 }
234
235 [[nodiscard]] eve::Result<void> rollback(const transaction::TransactionContext&) override {
236 if (committed_)
237 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "committed RTS Action requires compensation", "action"));
239 const auto* current = runtime_.find(*execution_);
240 if (current != nullptr && current->phase() != action::ActionPhase::Failed &&
242 auto cancelled = runtime_.cancel(*execution_, tick_);
243 if (!cancelled) return cancelled;
244 }
245 prepared_ = false;
247 }
248
249 [[nodiscard]] eve::Result<void> compensate(const transaction::TransactionContext&) override {
250 // ActionRuntime's terminal record is an audit record. It has no
251 // mutable business state once the other participants are compensated.
252 committed_ = false;
253 prepared_ = false;
255 }
256
257 [[nodiscard]] action::ActionExecutionId executionId() const noexcept {
258 return execution_.value_or(action::ActionExecutionId{});
259 }
260
261private:
262 action::ActionRuntime& runtime_;
263 action::ActionDefinition definition_;
264 action::ActionRequest request_;
265 SimulationTick tick_;
266 Duration delta_;
267 std::optional<action::ActionExecutionId> execution_;
268 bool prepared_ = false;
269 bool committed_ = false;
270};
271
272} // namespace
273
276 resource::CostSpec cost, std::string product,
277 Duration duration, std::string productionKind,
278 int priority, std::string transactionId,
279 std::vector<RTSProductionResourceReserve> resourceReserves,
281 const auto subject = building.identity()->subject;
282 if (!subject.isValid())
285 "RTS building build requires a valid building subject", "building.subject"));
286 auto* production = building.production()->values.queueForComposition();
287 auto* orders = building.orders()->values.queueForComposition();
288 if (production == nullptr || orders == nullptr)
290 eve::DiagnosticCode::InvariantViolation, "RTS building build components are not initialized", "building"));
291
293 request.production = production;
294 request.orders = orders;
295 request.action = &action;
296 request.account = &account;
297 request.cost = std::move(cost);
298 request.owner = subject.format();
299 request.productionKind = std::move(productionKind);
300 request.product = std::move(product);
301 request.duration = std::move(duration);
302 request.priority = priority;
303 request.settlementRequired = request.productionKind == "unit" || request.productionKind == "research";
304 request.resourceReserves = std::move(resourceReserves);
305 request.definition = std::move(definition);
306 request.transactionId = std::move(transactionId);
307 request.actionRequest.source = ecs::handle_of(&building);
308 return build(std::move(request));
309}
310
312 if (request.production == nullptr || request.orders == nullptr || request.action == nullptr ||
313 request.account == nullptr)
316 "RTS build requires production, orders, Action and account ports", "request"));
317 if (request.owner.empty() || request.productionKind.empty() || request.product.empty())
319 eve::DiagnosticCode::InvalidArgument, "RTS build requires owner, kind and product", "request"));
320 if (!request.cost.isValid())
322 eve::DiagnosticCode::InvalidArgument, "RTS build requires a validated resource cost", "cost"));
323 if (request.duration.nanoseconds() <= 0 || request.actionDelta.nanoseconds() < 0)
325 eve::DiagnosticCode::InvalidArgument, "RTS build durations are invalid", "duration"));
326 request.settlementRequired = request.settlementRequired || request.productionKind == "unit" ||
327 request.productionKind == "research";
328
329 std::vector<resource::ResourceCost> protectedItems = request.cost.items();
330 for (const auto& reserve : request.resourceReserves) {
331 if (!reserve.resource.resource.isValid() || reserve.resource.amount.isZero())
333 eve::DiagnosticCode::InvalidArgument, "RTS production reserve must contain a positive resource amount",
334 "resourceReserves"));
335 if (request.priority < reserve.minimumPriority) protectedItems.push_back(reserve.resource);
336 }
337 auto protectedCost = resource::CostSpec::create(std::move(protectedItems));
338 if (!protectedCost) return eve::Result<RTSBuildReceipt>::failure(protectedCost.status());
339 auto protectedAffordability = request.account->canAfford(protectedCost.value());
340 if (!protectedAffordability)
341 return eve::Result<RTSBuildReceipt>::failure(protectedAffordability.status());
342 if (!protectedAffordability.value().isAffordable())
345 "RTS production would consume a protected resource reserve", "resourceReserves"));
346
347 if (!request.actionDefinition.id.isValid()) {
348 auto id = eve::LogicalId::fromParts("rts", "build." + request.product);
349 if (!id)
351 eve::DiagnosticCode::InvalidArgument, "RTS product cannot form an Action id", "product"));
352 request.actionDefinition.id = *id;
353 }
354 if (request.actionDefinition.cost || request.actionDefinition.activeExecutionRequired ||
355 !request.actionDefinition.effectIds.empty())
357 eve::DiagnosticCode::InvalidArgument, "RTS build Action must be effect-free; effect is a transaction port",
358 "actionDefinition"));
359 request.actionDefinition.timing = {};
360 if (!request.actionRequest.actionId.isValid()) request.actionRequest.actionId = request.actionDefinition.id;
361 if (request.actionRequest.actionId != request.actionDefinition.id)
364 "RTS build Action request does not match definition", "actionRequest"));
365 request.actionRequest.requestedTick = request.tick;
366
367 if (request.transactionId.empty()) request.transactionId = "rts.build." + request.product;
368 transaction::TransactionContext context(std::move(request.transactionId));
369 OrderParticipant order(*request.orders, request.orderKind, request.orderPriority, request.orderTimeoutSeconds,
370 request.product, request.owner);
371 ProductionParticipant production(*request.production, request.owner, request.productionKind, request.product,
372 std::move(request.context), std::move(request.definition),
373 std::move(request.reservation), request.duration, request.priority,
374 request.settlementRequired);
375 ActionParticipant action(*request.action, std::move(request.actionDefinition), std::move(request.actionRequest),
376 request.tick, request.actionDelta);
377 std::vector<transaction::ITransactionParticipant*> participants;
378 participants.reserve(request.effect ? 4u : 3u);
379 participants.push_back(&order);
380 participants.push_back(&production);
381 if (request.effect != nullptr) participants.push_back(request.effect);
382 participants.push_back(&action);
383
385 context, *request.account, request.cost, std::span<transaction::ITransactionParticipant*>(participants));
386 if (!committed) return eve::Result<RTSBuildReceipt>::failure(committed.status());
387 auto receipt = std::move(committed).takeValue();
388 RTSBuildReceipt result{std::move(receipt), order.orderId(), production.taskId(), action.executionId()};
390}
391
393 Building& building, resource::IResourceAccount& account, std::string productionTaskId,
394 std::string orderId, resource::CostSpec refund, std::string reason) {
395 auto* production = building.production()->values.queueForComposition();
396 auto* orders = building.orders()->values.queueForComposition();
397 if (production == nullptr || orders == nullptr)
400 "RTS building production components are not initialized", "building"));
402 request.production = production;
403 request.orders = orders;
404 request.account = &account;
405 request.refund = std::move(refund);
406 request.productionTaskId = std::move(productionTaskId);
407 request.orderId = std::move(orderId);
408 request.reason = std::move(reason);
409 return cancel(std::move(request));
410}
411
414 if (request.production == nullptr || request.orders == nullptr || request.account == nullptr)
417 "RTS production cancellation requires production, orders and account ports", "request"));
418 if (!request.refund.isValid() || request.productionTaskId.empty() || request.orderId.empty() ||
419 request.reason.empty())
422 "RTS production cancellation requires task, order, refund and reason", "request"));
423
424 auto productionBefore = request.production->snapshot();
425 if (!productionBefore)
426 return eve::Result<RTSCancelProductionReceipt>::failure(productionBefore.status());
427 production::WorkQueue stagedProduction;
428 auto stagedRestore = stagedProduction.restore(productionBefore.value());
429 if (!stagedRestore)
430 return eve::Result<RTSCancelProductionReceipt>::failure(stagedRestore.status());
431 auto cancelledTask = stagedProduction.cancel(request.productionTaskId, request.reason);
432 if (!cancelledTask)
433 return eve::Result<RTSCancelProductionReceipt>::failure(cancelledTask.status());
434
435 orders::CommandQueue ordersBefore;
436 orders::CommandQueue stagedOrders;
437 try {
438 ordersBefore = *request.orders;
439 stagedOrders = ordersBefore;
440 } catch (const std::exception& exception) {
442 eve::DiagnosticCode::Failed, std::string("failed to stage RTS cancellation orders: ") + exception.what(),
443 "orders"));
444 }
445 auto cancelledOrder = stagedOrders.cancel(request.orderId, request.reason);
446 if (!cancelledOrder)
447 return eve::Result<RTSCancelProductionReceipt>::failure(cancelledOrder.status());
448 auto productionAfter = stagedProduction.snapshot();
449 if (!productionAfter)
450 return eve::Result<RTSCancelProductionReceipt>::failure(productionAfter.status());
451
452 auto publishedProduction = request.production->restore(productionAfter.value());
453 if (!publishedProduction)
454 return eve::Result<RTSCancelProductionReceipt>::failure(publishedProduction.status());
455 try {
456 *request.orders = stagedOrders;
457 } catch (const std::exception& exception) {
458 auto restored = request.production->restore(productionBefore.value());
459 restored.ignore("best-effort production cancellation rollback");
461 eve::DiagnosticCode::Failed, std::string("failed to publish RTS cancellation orders: ") + exception.what(),
462 "orders"));
463 }
464
465 auto credited = request.account->credit(request.refund);
466 if (!credited) {
467 auto restoredProduction = request.production->restore(productionBefore.value());
468 try {
469 *request.orders = ordersBefore;
470 } catch (...) {
473 "RTS cancellation refund failed and order rollback could not be restored", "orders"));
474 }
475 if (!restoredProduction)
478 "RTS cancellation refund failed and production rollback could not be restored", "production"));
479 return eve::Result<RTSCancelProductionReceipt>::failure(credited.status());
480 }
481
482 RTSCancelProductionReceipt result{std::move(credited).takeValue(),
483 std::move(request.productionTaskId), std::move(request.orderId)};
486}
487
488} // namespace eve::rts
float duration
int subject
Definition AnimSmr.cpp:163
eve::action::ActionExecutionId executionId
int priority
eve::resource::CostSpec cost
eve::resource::IResourceAccount * account
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
TokenKind kind
std::string name
std::vector< std::int32_t > order
std::string action
Definition PlayHost.cpp:117
std::string id
Definition PlayHost.cpp:108
std::string taskId
Atomic RTS build/production composition over the shared protocols.
RTS domain roots and composition-only components.
double current
SimulationTick tick
const VegetationPresetContext & context
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
Signed, fixed-resolution simulation duration in nanoseconds.
Definition Time.h:43
static std::optional< LogicalId > fromParts(std::string_view namespaceName, std::string_view name)
Builds a logical ID from separate namespace and name components.
Definition Identity.cpp:48
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
Deterministic action lifecycle coordinator.
Definition Action.h:485
Generic priority order queue with no gameplay or entity semantics.
eve::Result< void > restore(std::string_view json)
Restores a command queue transactionally from a JSON snapshot.
eve::Result< void > cancel(const std::string &id, const std::string &reason="cancelled")
Cancels an active or queued command.
Generic multi-owner, multi-slot continuous-progress work queue.
Definition Production.h:200
eve::Result< std::string > snapshot() const
Serializes the complete queue as deterministic JSON.
eve::Result< void > cancel(std::string_view taskId, std::string_view reason="cancelled")
Cancels a non-terminal task with a structured outcome.
eve::Result< void > restore(std::string_view json)
Transactionally restores a snapshot; failure preserves current state.
Immutable canonical multi-resource cost.
static eve::Result< CostSpec > create(std::vector< ResourceCost > items)
Validate and canonicalize resource items.
Provider-neutral resource account contract.
RTS building domain root with placement and production composition.
Definition RTSTypes.h:840
static eve::Result< RTSBuildReceipt > build(Building &building, action::ActionRuntime &action, resource::IResourceAccount &account, resource::CostSpec cost, std::string product, Duration duration, std::string productionKind="unit", int priority=0, std::string transactionId={}, std::vector< RTSProductionResourceReserve > resourceReserves={}, eve::definition::DefinitionHandle definition={})
Run the canonical build transaction against a live RTS Building.
static eve::Result< RTSCancelProductionReceipt > cancel(Building &building, resource::IResourceAccount &account, std::string productionTaskId, std::string orderId, resource::CostSpec refund, std::string reason="production cancelled")
Cancel a building production task and refund its complete canonical cost atomically.
static eve::Result< TransactionReceipt > execute(const TransactionContext &context, std::span< ITransactionParticipant * > participants)
Execute already prepared domain participants as one transaction.
Immutable context shared by every participant of one transaction.
Definition Transaction.h:30
eve::detail::StrongUint64< ActionExecutionIdTag > ActionExecutionId
Definition Action.h:157
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
A generation-qualified reference to one definition incarnation.
Result of an accepted atomic RTS build/production command.
Caller-owned inputs for one atomic RTS build or production command.
production::WorkQueue * production
Result of a production cancellation whose queues and refund all committed.
Caller-owned inputs for atomically cancelling one paid production task.