载入中...
搜索中...
未找到
SequenceRuntime.cpp
浏览该文件的文档.
2
3#include <string>
4#include <utility>
5
6namespace eve::dnut {
7
8namespace {
9
11void mergeMissing(eve::Value& target, const eve::Value& defaults) {
12 const auto* targetObject = target.getIf<eve::Value::Object>();
13 const auto* defaultsObject = defaults.getIf<eve::Value::Object>();
14 if (!targetObject || !defaultsObject) return;
15 for (const auto& entry : *defaultsObject) {
16 if (target.find(entry.first) == nullptr) target.set(entry.first, entry.second);
17 }
18}
19
20bool readString(const eve::Value& object, const std::string& key, std::string& out) {
21 const eve::Value* value = object.find(key);
22 if (!value || !value->isString()) return false;
23 out = value->asString();
24 return true;
25}
26
28 switch (value.kind()) {
29 case eve::StateValue::Kind::Null: return {};
36 out.reserve(value.arraySize());
37 for (std::size_t index = 0; index < value.arraySize(); ++index)
38 out.push_back(toCanonicalValue(value.at(index)));
39 return eve::Value(std::move(out));
40 }
43 for (const auto& key : value.keys()) out.emplace(key, toCanonicalValue(*value.find(key)));
44 return eve::Value(std::move(out));
45 }
46 }
47 return {};
48}
49
50bool parameterTypeMatches(const eve::Value& value, SequenceParameterType type) {
51 switch (type) {
52 case SequenceParameterType::Any: return true;
53 case SequenceParameterType::String: return value.isString();
54 case SequenceParameterType::Integer: return value.isInt64();
55 case SequenceParameterType::Number: return value.isNumeric();
56 case SequenceParameterType::Boolean: return value.isBool();
57 }
58 return false;
59}
60
61eve::Value captureFrame(const SequenceAsset* asset, const std::string& node, const eve::Value& bindings,
62 const eve::Value& locals) {
64 out.set("asset", eve::Value::string(asset ? asset->id : std::string{}));
65 out.set("version", eve::Value::integer(asset ? asset->version : 0));
66 out.set("node", eve::Value::string(node));
67 out.set("bindings", bindings);
68 out.set("locals", locals);
69 return out;
70}
71
72} // namespace
73
75 return asset_ ? asset_->findNode(nodeId_) : nullptr;
76}
77
78SequenceRuntime::ExecutionState SequenceRuntime::captureExecutionState() const {
79 return {asset_,
80 nodeId_,
81 bindings_,
82 locals_,
83 blocked_,
84 waitingStep_,
85 waitingCommand_,
86 callStack_,
87 lastStepResult_,
88 lastCommandRequest_,
89 pendingCommandRequestId_,
90 commandSequence_,
91 lastConditionResult_};
92}
93
94void SequenceRuntime::restoreExecutionState(ExecutionState state) {
95 asset_ = state.asset;
96 nodeId_ = std::move(state.nodeId);
97 bindings_ = std::move(state.bindings);
98 locals_ = std::move(state.locals);
99 blocked_ = state.blocked;
100 waitingStep_ = state.waitingStep;
101 waitingCommand_ = state.waitingCommand;
102 callStack_ = std::move(state.callStack);
103 lastStepResult_ = std::move(state.lastStepResult);
104 lastCommandRequest_ = std::move(state.lastCommandRequest);
105 pendingCommandRequestId_ = std::move(state.pendingCommandRequestId);
106 commandSequence_ = state.commandSequence;
107 lastConditionResult_ = std::move(state.lastConditionResult);
108}
109
110eve::Result<void> SequenceRuntime::fail(eve::DiagnosticCode code, std::string message, std::string path) {
111 failureText_ = message;
113 eve::Diagnostic::error(code, message, path, {}, "dnut.runtime"));
114}
115
116void SequenceRuntime::emit(EventKind kind, const SequenceNode* node, const std::string& detail) const {
117 if (!eventSink_) return;
118 eventSink_({kind, asset_ ? asset_->id : std::string{}, node ? node->id : nodeId_, detail});
119}
120
122 asset_ = nullptr;
123 nodeId_.clear();
124 bindings_ = eve::Value::Object{};
125 locals_ = eve::Value::Object{};
126 blocked_ = false;
127 waitingStep_ = false;
128 waitingCommand_ = false;
129 callStack_.clear();
130 lastStepResult_ = eve::Value{};
131 lastCommandRequest_.reset();
132 pendingCommandRequestId_.clear();
133 lastConditionResult_.reset();
134}
135
137 if (!name.empty() && handler) commandHandlers_[std::move(name)] = std::move(handler);
138}
139
140void SequenceRuntime::unregisterCommand(std::string_view name) { commandHandlers_.erase(std::string(name)); }
141
143 if (!asset) return fail(eve::DiagnosticCode::InvalidArgument, "sequence: null asset", "asset");
144 if (!bindings.isObject())
145 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: bindings must be an object", "bindings");
146 if (auto validation = asset->validate(); !validation.ok()) {
147 const auto* diagnostic = validation.error();
149 diagnostic ? diagnostic->message() : "sequence: asset validation failed", "asset");
150 }
151
152 eve::Value resolvedBindings = bindings;
153 for (const auto& parameter : asset->parameters) {
154 const eve::Value* value = resolvedBindings.find(parameter.name);
155 if (!value && !parameter.defaultValue.isNull()) {
156 resolvedBindings.set(parameter.name, parameter.defaultValue);
157 value = resolvedBindings.find(parameter.name);
158 }
159 if (!value && parameter.required)
161 "sequence '" + asset->id + "': missing required binding '" + parameter.name + "'",
162 "bindings." + parameter.name);
163 if (value && !parameterTypeMatches(*value, parameter.type))
165 "sequence '" + asset->id + "': binding '" + parameter.name + "' must be " +
166 sequenceParameterTypeName(parameter.type),
167 "bindings." + parameter.name);
168 }
169 for (const auto& key : bindings.keys()) {
170 bool declared = false;
171 for (const auto& parameter : asset->parameters)
172 if (parameter.name == key) {
173 declared = true;
174 break;
175 }
176 if (!declared)
178 "sequence '" + asset->id + "': undeclared binding '" + key + "'", "bindings." + key);
179 }
180
181 ExecutionState before = captureExecutionState();
182 failureText_.clear();
183 lastConditionResult_.reset();
184 lastStepResult_ = eve::Value{};
185 asset_ = asset;
186 bindings_ = std::move(resolvedBindings);
187 locals_ = eve::Value::Object{};
188 callStack_.clear();
189 blocked_ = false;
190 waitingStep_ = false;
191 waitingCommand_ = false;
192 pendingCommandRequestId_.clear();
193 lastCommandRequest_.reset();
194 emit(EventKind::Started);
195 if (auto entered = enter(asset_->entry); !entered.ok()) {
196 restoreExecutionState(std::move(before));
197 return entered;
198 }
199 auto ran = runUntilBlockedImpl();
200 if (!ran.ok()) restoreExecutionState(std::move(before));
201 return ran;
202}
203
204eve::Result<void> SequenceRuntime::enter(const std::string& nodeId) {
205 if (!asset_) return fail(eve::DiagnosticCode::PreconditionViolation, "sequence: no active asset", "node");
206 blocked_ = false;
207 waitingStep_ = false;
208 waitingCommand_ = false;
209 if (nodeId.empty()) {
210 stop();
212 }
213 const SequenceNode* node = asset_->findNode(nodeId);
214 if (!node)
216 "sequence: asset '" + asset_->id + "' has no node '" + nodeId + "'", "node");
217 nodeId_ = nodeId;
220}
221
222eve::Result<std::string> SequenceRuntime::evaluateRoute(const SequenceNode& node) {
223 for (const auto& route : node.routes) {
224 if (route.condition.isNull()) return eve::Result<std::string>::success(route.target);
225 if (!conditionEvaluator_)
228 "sequence: node '" + node.id + "' requires a condition evaluator", "condition", {},
229 "dnut.runtime"));
230 SequenceConditionOutcome outcome = conditionEvaluator_(route.condition);
231 lastConditionResult_ = outcome;
232 if (!outcome.error.empty())
234 eve::Diagnostic::error(eve::DiagnosticCode::Failed, outcome.error, "condition", {}, "dnut.runtime"));
235 if (outcome.passed) return eve::Result<std::string>::success(route.target);
236 }
238}
239
241 ExecutionState before = captureExecutionState();
242 auto ran = runUntilBlockedImpl();
243 if (!ran.ok()) restoreExecutionState(std::move(before));
244 return ran;
245}
246
247eve::Result<void> SequenceRuntime::runUntilBlockedImpl() {
248 int budget = kExecutionBudget;
249 while (asset_ != nullptr) {
250 if (budget-- <= 0)
251 return fail(eve::DiagnosticCode::InvariantViolation, "sequence: execution budget exceeded", "budget");
252 const SequenceNode* node = currentNode();
253 if (!node) return fail(eve::DiagnosticCode::InvariantViolation, "sequence: invalid execution cursor", "cursor");
254
255 if (node->type == "branch") {
256 auto target = evaluateRoute(*node);
257 if (!target.ok()) {
258 const auto* diagnostic = target.error();
260 diagnostic ? diagnostic->message() : "sequence: branch evaluation failed", "branch");
261 }
262 const std::string next = target.value();
263 if (auto entered = enter(next); !entered.ok()) return entered;
264 continue;
265 }
266 if (node->type == "choice") {
267 blocked_ = true;
268 waitingStep_ = false;
269 emit(EventKind::Blocked, node, "choice");
271 }
272 if (node->type == "wait") {
273 blocked_ = true;
274 waitingStep_ = false;
275 emit(EventKind::Blocked, node, "wait");
277 }
278 if (node->type == "end") {
279 if (callStack_.empty()) {
280 emit(EventKind::Ended, node);
281 stop();
283 }
284 Frame frame = std::move(callStack_.back());
285 callStack_.pop_back();
286 asset_ = frame.asset;
287 bindings_ = std::move(frame.bindings);
288 locals_ = std::move(frame.locals);
289 if (auto entered = enter(frame.returnNode); !entered.ok()) return entered;
290 continue;
291 }
292 if (node->type == "call") {
293 if (!assetResolver_)
295 "sequence: call node requires an asset resolver", "call");
296 const eve::Value* targetId = node->payload.find("target");
297 if (!targetId || !targetId->isString() || targetId->asString().empty())
299 "sequence: call node '" + node->id + "' has no target asset id", "call.target");
300 const SequenceAsset* target = assetResolver_(targetId->asString());
301 if (!target)
303 "sequence: missing called asset '" + targetId->asString() + "'", "call.target");
304 if (auto validation = target->validate(); !validation.ok()) {
305 const auto* diagnostic = validation.error();
307 diagnostic ? diagnostic->message() : "sequence: called asset is invalid", "call.target");
308 }
309 Frame frame;
310 frame.asset = asset_;
311 frame.returnNode = node->next;
312 if (const eve::Value* returnNode = node->payload.find("return");
313 returnNode && returnNode->isString() && !returnNode->asString().empty())
314 frame.returnNode = returnNode->asString();
315 frame.bindings = bindings_;
316 frame.locals = locals_;
317 callStack_.push_back(std::move(frame));
318 eve::Value targetBindings = eve::Value::Object{};
319 if (const eve::Value* arguments = node->payload.find("arguments"); arguments && arguments->isObject())
320 targetBindings = *arguments;
321 mergeMissing(targetBindings, bindings_);
322 asset_ = target;
323 bindings_ = std::move(targetBindings);
324 locals_ = eve::Value::Object{};
325 if (auto entered = enter(target->entry); !entered.ok()) return entered;
326 continue;
327 }
328
329 if (node->type == "command") {
330 const eve::Value* name = node->payload.find("name");
331 if (!name) name = node->payload.find("target");
332 if (!name || !name->isString() || name->asString().empty())
334 "sequence: command node '" + node->id + "' has no name", "command.name");
335 const auto named = commandHandlers_.find(name->asString());
336 if (named == commandHandlers_.end() && !commandDispatcher_)
338 "sequence: command '" + name->asString() + "' is not registered", "command.name");
339
340 SequenceCommandRequest request;
341 request.requestId = asset_->id + ":" + node->id + ":" + std::to_string(commandSequence_++);
342 request.name = name->asString();
343 if (const eve::Value* arguments = node->payload.find("arguments"); arguments && arguments->isObject())
344 request.arguments = *arguments;
345 request.bindings = bindings_;
346 request.locals = locals_;
347 request.payload = node->payload;
348 lastCommandRequest_ = request;
349 SequenceCommandResponse response =
350 named != commandHandlers_.end() ? named->second(request) : commandDispatcher_(request);
351 if (response.status == SequenceCommandResponse::Status::Failed)
352 return fail(eve::DiagnosticCode::Failed,
353 response.error.empty() ? "sequence: command failed" : response.error,
354 "command." + node->id);
355 if (response.status == SequenceCommandResponse::Status::Blocked) {
356 blocked_ = true;
357 waitingCommand_ = true;
358 waitingStep_ = false;
359 pendingCommandRequestId_ = request.requestId;
360 emit(EventKind::Blocked, node, "command");
362 }
363 const eve::Value* resultLocal = node->payload.find("resultLocal");
364 if (!resultLocal) resultLocal = node->payload.find("result");
365 if (resultLocal && resultLocal->isString() && !resultLocal->asString().empty())
366 locals_.set(resultLocal->asString(), response.value);
367 lastStepResult_ = std::move(response.value);
368 emit(EventKind::Stepped, node, "command");
369 if (auto entered = enter(node->next); !entered.ok()) return entered;
370 continue;
371 }
372
373 if (!registry_)
375 "sequence: step '" + node->type + "' requires a step registry", "steps." + node->id);
376 const StepContext context{asset_->id, node->id, &bindings_, &locals_, hostContext_};
377 StepOutcome outcome = registry_->dispatch(*node, context);
378 if (outcome.status == StepStatus::Failed)
379 return fail(eve::DiagnosticCode::Failed,
380 outcome.error.empty() ? "sequence: step '" + node->type + "' failed" : outcome.error,
381 "steps." + node->id);
382 if (outcome.status == StepStatus::Blocked) {
383 blocked_ = true;
384 waitingStep_ = true;
385 lastStepResult_ = outcome.value;
386 emit(EventKind::Blocked, node, node->type);
388 }
389 lastStepResult_ = std::move(outcome.value);
390 emit(EventKind::Stepped, node, node->type);
391 if (auto entered = enter(node->next); !entered.ok()) return entered;
392 }
394}
395
397 const SequenceNode* node = currentNode();
398 if (!node || !blocked_)
399 return fail(eve::DiagnosticCode::PreconditionViolation, "sequence: runtime is not blocked", "advance");
400 if (waitingCommand_)
402 "sequence: resume the pending command instead of advancing", "advance");
403 if (node->type == "choice")
405 "sequence: select a choice route instead of advancing", "advance");
406 ExecutionState before = captureExecutionState();
407 const std::string next = node->next;
408 waitingStep_ = false;
409 if (auto entered = enter(next); !entered.ok()) {
410 restoreExecutionState(std::move(before));
411 return entered;
412 }
413 auto ran = runUntilBlockedImpl();
414 if (!ran.ok()) restoreExecutionState(std::move(before));
415 return ran;
416}
417
418eve::Result<void> SequenceRuntime::select(std::string_view routeLabel) { return selectImpl(routeLabel); }
419
421 return selectImpl(routeLabel);
422}
423
424eve::Result<void> SequenceRuntime::selectImpl(std::string_view routeLabel) {
425 const SequenceNode* node = currentNode();
426 if (!node || !blocked_ || node->type != "choice")
428 "sequence: runtime is not waiting for a choice", "route");
429
430 for (const auto& route : node->routes) {
431 if (route.label != routeLabel) continue;
432 if (!route.condition.isNull()) {
433 if (!conditionEvaluator_)
435 "sequence: choice condition requires an evaluator", "route.condition");
436 SequenceConditionOutcome outcome = conditionEvaluator_(route.condition);
437 lastConditionResult_ = outcome;
438 if (!outcome.error.empty()) return fail(eve::DiagnosticCode::Failed, outcome.error, "route.condition");
439 if (!outcome.passed)
441 "sequence: choice condition rejected (" +
442 (outcome.reason.empty() ? std::string("rejected") : outcome.reason) + ")",
443 "route.condition");
444 }
445
446 ExecutionState before = captureExecutionState();
447 blocked_ = false;
448 waitingStep_ = false;
449 std::string error;
450 if (auto entered = enter(route.target); !entered.ok()) {
451 error = failureText_;
452 } else if (auto ran = runUntilBlockedImpl(); !ran.ok()) {
453 error = failureText_;
454 } else {
456 }
457 restoreExecutionState(std::move(before));
458 return fail(eve::DiagnosticCode::Failed,
459 error.empty() ? "sequence: choice selection failed" : std::move(error), "route");
460 }
462 "sequence: unknown choice route '" + std::string(routeLabel) + "'", "route");
463}
464
466 const SequenceNode* node = currentNode();
467 if (!node || !blocked_ || !waitingStep_)
469 "sequence: runtime is not waiting for a step result", "step");
470 ExecutionState before = captureExecutionState();
471
472 if (const eve::Value* key = node->payload.find("result"); key && key->isString() && !key->asString().empty())
473 locals_.set(key->asString(), result);
474 lastStepResult_ = std::move(result);
475 blocked_ = false;
476 waitingStep_ = false;
477
478 std::string error;
479 if (auto entered = enter(node->next); !entered.ok()) {
480 error = failureText_;
481 } else if (auto ran = runUntilBlockedImpl(); !ran.ok()) {
482 error = failureText_;
483 } else {
484 emit(EventKind::Resumed, node, node->type);
486 }
487 restoreExecutionState(std::move(before));
488 return fail(eve::DiagnosticCode::Failed,
489 error.empty() ? "sequence: step resume failed" : std::move(error), "step");
490}
491
493 return resumeCommandImpl(requestId, toCanonicalValue(result));
494}
495
497 return resumeCommandImpl(requestId, std::move(result));
498}
499
500eve::Result<void> SequenceRuntime::resumeCommandImpl(std::string_view requestId, eve::Value result) {
501 const SequenceNode* node = currentNode();
502 if (!node || !blocked_ || !waitingCommand_ || node->type != "command")
503 return fail(eve::DiagnosticCode::DialogueNotWaitingForCommand, "sequence: runtime is not waiting for a command",
504 "command");
505 if (requestId.empty() || requestId != pendingCommandRequestId_)
506 return fail(eve::DiagnosticCode::Conflict, "sequence: command request id is stale or does not match",
507 "command.requestId");
508
509 ExecutionState before = captureExecutionState();
510
511 const eve::Value* resultLocal = node->payload.find("resultLocal");
512 if (!resultLocal) resultLocal = node->payload.find("result");
513 if (resultLocal && resultLocal->isString() && !resultLocal->asString().empty())
514 locals_.set(resultLocal->asString(), result);
515 lastStepResult_ = std::move(result);
516 blocked_ = false;
517 waitingCommand_ = false;
518 pendingCommandRequestId_.clear();
519
520 std::string error;
521 if (auto entered = enter(node->next); !entered.ok()) {
522 error = failureText_;
523 } else if (auto ran = runUntilBlockedImpl(); !ran.ok()) {
524 error = failureText_;
525 } else {
526 emit(EventKind::Resumed, node, "command");
528 }
529 restoreExecutionState(std::move(before));
530 return fail(eve::DiagnosticCode::Failed, error.empty() ? "sequence: command resume failed" : std::move(error),
531 "command");
532}
533
535 out = eve::Value::Object{};
536 out.set("schema", eve::Value::string(std::string(SaveSchema)));
537 out.set("schemaVersion", eve::Value::integer(SaveVersion));
538 out.set("active", eve::Value::boolean(asset_ != nullptr));
539 if (!asset_) return eve::Result<void>::success();
540 out.set("current", captureFrame(asset_, nodeId_, bindings_, locals_));
541 out.set("blocked", eve::Value::boolean(blocked_));
542 out.set("waitingStep", eve::Value::boolean(waitingStep_));
543 out.set("waitingCommand", eve::Value::boolean(waitingCommand_));
544 out.set("pendingCommandRequestId", eve::Value::string(pendingCommandRequestId_));
545 out.set("commandSequence", eve::Value::integer(static_cast<std::int64_t>(commandSequence_)));
546 eve::Value::Array stack;
547 stack.reserve(callStack_.size());
548 for (const auto& frame : callStack_)
549 stack.push_back(captureFrame(frame.asset, frame.returnNode, frame.bindings, frame.locals));
550 out.set("stack", eve::Value(std::move(stack)));
552}
553
555 if (!in.isObject())
556 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: runtime state must be an object", "state");
557 const eve::Value* schema = in.find("schema");
558 const eve::Value* schemaVersion = in.find("schemaVersion");
559 if (!schema || !schema->isString() || schema->asString() != SaveSchema || !schemaVersion ||
560 !schemaVersion->isInt64() || schemaVersion->asInt() != SaveVersion)
561 return fail(eve::DiagnosticCode::UnknownVersion, "sequence: unsupported runtime save schema or version",
562 "state.schemaVersion");
563 const eve::Value* active = in.find("active");
564 if (!active || !active->isBool())
565 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: runtime state is missing 'active'", "state");
566 if (!active->asBool()) {
567 stop();
569 }
570 if (!assetResolver_)
572 "sequence: restore requires an asset resolver", "state");
573
574 const eve::Value* current = in.find("current");
575 if (!current || !current->isObject())
576 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: runtime state is missing 'current'", "state");
577 std::string assetId;
578 std::string nodeId;
579 if (!readString(*current, "asset", assetId) || !readString(*current, "node", nodeId))
580 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: saved frame is malformed", "state.current");
581
582 const SequenceAsset* restoredAsset = assetResolver_(assetId);
583 if (!restoredAsset)
584 return fail(eve::DiagnosticCode::NotFound, "sequence: saved asset '" + assetId + "' is missing", "state.asset");
585 const eve::Value* version = current->find("version");
586 if (!version || !version->isInt64() || version->asInt() != restoredAsset->version)
588 "sequence: saved asset version does not match '" + assetId + "'", "state.version");
589 const eve::Value* savedBindings = current->find("bindings");
590 const eve::Value* savedLocals = current->find("locals");
591 if (!savedBindings || !savedBindings->isObject() || !savedLocals || !savedLocals->isObject())
592 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: saved values are malformed", "state.current");
593
594 const eve::Value* stack = in.find("stack");
595 if (!stack || !stack->isArray())
596 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: runtime state is missing 'stack'", "state");
597 std::vector<Frame> restoredStack;
598 restoredStack.reserve(stack->arraySize());
599 for (std::size_t index = 0; index < stack->arraySize(); ++index) {
600 const eve::Value& saved = stack->at(index);
601 std::string savedAssetId;
602 std::string returnNode;
603 if (!saved.isObject() || !readString(saved, "asset", savedAssetId) ||
604 !readString(saved, "node", returnNode))
605 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: saved call frame is malformed",
606 "state.stack");
607 const SequenceAsset* savedAsset = assetResolver_(savedAssetId);
608 const eve::Value* savedVersion = saved.find("version");
609 const eve::Value* frameBindings = saved.find("bindings");
610 const eve::Value* frameLocals = saved.find("locals");
611 if (!savedAsset || !savedVersion || !savedVersion->isInt64() ||
612 savedVersion->asInt() != savedAsset->version || !frameBindings || !frameBindings->isObject() ||
613 !frameLocals || !frameLocals->isObject() || !savedAsset->findNode(returnNode))
614 return fail(eve::DiagnosticCode::Conflict, "sequence: saved call frame cannot be restored", "state.stack");
615 restoredStack.push_back({savedAsset, returnNode, *frameBindings, *frameLocals});
616 }
617 const eve::Value* blocked = in.find("blocked");
618 if (!blocked || !blocked->isBool())
619 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: runtime state is missing 'blocked'", "state");
620 const eve::Value* waitingStep = in.find("waitingStep");
621 if (waitingStep && !waitingStep->isBool())
622 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: waitingStep is malformed", "state");
623 const eve::Value* waitingCommand = in.find("waitingCommand");
624 if (waitingCommand && !waitingCommand->isBool())
625 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: waitingCommand is malformed", "state");
626 const eve::Value* pendingRequestId = in.find("pendingCommandRequestId");
627 const eve::Value* commandSequence = in.find("commandSequence");
628 if (!pendingRequestId || !pendingRequestId->isString() || !commandSequence || !commandSequence->isInt64() ||
629 commandSequence->asInt() < 1)
630 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: pending command state is malformed", "state");
631 const bool restoredWaitingCommand = waitingCommand && waitingCommand->asBool();
632 if (restoredWaitingCommand != !pendingRequestId->asString().empty())
634 "sequence: pending command identity does not match waiting state", "state");
635 if (!restoredAsset->findNode(nodeId))
636 return fail(eve::DiagnosticCode::NotFound, "sequence: saved node '" + nodeId + "' is missing", "state.node");
637 const SequenceNode* restoredNode = restoredAsset->findNode(nodeId);
638 if (restoredWaitingCommand && (!restoredNode || restoredNode->type != "command"))
639 return fail(eve::DiagnosticCode::InvalidArgument, "sequence: pending command cursor is not a command node",
640 "state.node");
641
642 asset_ = restoredAsset;
643 nodeId_ = std::move(nodeId);
644 bindings_ = *savedBindings;
645 locals_ = *savedLocals;
646 blocked_ = blocked->asBool();
647 waitingStep_ = waitingStep && waitingStep->asBool();
648 waitingCommand_ = restoredWaitingCommand;
649 pendingCommandRequestId_ = pendingRequestId->asString();
650 commandSequence_ = static_cast<std::uint64_t>(commandSequence->asInt());
651 callStack_ = std::move(restoredStack);
652 failureText_.clear();
653 lastCommandRequest_.reset();
654 if (waitingCommand_) {
656 request.requestId = pendingCommandRequestId_;
657 const eve::Value* name = restoredNode->payload.find("name");
658 if (!name) name = restoredNode->payload.find("target");
659 request.name = name && name->isString() ? name->asString() : std::string{};
660 if (const eve::Value* arguments = restoredNode->payload.find("arguments"); arguments && arguments->isObject())
661 request.arguments = *arguments;
662 request.bindings = bindings_;
663 request.locals = locals_;
664 request.payload = restoredNode->payload;
665 lastCommandRequest_ = std::move(request);
666 emit(EventKind::Blocked, restoredNode, "command");
667 }
669}
670
671} // namespace eve::dnut
LogicalId target
double value
bool & active
std::string nodeId
std::string message
DiagnosticCode code
const GltfImportRequest & request
std::uint32_t key
TokenKind kind
HSQOBJECT handler
std::string name
std::vector< std::shared_ptr< DeviceBytes > > bindings
Definition OnnxGpgpu.cpp:38
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::uint32_t targetId
int detail
const RoadNode * node
Cross-frame interpreter for compiled `.dnut` sequences.
double current
std::string string
uint32_t index
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
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:480
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
JSON-compatible state value tree used by state hot reload.
Definition StateValue.h:20
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
const std::string & asString() const
Return the string payload; the caller must have checked the kind.
Definition Value.cpp:87
bool isInt64() const noexcept
Return true when this value is an Int64.
Definition Value.h:91
std::map< std::string, Value > Object
Definition Value.h:34
bool isArray() const noexcept
Return true when this value is an array.
Definition Value.h:97
std::vector< std::string > keys() const
Return object member names in deterministic order.
Definition Value.cpp:138
void set(const std::string &key, Value value)
Insert or replace an object member.
Definition Value.cpp:118
static Value number(double value)
Compatibility factory for a Double value.
Definition Value.h:106
bool isString() const noexcept
Return true when this value is a string.
Definition Value.h:95
static Value string(std::string value)
Compatibility factory for a string value.
Definition Value.h:110
std::int64_t asInt() const
Return the Int64 payload; the caller must have checked the kind.
Definition Value.cpp:69
std::vector< Value > Array
Definition Value.h:33
static Value integer(std::int64_t value)
Compatibility factory for an Int64 value.
Definition Value.h:104
std::size_t arraySize() const
Return the number of array elements.
Definition Value.cpp:100
static Value boolean(bool value)
Compatibility factory for a boolean value.
Definition Value.h:108
bool asBool() const
Return the boolean payload; the caller must have checked the kind.
Definition Value.cpp:81
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
bool isBool() const noexcept
Return true when this value is a boolean.
Definition Value.h:89
bool isObject() const noexcept
Return true when this value is an object.
Definition Value.h:99
Value & at(std::size_t index)
Return a bounds-checked array element.
Definition Value.cpp:106
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
eve::Result< void > runUntilBlocked()
Execute nodes until the sequence blocks, ends or fails.
const SequenceNode * currentNode() const
Borrowed current node, or nullptr. @lifetime Invalidated by the next transition.
void stop()
Stop and clear the active sequence without emitting Ended.
static constexpr std::string_view SaveSchema
Stable cursor save schema retained across the L1 ownership migration.
eve::Result< void > captureState(eve::Value &out) const
Capture the complete cursor, bindings, locals and call stack.
static constexpr std::int64_t SaveVersion
Current cursor save schema version.
static constexpr int kExecutionBudget
Upper bound on nodes executed by one runUntilBlocked call.
eve::Result< void > advance()
Acknowledge a host-presented wait or handler-less Await step.
eve::Result< void > resumeCommand(std::string_view requestId, eve::StateValue result)
Resume the exact blocked command with a legacy state value.
eve::Result< void > start(const SequenceAsset *asset, eve::Value bindings=eve::Value::Object{})
Begin a sequence at the asset entry node.
void unregisterCommand(std::string_view name)
Remove a named generic command handler.
void registerCommand(std::string name, SequenceCommandHandler handler)
Register a named generic command handler.
eve::Result< void > resumeStep(eve::Value result)
Resume a step whose handler returned Blocked.
const SequenceAsset * asset() const
Borrowed active asset, or nullptr. @lifetime Owned by the caller of start.
const eve::Value & bindings() const
eve::Result< void > selectRouteForTransaction(std::string_view routeLabel)
Enter one already prepared choice route transactionally.
eve::Result< void > select(std::string_view routeLabel)
Answer a blocked choice node by route label.
eve::Result< void > restoreState(const eve::Value &in)
Restore a captured cursor through the configured asset resolver.
StepOutcome dispatch(const SequenceNode &node, const StepContext &context) const
Route one already validated node to its handler.
eve::Value toCanonicalValue(const eve::StateValue &value)
Convert the legacy reload value into the canonical common value.
const char * sequenceParameterTypeName(SequenceParameterType type) noexcept
Return the stable lowercase spelling of a parameter type.
SequenceParameterType
Declared value kind for one sequence invocation parameter.
std::function< SequenceCommandResponse(const SequenceCommandRequest &)> SequenceCommandHandler
Synchronous owner-thread handler for one command request.
bool readString(const eve::Value::Object &object, const char *name, std::string &output)
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
@ DialogueNotWaitingForCommand
Dialogue runner is not positioned at an asynchronous command.
Immutable, parameterized and versioned compiled sequence.
eve::Result< void > validate() const
Validate stable identifiers, the entry point and every graph reference.
const SequenceNode * findNode(const std::string &nodeId) const noexcept
Find one node by its stable identifier.
std::vector< SequenceParameter > parameters
Immutable request emitted by a generic command node.
Outcome of one authored condition, as seen by the language core.
std::string error
Evaluation failure; non-empty means no route may be selected.
std::string reason
Stable machine-readable reason; empty when passed is true.
One immutable step of a compiled sequence.