载入中...
搜索中...
未找到
SettlementBatch.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <exception>
5#include <memory>
6#include <utility>
7
8namespace eve::settlement {
9namespace {
10
11template <class T>
12eve::Result<T> failure(eve::DiagnosticCode code, std::string message, std::string path = {}) {
14}
15
16template <class T>
18 return eve::Result<T>::failure(std::move(status));
19}
20
21} // namespace
22
24 std::span<ISettlementPolicy*> policies,
26 if (requests.empty())
27 return failure<std::vector<SettlementResult>>(eve::DiagnosticCode::InvalidArgument,
28 "atomic settlement requires at least one request", "requests");
29 if (requests.size() != policies.size())
30 return failure<std::vector<SettlementResult>>(
31 eve::DiagnosticCode::InvalidArgument, "atomic settlement request and policy counts must match", "policies");
32
33 for (std::size_t index = 0; index < policies.size(); ++index) {
34 if (policies[index] == nullptr)
35 return failure<std::vector<SettlementResult>>(eve::DiagnosticCode::InvalidArgument,
36 "atomic settlement policy must not be null",
37 "policies[" + std::to_string(index) + "]");
38 for (std::size_t previous = 0; previous < index; ++previous) {
39 if (requests[previous].target == requests[index].target &&
40 (requests[previous].resource.empty() || requests[index].resource.empty() ||
41 requests[previous].resource == requests[index].resource) &&
42 policies[previous] != policies[index])
43 return failure<std::vector<SettlementResult>>(
45 "channels for the same target and resource must share one policy instance",
46 "policies[" + std::to_string(index) + "]");
47 }
48 }
49
50 std::vector<SettlementResult> results(requests.size());
51 std::vector<std::unique_ptr<SettlementContext>> contexts;
52 contexts.reserve(requests.size());
53 const auto rollbackAll = [&]() noexcept {
54 for (auto it = contexts.rbegin(); it != contexts.rend(); ++it)
55 if (auto* pending = (*it)->pendingApply()) pending->rollback();
56 };
57
58 for (std::size_t index = 0; index < requests.size(); ++index) {
59 results[index].requested = requests[index].magnitude;
60 results[index].tick = requests[index].tick;
61 contexts.push_back(std::unique_ptr<SettlementContext>(
62 new SettlementContext(requests[index], results[index], *policies[index])));
63 auto prepared = prepare(*contexts.back(), results[index]);
64 if (!prepared) {
65 const auto status = prepared.status();
66 rollbackAll();
67 return failure<std::vector<SettlementResult>>(status);
68 }
69 auto committed = contexts.back()->pendingApply()->commit();
70 if (!committed) {
71 const auto status = committed.status();
72 rollbackAll();
73 return failure<std::vector<SettlementResult>>(status);
74 }
75 }
76
77 const std::string eventSnapshot = events == nullptr ? std::string{} : events->snapshotJson();
78 for (std::size_t index = 0; index < contexts.size(); ++index) {
79 const auto* preparedEvent = contexts[index]->pendingEvent();
80 if (preparedEvent == nullptr) continue;
81 game_event::GameEvent envelope = *preparedEvent;
82 results[index].event = envelope;
83 if (events == nullptr) continue;
84 try {
85 auto appended = events->append(envelope);
86 if (!appended) {
87 const auto status = appended.status();
88 auto restored = events->restore(eventSnapshot);
89 if (!restored) {
90 const auto restoreStatus = restored.status();
91 rollbackAll();
92 return failure<std::vector<SettlementResult>>(restoreStatus);
93 }
94 rollbackAll();
95 return failure<std::vector<SettlementResult>>(status);
96 }
97 const auto sequence = std::move(appended).takeValue();
98 envelope.sequence = sequence;
99 if (const auto* stored = events->find(sequence)) {
100 results[index].event = *stored;
101 } else {
102 results[index].event = envelope;
103 }
104 } catch (const std::exception& exception) {
105 auto restored = events->restore(eventSnapshot);
106 if (!restored) {
107 const auto status = restored.status();
108 rollbackAll();
109 return failure<std::vector<SettlementResult>>(status);
110 }
111 rollbackAll();
112 return failure<std::vector<SettlementResult>>(
113 eve::DiagnosticCode::Failed, std::string("atomic settlement event append threw: ") + exception.what(),
114 "events");
115 } catch (...) {
116 auto restored = events->restore(eventSnapshot);
117 if (!restored) {
118 const auto status = restored.status();
119 rollbackAll();
120 return failure<std::vector<SettlementResult>>(status);
121 }
122 rollbackAll();
123 return failure<std::vector<SettlementResult>>(
124 eve::DiagnosticCode::Failed, "atomic settlement event append threw an unknown exception", "events");
125 }
126 }
127
128 const bool anyApplied =
129 std::any_of(results.begin(), results.end(), [](const auto& result) { return result.applied != 0.0; });
131 std::move(results), eve::Status::success(anyApplied ? eve::StatusCode::Applied : eve::StatusCode::NoOp));
132}
133
135 std::span<const SettlementRequest> requests, std::span<ISettlementPolicy*> policies,
137 if (requests.empty())
138 return failure<std::vector<SettlementBatchItemResult>>(
139 eve::DiagnosticCode::InvalidArgument, "independent settlement requires at least one request", "requests");
140 if (requests.size() != policies.size())
141 return failure<std::vector<SettlementBatchItemResult>>(
142 eve::DiagnosticCode::InvalidArgument, "independent settlement request and policy counts must match",
143 "policies");
144 for (std::size_t index = 0; index < policies.size(); ++index)
145 if (policies[index] == nullptr)
146 return failure<std::vector<SettlementBatchItemResult>>(eve::DiagnosticCode::InvalidArgument,
147 "independent settlement policy must not be null",
148 "policies[" + std::to_string(index) + "]");
149
150 bool anyApplied = false;
151 std::vector<SettlementBatchItemResult> outcomes;
152 outcomes.reserve(requests.size());
153 for (std::size_t index = 0; index < requests.size(); ++index) {
154 auto settled = settle(requests[index], *policies[index], events);
156 outcome.index = index;
157 outcome.request = requests[index];
158 outcome.status = settled.status();
159 if (settled) {
160 outcome.result = std::move(settled).takeValue();
161 anyApplied = anyApplied || outcome.result->applied != 0.0;
162 }
163 outcomes.push_back(std::move(outcome));
164 }
166 std::move(outcomes), eve::Status::success(anyApplied ? eve::StatusCode::Applied : eve::StatusCode::NoOp));
167}
168
170 const SettlementRequest& root, const RequestExecutor& execute, std::uint32_t maxDepth,
171 std::uint32_t maxSettlements) const {
172 if (!execute)
173 return failure<std::vector<SettlementBatchItemResult>>(
174 eve::DiagnosticCode::InvalidArgument, "settlement chain request executor must not be empty", "execute");
175 if (maxSettlements == 0)
176 return failure<std::vector<SettlementBatchItemResult>>(
177 eve::DiagnosticCode::InvalidArgument, "settlement chain limit must include at least the root request",
178 "maxSettlements");
179 if (root.chain.depth != 0 || root.chain.emittedCount != 0 || !root.chain.triggerPath.empty() ||
180 !root.trigger.empty())
181 return failure<std::vector<SettlementBatchItemResult>>(
182 eve::DiagnosticCode::InvalidArgument, "settlement chain root contains derived-chain metadata", "root.chain");
183
184 std::vector<SettlementRequest> pending{root};
185 std::vector<SettlementBatchItemResult> outcomes;
186 std::uint32_t emittedCount = 0;
187 bool anyApplied = false;
188
189 const auto appendFailure = [&](eve::Status status, SettlementRequest request = {}) {
191 outcome.index = outcomes.size();
192 outcome.request = std::move(request);
193 outcome.status = std::move(status);
194 outcomes.push_back(std::move(outcome));
195 };
196
197 for (std::size_t cursor = 0; cursor < pending.size(); ++cursor) {
198 auto settled = execute(pending[cursor]);
200 outcome.index = outcomes.size();
201 outcome.request = pending[cursor];
202 outcome.status = settled.status();
203 if (!settled) {
204 outcomes.push_back(std::move(outcome));
205 break;
206 }
207
208 outcome.result = std::move(settled).takeValue();
209 anyApplied = anyApplied || outcome.result->applied != 0.0;
210 auto derived = outcome.result->derived;
211 outcomes.push_back(std::move(outcome));
212
213 bool stop = false;
214 for (auto& child : derived) {
215 if (pending.size() >= maxSettlements) {
217 eve::DiagnosticCode::Conflict, "settlement chain exceeded its total settlement limit",
218 "maxSettlements")), child);
219 stop = true;
220 break;
221 }
222 if (child.trigger.empty()) {
224 eve::DiagnosticCode::InvalidArgument, "derived settlement request requires a trigger key",
225 "derived.trigger")), child);
226 stop = true;
227 break;
228 }
229 if (pending[cursor].chain.depth >= maxDepth) {
231 eve::DiagnosticCode::Conflict, "settlement chain exceeded its depth limit", "maxDepth")), child);
232 stop = true;
233 break;
234 }
235 if (std::find(pending[cursor].chain.triggerPath.begin(), pending[cursor].chain.triggerPath.end(),
236 child.trigger) != pending[cursor].chain.triggerPath.end()) {
238 eve::DiagnosticCode::Conflict, "settlement trigger re-entered its ancestry path",
239 "derived.trigger")), child);
240 stop = true;
241 break;
242 }
243 if (child.chain.depth != 0 || child.chain.emittedCount != 0 || !child.chain.triggerPath.empty()) {
245 eve::DiagnosticCode::InvalidArgument, "policy-produced request must not pre-populate chain metadata",
246 "derived.chain")), child);
247 stop = true;
248 break;
249 }
250
251 ++emittedCount;
252 child.chain = pending[cursor].chain;
253 child.chain.depth = pending[cursor].chain.depth + 1;
254 child.chain.emittedCount = emittedCount;
255 child.chain.triggerPath.push_back(child.trigger);
256 if (child.correlation.kind() == game_event::CorrelationId::Kind::None)
257 child.correlation = pending[cursor].correlation;
258 if (child.causation.kind() == game_event::CausationRef::Kind::None && outcomes.back().result->event &&
259 !outcomes.back().result->event->eventId.isNil())
260 child.causation = game_event::CausationRef::fromEventId(outcomes.back().result->event->eventId);
261 pending.push_back(std::move(child));
262 }
263 if (stop) break;
264 }
265
267 std::move(outcomes), eve::Status::success(anyApplied ? eve::StatusCode::Applied : eve::StatusCode::NoOp));
268}
269
270} // namespace eve::settlement
LogicalId target
int root
Definition AnimSmr.cpp:119
std::vector< QuestEvent > pending
std::vector< std::uint8_t > stored
Definition Evpack.cpp:171
std::string message
DiagnosticCode code
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
graphics::Canvas * previous
std::unique_ptr< gpgpu::Sequence > sequence
Definition OnnxGpgpu.cpp:43
std::string path
Definition PlayHost.cpp:110
std::string resource
Battle::Events events
std::size_t cursor
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
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
static CausationRef fromEventId(EventId value)
Creates a causation reference to an event.
Definition GameEvent.h:105
Deterministic in-memory stream of topic-neutral event envelopes.
Definition GameEvent.h:261
Mutable calculation frame visible to policy and custom stage code.
Definition Settlement.h:338
std::function< eve::Result< SettlementResult >(const SettlementRequest &)> RequestExecutor
Executes one owning chain request using its request-specific policy and rule snapshot.
Definition Settlement.h:546
eve::Result< SettlementResult > settle(const SettlementRequest &request, ISettlementPolicy &policy, game_event::GameEventLog *events=nullptr) const
Execute one settlement transaction against a domain policy.
eve::Result< std::vector< SettlementBatchItemResult > > settleChain(const SettlementRequest &root, const RequestExecutor &execute, std::uint32_t maxDepth, std::uint32_t maxSettlements) const
Settle a root request and its policy-produced derived requests in stable breadth-first order.
eve::Result< std::vector< SettlementBatchItemResult > > settleIndependent(std::span< const SettlementRequest > requests, std::span< ISettlementPolicy * > policies, game_event::GameEventLog *events=nullptr) const
Settle several channels independently in caller-provided deterministic order.
eve::Result< std::vector< SettlementResult > > settleAtomic(std::span< const SettlementRequest > requests, std::span< ISettlementPolicy * > policies, game_event::GameEventLog *events=nullptr) const
Prepare and commit several independent resource channels atomically.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
Domain-neutral, deterministic settlement pipeline.
Envelope metadata shared by event domains.
Definition GameEvent.h:183
EventSequence sequence
Sequence assigned by the owning stream; it is never global identity.
Definition GameEvent.h:187
Indexed owning outcome for one independently committed settlement item.
Definition Settlement.h:265
std::optional< SettlementResult > result
Definition Settlement.h:269
Domain-neutral input to one settlement operation.
Definition Settlement.h:107