21eve::Value encodeReservation(
const CraftingRecipe& recipe, std::uint32_t batchSize) {
23 outputs.reserve(recipe.outputs.size());
24 for (
const auto&
output : recipe.outputs) {
27 {
"quantity",
eve::Value(std::int64_t(
output.quantity) *
static_cast<std::int64_t
>(batchSize))}});
36 const auto multiplier =
static_cast<std::int64_t
>(batchSize);
37 std::vector<eve::resource::ResourceCost> items;
38 items.reserve(
source.items().size());
39 for (
const auto& item :
source.items()) {
40 if (item.amount.value() > std::numeric_limits<std::int64_t>::max() / multiplier)
42 "crafting batch ingredient quantity overflows",
"batchSize");
44 item.resource.value(), item.amount.value() * multiplier);
46 items.push_back(std::move(scaled).takeValue());
53 if (
object ==
nullptr)
54 return failure<std::vector<inventory::InventoryItemGrant>>(
56 const auto schema =
object->find(
"schema");
57 const auto version =
object->find(
"version");
58 const auto outputs =
object->find(
"outputs");
59 if (schema ==
object->end() || !schema->second.isString() ||
60 schema->second.asString() !=
"eve.rpg.crafting-reservation" || version ==
object->end() ||
61 !version->second.isInt64() || version->second.asInt() != 1 || outputs ==
object->end() ||
62 !outputs->second.isArray())
63 return failure<std::vector<inventory::InventoryItemGrant>>(
65 std::vector<inventory::InventoryItemGrant> result;
67 for (
const auto&
value : *outputValues) {
70 return failure<std::vector<inventory::InventoryItemGrant>>(
72 const auto item = entry->find(
"item");
73 const auto quantity = entry->find(
"quantity");
74 if (item == entry->end() || !item->second.isString() || item->second.asString().empty() ||
75 quantity == entry->end() || !quantity->second.isInt64() || quantity->second.asInt() <= 0 ||
76 quantity->second.asInt() > std::numeric_limits<int>::max())
77 return failure<std::vector<inventory::InventoryItemGrant>>(
79 result.push_back({item->second.asString(),
static_cast<int>(quantity->second.asInt())});
82 return failure<std::vector<inventory::InventoryItemGrant>>(
87class ProductionParticipant final :
public transaction::ITransactionParticipant {
89 ProductionParticipant(production::WorkQueue& queue, production::ProductionRequest
request)
90 : queue_(queue), request_(
std::move(
request)) {}
92 [[nodiscard]] std::string_view
name() const noexcept
override {
return "rpg-crafting-production"; }
94 [[nodiscard]]
eve::Result<void> prepare(
const transaction::TransactionContext&)
override {
95 auto snapshot = queue_.snapshot();
97 before_ = std::move(snapshot).takeValue();
98 staged_ = std::make_unique<production::WorkQueue>();
99 auto restored = staged_->restore(before_);
100 if (!restored)
return restored;
101 auto enqueued = staged_->enqueue(request_);
103 taskId_ = std::move(
enqueued).takeValue();
104 auto after = staged_->snapshot();
106 after_ = std::move(after).takeValue();
111 [[nodiscard]]
eve::Result<void> commit(
const transaction::TransactionContext&)
override {
112 if (!prepared_ || committed_)
114 auto current = queue_.snapshot();
116 if (
current.value() != before_)
118 "crafting production queue changed while staged",
"production");
119 auto restored = queue_.restore(after_);
120 if (!restored)
return restored;
125 [[nodiscard]]
eve::Result<void> rollback(
const transaction::TransactionContext&)
override {
133 [[nodiscard]]
eve::Result<void> compensate(
const transaction::TransactionContext&)
override {
136 auto current = queue_.snapshot();
140 "crafting production queue changed before compensation",
"production");
141 auto restored = queue_.restore(before_);
142 if (!restored)
return restored;
149 [[nodiscard]]
const std::string&
taskId() const noexcept {
return taskId_; }
152 production::WorkQueue& queue_;
153 production::ProductionRequest request_;
154 std::unique_ptr<production::WorkQueue> staged_;
158 bool prepared_ =
false;
159 bool committed_ =
false;
162class SettlementParticipant final :
public transaction::ITransactionParticipant {
164 SettlementParticipant(production::WorkQueue& queue, std::string
taskId,
165 production::ProductionSettlementReceipt receipt)
166 : queue_(queue), taskId_(
std::move(
taskId)), receipt_(
std::move(receipt)) {}
168 [[nodiscard]] std::string_view
name() const noexcept
override {
return "rpg-crafting-settlement"; }
170 [[nodiscard]]
eve::Result<void> prepare(
const transaction::TransactionContext&)
override {
171 auto before = queue_.snapshot();
173 before_ = std::move(before).takeValue();
174 production::WorkQueue staged;
175 auto restored = staged.restore(before_);
176 if (!restored)
return restored;
177 const auto task = staged.
find(taskId_);
181 auto retried = staged.retrySettlement(taskId_);
182 if (!retried)
return retried;
184 auto settled = staged.settle(taskId_, receipt_);
185 if (!settled)
return settled;
186 auto after = staged.snapshot();
188 after_ = std::move(after).takeValue();
193 [[nodiscard]]
eve::Result<void> commit(
const transaction::TransactionContext&)
override {
194 if (!prepared_ || committed_)
196 auto current = queue_.snapshot();
198 if (
current.value() != before_)
200 "crafting queue changed while settlement was staged",
"production");
201 auto restored = queue_.restore(after_);
202 if (!restored)
return restored;
207 [[nodiscard]]
eve::Result<void> rollback(
const transaction::TransactionContext&)
override {
212 [[nodiscard]]
eve::Result<void> compensate(
const transaction::TransactionContext&)
override {
215 auto current = queue_.snapshot();
219 "crafting queue changed before settlement compensation",
"production");
220 auto restored = queue_.restore(before_);
221 if (!restored)
return restored;
228 production::WorkQueue& queue_;
230 production::ProductionSettlementReceipt receipt_;
233 bool prepared_ =
false;
234 bool committed_ =
false;
237class InventoryAddParticipant final :
public transaction::ITransactionParticipant {
239 InventoryAddParticipant(inventory::Bag&
output, std::vector<inventory::InventoryItemGrant> grants)
240 : output_(
output), grants_(
std::move(grants)) {}
242 [[nodiscard]] std::string_view
name() const noexcept
override {
return "rpg-crafting-output"; }
244 [[nodiscard]]
eve::Result<void> prepare(
const transaction::TransactionContext&)
override {
247 prepared_.emplace(std::move(prepared).takeValue());
251 [[nodiscard]]
eve::Result<void> commit(
const transaction::TransactionContext&)
override {
257 added_ = std::move(committed).takeValue();
261 [[nodiscard]]
eve::Result<void> rollback(
const transaction::TransactionContext&)
override {
266 [[nodiscard]]
eve::Result<void> compensate(
const transaction::TransactionContext&)
override {
268 "committed crafting output cannot be compensated",
"output");
271 [[nodiscard]]
int added() const noexcept {
return added_; }
274 inventory::Bag& output_;
275 std::vector<inventory::InventoryItemGrant> grants_;
276 std::optional<inventory::PreparedInventoryAdd> prepared_;
283 if (
request.production ==
nullptr ||
request.ingredients ==
nullptr)
285 "crafting requires production and ingredient account ports",
"request");
288 "crafting owner, recipe id and process are required",
"request");
289 if (!
request.recipe.ingredients.isValid() ||
request.recipe.outputs.empty() ||
request.batchSize == 0 ||
290 request.recipe.duration.nanoseconds() <= 0)
292 "crafting recipe requires ingredients, outputs and positive duration",
294 std::int64_t totalOutput = 0;
296 const auto quantity =
static_cast<std::int64_t
>(
output.quantity);
297 const auto batchSize =
static_cast<std::int64_t
>(
request.batchSize);
298 if (
output.itemId.empty() || quantity <= 0 ||
299 quantity >
static_cast<std::int64_t
>(std::numeric_limits<int>::max()) / batchSize)
301 "crafting outputs require item ids and positive quantities",
303 const auto scaled = quantity * batchSize;
304 if (totalOutput > std::numeric_limits<int>::max() - scaled)
306 "crafting batch output total exceeds inventory limits",
308 totalOutput += scaled;
325 auto batchIngredients = scaleCost(
request.recipe.ingredients,
request.batchSize);
328 ProductionParticipant participant(*
request.production, std::move(productionRequest));
330 context, *
request.ingredients, batchIngredients.value(), participant);
332 CraftingReceipt receipt{std::move(committed).takeValue(), participant.taskId()};
338 std::string_view
taskId) {
342 const std::string settlementId =
"rpg.crafting:" + std::string(
taskId);
344 task->get().settlement.settlementId == settlementId)
349 auto outputs = decodeOutputs(task->get().reservation);
351 InventoryAddParticipant inventoryParticipant(
output, outputs.value());
355 SettlementParticipant settlementParticipant(queue, std::string(
taskId), std::move(receipt));
357 std::array<transaction::ITransactionParticipant*, 2> participants{
358 &settlementParticipant, &inventoryParticipant};
360 auto committed = coordinator.
execute(
context, std::span<transaction::ITransactionParticipant*>(participants));
362 const auto* diagnostic = committed.error();
364 diagnostic ==
nullptr ?
"crafting output rejected" : diagnostic->message());
Small transaction facade for a resource cost plus one domain effect.
RPG recipe adapter over the shared production and inventory protocols.
const GltfImportRequest & request
const UnitySourceAsset & source
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.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
The canonical owning dynamic value used by data-facing protocols.
std::map< std::string, Value > Object
std::vector< Value > Array
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
static eve::Result< int > commitAddBatch(PreparedInventoryAdd prepared)
Commit one prepared addition and then publish its change events.
static eve::Result< PreparedInventoryAdd > prepareAddBatch(Bag *bag, const std::vector< InventoryItemGrant > &grants)
Validate a multi-item addition against a private candidate Bag.
Generic multi-owner, multi-slot continuous-progress work queue.
eve::OptionalRef< const ProductionTask > find(std::string_view taskId)
Finds a retained task by stable ID.
eve::Result< void > failSettlement(std::string_view taskId, std::string_view reason)
Records a retryable domain settlement failure without losing completed work.
Immutable canonical multi-resource cost.
static eve::Result< CostSpec > create(std::vector< ResourceCost > items)
Validate and canonicalize resource items.
static eve::Result< int > settle(production::WorkQueue &queue, inventory::Bag &output, std::string_view taskId)
Publish every reserved recipe output and settle the task exactly once.
static eve::Result< CraftingReceipt > begin(CraftingRequest request)
Atomically debit ingredients and enqueue pinned recipe work.
static eve::Result< TransactionReceipt > execute(const TransactionContext &context, std::span< ITransactionParticipant * > participants)
Execute already prepared domain participants as one transaction.
Coordinates participant lifecycles with deterministic ordering.
Result< TransactionReceipt > execute(const TransactionContext &context, std::span< ITransactionParticipant * > participants) const
Atomically execute borrowed participants for one context.
Immutable context shared by every participant of one transaction.
RPG 模块的基础实体:把属性 / 状态 / 技能三张表挂到一个 ECS 实体上。
DiagnosticCode
Stable machine-readable diagnostic codes.
Validated input used to create a domain-neutral production task.
eve::definition::DefinitionHandle definition
Owning proof that a domain consumer published one task exactly once.
static eve::Result< ResourceCost > create(std::string_view resourceId, std::int64_t amountValue)
Construct and validate one cost item.
Receipt for an accepted recipe job.
Borrowed inputs for atomically paying ingredients and enqueuing recipe work.