载入中...
搜索中...
未找到
DialogueSequence.cpp
浏览该文件的文档.
2
3#include <string>
4#include <utility>
5#include <vector>
6
7namespace eve::dialogue {
8namespace {
9
10std::string mutationKindName(eve::MutationKind kind) {
11 switch (kind) {
12 case eve::MutationKind::Set: return "set";
13 case eve::MutationKind::Remove: return "remove";
14 case eve::MutationKind::AddTag: return "addTag";
15 case eve::MutationKind::RemoveTag: return "removeTag";
16 case eve::MutationKind::AddNumber: return "addNumber";
17 }
18 return {};
19}
20
21eve::Result<eve::StateMutation> decodeMutation(const eve::Value& value, std::size_t index) {
22 const std::string path = "stateMutations." + std::to_string(index);
24 path, {}, "dialogue.sequence"));
25 for (const auto& key : value.keys())
26 if (key != "subject" && key != "key" && key != "kind" && key != "value" && key != "persistent")
27 return eve::Result<eve::StateMutation>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unknown state mutation field '" + key + "'",
28 path + "." + key, {}, "dialogue.sequence"));
29
30 const eve::Value* subject = value.find("subject");
31 const eve::Value* key = value.find("key");
32 const eve::Value* kind = value.find("kind");
33 const eve::Value* rawValue = value.find("value");
34 const eve::Value* persistent = value.find("persistent");
35 if (!subject || !subject->isString() || subject->asString().empty())
36 return eve::Result<eve::StateMutation>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "state mutation subject must be a non-empty string",
37 path + ".subject", {}, "dialogue.sequence"));
38 if (!key || !key->isString() || key->asString().empty())
40 path + ".key", {}, "dialogue.sequence"));
41 if (!kind || !kind->isString())
43 path + ".kind", {}, "dialogue.sequence"));
44
45 eve::StateMutation mutation;
46 mutation.subject = subject->asString();
47 mutation.key = key->asString();
48 if (kind->asString() == "set")
49 mutation.kind = eve::MutationKind::Set;
50 else if (kind->asString() == "remove")
51 mutation.kind = eve::MutationKind::Remove;
52 else if (kind->asString() == "addTag")
53 mutation.kind = eve::MutationKind::AddTag;
54 else if (kind->asString() == "removeTag")
55 mutation.kind = eve::MutationKind::RemoveTag;
56 else if (kind->asString() == "addNumber")
57 mutation.kind = eve::MutationKind::AddNumber;
58 else
59 return eve::Result<eve::StateMutation>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "unsupported state mutation kind '" + kind->asString() + "'",
60 path + ".kind", {}, "dialogue.sequence"));
61
62 const bool needsValue = mutation.kind == eve::MutationKind::Set || mutation.kind == eve::MutationKind::AddNumber;
63 if (needsValue && !rawValue)
65 path + ".value", {}, "dialogue.sequence"));
66 if (!needsValue && rawValue)
68 path + ".value", {}, "dialogue.sequence"));
69 if (rawValue) mutation.value = *rawValue;
70 if (persistent) {
71 if (!persistent->isBool())
73 path + ".persistent", {}, "dialogue.sequence"));
74 mutation.persistent = persistent->asBool();
75 }
76 return eve::Result<eve::StateMutation>::success(std::move(mutation));
77}
78
79eve::Result<void> validateChoice(const eve::dnut::SequenceNode& node) {
80 for (const auto& route : node.routes) {
81 auto payment = decodeSequencePayment(route.payload);
82 if (!payment.ok()) return eve::Result<void>::failure(payment.status());
83 auto mutations = decodeSequenceStateMutations(route.payload);
84 if (!mutations.ok()) return eve::Result<void>::failure(mutations.status());
85 }
87}
88
90 const eve::Value* name = node.payload.find("name");
91 if (!name) name = node.payload.find("target");
92 if (!name || !name->isString() || name->asString().empty())
93 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dialogue command requires a non-empty name",
94 "payload.name", {}, "dialogue.sequence"));
95 const eve::Value* kind = node.payload.find("kind");
96 if (kind && (!kind->isString() || (kind->asString() != "operation" && kind->asString() != "gameplay")))
97 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dialogue command kind must be operation or gameplay",
98 "payload.kind", {}, "dialogue.sequence"));
99 auto payment = decodeSequencePayment(node.payload);
100 if (!payment.ok()) return eve::Result<void>::failure(payment.status());
101 auto mutations = decodeSequenceStateMutations(node.payload);
102 if (!mutations.ok()) return eve::Result<void>::failure(mutations.status());
104}
105
106} // namespace
107
108std::string sequencePayloadString(const eve::dnut::SequenceNode& node, std::string_view key) {
109 const eve::Value* value = node.payload.find(std::string(key));
110 return value && value->isString() ? value->asString() : std::string{};
111}
112
113std::string sequenceRoutePayloadString(const eve::dnut::SequenceRoute& route, std::string_view key) {
114 const eve::Value* value = route.payload.find(std::string(key));
115 return value && value->isString() ? value->asString() : std::string{};
116}
117
119 if (!payload.isObject()) return eve::Result<PaymentSpec>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dialogue payload must be an object",
120 "payload", {}, "dialogue.sequence"));
121 const eve::Value* payment = payload.find("payment");
122 if (!payment) return eve::Result<PaymentSpec>::success(PaymentSpec{});
123 return PaymentSpec::fromValue(*payment);
124}
125
127 if (!payload.isObject())
129 "payload", {}, "dialogue.sequence"));
130 const eve::Value* values = payload.find("stateMutations");
131 if (!values) return eve::Result<std::vector<eve::StateMutation>>::success({});
132 if (!values->isArray())
134 "payload.stateMutations", {}, "dialogue.sequence"));
135 std::vector<eve::StateMutation> mutations;
136 mutations.reserve(values->arraySize());
137 for (std::size_t index = 0; index < values->arraySize(); ++index) {
138 auto mutation = decodeMutation(values->at(index), index);
139 if (!mutation.ok()) return eve::Result<std::vector<eve::StateMutation>>::failure(mutation.status());
140 mutations.push_back(std::move(mutation).takeValue());
141 }
142 return eve::Result<std::vector<eve::StateMutation>>::success(std::move(mutations));
143}
144
145eve::Value encodeSequenceStateMutations(std::span<const eve::StateMutation> mutations) {
147 values.reserve(mutations.size());
148 for (const auto& mutation : mutations) {
150 value.set("subject", eve::Value::string(mutation.subject));
151 value.set("key", eve::Value::string(mutation.key));
152 value.set("kind", eve::Value::string(mutationKindName(mutation.kind)));
153 if (mutation.kind == eve::MutationKind::Set || mutation.kind == eve::MutationKind::AddNumber)
154 value.set("value", mutation.value);
155 if (mutation.persistent) value.set("persistent", eve::Value::boolean(true));
156 values.push_back(std::move(value));
157 }
158 return eve::Value(std::move(values));
159}
160
162 eve::dnut::StepKindRegistry candidate = registry;
167
168 auto line = candidate.registerStep(StepKindDescriptor{
169 "line",
170 "Line",
171 "dialogue",
172 StepShape::Await,
173 {StepField{"speaker", StepFieldType::String, false}, StepField{"text", StepFieldType::String, false},
174 StepField{"pool", StepFieldType::String, false}, StepField{"i18n", StepFieldType::String, false},
175 StepField{"voice", StepFieldType::String, false}, StepField{"expression", StepFieldType::String, false}}});
176 if (!line.ok()) return line;
177 auto choice = candidate.registerStep(
178 StepKindDescriptor{"choice", "Choice", "dialogue", StepShape::Await, {}, validateChoice});
179 if (!choice.ok()) return choice;
180 auto command = candidate.registerStep(StepKindDescriptor{
181 "command",
182 "Command",
183 "dialogue",
184 StepShape::Await,
185 {StepField{"name", StepFieldType::String, false}, StepField{"target", StepFieldType::String, false},
186 StepField{"kind", StepFieldType::String, false}, StepField{"arguments", StepFieldType::Any, false},
187 StepField{"payment", StepFieldType::Any, false}, StepField{"stateMutations", StepFieldType::Any, false},
188 StepField{"resultLocal", StepFieldType::String, false}, StepField{"result", StepFieldType::String, false}},
190 if (!command.ok()) return command;
191 registry = std::move(candidate);
193}
194
196 const eve::dnut::StepKindRegistry& registry) {
197 auto graph = asset.validate();
198 if (!graph.ok()) return graph;
199 for (const auto& node : asset.nodes) {
200 if (registry.contains(node.type)) {
201 auto validated = registry.validate(node);
202 if (!validated.ok()) return validated;
203 }
204 if (node.type != "command" && (node.payload.find("payment") || node.payload.find("stateMutations")))
205 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "payment and stateMutations are valid only on command nodes",
206 "nodes." + node.id + ".payload", {}, "dialogue.sequence"));
207 for (const auto& route : node.routes)
208 if (node.type != "choice" && (route.payload.find("payment") || route.payload.find("stateMutations")))
209 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "payment and stateMutations are valid only on choice routes",
210 "nodes." + node.id + ".routes", {}, "dialogue.sequence"));
211 }
213}
214
216 CommandRequest decoded;
217 decoded.requestId = request.requestId;
218 decoded.name = request.name;
219 decoded.arguments = request.arguments;
220 decoded.bindings = request.bindings;
221 decoded.locals = request.locals;
222 const eve::Value* kind = request.payload.find("kind");
223 decoded.kind = kind && kind->isString() && kind->asString() == "gameplay" ? CommandRequestKind::GameplayAction
225 auto payment = decodeSequencePayment(request.payload);
226 if (!payment.ok()) return eve::Result<CommandRequest>::failure(payment.status());
227 decoded.payment = std::move(payment).takeValue();
228 auto mutations = decodeSequenceStateMutations(request.payload);
229 if (!mutations.ok()) return eve::Result<CommandRequest>::failure(mutations.status());
230 decoded.stateMutations = std::move(mutations).takeValue();
231 return eve::Result<CommandRequest>::success(std::move(decoded));
232}
233
234} // namespace eve::dialogue
double value
Value::Object payload
int subject
Definition AnimSmr.cpp:163
CLI::App * validateCommand
Dialogue payload codecs for L1 sequence assets.
std::map< std::string, Var > values
const GltfImportRequest & request
std::uint32_t key
TokenKind kind
std::string name
std::string error
Definition Package.cpp:60
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
std::string path
Definition PlayHost.cpp:110
const RoadNode * node
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
void set(const std::string &key, Value value)
Insert or replace an object member.
Definition Value.cpp:118
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::vector< Value > Array
Definition Value.h:33
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
bool isBool() const noexcept
Return true when this value is a boolean.
Definition Value.h:89
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
Canonical step vocabulary owner and runtime handler router.
eve::Result< void > registerStep(StepKindDescriptor descriptor)
Declare one step type.
eve::Result< void > validate(const SequenceNode &node) const
Validate one node against its declared payload schema.
bool contains(std::string_view type) const
Return whether type is declared.
eve::Result< std::vector< eve::StateMutation > > decodeSequenceStateMutations(const eve::Value &payload)
Decode dialogue state mutations from one canonical payload object.
eve::Result< CommandRequest > decodeDialogueCommandRequest(const eve::dnut::SequenceCommandRequest &request)
Adapt an L1 command request to dialogue's transaction-bearing request.
std::string sequenceRoutePayloadString(const eve::dnut::SequenceRoute &route, std::string_view key)
Read an optional string from a route payload; missing or ill-typed values yield empty.
std::string sequencePayloadString(const eve::dnut::SequenceNode &node, std::string_view key)
Read an optional string from a node payload; missing or ill-typed values yield empty.
eve::Result< PaymentSpec > decodeSequencePayment(const eve::Value &payload)
Decode an optional dialogue payment from one canonical payload object.
eve::Result< void > validateDialogueSequenceAsset(const eve::dnut::SequenceAsset &asset, const eve::dnut::StepKindRegistry &registry)
Validate every dialogue-owned payload without taking control-flow ownership.
eve::Result< void > registerDialogueSequenceSteps(eve::dnut::StepKindRegistry &registry)
Register dialogue's line, choice, and command contracts and validators.
eve::Value encodeSequenceStateMutations(std::span< const eve::StateMutation > mutations)
Encode dialogue state mutations as the canonical payload array.
StepFieldType
Accepted value kind of one declared payload field.
StepShape
Declares whether a step completes inside its call or suspends the runtime.
eve::Diagnostic Diagnostic
eve::Result< T > Result
MutationKind
Kind of one requested world-state mutation.
Definition StateAccess.h:26
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
state::StateMutation StateMutation
Immutable request emitted by a command node.
PaymentSpec payment
Optional atomic money/reputation payment for this command.
std::vector< eve::StateMutation > stateMutations
Optional StatePatch mutations committed in the same transaction.
std::string requestId
Stable identifier for this exact suspended command execution.
Canonical money/reputation cost attached to a dialogue choice or command.
static eve::Result< PaymentSpec > fromValue(const eve::Value &value)
Parse { "money": n, "reputation": n } from canonical Value.
Immutable, parameterized and versioned compiled sequence.
eve::Result< void > validate() const
Validate stable identifiers, the entry point and every graph reference.
std::vector< SequenceNode > nodes
Immutable request emitted by a generic command node.
One immutable step of a compiled sequence.
One outgoing edge of a sequence node.
One declared payload field of a step type.
Owning authoring/runtime contract for one step type.
One immutable mutation request owned by its caller.
Definition StateAccess.h:42