载入中...
搜索中...
未找到
Decision.cpp
浏览该文件的文档.
1#include "decision/Decision.h"
2
3#include "common/Json.h"
5
6#include <algorithm>
7#include <cmath>
8#include <functional>
9#include <iomanip>
10#include <limits>
11#include <simplesquirrel/simplesquirrel.hpp>
12#include <sstream>
13#include <utility>
14
15namespace eve::decision {
16namespace {
17
19struct ScriptDecisionContext {
20 explicit ScriptDecisionContext(DecisionContextHandleRef value) : reference(value) {}
21 ~ScriptDecisionContext() noexcept {
22 Decision::release(reference).ignore("script decision context proxy destruction");
23 }
25};
26
27eve::Result<void> decisionApplied() {
29}
30
31eve::Result<bool> decisionTriggered(bool triggered) {
34}
35
36template <class Ref, class Proxy, class Release>
37ssq::Table makeOwnedProxy(HSQUIRRELVM vm, eve::Result<Ref>&& reference, Release&& release) {
38 if (!reference) {
40 }
41
42 const Ref ref = std::move(reference).takeValue();
43 auto object = eve::script::makeOwnedSquirrelInstance<Proxy>(vm, std::make_unique<Proxy>(ref));
44 if (!object) {
45 const eve::Status status = object.status();
46 object.ignore("failed to create owned decision proxy");
47 std::invoke(std::forward<Release>(release), ref).ignore("rollback failed owned decision allocation");
49 }
50 ssq::Object owned = std::move(object).takeValue();
52 result.set("value", owned);
53 result.set("ownership", std::string("owned"));
54 result.set("ownerEpoch", static_cast<std::int64_t>(ref.ownerEpoch));
55 result.set("handle", static_cast<std::int64_t>(ref.packed()));
56 return result;
57}
58
59std::string quote(const std::string& value) {
60 std::ostringstream output;
61 output << '"';
62 for (char character : value) {
63 if (character == '"' || character == '\\') {
64 output << '\\';
65 }
66 output << character;
67 }
68 return output.str() + '"';
69}
70
71bool isScalar(const eve::json::Value& value) {
72 return value.isNull() || value.isBool() || value.isNumber() || value.isString();
73}
74
75std::string canonicalize(const eve::json::Value& value) {
76 if (value.isNull()) {
77 return "null";
78 }
79 if (value.isBool()) {
80 return value.asBool() ? "true" : "false";
81 }
82 if (value.isNumber()) {
83 return value.asString();
84 }
85 return quote(value.asString());
86}
87
88bool parseNumber(const std::string& text, float& value) {
89 try {
90 size_t parsedLength = 0;
91 value = std::stof(text, &parsedLength);
92 return parsedLength == text.size() && std::isfinite(value);
93 } catch (...) {
94 return false;
95 }
96}
97
98} // namespace
99
100eve::Result<void> DecisionContext::set(const std::string& board, const std::string& key, const std::string& valueJson) {
101 std::string parseError;
102 auto document = eve::json::Document::parse(valueJson, &parseError);
103 if (board.empty()) {
105 eve::DiagnosticCode::InvalidArgument, "blackboard name must not be empty", "board", {}, "decision"));
106 }
107 if (key.empty()) {
109 eve::DiagnosticCode::InvalidArgument, "blackboard key must not be empty", "key", {}, "decision"));
110 }
111 if (!document.valid()) {
113 eve::DiagnosticCode::ParseError, parseError.empty() ? "invalid blackboard JSON" : parseError, "value", {},
114 "decision"));
115 }
116 if (!isScalar(document.root())) {
118 eve::DiagnosticCode::InvalidArgument, "blackboard value must be a JSON scalar", "value", {}, "decision"));
119 }
120
121 boards_[board][key] = canonicalize(document.root());
122 return decisionApplied();
123}
124
125std::string DecisionContext::get(const std::string& board, const std::string& key,
126 const std::string& fallbackJson) const {
127 auto boardIt = boards_.find(board);
128 if (boardIt == boards_.end()) {
129 return fallbackJson;
130 }
131
132 auto valueIt = boardIt->second.find(key);
133 return valueIt == boardIt->second.end() ? fallbackJson : valueIt->second;
134}
135eve::Result<void> DecisionContext::addTransition(const std::string& m, const std::string& f, const std::string& t,
136 const std::string& to) {
137 if (m.empty() || f.empty() || t.empty() || to.empty())
139 "FSM transition names must not be empty", "transition",
140 {}, "decision"));
141 transitions_[m][{f, t}] = to;
142 return decisionApplied();
143}
144eve::Result<void> DecisionContext::setState(const std::string& m, const std::string& s) {
145 if (m.empty() || s.empty())
147 "FSM machine and state names must not be empty",
148 "state", {}, "decision"));
149 states_[m] = s;
150 return decisionApplied();
151}
152eve::Result<bool> DecisionContext::trigger(const std::string& m, const std::string& t) {
153 if (m.empty() || t.empty())
155 "FSM machine and trigger names must not be empty",
156 "trigger", {}, "decision"));
157 auto s = states_.find(m);
158 auto a = transitions_.find(m);
159 if (s == states_.end())
161 eve::DiagnosticCode::NotFound, "FSM machine has no current state", "machine", {}, "decision"));
162 if (a == transitions_.end()) return decisionTriggered(false);
163 auto i = a->second.find({s->second, t});
164 if (i == a->second.end()) return decisionTriggered(false);
165 s->second = i->second;
166 return decisionTriggered(true);
167}
168std::string DecisionContext::state(const std::string& m) const {
169 auto i = states_.find(m);
170 return i == states_.end() ? std::string{} : i->second;
171}
172float DecisionContext::utility(const std::string& considerationsCsv) {
173 std::stringstream input(considerationsCsv);
174 std::string consideration;
175 double weightedSum = 0;
176 double totalWeight = 0;
177
178 while (std::getline(input, consideration, ',')) {
179 auto separator = consideration.find(':');
180 if (separator == std::string::npos) {
181 continue;
182 }
183
184 float value = 0;
185 float weight = 0;
186 if (parseNumber(consideration.substr(0, separator), value) &&
187 parseNumber(consideration.substr(separator + 1), weight) && weight > 0) {
188 weightedSum += std::clamp(value, 0.f, 1.f) * weight;
189 totalWeight += weight;
190 }
191 }
192
193 return totalWeight ? float(weightedSum / totalWeight) : 0.f;
194}
195
196std::string DecisionContext::choose(const std::string& options) {
197 std::stringstream input(options);
198 std::string option;
199 std::string bestName;
200 float bestScore = -1;
201
202 while (std::getline(input, option, ';')) {
203 auto separator = option.find('=');
204 if (separator == std::string::npos || separator == 0) {
205 continue;
206 }
207
208 auto name = option.substr(0, separator);
209 float score = utility(option.substr(separator + 1));
210 if (score > bestScore || (score == bestScore && (bestName.empty() || name < bestName))) {
211 bestScore = score;
212 bestName = name;
213 }
214 }
215
216 return bestName;
217}
218
219eve::Result<void> DecisionContext::newGrid(const std::string& name, int width, int height, float cellSize,
220 float originX, float originY) {
221 if (name.empty()) {
223 eve::DiagnosticCode::InvalidArgument, "influence grid name must not be empty", "name", {}, "decision"));
224 }
225 if (width <= 0 || height <= 0 || size_t(width) > 10000000 / size_t(height)) {
227 "influence grid dimensions are invalid or too large",
228 "dimensions", {}, "decision"));
229 }
230 if (!std::isfinite(cellSize) || cellSize <= 0 || !std::isfinite(originX) || !std::isfinite(originY)) {
233 "influence grid geometry must be finite and have a positive cell size", "geometry", {}, "decision"));
234 }
235
236 grids_[name] = {width, height, cellSize, originX, originY, std::vector<float>(size_t(width) * height)};
237 return decisionApplied();
238}
239
240eve::Result<void> DecisionContext::setCell(const std::string& name, int x, int y, float value) {
241 auto gridIt = grids_.find(name);
242 if (gridIt == grids_.end()) {
244 eve::DiagnosticCode::NotFound, "influence grid does not exist", "grid", {}, "decision"));
245 }
246 if (x < 0 || y < 0 || x >= gridIt->second.width || y >= gridIt->second.height || !std::isfinite(value)) {
248 eve::DiagnosticCode::InvalidArgument, "influence grid cell or value is invalid", "cell", {}, "decision"));
249 }
250
251 gridIt->second.values[size_t(y) * gridIt->second.width + x] = value;
252 return decisionApplied();
253}
254
255eve::Result<void> DecisionContext::addCell(const std::string& name, int x, int y, float delta) {
256 auto gridIt = grids_.find(name);
257 if (gridIt == grids_.end()) {
259 eve::DiagnosticCode::NotFound, "influence grid does not exist", "grid", {}, "decision"));
260 }
261 if (x < 0 || y < 0 || x >= gridIt->second.width || y >= gridIt->second.height || !std::isfinite(delta)) {
263 eve::DiagnosticCode::InvalidArgument, "influence grid cell or value is invalid", "cell", {}, "decision"));
264 }
265
266 auto& cell = gridIt->second.values[size_t(y) * gridIt->second.width + x];
267 if (!std::isfinite(cell + delta)) {
269 eve::DiagnosticCode::Failed, "influence grid cell update overflowed", "cell", {}, "decision"));
270 }
271
272 cell += delta;
273 return decisionApplied();
274}
275
276float DecisionContext::sample(const std::string& name, float worldX, float worldY, float fallback) const {
277 auto gridIt = grids_.find(name);
278 if (gridIt == grids_.end()) {
279 return fallback;
280 }
281
282 const Grid& grid = gridIt->second;
283 int cellX = int(std::floor((worldX - grid.originX) / grid.cellSize));
284 int cellY = int(std::floor((worldY - grid.originY) / grid.cellSize));
285 if (cellX < 0 || cellY < 0 || cellX >= grid.width || cellY >= grid.height) {
286 return fallback;
287 }
288
289 return grid.values[size_t(cellY) * grid.width + cellX];
290}
291std::string DecisionContext::snapshotJson() const {
292 std::ostringstream o;
293 o << std::setprecision(9) << "{\"version\":1,\"boards\":{";
294 bool a = true;
295 for (auto& [b, vs] : boards_) {
296 if (!a) o << ',';
297 a = false;
298 o << quote(b) << ":{";
299 bool f = true;
300 for (auto& [k, v] : vs) {
301 if (!f) o << ',';
302 f = false;
303 o << quote(k) << ':' << v;
304 }
305 o << '}';
306 }
307 o << "},\"states\":{";
308 a = true;
309 for (auto& [m, s] : states_) {
310 if (!a) o << ',';
311 a = false;
312 o << quote(m) << ':' << quote(s);
313 }
314 o << "},\"transitions\":[";
315 a = true;
316 for (auto& [m, ts] : transitions_)
317 for (auto& [key, to] : ts) {
318 if (!a) o << ',';
319 a = false;
320 o << "[" << quote(m) << ',' << quote(key.first) << ',' << quote(key.second) << ',' << quote(to) << "]";
321 }
322 o << "],\"grids\":[";
323 a = true;
324 for (auto& [n, g] : grids_) {
325 if (!a) o << ',';
326 a = false;
327 o << "{\"name\":" << quote(n) << ",\"w\":" << g.width << ",\"h\":" << g.height << ",\"cell\":" << g.cellSize
328 << ",\"ox\":" << g.originX << ",\"oy\":" << g.originY << ",\"values\":[";
329 for (size_t i = 0; i < g.values.size(); ++i) {
330 if (i) o << ',';
331 o << g.values[i];
332 }
333 o << "]}";
334 }
335 return o.str() + "]}";
336}
338 std::string e;
339 auto d = eve::json::Document::parse(j, &e);
340 if (!d.valid())
342 e.empty() ? "invalid decision snapshot JSON" : e,
343 "snapshot", {}, "decision"));
345 "decision snapshot must be a JSON object", "snapshot", {}, "decision"));
346 const auto version = d.root().get("version");
347 if (!version.isIntegerLiteral() || version.asInt() != 1)
349 eve::DiagnosticCode::UnknownVersion, "unsupported decision snapshot version", "version", {}, "decision"));
351 auto b = d.root().get("boards");
353 "decision snapshot boards must be an object", "boards", {}, "decision"));
354 for (auto& bn : b.keys()) {
355 auto v = b.get(bn.c_str());
357 "blackboard must be an object", "boards." + bn, {}, "decision"));
358 for (auto& k : v.keys()) {
359 const auto value = v.get(k.c_str());
360 if (!isScalar(value))
362 "blackboard value must be a JSON scalar", "boards." + bn + "." + k, {}, "decision"));
363 auto result = n.set(bn, k, canonicalize(value));
364 if (!result.ok()) return eve::Result<void>::failure(result.status());
365 }
366 }
367 auto st = d.root().get("states");
369 "decision snapshot states must be an object", "states", {}, "decision"));
370 for (auto& m : st.keys()) {
371 const auto state = st.get(m.c_str());
373 "FSM state must be a string", "states." + m, {}, "decision"));
374 auto result = n.setState(m, state.asString());
375 if (!result.ok()) return eve::Result<void>::failure(result.status());
376 }
377 auto tr = d.root().get("transitions");
379 "decision snapshot transitions must be an array", "transitions", {}, "decision"));
380 for (size_t i = 0; i < tr.size(); ++i) {
381 auto v = tr.at(i);
382 if (!v.isArray() || v.size() != 4)
384 "FSM transition must contain four strings", "transitions[" + std::to_string(i) + "]", {}, "decision"));
385 for (size_t field = 0; field < 4; ++field)
386 if (!v.at(field).isString())
388 "FSM transition must contain four strings", "transitions[" + std::to_string(i) + "]", {}, "decision"));
389 auto result = n.addTransition(v.at(0).asString(), v.at(1).asString(), v.at(2).asString(), v.at(3).asString());
390 if (!result.ok()) return eve::Result<void>::failure(result.status());
391 }
392 auto gs = d.root().get("grids");
394 "decision snapshot grids must be an array", "grids", {}, "decision"));
395 for (size_t i = 0; i < gs.size(); ++i) {
396 auto v = gs.at(i);
397 if (!v.isObject())
399 "influence grid must be an object", "grids[" + std::to_string(i) + "]", {}, "decision"));
400 int w = v.getInt("w"), h = v.getInt("h");
401 const auto name = v.get("name");
402 const auto cell = v.get("cell");
403 const auto ox = v.get("ox");
404 const auto oy = v.get("oy");
405 if (!name.isString() || !cell.isNumber() || !ox.isNumber() || !oy.isNumber())
407 "influence grid fields are malformed", "grids[" + std::to_string(i) + "]", {}, "decision"));
408 auto grid =
409 n.newGrid(name.asString(), w, h, float(cell.asDouble()), float(ox.asDouble()), float(oy.asDouble()));
410 if (!grid.ok()) return eve::Result<void>::failure(grid.status());
411 auto vals = v.get("values");
412 if (!vals.isArray() || vals.size() != size_t(w) * h)
414 "influence grid values have the wrong size", "grids[" + std::to_string(i) + "].values", {}, "decision"));
415 for (size_t z = 0; z < vals.size(); ++z) {
416 const auto value = vals.at(z);
417 if (!value.isNumber() || !std::isfinite(value.asDouble()))
419 "influence grid cell must be a finite number", "grids[" + std::to_string(i) + "].values[" + std::to_string(z) + "]", {}, "decision"));
420 auto result = n.setCell(name.asString(), int(z % size_t(w)), int(z / size_t(w)), float(value.asDouble()));
421 if (!result.ok()) return eve::Result<void>::failure(result.status());
422 }
423 }
424 boards_ = std::move(n.boards_);
425 states_ = std::move(n.states_);
426 transitions_ = std::move(n.transitions_);
427 grids_ = std::move(n.grids_);
428 return decisionApplied();
429}
431 Decision* module = Decision::create();
432 return module->contexts_.emplace(std::make_unique<DecisionContext>());
433}
434
436 Decision* module = ModuleManager::getInstance<Decision>("Decision");
437 if (!module) return {};
438 return module->contexts_.resolve(reference);
439}
440
442 Decision* module = ModuleManager::getInstance<Decision>("Decision");
443 if (!module)
445 eve::DiagnosticCode::StaleHandle, "Decision module is no longer loaded", "context", {}, "decision"));
446 return module->contexts_.erase(reference);
447}
448
450 if (!reference.isValid()) return false;
451 Decision* module = ModuleManager::getInstance<Decision>("Decision");
452 return !module || module->contexts_.isStale(reference);
453}
454
456void Decision::expose(ssq::Table& t) {
457 const HSQUIRRELVM vm = t.getHandle();
458 auto x = t.addClass<DecisionContext>("DecisionContext", std::function<DecisionContext*()>([]() { return nullptr; }),
459 false);
460 x.addFunc("set",
461 [vm](DecisionContext* value, const std::string& board, const std::string& key, const std::string& json) {
462 if (!value)
465 "decision context must not be null",
466 "context", {}, "decision")));
467 return eve::script::projectResult(vm, value->set(board, key, json));
468 });
469 x.addFunc("get", &DecisionContext::get);
470 x.addFunc("addTransition", [vm](DecisionContext* value, const std::string& machine, const std::string& from,
471 const std::string& trigger, const std::string& to) {
472 if (!value)
475 "decision context must not be null", "context", {}, "decision")));
476 return eve::script::projectResult(vm, value->addTransition(machine, from, trigger, to));
477 });
478 x.addFunc("setState", [vm](DecisionContext* value, const std::string& machine, const std::string& state) {
479 if (!value)
482 "decision context must not be null", "context", {}, "decision")));
483 return eve::script::projectResult(vm, value->setState(machine, state));
484 });
485 x.addFunc("trigger", [vm](DecisionContext* value, const std::string& machine, const std::string& trigger) {
486 if (!value)
490 "decision context must not be null", "context", {}, "decision")),
491 [](bool fired) { return eve::Value(fired); });
492 return eve::script::projectResult(vm, value->trigger(machine, trigger),
493 [](bool fired) { return eve::Value(fired); });
494 });
495 x.addFunc("state", &DecisionContext::state);
496 x.addFunc("utility", [](DecisionContext*, const std::string& s) { return DecisionContext::utility(s); });
497 x.addFunc("choose", [](DecisionContext*, const std::string& s) { return DecisionContext::choose(s); });
498 x.addFunc("newGrid", [vm](DecisionContext* value, const std::string& name, int width, int height, float cellSize,
499 float originX, float originY) {
500 if (!value)
503 "decision context must not be null", "context", {}, "decision")));
504 return eve::script::projectResult(vm, value->newGrid(name, width, height, cellSize, originX, originY));
505 });
506 x.addFunc("setCell", [vm](DecisionContext* value, const std::string& name, int x, int y, float cell) {
507 if (!value)
510 "decision context must not be null", "context", {}, "decision")));
511 return eve::script::projectResult(vm, value->setCell(name, x, y, cell));
512 });
513 x.addFunc("addCell", [vm](DecisionContext* value, const std::string& name, int x, int y, float delta) {
514 if (!value)
517 "decision context must not be null", "context", {}, "decision")));
518 return eve::script::projectResult(vm, value->addCell(name, x, y, delta));
519 });
520 x.addFunc("sample", &DecisionContext::sample);
521 x.addFunc("snapshotJson", &DecisionContext::snapshotJson);
522 x.addFunc("restoreJson", [vm](DecisionContext* value, const std::string& json) {
523 if (!value)
526 "decision context must not be null", "context", {}, "decision")));
527 return eve::script::projectResult(vm, value->restoreJson(json));
528 });
529 auto owned = t.addClass<ScriptDecisionContext>(
530 "DecisionContextProxy", std::function<ScriptDecisionContext*()>([] { return nullptr; }), true);
531 owned.addFunc("ownership", [](ScriptDecisionContext*) { return std::string("owned"); });
532 owned.addFunc("ownerEpoch", [](ScriptDecisionContext* value) {
533 return value ? static_cast<int64_t>(value->reference.ownerEpoch) : int64_t{0};
534 });
535 owned.addFunc("handle", [](ScriptDecisionContext* value) {
536 return value ? static_cast<int64_t>(value->reference.packed()) : int64_t{0};
537 });
538 owned.addFunc("isStale",
539 [](ScriptDecisionContext* value) { return !value || Decision::isStale(value->reference); });
540 owned.addFunc("release", [vm](ScriptDecisionContext* value) {
541 if (!value)
544 "owned decision context proxy must not be null",
545 "context", {}, "decision")));
547 });
548 owned.addFunc("setState", [vm](ScriptDecisionContext* value, const std::string& machine, const std::string& state) {
549 if (!value)
552 "owned decision context proxy must not be null",
553 "context", {}, "decision")));
554 auto view = Decision::resolve(value->reference);
555 if (!view.isBound())
558 "owned decision context handle is stale",
559 "context", {}, "decision")));
560 return eve::script::projectResult(vm, view->setState(machine, state));
561 });
562 owned.addFunc("set", [vm](ScriptDecisionContext* value, const std::string& board, const std::string& key,
563 const std::string& json) {
564 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
565 if (!view.isBound())
568 "owned decision context handle is stale",
569 "context", {}, "decision")));
570 return eve::script::projectResult(vm, view->set(board, key, json));
571 });
572 owned.addFunc("get", [](ScriptDecisionContext* value, const std::string& board, const std::string& key,
573 const std::string& fallback) {
574 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
575 return view.isBound() ? view->get(board, key, fallback) : fallback;
576 });
577 owned.addFunc("addTransition", [vm](ScriptDecisionContext* value, const std::string& machine,
578 const std::string& from, const std::string& trigger, const std::string& to) {
579 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
580 if (!view.isBound())
583 "owned decision context handle is stale",
584 "context", {}, "decision")));
585 return eve::script::projectResult(vm, view->addTransition(machine, from, trigger, to));
586 });
587 owned.addFunc(
588 "trigger", [vm](ScriptDecisionContext* value, const std::string& machine, const std::string& trigger) {
589 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
590 if (!view.isBound())
592 vm,
594 "owned decision context handle is stale",
595 "context", {}, "decision")),
596 [](bool fired) { return eve::Value(fired); });
597 return eve::script::projectResult(vm, view->trigger(machine, trigger),
598 [](bool fired) { return eve::Value(fired); });
599 });
600 owned.addFunc("state", [](ScriptDecisionContext* value, const std::string& machine) {
601 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
602 return view.isBound() ? view->state(machine) : std::string{};
603 });
604 owned.addFunc("newGrid", [vm](ScriptDecisionContext* value, const std::string& name, int width, int height,
605 float cellSize, float originX, float originY) {
606 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
607 if (!view.isBound())
610 "owned decision context handle is stale",
611 "context", {}, "decision")));
612 return eve::script::projectResult(vm, view->newGrid(name, width, height, cellSize, originX, originY));
613 });
614 owned.addFunc("setCell", [vm](ScriptDecisionContext* value, const std::string& name, int x, int y, float cell) {
615 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
616 if (!view.isBound())
619 "owned decision context handle is stale",
620 "context", {}, "decision")));
621 return eve::script::projectResult(vm, view->setCell(name, x, y, cell));
622 });
623 owned.addFunc("addCell", [vm](ScriptDecisionContext* value, const std::string& name, int x, int y, float delta) {
624 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
625 if (!view.isBound())
628 "owned decision context handle is stale",
629 "context", {}, "decision")));
630 return eve::script::projectResult(vm, view->addCell(name, x, y, delta));
631 });
632 owned.addFunc("sample", [](ScriptDecisionContext* value, const std::string& name, float worldX, float worldY,
633 float fallback) {
634 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
635 return view.isBound() ? view->sample(name, worldX, worldY, fallback) : fallback;
636 });
637 owned.addFunc("snapshotJson", [](ScriptDecisionContext* value) {
638 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
639 return view.isBound() ? view->snapshotJson() : std::string{};
640 });
641 owned.addFunc("restoreJson", [vm](ScriptDecisionContext* value, const std::string& json) {
642 auto view = value ? Decision::resolve(value->reference) : eve::script::Borrowed<DecisionContext>();
643 if (!view.isBound())
646 "owned decision context handle is stale",
647 "context", {}, "decision")));
648 return eve::script::projectResult(vm, view->restoreJson(json));
649 });
650 owned.addFunc("utility",
651 [](ScriptDecisionContext*, const std::string& input) { return DecisionContext::utility(input); });
652 owned.addFunc("choose",
653 [](ScriptDecisionContext*, const std::string& input) { return DecisionContext::choose(input); });
654 auto c = t.addClass(name, Decision::create, false);
655 expose(c);
656}
657void Decision::expose(ssq::Class& c) {
658 c.addFunc("getName", &Decision::getName);
659 c.addFunc("newContext", [vm = c.getHandle()](Decision*) -> ssq::Table {
660 return makeOwnedProxy<DecisionContextHandleRef, ScriptDecisionContext>(
661 vm, Decision::newContext(), [](DecisionContextHandleRef ref) { return Decision::release(ref); });
662 });
663}
664} // namespace eve::decision
double value
double score
Definition Agent.cpp:50
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
std::string from
ActiveSource owned
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
const std::string & s
struct SQVM * HSQUIRRELVM
HSQUIRRELVM vm
Definition ECS.cpp:20
EvpackChunkInput input
Definition Evpack.cpp:170
tensor::Graph g
Definition GpuGraph.cpp:7
std::uint32_t key
wgpu::PopErrorScopeStatus status
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
int h
std::uint32_t height
std::uint32_t width
std::string text
char quote
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float tr
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:26
float f
std::string script
Definition PlayHost.cpp:116
std::function< eve::Result< void >()> release
Definition Procgen.cpp:86
float d
float t
glm::mat4 view
const SquirrelValueOptions & options
The single Squirrel projection for common Result, Status and Value.
Cell cell
BoardState board
double oy
double ox
float m[16]
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
virtual std::string getName() const =0
Returns the name.
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
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
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
Data-oriented blackboard, FSM, utility and influence-map workspace.
Definition Decision.h:23
eve::Result< void > setState(const std::string &machine, const std::string &state)
Sets the current state explicitly.
Definition Decision.cpp:144
static std::string choose(const std::string &options)
Selects an option from name=considerations entries; ties use lexical name.
Definition Decision.cpp:196
std::string state(const std::string &machine) const
Gets current machine state.
Definition Decision.cpp:168
float sample(const std::string &name, float worldX, float worldY, float fallback) const
Samples the containing cell, returning fallback outside the grid.
Definition Decision.cpp:276
std::string snapshotJson() const
Exports deterministic JSON.
Definition Decision.cpp:291
eve::Result< void > set(const std::string &board, const std::string &key, const std::string &valueJson)
Sets a scalar JSON value on a named blackboard.
Definition Decision.cpp:100
eve::Result< void > addTransition(const std::string &machine, const std::string &from, const std::string &trigger, const std::string &to)
Adds or replaces a finite-state transition.
Definition Decision.cpp:135
static float utility(const std::string &considerationsCsv)
Scores CSV considerations formatted value:weight using a weighted mean.
Definition Decision.cpp:172
eve::Result< void > addCell(const std::string &name, int x, int y, float delta)
Adds to one grid cell, or returns a structured not-found/validation failure.
Definition Decision.cpp:255
eve::Result< bool > trigger(const std::string &machine, const std::string &trigger)
Applies an explicit trigger.
Definition Decision.cpp:152
eve::Result< void > restoreJson(const std::string &json)
Restores a version-1 snapshot transactionally.
Definition Decision.cpp:337
eve::Result< void > setCell(const std::string &name, int x, int y, float value)
Sets one grid cell, or returns a structured not-found/validation failure.
Definition Decision.cpp:240
eve::Result< void > newGrid(const std::string &name, int width, int height, float cellSize, float originX, float originY)
Creates or replaces a zero-filled 2D influence grid.
Definition Decision.cpp:219
std::string get(const std::string &board, const std::string &key, const std::string &fallbackJson) const
Gets canonical scalar JSON or the supplied fallback.
Definition Decision.cpp:125
Script factory for independent decision contexts.
Definition Decision.h:102
static eve::Result< void > release(DecisionContextHandleRef reference)
Releases a module-owned decision context.
Definition Decision.cpp:441
static bool isStale(DecisionContextHandleRef reference) noexcept
Reports whether a decision context reference is stale.
Definition Decision.cpp:449
static eve::script::Borrowed< DecisionContext > resolve(DecisionContextHandleRef reference) noexcept
Resolves a live context as a non-owning observation.
Definition Decision.cpp:435
static eve::Result< DecisionContextHandleRef > newContext()
Allocates a module-owned decision context and returns its handle.
Definition Decision.cpp:430
static Document parse(const std::string &text, std::string *error=nullptr)
Parse.
Definition Json.cpp:581
EVENGINE_API_FOUNDATION public API.
Definition Json.h:38
Non-owning C++ observation with no implicit lifetime extension.
eve::script::RuntimeHandleRef< DecisionContextHandleTag > DecisionContextHandleRef
Generation- and module-epoch-qualified decision context reference.
Definition Decision.h:20
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.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
Handle plus owner-lifetime epoch for a non-ECS registry object.