载入中...
搜索中...
未找到
SettlementModule.cpp
浏览该文件的文档.
2
3#include "common/Identity.h"
7#include "common/SubjectRef.h"
10
11#include <simplesquirrel/simplesquirrel.hpp>
12
13#include <cmath>
14#include <cstdint>
15#include <functional>
16#include <map>
17#include <memory>
18#include <string>
19#include <string_view>
20#include <utility>
21
22namespace eve::settlement {
23namespace {
24
25struct ResourceKey {
26 std::string subject;
27 std::string resource;
28 bool operator<(const ResourceKey& other) const {
29 if (subject != other.subject) return subject < other.subject;
30 return resource < other.resource;
31 }
32};
33
34struct ResourceState {
35 double current = 0.0;
36 double maximum = 0.0;
37};
38
39bool isCreditKind(std::string_view kind) noexcept { return kind == "heal" || kind == "gain"; }
40
41bool isDebitKind(std::string_view kind) noexcept { return kind == "damage" || kind == "spend"; }
42
43bool isLedgerKind(std::string_view kind) noexcept { return isCreditKind(kind) || isDebitKind(kind); }
44
45class LedgerPolicy final : public ISettlementPolicy {
46public:
47 explicit LedgerPolicy(ResourceState& state) : state_(state) {}
48
49 Result<void> validate(SettlementContext&) override {
50 if (!(state_.current >= 0.0) || !(state_.current <= state_.maximum) || !(state_.maximum >= 0.0) ||
51 !std::isfinite(state_.current) || !std::isfinite(state_.maximum))
53 "settlement ledger resource is outside bounds", "resource",
54 {}, "settlement.squirrel"));
55 return Result<void>::success();
56 }
57 Result<void> sourceModifiers(SettlementContext&) override { return Result<void>::success(); }
58 Result<void> targetMitigation(SettlementContext&) override { return Result<void>::success(); }
59 Result<void> armorShield(SettlementContext&) override { return Result<void>::success(); }
60 Result<void> clamp(SettlementContext& context) override {
61 const auto& kind = context.request().kind;
62 if (isCreditKind(kind)) return context.setClampMax(state_.maximum - state_.current);
63 if (isDebitKind(kind)) return context.setClampMax(state_.current);
65 "settlement ledger kind must be damage, spend, heal, or gain",
66 std::string(kind), {}, "settlement.squirrel"));
67 }
68 Result<PreparedApply> prepareApply(const SettlementContext& context) override {
69 const double before = state_.current;
70 const auto& kind = context.request().kind;
71 double after = before;
72 if (isCreditKind(kind))
73 after = before + context.magnitude();
74 else if (isDebitKind(kind))
75 after = before - context.magnitude();
76 else
78 "settlement ledger kind must be damage, spend, heal, or gain",
79 std::string(kind), {}, "settlement.squirrel")).status());
80 return Result<PreparedApply>::success(PreparedApply(
81 [this, after]() {
82 state_.current = after;
83 return Result<void>::success();
84 },
85 [this, before]() { state_.current = before; }));
86 }
87
88private:
89 ResourceState& state_;
90};
91
92class ScriptSettlementRuntime {
93public:
94 Result<Value> configureRulesJson(const std::string& json) {
95 SettlementRuleSet candidate;
96 auto configured = candidate.configureJson(json);
97 if (!configured) return Result<Value>::failure(configured.status());
98 SettlementPipeline pipeline;
99 auto installed = candidate.install(pipeline);
100 if (!installed) return Result<Value>::failure(installed.status());
101 rules_ = std::move(candidate);
102 pipeline_ = std::move(pipeline);
103 Value::Object result;
104 result["ruleCount"] = static_cast<std::int64_t>(rules_.size());
106 }
107
108 Result<Value> validateRulesJson(const std::string& json) const {
109 auto inspected = SettlementRuleSet::fromJson(json);
110 if (!inspected) return Result<Value>::failure(inspected.status());
111 Value::Object result;
112 result["ruleCount"] = static_cast<std::int64_t>(inspected.value().size());
113 return Result<Value>::success(Value(std::move(result)));
114 }
115
116 Result<Value> rulesCanonicalJson() const {
117 auto json = rules_.canonicalJson();
118 if (!json) return Result<Value>::failure(json.status());
119 return Result<Value>::success(Value(json.value()));
120 }
121
122 Result<Value> rulesDigest() const {
123 auto digest = rules_.digest(snapshotContentHashProvider());
124 if (!digest) return Result<Value>::failure(digest.status());
125 return Result<Value>::success(Value(digest.value().format()));
126 }
127
128 std::int64_t ruleCount() const { return static_cast<std::int64_t>(rules_.size()); }
129
130 Result<Value> upsertResource(const std::string& subject, const std::string& resource, double current,
131 double maximum) {
132 auto parsed = PersistentId::parse(subject);
133 if (!parsed)
135 "settlement subject must be a UUID", "subject", {},
136 "settlement.squirrel"));
137 if (resource.empty() || !std::isfinite(current) || !std::isfinite(maximum) || maximum < 0.0 || current < 0.0 ||
140 "settlement resource bounds are invalid", "resource", {},
141 "settlement.squirrel"));
142 const std::string subjectKey = parsed->format();
143 resources_[{subjectKey, resource}] = ResourceState{current, maximum};
144 Value::Object result;
145 result["subject"] = subjectKey;
146 result["resource"] = resource;
147 result["current"] = current;
148 result["maximum"] = maximum;
150 }
151
152 Result<Value> getResource(const std::string& subject, const std::string& resource) const {
153 auto parsed = PersistentId::parse(subject);
154 if (!parsed)
156 "settlement subject must be a UUID", "subject", {},
157 "settlement.squirrel"));
158 const std::string subjectKey = parsed->format();
159 const auto found = resources_.find({subjectKey, resource});
160 if (found == resources_.end())
162 "settlement ledger resource was not found", "resource", {},
163 "settlement.squirrel"));
164 Value::Object result;
165 result["subject"] = subjectKey;
166 result["resource"] = resource;
167 result["current"] = found->second.current;
168 result["maximum"] = found->second.maximum;
169 return Result<Value>::success(Value(std::move(result)));
170 }
171
172 Result<Value> removeResource(const std::string& subject, const std::string& resource) {
173 auto parsed = PersistentId::parse(subject);
174 if (!parsed)
176 "settlement subject must be a UUID", "subject", {},
177 "settlement.squirrel"));
178 if (resources_.erase({parsed->format(), resource}) == 0)
180 "settlement ledger resource was not found", "resource", {},
181 "settlement.squirrel"));
183 }
184
185 void clearResources() { resources_.clear(); }
186
187 Result<Value> settle(const std::string& sourceText, const std::string& targetText, const std::string& kind,
188 const std::string& resource, double magnitude, const std::string& tagsCsv,
189 const std::string& contextJson, std::int64_t tick) {
190 auto sourceId = PersistentId::parse(sourceText);
191 if (!sourceId)
193 "settlement source must be a UUID", "source", {},
194 "settlement.squirrel"));
195 auto targetId = PersistentId::parse(targetText);
196 if (!targetId)
198 "settlement target must be a UUID", "target", {},
199 "settlement.squirrel"));
200 if (kind.empty() || resource.empty() || !std::isfinite(magnitude) || magnitude < 0.0 || tick < 0)
202 "settlement request fields are invalid", "request", {},
203 "settlement.squirrel"));
205 "settlement ledger kind must be damage, spend, heal, or gain",
206 std::string(kind), {}, "settlement.squirrel")).status());
207 const std::string targetKey = targetId->format();
208 auto found = resources_.find({targetKey, resource});
209 if (found == resources_.end())
211 "settlement ledger target resource was not found",
212 "resource", {}, "settlement.squirrel"));
213
214 SettlementRequest request;
217 request.kind = kind;
218 request.resource = resource;
219 request.magnitude = magnitude;
220 request.tick = SimulationTick(static_cast<std::uint64_t>(tick));
221 if (!tagsCsv.empty()) {
222 std::string tag;
223 for (char ch : tagsCsv) {
224 if (ch == ',') {
225 if (!tag.empty()) {
226 request.tags.push_back(tag);
227 tag.clear();
228 }
229 } else if (ch != ' ') {
230 tag.push_back(ch);
231 }
232 }
233 if (!tag.empty()) request.tags.push_back(tag);
234 }
235 if (!contextJson.empty()) {
236 auto context = Value::fromJson(contextJson);
237 if (!context) return Result<Value>::failure(context.status());
238 request.context = std::move(context).takeValue();
239 }
240
241 LedgerPolicy policy(found->second);
242 auto settled = pipeline_.settle(request, policy);
243 if (!settled) return Result<Value>::failure(settled.status());
244 auto json = settlementResultCanonicalJson(settled.value());
245 if (!json) return Result<Value>::failure(json.status());
246 Value::Object result;
247 result["resultJson"] = json.value();
248 result["applied"] = settled.value().applied;
249 result["absorbed"] = settled.value().absorbed;
250 result["resisted"] = settled.value().resisted;
251 result["disposition"] = std::string(settlementDispositionName(settled.value().disposition));
252 result["current"] = found->second.current;
253 result["maximum"] = found->second.maximum;
255 }
256
257private:
258 SettlementPipeline pipeline_;
259 SettlementRuleSet rules_;
260 std::map<ResourceKey, ResourceState> resources_;
261};
262
263ssq::Table project(HSQUIRRELVM vm, Result<Value>&& result) {
264 return script::projectResult(vm, std::move(result), [](Value value) { return value; });
265}
266
267ssq::Table nullRuntime(HSQUIRRELVM vm) {
269 "settlement runtime must not be null", "runtime", {},
270 "settlement.squirrel")));
271}
272
273ssq::Table newRuntime(HSQUIRRELVM vm) {
274 auto runtime = std::make_unique<ScriptSettlementRuntime>();
275 auto object = script::makeOwnedSquirrelInstance<ScriptSettlementRuntime>(vm, std::move(runtime));
276 if (!object) return script::projectStatusResult(vm, object.status());
278 result.set("value", std::move(object).takeValue());
279 result.set("ownership", std::string("owned"));
280 return result;
281}
282
283} // namespace
284
286
287void Settlement::expose(ssq::Table& table) {
288 const HSQUIRRELVM vm = table.getHandle();
289 auto runtime = table.addClass<ScriptSettlementRuntime>(
290 "SettlementRuntime", std::function<ScriptSettlementRuntime*()>([] { return nullptr; }), true);
291 runtime.addFunc("ownership", [](ScriptSettlementRuntime*) { return std::string("owned"); });
292 runtime.addFunc("configureRulesJson", [vm](ScriptSettlementRuntime* self, const std::string& json) {
293 return self ? project(vm, self->configureRulesJson(json)) : nullRuntime(vm);
294 });
295 runtime.addFunc("validateRulesJson", [vm](ScriptSettlementRuntime* self, const std::string& json) {
296 return self ? project(vm, self->validateRulesJson(json)) : nullRuntime(vm);
297 });
298 runtime.addFunc("rulesCanonicalJson", [vm](ScriptSettlementRuntime* self) {
299 return self ? project(vm, self->rulesCanonicalJson()) : nullRuntime(vm);
300 });
301 runtime.addFunc("rulesDigest", [vm](ScriptSettlementRuntime* self) {
302 return self ? project(vm, self->rulesDigest()) : nullRuntime(vm);
303 });
304 runtime.addFunc("ruleCount",
305 [](ScriptSettlementRuntime* self) { return self ? self->ruleCount() : std::int64_t{0}; });
306 runtime.addFunc("upsertResource", [vm](ScriptSettlementRuntime* self, const std::string& subject,
307 const std::string& resource, float current, float maximum) {
308 return self ? project(vm, self->upsertResource(subject, resource, current, maximum)) : nullRuntime(vm);
309 });
310 runtime.addFunc("getResource", [vm](ScriptSettlementRuntime* self, const std::string& subject,
311 const std::string& resource) {
312 return self ? project(vm, self->getResource(subject, resource)) : nullRuntime(vm);
313 });
314 runtime.addFunc("removeResource", [vm](ScriptSettlementRuntime* self, const std::string& subject,
315 const std::string& resource) {
316 return self ? project(vm, self->removeResource(subject, resource)) : nullRuntime(vm);
317 });
318 runtime.addFunc("clearResources", [](ScriptSettlementRuntime* self) {
319 if (self) self->clearResources();
320 });
321 runtime.addFunc("settle", [vm](ScriptSettlementRuntime* self, const std::string& source, const std::string& target,
322 const std::string& kind, const std::string& resource, float magnitude,
323 const std::string& tagsCsv, const std::string& contextJson, std::int64_t tick) {
324 return self ? project(vm, self->settle(source, target, kind, resource, magnitude, tagsCsv, contextJson, tick))
325 : nullRuntime(vm);
326 });
327
328 auto module = table.addClass(name, Settlement::create, false);
329 module.addFunc("getName", &Settlement::getName);
330 module.addFunc("newRuntime", [vm](Settlement*) { return newRuntime(vm); });
331}
332
333void Settlement::expose(ssq::Class& cls) {
334 cls.addFunc("getName", &Settlement::getName);
335 cls.addFunc("newRuntime", [vm = cls.getHandle()](Settlement*) { return newRuntime(vm); });
336}
337
338} // namespace eve::settlement
LogicalId target
double value
int subject
Definition AnimSmr.cpp:163
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::uint64_t sourceId
float maximum[3]
const GltfImportRequest & request
wgpu::PopErrorScopeStatus status
Strong identity values shared by persistence, content and logical-name APIs.
TokenKind kind
const std::string * tag
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
std::uint32_t targetId
bool found
double current
std::string resource
Script-facing factory for settlement rule + ledger runtimes.
std::string digest
Declarative, domain-neutral rules for buffs and gameplay effects.
Injectable content-digest provider for snapshots, with an honest default.
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
Stable reference to a runtime gameplay subject.
SimulationTick tick
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.
Definition Diagnostic.h:125
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
static SubjectRef fromPersistentId(PersistentId id) noexcept
Wrap a persistent identity without changing its bytes.
Definition SubjectRef.h:32
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
static std::optional< Id128 > parse(std::string_view text) noexcept
Parses canonical UUID text.
Definition Identity.h:151
static eve::Result< SettlementRuleSet > fromJson(std::string_view json)
Decode and compile a strict versioned JSON rule document without mutating an existing rule set.
Factory module for script-owned settlement protocol runtimes.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
ssq::Table project(HSQUIRRELVM vm, const eve::Result< void > &result)
Project a completed editing result that has no payload.
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
const char * settlementDispositionName(SettlementDisposition disposition) noexcept
Return the stable lowercase spelling of a settlement disposition.
EVENGINE_API_FOUNDATION eve::Result< std::string > settlementResultCanonicalJson(const SettlementResult &result)
Serialize the stable semantic fields of a settlement result as canonical JSON.
void validate(const RuntimeTensor &v)
Validate.
SnapshotHashProvider snapshotContentHashProvider()
A SnapshotHashProvider backed by the active hasher.
detail::StrongUint64< detail::SimulationTickTag > SimulationTick
Deterministic simulation time step; it is not wall-clock time.
Definition Time.h:31
Domain-neutral, deterministic settlement pipeline.