载入中...
搜索中...
未找到
PcgGraph.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <limits>
9#include <memory>
10#include <unordered_map>
11
12namespace eve::procgen_editing {
13namespace {
14
15EditorDiagnostic pcgDiagnostic(const char* rule, DiagnosticSeverity severity,
16 std::string message) {
18 severity, std::move(message));
19}
20
21const GraphNodeRecord* findNode(const GraphDocumentData& graph, const GraphNodeId& id) {
22 const auto found = std::find_if(graph.nodes.begin(), graph.nodes.end(),
23 [&](const GraphNodeRecord& node) { return node.id == id; });
24 return found == graph.nodes.end() ? nullptr : &*found;
25}
26
27const GraphPinRecord* findPin(const GraphDocumentData& graph, const GraphPinId& id) {
28 for (const auto& node : graph.nodes) {
29 const auto found = std::find_if(node.pins.begin(), node.pins.end(),
30 [&](const GraphPinRecord& pin) { return pin.id == id; });
31 if (found != node.pins.end()) return &*found;
32 }
33 return nullptr;
34}
35
36int inputIndex(const GraphNodeRecord& node, const GraphPinId& pin) {
37 int index = 0;
38 for (const auto& candidate : node.pins) {
39 if (candidate.direction != GraphPinDirection::Input) continue;
40 if (candidate.id == pin) return index;
41 ++index;
42 }
43 return -1;
44}
45
46EditorValue defaultValue(const std::string& kind, const std::string& encoded) {
47 if (kind == "bool") return EditorValue(encoded == "true");
48 if (kind == "int") return EditorValue(static_cast<int64_t>(std::stoll(encoded)));
49 if (kind == "float") return EditorValue(std::stod(encoded));
50 return EditorValue(encoded);
51}
52
53} // namespace
54
56 const GraphPinRecord& to) const {
58 decision.allowed = from.direction == GraphPinDirection::Output &&
59 to.direction == GraphPinDirection::Input && from.type == "point" &&
60 to.type == "point" && from.node != to.node;
61 if (!decision.allowed)
62 decision.diagnostics.push_back(pcgDiagnostic(
63 "editor.pcg.invalid-connection", DiagnosticSeverity::Error,
64 "PCG graph connections require point output-to-input pins on distinct nodes"));
65 return decision;
66}
67
69 const std::string& operation) const {
71 return eve::editing::failed<GraphNodeRecord>(EditorStatus::Rejected, RuleId("editor.pcg.unknown-operation"),
72 "Unknown PointGraph operation");
74 node.id = id;
75 node.type = operation;
78 for (int i = 0; i < parameterCount; ++i) {
82 }
83 if (operation == "subgraph") {
84 properties["definition"] = EditorValue("");
85 properties["inputNode"] = EditorValue("input");
86 properties["outputNode"] = EditorValue("output");
87 }
88 node.properties = EditorValue(std::move(properties));
90 for (int i = 0; i < inputs; ++i)
91 node.pins.push_back({GraphPinId(id.value() + ".in" + std::to_string(i)), id, "point",
92 GraphPinDirection::Input});
93 node.pins.push_back(
94 {GraphPinId(id.value() + ".out"), id, "point", GraphPinDirection::Output});
95 return eve::editing::applied<GraphNodeRecord>(std::move(node));
96}
97
100 result.fromVersion = graph.schemaVersion;
101 result.graph = graph;
102 if (graph.domain != domain()) {
103 result.status = EditorStatus::Rejected;
104 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.wrong-domain",
105 DiagnosticSeverity::Error,
106 "Graph is not a procgen.point document"));
107 return result;
108 }
109 if (graph.schemaVersion == 1) {
110 result.status = EditorStatus::NoOp;
111 return result;
112 }
113 if (graph.schemaVersion != 0) {
114 result.status = EditorStatus::Unsupported;
115 result.diagnostics.push_back(pcgDiagnostic(
116 "editor.pcg.unsupported-schema", DiagnosticSeverity::Error,
117 "Unsupported procgen.point graph schemaVersion: " +
118 std::to_string(graph.schemaVersion)));
119 return result;
120 }
121
122 std::unordered_map<std::string, GraphPinId> pinRemap;
123 std::vector<GraphNodeRecord> migratedNodes;
124 migratedNodes.reserve(graph.nodes.size());
125 for (const auto& legacy : graph.nodes) {
126 auto canonical = makeNode(legacy.id, legacy.type);
127 if (!canonical.ok()) {
128 result.status = EditorStatus::Rejected;
129 result.diagnostics.push_back(pcgDiagnostic(
130 "editor.pcg.migration-unknown-operation", DiagnosticSeverity::Error,
131 "Cannot migrate unknown PCG operation: " + legacy.type));
132 return result;
133 }
134 auto* defaults = canonical.value().properties.getIf<EditorValue::Object>();
135 const auto* properties = legacy.properties.getIf<EditorValue::Object>();
136 if (legacy.properties.type() != EditorValue::Type::Null && !properties) {
137 result.status = EditorStatus::Rejected;
138 result.diagnostics.push_back(pcgDiagnostic(
139 "editor.pcg.migration-invalid-properties", DiagnosticSeverity::Error,
140 "Legacy PCG node properties must be an object: " + legacy.id.value()));
141 return result;
142 }
143 if (properties)
144 for (const auto& [key, value] : *properties) (*defaults)[key] = value;
145
146 int input = 0;
147 int output = 0;
148 for (const auto& pin : legacy.pins) {
149 const int ordinal = pin.direction == GraphPinDirection::Input ? input++ : output++;
150 int canonicalOrdinal = 0;
151 const auto replacement = std::find_if(
152 canonical.value().pins.begin(), canonical.value().pins.end(),
153 [&](const GraphPinRecord& candidate) {
154 if (candidate.direction != pin.direction) return false;
155 return canonicalOrdinal++ == ordinal;
156 });
157 if (replacement == canonical.value().pins.end()) continue;
158 if (!pinRemap.emplace(pin.id.value(), replacement->id).second) {
159 result.status = EditorStatus::Rejected;
160 result.diagnostics.push_back(pcgDiagnostic(
161 "editor.pcg.migration-duplicate-pin", DiagnosticSeverity::Error,
162 "Legacy PCG graph contains duplicate pin id: " + pin.id.value()));
163 return result;
164 }
165 }
166 migratedNodes.push_back(std::move(canonical.value()));
167 }
168 result.graph.nodes = std::move(migratedNodes);
169 for (auto& edge : result.graph.edges) {
170 const auto from = pinRemap.find(edge.from.value());
171 const auto to = pinRemap.find(edge.to.value());
172 if (from != pinRemap.end()) edge.from = from->second;
173 if (to != pinRemap.end()) edge.to = to->second;
174 }
175 result.graph.schemaVersion = 1;
176 result.status = EditorStatus::Applied;
177 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.migrated-v0-v1",
178 DiagnosticSeverity::Info,
179 "Migrated procgen.point graph schemaVersion 0 to 1"));
180 return result;
181}
182
185 result.documentRevision = graph.revision;
186 const auto migration = migrate(graph);
187 if (migration.status != EditorStatus::Applied && migration.status != EditorStatus::NoOp) {
188 result.status = migration.status;
189 result.diagnostics = migration.diagnostics;
190 return result;
191 }
192 result.diagnostics = migration.diagnostics;
193 const auto& source = migration.graph;
194 procgen::PointGraph compiled;
195 for (const auto& node : source.nodes) {
196 if (!compiled.addNode(node.id.value(), node.type)) {
197 result.status = EditorStatus::Failed;
198 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.invalid-node",
199 DiagnosticSeverity::Error,
200 "Unknown or duplicated PCG node: " +
201 node.id.value()));
202 return result;
203 }
204 const auto* properties = node.properties.getIf<EditorValue::Object>();
205 if (!properties) {
206 result.status = EditorStatus::Failed;
207 result.diagnostics.push_back(pcgDiagnostic(
208 "editor.pcg.invalid-properties", DiagnosticSeverity::Error,
209 "PCG node properties must be an object: " + node.id.value()));
210 return result;
211 }
212 for (const auto& [key, value] : *properties) {
213 const bool subgraphProperty =
214 node.type == "subgraph" &&
215 (key == "definition" || key == "inputNode" || key == "outputNode");
216 if (subgraphProperty) continue;
217 int parameterIndex = -1;
218 const int parameterCount = procgen::PointGraph::getOperationParamCount(node.type);
219 for (int index = 0; index < parameterCount; ++index)
221 parameterIndex = index;
222 break;
223 }
224 const std::string kind = parameterIndex >= 0
226 node.type, parameterIndex)
227 : std::string();
228 bool applied = false;
229 if (kind == "bool") {
230 if (const auto* boolean = value.getIf<bool>())
231 applied = compiled.setNodeInt(node.id.value(), key, *boolean ? 1 : 0);
232 } else if (kind == "int") {
233 if (const auto* integer = value.getIf<int64_t>();
234 integer && *integer >= std::numeric_limits<int>::min() &&
235 *integer <= std::numeric_limits<int>::max())
236 applied = compiled.setNodeInt(node.id.value(), key, int(*integer));
237 } else if (kind == "float") {
238 if (const auto* number = value.getIf<double>();
239 number && std::isfinite(*number) &&
240 std::abs(*number) <= std::numeric_limits<float>::max())
241 applied = compiled.setNodeFloat(node.id.value(), key, float(*number));
242 } else if (kind == "string") {
243 if (const auto* text = value.getIf<std::string>())
244 applied = compiled.setNodeString(node.id.value(), key, *text);
245 }
246 if (!applied) {
247 result.status = EditorStatus::Failed;
248 result.diagnostics.push_back(pcgDiagnostic(
249 parameterIndex < 0 ? "editor.pcg.unknown-property"
250 : "editor.pcg.property-type-mismatch",
251 DiagnosticSeverity::Error,
252 "Invalid PCG property " + node.id.value() + "." + key));
253 return result;
254 }
255 }
256 if (node.type == "subgraph") {
257 const auto definition = properties->find("definition");
258 const auto inputNode = properties->find("inputNode");
259 const auto outputNode = properties->find("outputNode");
260 const auto* definitionText =
261 definition == properties->end() ? nullptr : definition->second.getIf<std::string>();
262 const auto* inputText =
263 inputNode == properties->end() ? nullptr : inputNode->second.getIf<std::string>();
264 const auto* outputText =
265 outputNode == properties->end() ? nullptr : outputNode->second.getIf<std::string>();
266 procgen::PointGraph nested;
267 if (!definitionText || !inputText || !outputText || definitionText->empty() ||
268 !nested.deserializeDefinition(*definitionText) ||
269 !compiled.setNodeSubgraph(node.id.value(), &nested, *inputText, *outputText)) {
270 result.status = EditorStatus::Failed;
271 result.diagnostics.push_back(pcgDiagnostic(
272 "editor.pcg.invalid-subgraph", DiagnosticSeverity::Error,
273 "PCG subgraph node requires a valid definition and input/output node ids"));
274 return result;
275 }
276 }
277 }
278 if (source.parameters.type() != EditorValue::Type::Null) {
279 const auto* parameters = source.parameters.getIf<EditorValue::Object>();
280 if (!parameters) {
281 result.status = EditorStatus::Failed;
282 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.invalid-parameters",
283 DiagnosticSeverity::Error,
284 "PCG graph parameters must be an object"));
285 return result;
286 }
287 for (const auto& [name, value] : *parameters) {
288 const auto* binding = value.getIf<EditorValue::Object>();
289 const auto node = binding ? binding->find("node") : EditorValue::Object::const_iterator{};
290 const auto key = binding ? binding->find("key") : EditorValue::Object::const_iterator{};
291 const auto* nodeId = binding && node != binding->end()
292 ? node->second.getIf<std::string>()
293 : nullptr;
294 const auto* parameterKey = binding && key != binding->end()
295 ? key->second.getIf<std::string>()
296 : nullptr;
297 if (!nodeId || !parameterKey ||
298 !compiled.exposeParameter(name, *nodeId, *parameterKey)) {
299 result.status = EditorStatus::Failed;
300 result.diagnostics.push_back(pcgDiagnostic(
301 "editor.pcg.invalid-parameter-binding", DiagnosticSeverity::Error,
302 "Invalid PCG graph parameter binding: " + name));
303 return result;
304 }
305 }
306 }
307 std::unordered_map<std::string, StableId> connectedInputs;
308 for (const auto& edge : source.edges) {
309 const GraphPinRecord* from = findPin(source, edge.from);
310 const GraphPinRecord* to = findPin(source, edge.to);
311 const GraphNodeRecord* toNode = to ? findNode(source, to->node) : nullptr;
312 const int slot = toNode && to ? inputIndex(*toNode, to->id) : -1;
313 const std::string inputKey = to ? to->id.value() : std::string();
314 if (!inputKey.empty() && connectedInputs.find(inputKey) != connectedInputs.end()) {
315 result.status = EditorStatus::Failed;
316 result.diagnostics.push_back(pcgDiagnostic(
317 "editor.pcg.duplicate-input", DiagnosticSeverity::Error,
318 "PCG input pin has more than one incoming edge: " + inputKey));
319 return result;
320 }
321 if (!from || !to || !toNode || !canConnect(*from, *to).allowed || slot < 0 ||
322 !compiled.connect(from->node.value(), to->node.value(), slot)) {
323 result.status = EditorStatus::Failed;
324 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.invalid-edge",
325 DiagnosticSeverity::Error,
326 "Invalid PCG graph edge: " +
327 edge.id.value()));
328 return result;
329 }
330 connectedInputs.emplace(inputKey, edge.id);
331 }
332 result.definition = compiled.serializeDefinition();
333 procgen::PointGraph structuralCheck;
334 if (!structuralCheck.deserializeDefinition(result.definition)) {
335 result.status = EditorStatus::Failed;
336 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.cycle", DiagnosticSeverity::Error,
337 structuralCheck.getError()));
338 result.definition.clear();
339 return result;
340 }
341 result.status = EditorStatus::Applied;
342 return result;
343}
344
346 const std::string& inputNode,
347 procgen::PointSet* previewInput,
348 const std::string& outputNode,
349 int nodeBudget, int pointBudget) const {
351 result.documentRevision = graph.revision;
352 const auto compiled = compile(graph);
353 result.diagnostics = compiled.diagnostics;
354 if (compiled.status != EditorStatus::Applied) {
355 result.status = compiled.status;
356 return result;
357 }
358 if (!previewInput || inputNode.empty() || outputNode.empty()) {
359 result.status = EditorStatus::Rejected;
360 result.diagnostics.push_back(pcgDiagnostic(
361 "editor.pcg.preview-invalid-input", DiagnosticSeverity::Error,
362 "PCG preview requires input/output node ids and point input"));
363 return result;
364 }
365 procgen::PointGraph runtime;
366 if (!runtime.deserializeDefinition(compiled.definition)) {
367 result.status = EditorStatus::Failed;
368 result.diagnostics.push_back(pcgDiagnostic("editor.pcg.preview-bind-failed",
369 DiagnosticSeverity::Error,
370 "PCG preview definition could not be loaded"));
371 return result;
372 }
373 runtime.setExecutionNodeBudget(nodeBudget);
374 runtime.setMaxNodeOutputPoints(pointBudget);
375 if (!runtime.setNodePoints(inputNode, previewInput)) {
376 result.status = EditorStatus::Failed;
377 result.diagnostics.push_back(pcgDiagnostic(
378 "editor.pcg.preview-bind-failed", DiagnosticSeverity::Error,
379 runtime.getError().empty() ? "PCG preview input node is invalid: " + inputNode
380 : runtime.getError()));
381 return result;
382 }
383 auto output = runtime.executeResult(outputNode);
384 if (!output.ok()) {
385 const bool cancelled = output.status().code() == eve::StatusCode::Cancelled;
386 result.status = cancelled ? EditorStatus::Cancelled : EditorStatus::Failed;
387 result.diagnostics.push_back(pcgDiagnostic(
388 cancelled ? "editor.pcg.preview-cancelled" : "editor.pcg.preview-execution-failed",
389 cancelled ? DiagnosticSeverity::Info : DiagnosticSeverity::Error, output.status().describe()));
390 return result;
391 }
392 result.outputCount = output.value().getCount();
393 result.metrics.reserve(size_t(runtime.getMetricCount()));
394 for (int index = 0; index < runtime.getMetricCount(); ++index) {
396 metric.nodeId = runtime.getMetricNodeId(index);
397 metric.outputCount = runtime.getMetricOutputCount(index);
398 metric.milliseconds = runtime.getMetricMilliseconds(index);
399 metric.cacheHit = runtime.isMetricCacheHit(index);
400 metric.minX = runtime.getMetricMinX(index);
401 metric.minY = runtime.getMetricMinY(index);
402 metric.minZ = runtime.getMetricMinZ(index);
403 metric.maxX = runtime.getMetricMaxX(index);
404 metric.maxY = runtime.getMetricMaxY(index);
405 metric.maxZ = runtime.getMetricMaxZ(index);
406 metric.averageDensity = runtime.getMetricAverageDensity(index);
407 result.metrics.push_back(std::move(metric));
408 }
409 result.status = EditorStatus::Applied;
410 return result;
411}
412
413} // namespace eve::procgen_editing
ActionParameterOperation operation
double value
std::string output
std::string from
std::string nodeId
EvpackChunkInput input
Definition Evpack.cpp:170
std::string message
std::uint32_t key
int inputs
Definition GridGraph.cpp:23
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
TokenKind kind
std::map< std::string, PropertyRule > properties
std::string name
std::vector< BvhNode > nodes
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
eve::action::ActionVfxBinding binding
std::string id
Definition PlayHost.cpp:108
const RoadNode * node
const RoadEdge * edge
double number
bool found
uint32_t index
const UnitySourceAsset & source
Move-only operation result carrying either a value or Status.
Definition Result.h:155
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
std::map< std::string, Value > Object
EVENGINE_API_DOMAINS public API.
Definition PointGraph.h:66
static std::string getOperationParamDefault(const std::string &operation, int index)
Returns the operation param default.
bool setNodeFloat(const std::string &id, const std::string &key, float value)
Sets the node float.
bool exposeParameter(const std::string &name, const std::string &nodeId, const std::string &key)
Expose one reflected node parameter for per-instance overrides.
bool setNodeString(const std::string &id, const std::string &key, const std::string &value)
Sets the node string.
bool connect(const std::string &fromId, const std::string &toId, int inputIndex=0)
Connect an output to input slot 0 or 1. Replaces that input connection.
std::string serializeDefinition() const
Serialize graph topology, parameters and nested graphs.
static int getOperationParamCount(const std::string &operation)
Returns the operation param count.
bool deserializeDefinition(const std::string &definition)
Transactionally replace this graph from serializeDefinition output.
bool setNodeSubgraph(const std::string &id, PointGraph *graph, const std::string &inputNode, const std::string &outputNode)
Assign an immutable nested graph.
static std::string getOperationParamKind(const std::string &operation, int index)
Returns the operation param kind.
std::string getError() const
Render the last compatibility execute/validate or legacy authoring error; canonical Result calls do n...
bool setNodeInt(const std::string &id, const std::string &key, int value)
Sets the node int.
static std::string getOperationParamKey(const std::string &operation, int index)
Returns the operation param key.
bool addNode(const std::string &id, const std::string &operation)
Add a node using one of the operations returned by operationAt().
static int getOperationInputCount(const std::string &operation)
Returns the operation input count.
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
bool empty() const
Empty.
Definition PointSet.cpp:49
PcgGraphMigrationResult migrate(const GraphDocumentData &graph) const
Upgrade a legacy graph without mutating the source document.
Definition PcgGraph.cpp:98
Result< GraphNodeRecord > makeNode(const GraphNodeId &id, const std::string &operation) const
Construct a generic editor node from one reflected PointGraph operation.
Definition PcgGraph.cpp:68
std::string domain() const override
Domain.
Definition PcgGraph.h:65
PcgGraphCompileResult compile(const GraphDocumentData &graph) const
Compile topology and scalar properties into a reusable PointGraph definition.
Definition PcgGraph.cpp:183
PcgGraphPreviewResult preview(const GraphDocumentData &graph, const std::string &inputNode, procgen::PointSet *previewInput, const std::string &outputNode, int nodeBudget=0, int pointBudget=100000) const
Compile and execute an isolated point-input preview for editor visualization.
Definition PcgGraph.cpp:345
GraphConnectionDecision canConnect(const GraphPinRecord &from, const GraphPinRecord &to) const override
Can connect.
Definition PcgGraph.cpp:55
const char * defaultValue
eve::Severity DiagnosticSeverity
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
StrongId< GraphPinIdTag > GraphPinId
Definition EditingIds.h:69
Result< T > applied(T value, std::vector< Diagnostic > diagnostics={})
Construct an Applied result with an owning payload and optional diagnostics.
StrongId< GraphNodeIdTag > GraphNodeId
Definition EditingIds.h:68
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
editing::GraphPinRecord GraphPinRecord
editing::GraphNodeRecord GraphNodeRecord
editing::Diagnostic EditorDiagnostic
Result of domain-specific connection validation.
std::vector< Diagnostic > diagnostics
Immutable graph document value sent to a domain compiler.
std::vector< GraphNodeRecord > nodes
std::vector< GraphEdgeRecord > edges
Generic graph node independent of its domain.
Stable typed pin owned by a graph node.
Result of compiling a generic editor graph into a PointGraph asset definition.
Definition PcgGraph.h:15
std::vector< EditorDiagnostic > diagnostics
Definition PcgGraph.h:19
Transactional result of upgrading an editor PCG graph to the current schema.
Definition PcgGraph.h:23
std::vector< EditorDiagnostic > diagnostics
Definition PcgGraph.h:28
One node's isolated PointGraph preview telemetry for graph overlays.
Definition PcgGraph.h:32
Revision-tagged result of compiling and executing an isolated editor preview.
Definition PcgGraph.h:47
std::vector< EditorDiagnostic > diagnostics
Definition PcgGraph.h:52
std::vector< PcgGraphPreviewMetric > metrics
Definition PcgGraph.h:51