载入中...
搜索中...
未找到
GridGraph.cpp
浏览该文件的文档.
1#include "procgen/GridGraph.h"
2
3#include "common/Diagnostic.h"
6#include "procgen/Params.h"
8#include "procgen/Semantic.h"
10
11#include <algorithm>
12#include <cmath>
13#include <iomanip>
14#include <queue>
15#include <sstream>
16#include <unordered_set>
17
18namespace eve::procgen {
19namespace {
20
21struct OperationSpec {
22 const char* id;
23 int inputs;
25};
26
27const std::vector<OperationSpec>& specs() {
28 static const std::vector<OperationSpec> value = {
29 {"grid.input", 0, GridGraphValueType::Grid},
30 {"grid.union", 2, GridGraphValueType::Grid},
31 {"grid.intersect", 2, GridGraphValueType::Grid},
32 {"grid.subtract", 2, GridGraphValueType::Grid},
33 {"grid.invert", 1, GridGraphValueType::Grid},
34 {"grid.expand", 1, GridGraphValueType::Grid},
35 {"grid.shrink", 1, GridGraphValueType::Grid},
36 {"grid.smooth", 1, GridGraphValueType::Grid},
37 {"grid.autotile", 1, GridGraphValueType::Grid},
38 {"generate.fill", 0, GridGraphValueType::Grid},
39 {"generate.random_noise", 0, GridGraphValueType::Grid},
40 {"generate.checkerboard", 0, GridGraphValueType::Grid},
41 {"generate.dot_grid", 0, GridGraphValueType::Grid},
42 {"generate.shape", 0, GridGraphValueType::Grid},
43 {"generate.cellular", 0, GridGraphValueType::Grid},
44 {"generate.random_walk", 0, GridGraphValueType::Grid},
45 {"generate.maze", 0, GridGraphValueType::Grid},
46 {"generate.poisson", 0, GridGraphValueType::Grid},
47 {"generate.registry", 0, GridGraphValueType::Grid},
48 {"select.random", 1, GridGraphValueType::Grid},
49 {"select.border", 1, GridGraphValueType::Grid},
50 {"select.fill", 1, GridGraphValueType::Grid},
51 {"select.neighbors", 1, GridGraphValueType::Grid},
52 {"select.rule", 1, GridGraphValueType::Grid},
53 {"select.islands", 1, GridGraphValueType::Grid},
54 {"select.island_centers", 1, GridGraphValueType::Grid},
55 {"select.detail_range", 1, GridGraphValueType::Grid},
56 {"select.semantic", 1, GridGraphValueType::Grid},
57 {"grid.path", 3, GridGraphValueType::Grid},
58 {"convert.grid_to_points", 1, GridGraphValueType::PointSet},
59 {"point.subgraph", 1, GridGraphValueType::PointSet},
60 };
61 return value;
62}
63
64const OperationSpec* specFor(std::string_view id) {
65 const auto found =
66 std::find_if(specs().begin(), specs().end(), [id](const OperationSpec& spec) { return id == spec.id; });
67 return found == specs().end() ? nullptr : &*found;
68}
69
70bool sameSize(const Grid2D& a, const Grid2D& b) {
71 return a.getWidth() == b.getWidth() && a.getHeight() == b.getHeight();
72}
73
74bool occupied(const Grid2D& grid, int x, int y) { return grid.getCell(x, y) != int(Semantic::Empty); }
75
76Grid2D combine(const Grid2D& a, const Grid2D& b, std::string_view operation) {
77 Grid2D out = a;
78 for (int y = 0; y < a.getHeight(); ++y) {
79 for (int x = 0; x < a.getWidth(); ++x) {
80 const bool left = occupied(a, x, y);
81 const bool right = occupied(b, x, y);
82 bool keep = false;
83 if (operation == "grid.union") keep = left || right;
84 if (operation == "grid.intersect") keep = left && right;
85 if (operation == "grid.subtract") keep = left && !right;
86 if (!keep) {
87 out.setCell(x, y, int(Semantic::Empty));
88 out.setDetail(x, y, 0);
89 } else if (!left) {
90 out.setCell(x, y, b.getCell(x, y));
91 out.setDetail(x, y, b.getDetail(x, y));
92 }
93 }
94 }
95 return out;
96}
97
98Grid2D morphology(const Grid2D& input, int radius, bool expand) {
99 Grid2D out = input;
100 radius = std::max(1, radius);
101 for (int y = 0; y < input.getHeight(); ++y) {
102 for (int x = 0; x < input.getWidth(); ++x) {
103 bool result = expand ? occupied(input, x, y) : true;
104 for (int oy = -radius; oy <= radius && (expand ? !result : result); ++oy) {
105 for (int ox = -radius; ox <= radius; ++ox) {
106 const bool sample = occupied(input, x + ox, y + oy);
107 result = expand ? (result || sample) : (result && sample);
108 if (expand ? result : !result) break;
109 }
110 }
111 if (result) {
112 if (!occupied(out, x, y)) out.setCell(x, y, int(Semantic::Floor));
113 } else {
114 out.setCell(x, y, int(Semantic::Empty));
115 out.setDetail(x, y, 0);
116 }
117 }
118 }
119 return out;
120}
121
122std::uint64_t hashText(std::string_view text) {
123 std::uint64_t hash = 1469598103934665603ull;
124 for (const unsigned char value : text) {
125 hash ^= value;
126 hash *= 1099511628211ull;
127 }
128 return hash == 0 ? 1 : hash;
129}
130
131Result<PointSet> gridToPoints(const Grid2D& grid, int semantic, float cellSize, float originX, float originZ,
132 std::string_view nodeId) {
133 if (!std::isfinite(cellSize) || cellSize <= 0.f)
135 "cellSize must be finite and positive", std::string(nodeId),
136 {}, "procgen.gridGraph"));
138 points.reserve(grid.cells().size());
139 std::uint64_t ordinal = 0;
140 for (int y = 0; y < grid.getHeight(); ++y) {
141 for (int x = 0; x < grid.getWidth(); ++x) {
142 const int cell = grid.getCell(x, y);
143 if (cell == int(Semantic::Empty) || (semantic >= 0 && cell != semantic)) continue;
144 const int index =
145 points.add(originX + (float(x) + 0.5f) * cellSize, 0.f, originZ + (float(y) + 0.5f) * cellSize);
146 auto idResult = points.trySetPointId(index, derivePointId(hashText(nodeId), ++ordinal));
147 if (!idResult.ok()) return Result<PointSet>::failure(idResult.status());
148 auto xResult = points.trySetIntAttribute(index, "cell_x", x);
149 if (!xResult.ok()) return Result<PointSet>::failure(xResult.status());
150 auto yResult = points.trySetIntAttribute(index, "cell_y", y);
151 if (!yResult.ok()) return Result<PointSet>::failure(yResult.status());
152 auto semanticResult = points.trySetIntAttribute(index, "semantic", cell);
153 if (!semanticResult.ok()) return Result<PointSet>::failure(semanticResult.status());
154 auto detailResult = points.trySetIntAttribute(index, "detail", grid.getDetail(x, y));
155 if (!detailResult.ok()) return Result<PointSet>::failure(detailResult.status());
156 }
157 }
158 return Result<PointSet>::success(std::move(points));
159}
160
161} // namespace
162
163Result<void> GridGraph::addNode(std::string id, std::string operation) {
164 if (id.empty()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "node id is empty", {}, {}, "procgen.gridGraph"));
165 if (!specFor(operation)) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown operation: " + operation, id, {}, "procgen.gridGraph"));
166 if (nodes_.contains(id)) return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "duplicate node id: " + id, id, {}, "procgen.gridGraph"));
167 Node node;
168 node.id = id;
169 node.operation = std::move(operation);
170 nodes_.emplace(id, std::move(node));
171 order_.push_back(std::move(id));
172 ++revision_;
173 return Result<void>::success();
174}
175
176Result<void> GridGraph::connect(std::string_view fromId, std::string_view toId, int inputIndex) {
177 const auto from = nodes_.find(std::string(fromId));
178 const auto to = nodes_.find(std::string(toId));
179 if (from == nodes_.end() || to == nodes_.end())
180 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "connection references an unknown node", std::string(toId), {}, "procgen.gridGraph"));
181 const OperationSpec* target = specFor(to->second.operation);
182 if (!target || inputIndex < 0 || inputIndex >= target->inputs)
183 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "input slot is outside the operation contract", std::string(toId), {}, "procgen.gridGraph"));
184 const auto sourceType = operationOutputType(from->second.operation);
185 if (!sourceType.ok()) return Result<void>::failure(sourceType.status());
186 const GridGraphValueType expected =
187 to->second.operation == "point.subgraph" ? GridGraphValueType::PointSet : GridGraphValueType::Grid;
188 if (sourceType.value() != expected)
189 return Result<void>::failure(Diagnostic::error(DiagnosticCode::TypeMismatch, "typed graph ports are incompatible", std::string(toId), {}, "procgen.gridGraph"));
190 to->second.inputs[inputIndex] = std::string(fromId);
191 invalidateFrom(toId);
192 ++revision_;
193 return Result<void>::success();
194}
195
196Result<void> GridGraph::setNodeGrid(std::string_view id, const Grid2D& grid) {
197 const auto found = nodes_.find(std::string(id));
198 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
199 if (found->second.operation != "grid.input")
200 return Result<void>::failure(Diagnostic::error(DiagnosticCode::TypeMismatch, "node is not a grid.input", std::string(id), {}, "procgen.gridGraph"));
201 found->second.inputGrid = grid;
202 found->second.hasInputGrid = true;
203 invalidateFrom(id);
204 ++revision_;
205 return Result<void>::success();
206}
207
208Result<void> GridGraph::setNodeInt(std::string_view id, std::string key, int value) {
209 const auto found = nodes_.find(std::string(id));
210 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
211 const bool declared =
212 found->second.operation.starts_with("generate.") || found->second.operation.starts_with("select.") ||
213 found->second.operation == "grid.path" ||
214 ((found->second.operation == "grid.expand" || found->second.operation == "grid.shrink") && key == "radius") ||
215 (found->second.operation == "grid.smooth" && key == "threshold") ||
216 ((found->second.operation == "grid.invert" || found->second.operation == "convert.grid_to_points") &&
217 key == "semantic");
218 if (!declared)
219 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "unknown integer parameter: " + key, std::string(id), {}, "procgen.gridGraph"));
220 found->second.ints[std::move(key)] = value;
221 invalidateFrom(id);
222 ++revision_;
223 return Result<void>::success();
224}
225
226Result<void> GridGraph::setNodeFloat(std::string_view id, std::string key, float value) {
227 const auto found = nodes_.find(std::string(id));
228 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
229 if (!found->second.operation.starts_with("generate.") && !found->second.operation.starts_with("select.") &&
230 (found->second.operation != "convert.grid_to_points" ||
231 (key != "cellSize" && key != "originX" && key != "originZ")))
232 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "unknown float parameter: " + key, std::string(id), {}, "procgen.gridGraph"));
233 found->second.floats[std::move(key)] = value;
234 invalidateFrom(id);
235 ++revision_;
236 return Result<void>::success();
237}
238
239Result<void> GridGraph::setNodeString(std::string_view id, std::string key, std::string value) {
240 const auto found = nodes_.find(std::string(id));
241 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
242 const bool declared = (found->second.operation == "select.rule" && key == "rule") ||
243 found->second.operation == "generate.registry";
244 if (!declared)
245 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "unknown string parameter: " + key, std::string(id), {}, "procgen.gridGraph"));
246 found->second.strings[std::move(key)] = std::move(value);
247 invalidateFrom(id);
248 ++revision_;
249 return Result<void>::success();
250}
251
252Result<void> GridGraph::setNodePointSubgraph(std::string_view id, const PointGraph& graph, std::string inputNode,
253 std::string outputNode) {
254 const auto found = nodes_.find(std::string(id));
255 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
256 if (found->second.operation != "point.subgraph")
257 return Result<void>::failure(Diagnostic::error(DiagnosticCode::TypeMismatch, "node is not a point.subgraph", std::string(id), {}, "procgen.gridGraph"));
258 if (inputNode.empty() || outputNode.empty())
259 return Result<void>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "subgraph ports must be non-empty", std::string(id), {}, "procgen.gridGraph"));
260 found->second.pointSubgraph = std::make_shared<PointGraph>(graph);
261 found->second.pointInputNode = std::move(inputNode);
262 found->second.pointOutputNode = std::move(outputNode);
263 invalidateFrom(id);
264 ++revision_;
265 return Result<void>::success();
266}
267
268ResultRef<const GridGraphValue> GridGraph::evaluate(std::string_view id, std::unordered_map<std::string, int>& states) {
269 const auto found = nodes_.find(std::string(id));
270 if (found == nodes_.end())
272 Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
273 Node& node = found->second;
274 if (node.cacheValid) return ResultRef<const GridGraphValue>::success(std::cref(node.cache));
275 if (states[node.id] == 1)
277 Diagnostic::error(DiagnosticCode::Conflict, "graph contains a cycle", node.id, {}, "procgen.gridGraph"));
278 states[node.id] = 1;
279 const OperationSpec* spec = specFor(node.operation);
280 std::vector<std::reference_wrapper<const GridGraphValue>> inputs;
281 for (int i = 0; i < spec->inputs; ++i) {
282 if (node.inputs[i].empty())
284 DiagnosticCode::NotFound, "required input is disconnected", node.id, {}, "procgen.gridGraph"));
285 auto input = evaluate(node.inputs[i], states);
286 if (!input.ok()) return ResultRef<const GridGraphValue>::failure(input.status());
287 inputs.push_back(input.value());
288 }
289
290 if (node.operation == "grid.input") {
291 if (!node.hasInputGrid)
292 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
293 DiagnosticCode::NotFound, "grid.input has no bound value", node.id, {}, "procgen.gridGraph"));
294 node.cache = node.inputGrid;
295 } else if (node.operation == "generate.registry") {
296 const auto algorithm = node.strings.find("algorithm");
297 if (algorithm == node.strings.end() || algorithm->second.empty())
298 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
299 DiagnosticCode::InvalidArgument, "generate.registry requires a non-empty algorithm parameter", node.id,
300 {}, "procgen.gridGraph"));
301 Params params;
302 params.setSize(node.ints.contains("width") ? node.ints["width"] : 32,
303 node.ints.contains("height") ? node.ints["height"] : 32);
304 params.setSeed(std::uint32_t(node.ints.contains("seed") ? node.ints["seed"] : 1));
305 for (const auto& [key, value] : node.ints)
306 if (key != "width" && key != "height" && key != "seed") params.setInt(key, value);
307 for (const auto& [key, value] : node.floats) params.setFloat(key, value);
308 for (const auto& [key, value] : node.strings)
309 if (key != "algorithm") params.setString(key, value);
310 auto& registry = GeneratorRegistry::instance();
311 registry.registerBuiltins();
312 if (!registry.has(algorithm->second))
313 return Result<std::reference_wrapper<const GridGraphValue>>::failure(
314 Diagnostic::error(DiagnosticCode::NotFound, "registered generator was not found: " + algorithm->second,
315 node.id, {}, "procgen.gridGraph"));
316 Grid2D generated;
317 std::string error;
318 if (!registry.generate(algorithm->second, params, generated, error))
319 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
320 DiagnosticCode::Failed, error.empty() ? "registered generator failed" : error, node.id, {},
321 "procgen.gridGraph"));
322 node.cache = std::move(generated);
323 } else if (node.operation.starts_with("generate.")) {
324 gridgraph::GenerateSettings settings;
325 settings.width = node.ints.contains("width") ? node.ints["width"] : 32;
326 settings.height = node.ints.contains("height") ? node.ints["height"] : 32;
327 settings.semantic = node.ints.contains("semantic") ? node.ints["semantic"] : int(Semantic::Floor);
328 settings.seed = std::uint64_t(node.ints.contains("seed") ? node.ints["seed"] : 1);
329 settings.a = node.ints.contains("a") ? node.ints["a"] : 1;
330 settings.b = node.ints.contains("b") ? node.ints["b"] : 0;
331 settings.c = node.ints.contains("c") ? node.ints["c"] : 1;
332 settings.x = node.floats.contains("x") ? node.floats["x"] : 0.5f;
333 settings.y = node.floats.contains("y") ? node.floats["y"] : 0.f;
334 auto generated = gridgraph::generate(node.operation, settings);
335 if (!generated.ok()) return ResultRef<const GridGraphValue>::failure(generated.status());
336 node.cache = std::move(generated).takeValue();
337 } else if (node.operation == "select.semantic") {
338 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
339 if (!source)
340 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
341 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
342 Grid2D out;
343 out.resize(source->getWidth(), source->getHeight());
344 const int semantic = node.ints.contains("semantic") ? node.ints["semantic"] : int(Semantic::Floor);
345 for (int y = 0; y < source->getHeight(); ++y)
346 for (int x = 0; x < source->getWidth(); ++x)
347 if (source->getCell(x, y) == semantic) {
348 out.setCell(x, y, semantic);
349 out.setDetail(x, y, source->getDetail(x, y));
350 }
351 for (const auto& [key, value] : source->metadata()) out.setMeta(key, value);
352 node.cache = std::move(out);
353 } else if (node.operation == "select.detail_range") {
354 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
355 if (!source)
356 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
357 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
358 Grid2D out;
359 out.resize(source->getWidth(), source->getHeight());
360 const int minimum = node.ints.contains("min") ? node.ints["min"] : 0;
361 const int maximum = node.ints.contains("max") ? node.ints["max"] : 255;
362 for (int y = 0; y < source->getHeight(); ++y)
363 for (int x = 0; x < source->getWidth(); ++x)
364 if (occupied(*source, x, y) && source->getDetail(x, y) >= minimum &&
365 source->getDetail(x, y) <= maximum) {
366 out.setCell(x, y, source->getCell(x, y));
367 out.setDetail(x, y, source->getDetail(x, y));
368 }
369 node.cache = std::move(out);
370 } else if (node.operation.starts_with("select.")) {
371 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
372 if (!source)
373 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
374 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
375 auto selected = gridgraph::select(*source, node.operation, node.ints.contains("mode") ? node.ints["mode"] : 0,
376 node.ints.contains("count") ? node.ints["count"] : 0,
377 node.floats.contains("weight") ? node.floats["weight"] : 0.5f,
378 std::uint64_t(node.ints.contains("seed") ? node.ints["seed"] : 1),
379 node.strings.contains("rule") ? node.strings["rule"] : "*********");
380 if (!selected.ok()) return ResultRef<const GridGraphValue>::failure(selected.status());
381 node.cache = std::move(selected).takeValue();
382 } else if (node.operation == "grid.path") {
383 const auto* navigation = std::get_if<Grid2D>(&inputs[0].get());
384 const auto* starts = std::get_if<Grid2D>(&inputs[1].get());
385 const auto* targets = std::get_if<Grid2D>(&inputs[2].get());
386 if (!navigation || !starts || !targets)
387 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
388 DiagnosticCode::TypeMismatch, "three grid inputs required", node.id, {}, "procgen.gridGraph"));
389 auto path = gridgraph::findPath(*navigation, *starts, *targets,
390 node.ints.contains("semantic") ? node.ints["semantic"] : int(Semantic::Floor));
391 if (!path.ok()) return ResultRef<const GridGraphValue>::failure(path.status());
392 node.cache = std::move(path).takeValue();
393 } else if (node.operation == "grid.union" || node.operation == "grid.intersect" ||
394 node.operation == "grid.subtract") {
395 const auto* first = std::get_if<Grid2D>(&inputs[0].get());
396 const auto* second = std::get_if<Grid2D>(&inputs[1].get());
397 if (!first || !second || !sameSize(*first, *second))
398 return Result<std::reference_wrapper<const GridGraphValue>>::failure(
399 Diagnostic::error(DiagnosticCode::TypeMismatch, "grid inputs must have equal dimensions", node.id, {},
400 "procgen.gridGraph"));
401 node.cache = combine(*first, *second, node.operation);
402 } else if (node.operation == "grid.invert") {
403 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
404 if (!source)
405 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
406 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
407 Grid2D out = *source;
408 const int semantic = node.ints.contains("semantic") ? node.ints["semantic"] : int(Semantic::Floor);
409 for (int y = 0; y < out.getHeight(); ++y)
410 for (int x = 0; x < out.getWidth(); ++x)
411 out.setCell(x, y, occupied(*source, x, y) ? int(Semantic::Empty) : semantic);
412 node.cache = std::move(out);
413 } else if (node.operation == "grid.expand" || node.operation == "grid.shrink") {
414 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
415 if (!source)
416 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
417 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
418 node.cache = morphology(*source, node.ints.contains("radius") ? node.ints["radius"] : 1,
419 node.operation == "grid.expand");
420 } else if (node.operation == "grid.smooth") {
421 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
422 if (!source)
423 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
424 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
425 Grid2D out = *source;
426 const int threshold = node.ints.contains("threshold") ? node.ints["threshold"] : 4;
427 for (int y = 0; y < source->getHeight(); ++y) {
428 for (int x = 0; x < source->getWidth(); ++x) {
429 int neighbors = 0;
430 for (int oy = -1; oy <= 1; ++oy)
431 for (int ox = -1; ox <= 1; ++ox)
432 if ((ox != 0 || oy != 0) && occupied(*source, x + ox, y + oy)) ++neighbors;
433 out.setCell(x, y, neighbors >= threshold ? int(Semantic::Floor) : int(Semantic::Empty));
434 }
435 }
436 node.cache = std::move(out);
437 } else if (node.operation == "grid.autotile") {
438 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
439 if (!source)
440 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
441 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
442 Grid2D out = *source;
443 auto autotiled = autotileOccupiedGridInPlace(out);
444 if (!autotiled.ok())
445 return ResultRef<const GridGraphValue>::failure(autotiled.status());
446 node.cache = std::move(out);
447 } else if (node.operation == "convert.grid_to_points") {
448 const auto* source = std::get_if<Grid2D>(&inputs[0].get());
449 if (!source)
450 return Result<std::reference_wrapper<const GridGraphValue>>::failure(Diagnostic::error(
451 DiagnosticCode::TypeMismatch, "grid input required", node.id, {}, "procgen.gridGraph"));
452 auto points = gridToPoints(*source, node.ints.contains("semantic") ? node.ints["semantic"] : -1,
453 node.floats.contains("cellSize") ? node.floats["cellSize"] : 1.f,
454 node.floats.contains("originX") ? node.floats["originX"] : 0.f,
455 node.floats.contains("originZ") ? node.floats["originZ"] : 0.f, node.id);
456 if (!points.ok()) return ResultRef<const GridGraphValue>::failure(points.status());
457 node.cache = std::move(points).takeValue();
458 } else if (node.operation == "point.subgraph") {
459 const auto* source = std::get_if<PointSet>(&inputs[0].get());
460 if (!source || !node.pointSubgraph)
461 return Result<std::reference_wrapper<const GridGraphValue>>::failure(
462 Diagnostic::error(DiagnosticCode::TypeMismatch, "bound PointGraph and PointSet input required", node.id,
463 {}, "procgen.gridGraph"));
464 PointSet copy = *source;
465 if (!node.pointSubgraph->setNodePoints(node.pointInputNode, &copy))
466 return Result<std::reference_wrapper<const GridGraphValue>>::failure(
467 Diagnostic::error(DiagnosticCode::InvalidArgument, "nested PointGraph input binding failed", node.id,
468 {}, "procgen.gridGraph"));
469 auto output = node.pointSubgraph->executeResult(node.pointOutputNode);
470 if (!output.ok()) return ResultRef<const GridGraphValue>::failure(output.status());
471 node.cache = std::move(output).takeValue();
472 }
473 states[node.id] = 2;
474 node.cacheValid = true;
475 return ResultRef<const GridGraphValue>::success(std::cref(node.cache));
476}
477
478Result<GridGraphValue> GridGraph::execute(std::string_view outputId) {
479 std::unordered_map<std::string, int> states;
480 auto result = evaluate(outputId, states);
481 if (!result.ok()) return Result<GridGraphValue>::failure(result.status());
482 return Result<GridGraphValue>::success(result.value().get());
483}
484
485Result<void> GridGraph::validate(std::string_view outputId) const {
486 std::unordered_map<std::string, int> states;
487 const auto visit = [&](const auto& self, std::string_view id) -> Result<void> {
488 const auto found = nodes_.find(std::string(id));
489 if (found == nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", std::string(id), {}, "procgen.gridGraph"));
490 if (states[found->first] == 1)
491 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "graph contains a cycle", found->first, {}, "procgen.gridGraph"));
492 if (states[found->first] == 2) return Result<void>::success();
493 states[found->first] = 1;
494 const OperationSpec* spec = specFor(found->second.operation);
495 for (int index = 0; index < spec->inputs; ++index) {
496 if (found->second.inputs[index].empty())
497 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "required input is disconnected", found->first, {}, "procgen.gridGraph"));
498 auto input = self(self, found->second.inputs[index]);
499 if (!input.ok()) return input;
500 }
501 if (found->second.operation == "grid.input" && !found->second.hasInputGrid)
502 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "grid.input has no bound value", found->first, {}, "procgen.gridGraph"));
503 if (found->second.operation == "point.subgraph" &&
504 (!found->second.pointSubgraph || found->second.pointInputNode.empty() ||
505 found->second.pointOutputNode.empty()))
506 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "point.subgraph has no complete binding", found->first, {}, "procgen.gridGraph"));
507 states[found->first] = 2;
508 return Result<void>::success();
509 };
510 return visit(visit, outputId);
511}
512
514 for (auto& [id, node] : nodes_) node.cacheValid = false;
515}
516
517void GridGraph::invalidateFrom(std::string_view id) {
518 std::queue<std::string> pending;
519 std::unordered_set<std::string> visited;
520 pending.push(std::string(id));
521 while (!pending.empty()) {
522 const std::string current = pending.front();
523 pending.pop();
524 if (!visited.insert(current).second) continue;
525 const auto found = nodes_.find(current);
526 if (found != nodes_.end()) found->second.cacheValid = false;
527 for (const auto& [otherId, node] : nodes_)
528 if (node.inputs[0] == current || node.inputs[1] == current || node.inputs[2] == current)
529 pending.push(otherId);
530 }
531}
532
533int GridGraph::operationCount() { return int(specs().size()); }
534
536 return index >= 0 && index < int(specs().size()) ? specs()[std::size_t(index)].id : std::string();
537}
538
540 const OperationSpec* spec = specFor(operation);
541 if (!spec)
543 DiagnosticCode::NotFound, "unknown operation: " + std::string(operation), {}, {}, "procgen.gridGraph"));
544 return Result<GridGraphValueType>::success(spec->output);
545}
546
548 std::ostringstream out;
549 out << "EVPCG_GRID_GRAPH 1\n" << std::setprecision(9);
550 for (const auto& id : order_) {
551 const auto& node = nodes_.at(id);
552 out << "NODE " << std::quoted(id) << ' ' << std::quoted(node.operation) << '\n';
553 std::vector<std::string> keys;
554 for (const auto& [key, value] : node.ints) keys.push_back(key);
555 std::sort(keys.begin(), keys.end());
556 for (const auto& key : keys)
557 out << "INT " << std::quoted(id) << ' ' << std::quoted(key) << ' ' << node.ints.at(key) << '\n';
558 keys.clear();
559 for (const auto& [key, value] : node.floats) keys.push_back(key);
560 std::sort(keys.begin(), keys.end());
561 for (const auto& key : keys)
562 out << "FLOAT " << std::quoted(id) << ' ' << std::quoted(key) << ' ' << node.floats.at(key) << '\n';
563 keys.clear();
564 for (const auto& [key, value] : node.strings) keys.push_back(key);
565 std::sort(keys.begin(), keys.end());
566 for (const auto& key : keys)
567 out << "STRING " << std::quoted(id) << ' ' << std::quoted(key) << ' ' << std::quoted(node.strings.at(key))
568 << '\n';
569 }
570 for (const auto& id : order_) {
571 const auto& node = nodes_.at(id);
572 for (int input = 0; input < 3; ++input)
573 if (!node.inputs[input].empty())
574 out << "EDGE " << std::quoted(node.inputs[input]) << ' ' << std::quoted(id) << ' ' << input << '\n';
575 }
576 out << "END\n";
577 return out.str();
578}
579
580Result<void> GridGraph::deserializeDefinition(std::string_view definition) {
581 GridGraph replacement;
582 std::istringstream input{std::string(definition)};
583 std::string magic;
584 int version = 0;
585 if (!(input >> magic >> version) || magic != "EVPCG_GRID_GRAPH" || version != 1)
586 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid grid graph header", {}, {}, "procgen.gridGraph"));
587 std::string line;
588 std::getline(input, line);
589 bool ended = false;
590 while (std::getline(input, line)) {
591 if (line.empty()) continue;
592 std::istringstream record(line);
593 std::string kind;
594 record >> kind;
595 if (kind == "END") {
596 ended = true;
597 break;
598 }
599 std::string id;
600 if (kind == "NODE") {
601 std::string operation;
602 if (!(record >> std::quoted(id) >> std::quoted(operation)))
603 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid NODE record", {}, {}, "procgen.gridGraph"));
604 auto result = replacement.addNode(std::move(id), std::move(operation));
605 if (!result.ok()) return result;
606 } else if (kind == "EDGE") {
607 std::string target;
608 int slot = -1;
609 if (!(record >> std::quoted(id) >> std::quoted(target) >> slot))
610 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid EDGE record", {}, {}, "procgen.gridGraph"));
611 auto result = replacement.connect(id, target, slot);
612 if (!result.ok()) return result;
613 } else if (kind == "INT") {
614 std::string key;
615 int value = 0;
616 if (!(record >> std::quoted(id) >> std::quoted(key) >> value))
617 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid INT record", {}, {}, "procgen.gridGraph"));
618 auto result = replacement.setNodeInt(id, std::move(key), value);
619 if (!result.ok()) return result;
620 } else if (kind == "FLOAT") {
621 std::string key;
622 float value = 0.f;
623 if (!(record >> std::quoted(id) >> std::quoted(key) >> value))
624 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid FLOAT record", {}, {}, "procgen.gridGraph"));
625 auto result = replacement.setNodeFloat(id, std::move(key), value);
626 if (!result.ok()) return result;
627 } else if (kind == "STRING") {
628 std::string key;
629 std::string value;
630 if (!(record >> std::quoted(id) >> std::quoted(key) >> std::quoted(value)))
631 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "invalid STRING record", {}, {}, "procgen.gridGraph"));
632 auto result = replacement.setNodeString(id, std::move(key), std::move(value));
633 if (!result.ok()) return result;
634 } else {
635 return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "unknown grid graph record: " + kind, {}, {}, "procgen.gridGraph"));
636 }
637 record >> std::ws;
638 if (!record.eof()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "trailing grid graph record data", {}, {}, "procgen.gridGraph"));
639 }
640 if (!ended) return Result<void>::failure(Diagnostic::error(DiagnosticCode::ParseError, "grid graph END record is missing", {}, {}, "procgen.gridGraph"));
641 std::unordered_map<std::string, int> states;
642 const auto visit = [&](const auto& self, const std::string& id) -> Result<void> {
643 if (states[id] == 2) return Result<void>::success();
644 if (states[id] == 1) return Result<void>::failure(Diagnostic::error(DiagnosticCode::Conflict, "cycle in serialized graph", id, {}, "procgen.gridGraph"));
645 states[id] = 1;
646 const auto found = replacement.nodes_.find(id);
647 if (found == replacement.nodes_.end()) return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "unknown node", id, {}, "procgen.gridGraph"));
648 for (const auto& dependency : found->second.inputs) {
649 if (dependency.empty()) continue;
650 auto result = self(self, dependency);
651 if (!result.ok()) return result;
652 }
653 states[id] = 2;
654 return Result<void>::success();
655 };
656 for (const auto& id : replacement.order_) {
657 auto result = visit(visit, id);
658 if (!result.ok()) return result;
659 }
660 *this = std::move(replacement);
661 return Result<void>::success();
662}
663
664} // namespace eve::procgen
LogicalId target
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::string output
std::string from
std::vector< QuestEvent > pending
Stable, structured diagnostics shared by engine modules.
std::string nodeId
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
EvpackChunkInput input
Definition Evpack.cpp:170
float maximum[3]
float minimum[3]
std::uint32_t key
int inputs
Definition GridGraph.cpp:23
HexVec3 left
HexVec3 right
std::int32_t second
std::int32_t first
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
std::string text
TokenKind kind
bool ended
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
float radius
std::string path
Definition PlayHost.cpp:110
std::string id
Definition PlayHost.cpp:108
std::shared_ptr< const std::vector< glm::vec2 > > points
float begin
const RoadNode * node
bool found
double current
bool occupied
Cell cell
TerrainThermalSettings settings
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
double oy
double ox
uint32_t semantic
Definition WfcSimple.cpp:15
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
static GeneratorRegistry & instance()
Return the process-wide generator registry.
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
Definition Grid2D.h:36
Deterministic data-flow graph for grid generation and explicit grid-to-point composition.
Definition GridGraph.h:35
Result< void > setNodeFloat(std::string_view id, std::string key, float value)
Set one floating-point parameter declared by the node operation.
static int operationCount()
Return the number of reflected operations.
Result< GridGraphValue > execute(std::string_view outputId)
Validate and evaluate one output into an independent owning value.
std::string serializeDefinition() const
Serialize topology and scalar parameters using the versioned EVPCG grid schema.
static std::string operationId(int index)
Return a reflected operation id, or empty for an invalid index.
static Result< GridGraphValueType > operationOutputType(std::string_view operation)
Return a reflected operation's output kind.
Result< void > deserializeDefinition(std::string_view definition)
Atomically replace this graph from a versioned definition.
Result< void > connect(std::string_view fromId, std::string_view toId, int inputIndex=0)
Connect one node output to a typed input slot.
void clearCache()
Remove all cached node values while retaining topology and parameters.
Result< void > setNodeGrid(std::string_view id, const Grid2D &grid)
Assign an owning copy to a grid.input node.
Result< void > addNode(std::string id, std::string operation)
Add a reflected operation under a stable, non-empty node id.
Result< void > setNodePointSubgraph(std::string_view id, const PointGraph &graph, std::string inputNode, std::string outputNode)
Bind an owning PointGraph copy to a point.subgraph bridge.
Result< void > setNodeString(std::string_view id, std::string key, std::string value)
Set one string parameter declared by the node operation.
Result< void > validate(std::string_view outputId) const
Validate topology, required inputs and cross-graph bindings without evaluating values.
Result< void > setNodeInt(std::string_view id, std::string key, int value)
Set one integer parameter declared by the node operation.
EVENGINE_API_DOMAINS public API.
Definition PointGraph.h:66
std::vector< ParamSpec > params
constexpr uint32_t Floor
Definition Semantic.h:14
constexpr uint32_t Empty
Definition Semantic.h:12
Result< Grid2D > findPath(const Grid2D &navigation, const Grid2D &starts, const Grid2D &targets, int semantic)
Finds path.
Result< Grid2D > generate(std::string_view operation, const GenerateSettings &settings)
Generate.
Result< Grid2D > select(const Grid2D &input, std::string_view operation, int mode, int count, float weight, std::uint64_t seed, std::string_view rule)
Select.
double sample(const Heightmap &map, double u, double v)
Sample.
GridGraphValueType
Declared output kind of a GridGraph operation.
Definition GridGraph.h:23
eve::Result< void > autotileOccupiedGridInPlace(Grid2D &grid)
Write an eight-neighbour occupancy mask into every non-empty cell's detail value.
std::uint64_t derivePointId(std::uint64_t namespaceId, std::uint64_t ordinal)
Deterministically derive a non-zero stable point identity.
Definition PointSet.cpp:469
std::vector< int64_t > ints(const RuntimeTensor &v)
Ints.
std::vector< float > floats(const RuntimeTensor &v)
Floats.
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
@ TypeMismatch
A stable reference resolved to a different canonical domain type.