载入中...
搜索中...
未找到
InventoryResourceAccount.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cstddef>
7#include <exception>
8#include <limits>
9#include <string>
10#include <utility>
11#include <vector>
12
13namespace eve::inventory {
14namespace {
15
24
25eve::Result<void> validateCost(const CostSpec& cost) {
26 if (!cost.isValid())
28 eve::DiagnosticCode::InvalidArgument, "inventory account requires a validated non-empty CostSpec", "cost"));
30}
31
32eve::Status insufficient(const Affordability& affordability) {
34 for (const auto& shortfall : affordability.shortfalls) {
35 details.emplace_back("resource", shortfall.resource.value());
36 details.emplace_back("required", std::to_string(shortfall.required.value()));
37 details.emplace_back("available", std::to_string(shortfall.available.value()));
38 }
42 "inventory item cost exceeds available quantity", "cost", details));
43}
44
45eve::Status foreignReservation() {
49 "resource reservation belongs to a different inventory account", "reservation.account"));
50}
51
52eve::Status terminalReservation() {
55 eve::Diagnostic::error(eve::DiagnosticCode::Conflict, "inventory resource reservation is no longer active",
56 "reservation"));
57}
58
59eve::Status unknownReservation() {
63 "inventory resource reservation was not issued by this account", "reservation"));
64}
65
66eve::Status receiptExhausted() {
69 "inventory resource receipt id exhausted", "receipt"));
70}
71
72eve::Status identityUnavailable() {
76 "inventory resource account has no valid identity nonce", "account"));
77}
78
79} // namespace
80
83 if (nonce)
84 accountNonce_ = std::move(nonce).takeValue();
85 else
86 nonce.ignore("inventory account identity exhausted; adapter remains unusable");
87}
88
89eve::Result<std::int64_t> InventoryResourceAccount::balanceOf(const ResourceId& resource) const {
90 const int quantity = bag_.countItem(resource.value());
91 if (quantity < 0)
93 eve::DiagnosticCode::InvariantViolation, "inventory item quantity must not be negative", resource.value()));
94 return eve::Result<std::int64_t>::success(static_cast<std::int64_t>(quantity));
95}
96
97eve::Result<std::int64_t> InventoryResourceAccount::activeReservationsFor(const ResourceId& resource) const {
98 std::int64_t total = 0;
99 for (const auto& [id, record] : reservations_) {
100 (void)id;
101 if (record.state != ReservationState::Active) continue;
102 for (const auto& item : record.cost.items()) {
103 if (item.resource != resource) continue;
104 if (item.amount.value() > std::numeric_limits<std::int64_t>::max() - total)
107 "active inventory reservations overflowed", resource.value()));
108 total += item.amount.value();
109 }
110 }
112}
113
115 if (accountNonce_.isZero()) return eve::Result<Affordability>::failure(identityUnavailable());
116 auto valid = validateCost(cost);
117 if (!valid) return eve::Result<Affordability>::failure(valid.status());
118
119 Affordability result;
120 result.affordable = true;
121 for (const auto& item : cost.items()) {
122 auto balance = balanceOf(item.resource);
123 if (!balance) return eve::Result<Affordability>::failure(balance.status());
124 auto reserved = activeReservationsFor(item.resource);
125 if (!reserved) return eve::Result<Affordability>::failure(reserved.status());
126 const std::int64_t available = balance.value() >= reserved.value() ? balance.value() - reserved.value() : 0;
127 if (available < item.amount.value()) {
128 result.affordable = false;
129 result.shortfalls.push_back({item.resource, item.amount, Amount(available)});
130 }
131 }
132 return eve::Result<Affordability>::success(std::move(result));
133}
134
136 if (accountNonce_.isZero()) return eve::Result<Reservation>::failure(identityUnavailable());
137 auto affordability = canAfford(cost);
138 if (!affordability) return eve::Result<Reservation>::failure(affordability.status());
139 const auto& checked = affordability.value();
140 if (!checked.affordable) return eve::Result<Reservation>::failure(insufficient(checked));
141 if (nextReservation_.value() == std::numeric_limits<std::uint64_t>::max())
143 eve::DiagnosticCode::InvariantViolation, "inventory resource reservation id exhausted", "reservation"));
144
145 const ReservationId id = nextReservation_;
146 const auto [it, inserted] = reservations_.emplace(id, ReservationRecord{cost, ReservationState::Active});
147 if (!inserted)
149 eve::DiagnosticCode::Conflict, "inventory resource reservation id was already used", "reservation"));
150 nextReservation_ = ReservationId(id.value() + 1);
151 return eve::Result<Reservation>::success(Reservation{accountNonce_, id, it->second.cost},
153}
154
155eve::Result<void> InventoryResourceAccount::applyDelta(const CostSpec& cost, bool debit) {
156 auto valid = validateCost(cost);
157 if (!valid) return valid;
158 for (const auto& item : cost.items()) {
159 if (item.amount.value() > std::numeric_limits<int>::max())
162 "inventory item quantity exceeds the Bag integer range", item.resource.value()));
163 }
164
165 const auto originalSlots = bag_.slots();
166 const int originalInstanceCounter = InventorySystem::instanceCounter();
167 const std::size_t originalEventCount = InventorySystem::eventQueue().size();
168 const bool oldHookSuppression = InventorySystem::changeHooksSuppressed();
169 InventorySystem::changeHooksSuppressed() = true;
170
171 auto restore = [&] {
172 bag_.slots() = originalSlots;
173 InventorySystem::instanceCounter() = originalInstanceCounter;
174 InventorySystem::eventQueue().resize(originalEventCount);
175 InventorySystem::changeHooksSuppressed() = oldHookSuppression;
176 };
177
178 try {
179 for (const auto& item : cost.items()) {
180 const int amount = static_cast<int>(item.amount.value());
181 if (debit) {
182 auto balance = balanceOf(item.resource);
183 if (!balance || balance.value() < amount) {
184 restore();
186 eve::DiagnosticCode::PreconditionViolation, "inventory debit would make item quantity negative",
187 item.resource.value()));
188 }
189 if (InventorySystem::removeItem(&bag_, item.resource.value(), amount) != amount) {
190 restore();
192 eve::DiagnosticCode::Failed, "inventory debit was not applied in full", item.resource.value()));
193 }
194 } else {
195 std::string reason;
196 if (!InventorySystem::canAdd(&bag_, item.resource.value(), amount, &reason)) {
197 restore();
200 "inventory credit was rejected by the Bag policy: " + reason, item.resource.value()));
201 }
202 if (InventorySystem::addItem(&bag_, item.resource.value(), amount) != amount) {
203 restore();
205 "inventory credit was not applied in full",
206 item.resource.value()));
207 }
208 }
209 }
210 } catch (const std::exception& exception) {
211 restore();
214 std::string(debit ? "debit" : "credit") +
215 " failed while staging an inventory candidate: " + exception.what(),
216 "inventory.account"));
217 } catch (...) {
218 restore();
221 std::string(debit ? "debit" : "credit") + " failed while staging an inventory candidate",
222 "inventory.account"));
223 }
224
225 InventorySystem::changeHooksSuppressed() = oldHookSuppression;
226
227 // The state is already committed. Deliver a snapshot of the events after
228 // the mutation, outside the suppressed section and without retaining any
229 // internal container reference across callbacks.
230 const std::vector<InventoryChangeEvent> emitted(
231 InventorySystem::eventQueue().begin() + static_cast<std::ptrdiff_t>(originalEventCount),
232 InventorySystem::eventQueue().end());
233 const auto hooks = InventorySystem::changeHooks();
234 for (const auto& event : emitted) {
235 for (const auto& [name, hook] : hooks) {
236 (void)name;
237 if (!hook) continue;
238 try {
239 hook(event);
240 } catch (...) {
241 // Event hooks are observers, not part of the account commit.
242 }
243 }
244 }
246}
247
249 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(identityUnavailable());
250 auto affordability = canAfford(cost);
251 if (!affordability) return eve::Result<Receipt>::failure(affordability.status());
252 const auto& checked = affordability.value();
253 if (!checked.affordable) return eve::Result<Receipt>::failure(insufficient(checked));
254 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
255 return eve::Result<Receipt>::failure(receiptExhausted());
256
257 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Debit, cost};
258 auto applied = applyDelta(cost, true);
259 if (!applied) return eve::Result<Receipt>::failure(applied.status());
260 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
262}
263
265 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(identityUnavailable());
266 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
267 return eve::Result<Receipt>::failure(receiptExhausted());
268 Receipt receipt{accountNonce_, nextReceipt_, ReservationId{}, ReceiptOperation::Credit, cost};
269 auto applied = applyDelta(cost, false);
270 if (!applied) return eve::Result<Receipt>::failure(applied.status());
271 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
273}
274
275eve::Result<void> InventoryResourceAccount::activeReservationsAreCovered() const {
276 for (const auto& [id, record] : reservations_) {
277 (void)id;
278 if (record.state != ReservationState::Active) continue;
279 for (const auto& item : record.cost.items()) {
280 auto balance = balanceOf(item.resource);
281 if (!balance) return eve::Result<void>::failure(balance.status());
282 auto reserved = activeReservationsFor(item.resource);
283 if (!reserved) return eve::Result<void>::failure(reserved.status());
284 if (balance.value() < reserved.value())
286 eve::DiagnosticCode::Conflict, "external Bag mutation invalidated active item reservations",
287 item.resource.value()));
288 }
289 }
291}
292
294 if (accountNonce_.isZero()) return eve::Result<Receipt>::failure(identityUnavailable());
295 if (!reservation.isValid())
297 eve::DiagnosticCode::InvalidArgument, "inventory resource reservation is invalid", "reservation"));
298 if (reservation.account != accountNonce_) return eve::Result<Receipt>::failure(foreignReservation());
299 const auto it = reservations_.find(reservation.id);
300 if (it == reservations_.end()) return eve::Result<Receipt>::failure(unknownReservation());
301 if (it->second.cost != reservation.cost)
304 "inventory reservation cost does not match its account record", "reservation.cost"));
305 if (it->second.state != ReservationState::Active) return eve::Result<Receipt>::failure(terminalReservation());
306 auto covered = activeReservationsAreCovered();
307 if (!covered) return eve::Result<Receipt>::failure(covered.status());
308 if (nextReceipt_.value() == std::numeric_limits<std::uint64_t>::max())
309 return eve::Result<Receipt>::failure(receiptExhausted());
310
311 Receipt receipt{accountNonce_, nextReceipt_, reservation.id, ReceiptOperation::Debit, it->second.cost};
312 auto applied = applyDelta(it->second.cost, true);
313 if (!applied) return eve::Result<Receipt>::failure(applied.status());
314 it->second.state = ReservationState::Committed;
315 nextReceipt_ = eve::resource::ReceiptId(nextReceipt_.value() + 1);
317}
318
320 if (accountNonce_.isZero()) return eve::Result<void>::failure(identityUnavailable());
321 if (!reservation.isValid())
323 eve::DiagnosticCode::InvalidArgument, "inventory resource reservation is invalid", "reservation"));
324 if (reservation.account != accountNonce_) return eve::Result<void>::failure(foreignReservation());
325 const auto it = reservations_.find(reservation.id);
326 if (it == reservations_.end()) return eve::Result<void>::failure(unknownReservation());
327 if (it->second.cost != reservation.cost)
330 "inventory reservation cost does not match its account record", "reservation.cost"));
331 if (it->second.state != ReservationState::Active) return eve::Result<void>::failure(terminalReservation());
332 it->second.state = ReservationState::RolledBack;
334}
335
336eve::Result<CostSpec> ItemCostAdapter::itemCost(std::string_view itemId, std::int64_t quantity) {
337 return CostSpec::single(itemId, quantity);
338}
339
340eve::Result<CostSpec> ItemCostAdapter::ammoCost(std::string_view ammoItemId, std::int64_t quantity) {
341 return CostSpec::single(ammoItemId, quantity);
342}
343
344} // namespace eve::inventory
double value
LogicalId itemId
eve::resource::CostSpec cost
Resource-account and cost adapters backed by Bag item stacks.
std::string name
bool valid
std::string id
Definition PlayHost.cpp:108
float begin
std::string resource
int covered
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
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
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
格子型物品容器(脚本可直接操作)。
Definition Bag.h:21
const std::vector< ItemStack > & slots() const
Slots.
Definition Bag.h:149
int countItem(const std::string &itemId) const
Returns the number of item.
Definition Bag.cpp:87
eve::Result< eve::resource::Reservation > reserve(const eve::resource::CostSpec &cost) override
Reserve all items of a cost without changing visible balances.
eve::Result< eve::resource::Receipt > debit(const eve::resource::CostSpec &cost) override
Atomically debit all items immediately.
eve::Result< eve::resource::Receipt > credit(const eve::resource::CostSpec &cost) override
Atomically credit all items immediately.
eve::Result< void > rollback(const eve::resource::Reservation &reservation) override
Release one active reservation without changing balances.
eve::Result< eve::resource::Receipt > commit(const eve::resource::Reservation &reservation) override
Commit one active reservation as an atomic debit.
InventoryResourceAccount(Bag &bag)
Bind the adapter to one caller-owned inventory bag.
eve::Result< eve::resource::Affordability > canAfford(const eve::resource::CostSpec &cost) const override
Query whether a cost can be paid from currently available funds.
static int addItem(Bag *bag, const std::string &itemId, int quantity)
返回实际放入数量(可能部分成功)。
static bool canAdd(Bag *bag, const std::string &itemId, int quantity, std::string *reason=nullptr)
Can add.
static int removeItem(Bag *bag, const std::string &itemId, int quantity)
static eve::Result< eve::resource::CostSpec > itemCost(std::string_view itemId, std::int64_t quantity)
Build a positive cost for an inventory item.
static eve::Result< eve::resource::CostSpec > ammoCost(std::string_view ammoItemId, std::int64_t quantity)
Build a positive cost for ammunition stored as an item stack.
constexpr bool isZero() const noexcept
Whether this nonce is invalid/zero.
Non-negative integral quantity used by resource accounts.
Immutable canonical multi-resource cost.
const std::vector< ResourceCost > & items() const noexcept
Return canonical items as a read-only borrowed view.
bool isValid() const noexcept
Whether this value contains a validated non-empty cost.
Strong identity of a resource kind, such as mana or gold.
运行时容器:固定格数的背包 / 箱子 / 商店栏等。 行为由 InventorySystem 提供;本类暴露便于脚本绑定的薄封装方法。
eve::Result< AccountNonce > allocateAccountNonce()
Allocate a process-local account identity for a credential issuer.
eve::detail::StrongUint64< eve::detail::ResourceReceiptIdTag > ReceiptId
Process-local id of an applied resource operation.
ReceiptOperation
Operation represented by a receipt returned from an account.
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
Structured answer to a canAfford query.
Receipt proving one atomic debit or credit was applied.
Reservation credential returned by a successful reserve operation.