载入中...
搜索中...
未找到
PixelWorldAutomation.cpp
浏览该文件的文档.
1#include "common/Capability.h"
5
6#if defined(__EMSCRIPTEN__)
7
8namespace eve::pixelworld {
9namespace {
10
11class PixelWorldAutomation final : public eve::IPixelWorldAutomation {
12public:
13 std::string invoke(const std::string&, const std::string&) override {
14 return R"({"ok":false,"status":"Unsupported","error":"PixelWorld MCP automation is unavailable on the Web build"})";
15 }
16};
17
18PixelWorldAutomation automation;
19
20} // namespace
21
22void registerPixelWorldAutomation() { eve::cap::provide<eve::IPixelWorldAutomation>(&automation); }
23void unregisterPixelWorldAutomation() { eve::cap::revoke<eve::IPixelWorldAutomation>(&automation); }
24
25} // namespace eve::pixelworld
26
27#else
28
29#include <Poco/Dynamic/Var.h>
30#include <Poco/JSON/Array.h>
31#include <Poco/JSON/Object.h>
32#include <Poco/JSON/Parser.h>
33#include <Poco/JSON/Stringifier.h>
34
35#include <algorithm>
36#include <iomanip>
37#include <sstream>
38
39namespace eve::pixelworld {
40namespace {
41
42using Object = Poco::JSON::Object;
43
44std::string stringify(Object::Ptr object) {
45 std::ostringstream out;
46 Poco::JSON::Stringifier::stringify(Poco::Dynamic::Var(object), out);
47 return out.str();
48}
49
50std::string stringify(const Poco::Dynamic::Var& value) {
51 std::ostringstream out;
52 Poco::JSON::Stringifier::stringify(value, out);
53 return out.str();
54}
55
56Object::Ptr response(bool ok) {
57 Object::Ptr out(new Object);
58 out->set("ok", ok);
59 return out;
60}
61
62template <class T>
63std::string failure(const eve::Result<T>& result) {
64 Object::Ptr out = response(false);
65 out->set("status", std::string(eve::statusCodeName(result.status().code())));
66 if (result.status().hasDiagnostics())
67 out->set("error", result.status().diagnostics().front().message());
68 return stringify(out);
69}
70
71Object::Ptr parse(const std::string& json) {
72 try {
73 return Poco::JSON::Parser().parse(json).extract<Object::Ptr>();
74 } catch (...) {
75 return Object::Ptr(new Object);
76 }
77}
78
79std::string hex(std::span<const std::byte> bytes) {
80 std::ostringstream out;
81 out << std::hex << std::setfill('0');
82 for (const std::byte value : bytes) out << std::setw(2) << std::to_integer<unsigned>(value);
83 return out.str();
84}
85
86eve::Result<std::vector<std::byte>> unhex(const std::string& value) {
87 if ((value.size() & 1U) != 0)
89 eve::DiagnosticCode::InvalidArgument, "snapshot hex length must be even", "snapshot", {},
90 "pixelworld.automation"));
91 std::vector<std::byte> bytes;
92 bytes.reserve(value.size() / 2);
93 for (std::size_t index = 0; index < value.size(); index += 2) {
94 unsigned parsed = 0;
95 std::istringstream in(value.substr(index, 2));
96 in >> std::hex >> parsed;
97 if (!in || !in.eof())
99 eve::DiagnosticCode::InvalidArgument, "snapshot contains non-hex data", "snapshot", {},
100 "pixelworld.automation"));
101 bytes.push_back(std::byte(parsed));
102 }
103 return eve::Result<std::vector<std::byte>>::success(std::move(bytes));
104}
105
106class PixelWorldAutomation final : public eve::IPixelWorldAutomation {
107public:
108 std::string invoke(const std::string& operation, const std::string& requestJson) override {
109 auto args = parse(requestJson);
110 auto& control = pixelWorldControlService();
111 if (operation == "worlds") {
112 Poco::JSON::Array::Ptr items(new Poco::JSON::Array);
113 for (const auto& world : control.worlds()) {
114 Object::Ptr item(new Object);
115 item->set("world", world.link.world);
116 item->set("epoch", world.link.epoch);
117 item->set("seed", world.seed);
118 item->set("revision", world.revision);
119 item->set("tick", world.tick);
120 item->set("lastEditSequence", world.lastEditSequence);
121 item->set("chunks", world.chunks);
122 item->set("activeChunks", world.activeChunks);
123 item->set("paused", world.paused);
124 items->add(item);
125 }
126 auto out = response(true);
127 out->set("worlds", items);
128 return stringify(out);
129 }
130 if (operation == "catalog_builtin") {
131 const MaterialCatalog catalog = MaterialCatalog::builtIn();
132 auto encoded = encodeMaterialCatalogJson(catalog);
133 if (!encoded.ok()) return failure(encoded);
134 auto out = response(true);
135 out->set("fingerprint", std::to_string(catalog.fingerprint()));
136 out->set("catalog", encoded.value());
137 return stringify(out);
138 }
139 if (operation == "catalog_validate") {
140 std::string document;
141 if (args->has("catalog")) {
142 const auto value = args->get("catalog");
143 document = value.isString() ? value.convert<std::string>() : stringify(value);
144 }
145 auto decoded = decodeMaterialCatalogJson(document);
146 if (!decoded.ok()) return failure(decoded);
147 auto canonical = encodeMaterialCatalogJson(decoded.value());
148 if (!canonical.ok()) return failure(canonical);
149 auto out = response(true);
150 out->set("fingerprint", std::to_string(decoded.value().fingerprint()));
151 out->set("materialCount", decoded.value().definitions().size());
152 out->set("reactionCount", decoded.value().reactions().size());
153 out->set("phaseRuleCount", decoded.value().phaseRules().size());
154 out->set("canonicalCatalog", canonical.value());
155 return stringify(out);
156 }
157 if (operation == "catalog_apply") {
158 std::string document;
159 if (args->has("catalog")) {
160 const auto value = args->get("catalog");
161 document = value.isString() ? value.convert<std::string>() : stringify(value);
162 }
163 std::uint64_t expected = 0;
164 try {
165 expected = std::stoull(args->optValue<std::string>("expectedFingerprint", "0"));
166 } catch (...) {
167 auto out = response(false);
168 out->set("error", "expectedFingerprint must be an unsigned decimal string");
169 return stringify(out);
170 }
171 auto result = control.reloadMaterialCatalog(
172 args->optValue<std::uint64_t>("world", 0), document, expected);
173 if (!result.ok()) return failure(result);
174 auto out = response(true);
175 out->set("fingerprintBefore", std::to_string(result.value().fingerprintBefore));
176 out->set("fingerprintAfter", std::to_string(result.value().fingerprintAfter));
177 out->set("revisionAfter", result.value().revisionAfter);
178 out->set("worldEpoch", result.value().worldEpoch);
179 out->set("chunksRebuilt", result.value().chunksRebuilt);
180 out->set("replayHistoryInvalidated", result.value().replayHistoryInvalidated);
181 return stringify(out);
182 }
183 const auto worldId = args->optValue<std::uint64_t>("world", 0);
184 if (operation == "pause") {
185 auto result = control.setPaused(worldId, args->optValue<bool>("paused", true));
186 if (!result.ok()) return failure(result);
187 auto out = response(true);
188 out->set("paused", control.world(worldId).value().paused);
189 return stringify(out);
190 }
191 if (operation == "step") {
192 auto result = control.step(worldId, args->optValue<std::uint32_t>("count", 1));
193 if (!result.ok()) return failure(result);
194 const auto& receipt = result.value();
195 auto out = response(true);
196 out->set("steps", receipt.steps);
197 out->set("firstTick", receipt.firstTick);
198 out->set("lastTick", receipt.lastTick);
199 out->set("elapsedMicroseconds", receipt.elapsedMicroseconds);
200 out->set("cellsChanged", receipt.finalStep.cellsChanged);
201 out->set("cellsMoved", receipt.finalStep.cellsMoved);
202 return stringify(out);
203 }
204 if (operation == "edit") {
205 PixelEditCommand command;
206 command.sequence = args->optValue<std::uint64_t>("sequence", 0);
207 const std::string kind = args->optValue<std::string>("kind", "paint");
208 command.kind = kind == "heat" ? PixelEditKind::HeatCircle
209 : kind == "explosion" ? PixelEditKind::Explosion
211 command.centerX = args->optValue<int>("x", 0);
212 command.centerY = args->optValue<int>("y", 0);
213 command.radius = args->optValue<int>("radius", 0);
214 command.material = MaterialId(args->optValue<unsigned>("material", 0));
215 command.strength = args->optValue<int>("strength", 0);
216 command.temperatureDelta = std::int16_t(std::clamp(
217 args->optValue<int>("temperatureDelta", 0), -32768, 32767));
218 auto result = control.applyEdit(worldId, command);
219 if (!result.ok()) return failure(result);
220 auto out = response(true);
221 out->set("revisionBefore", result.value().revisionBefore);
222 out->set("revisionAfter", result.value().revisionAfter);
223 out->set("cellsChanged", result.value().cellsChanged);
224 return stringify(out);
225 }
226 if (operation == "diagnostics") {
227 PixelChunkRegion region{args->optValue<int>("minX", 0), args->optValue<int>("minY", 0),
228 args->optValue<int>("maxX", 0), args->optValue<int>("maxY", 0)};
229 auto result = control.chunkDiagnostics(worldId, region);
230 if (!result.ok()) return failure(result);
231 Poco::JSON::Array::Ptr chunks(new Poco::JSON::Array);
232 for (const auto& diagnostic : result.value()) {
233 Object::Ptr item(new Object);
234 item->set("x", diagnostic.x);
235 item->set("y", diagnostic.y);
236 item->set("revision", diagnostic.revision);
237 item->set("nonAirCells", diagnostic.nonAirCells);
238 item->set("mobileCells", diagnostic.mobileCells);
239 item->set("minimumTemperature", diagnostic.minimumTemperature);
240 item->set("maximumTemperature", diagnostic.maximumTemperature);
241 item->set("idleTicks", diagnostic.idleTicks);
242 item->set("active", diagnostic.active);
243 chunks->add(item);
244 }
245 auto out = response(true);
246 out->set("chunks", chunks);
247 return stringify(out);
248 }
249 if (operation == "samples") {
250 auto result = control.performanceSamples(
251 worldId, args->optValue<std::uint32_t>("limit", 120));
252 if (!result.ok()) return failure(result);
253 Poco::JSON::Array::Ptr samples(new Poco::JSON::Array);
254 for (const auto& sample : result.value()) {
255 Object::Ptr item(new Object);
256 item->set("tick", sample.tick);
257 item->set("elapsedMicroseconds", sample.elapsedMicroseconds);
258 item->set("chunksVisited", sample.stats.chunksVisited);
259 item->set("cellsVisited", sample.stats.cellsVisited);
260 item->set("cellsChanged", sample.stats.cellsChanged);
261 item->set("cellsMoved", sample.stats.cellsMoved);
262 item->set("parallelTasks", sample.stats.parallelTasks);
263 samples->add(item);
264 }
265 auto out = response(true);
266 out->set("samples", samples);
267 return stringify(out);
268 }
269 if (operation == "snapshot_capture") {
270 auto result = control.captureSnapshot(worldId);
271 if (!result.ok()) return failure(result);
272 auto out = response(true);
273 out->set("encoding", "hex");
274 out->set("snapshot", hex(result.value()));
275 return stringify(out);
276 }
277 if (operation == "snapshot_restore") {
278 auto decoded = unhex(args->optValue<std::string>("snapshot", ""));
279 if (!decoded.ok()) return failure(decoded);
280 auto result = control.restoreSnapshot(worldId, decoded.value());
281 if (!result.ok()) return failure(result);
282 return stringify(response(true));
283 }
284 auto out = response(false);
285 out->set("error", "unknown PixelWorld automation operation");
286 return stringify(out);
287 }
288};
289
290PixelWorldAutomation automation;
291
292} // namespace
293
294void registerPixelWorldAutomation() { eve::cap::provide<eve::IPixelWorldAutomation>(&automation); }
295void unregisterPixelWorldAutomation() { eve::cap::revoke<eve::IPixelWorldAutomation>(&automation); }
296
297} // namespace eve::pixelworld
298
299#endif // defined(__EMSCRIPTEN__)
ActionParameterOperation operation
double value
TokenKind kind
std::uint64_t bytes
World3D * world
uint32_t index
std::unordered_map< PhysicsWorldHandle::index_type, World3D * > worlds
Definition World3D.cpp:164
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
Module-neutral JSON bridge from DevTools/MCP to PixelWorld tooling.
Move-only operation result carrying either a value or Status.
Definition Result.h:155
const T & value() const &
Borrow the value from a const lvalue after checking success.
Definition Result.h:308
bool ok() const noexcept
Whether this result represents a non-failure outcome.
Definition Result.h:255
const Status & status() const noexcept
Inspect the structured operation status.
Definition Result.h:269
StatusCode code() const noexcept
Stable operation outcome.
Definition Status.h:98
const std::vector< Diagnostic > & diagnostics() const noexcept
All diagnostics in stable insertion order.
Definition Status.h:124
bool hasDiagnostics() const noexcept
True when this status has at least one diagnostic.
Definition Status.h:121
static MaterialCatalog builtIn()
Construct the engine's canonical built-in catalog.
Result< std::string > stringify(const eve::Value &value)
Serialize the canonical owning Value as deterministic compact JSON.
Definition Json.cpp:563
eve::Result< MaterialCatalog > decodeMaterialCatalogJson(std::string_view json)
Decode and validate schema eve.pixelworld.material-catalog version 1.
PixelWorldControlService & pixelWorldControlService()
Process-lifetime PixelWorld tooling registry.
MaterialId
Compact stable material identifier stored in each authoritative cell.
eve::Result< std::string > encodeMaterialCatalogJson(const MaterialCatalog &catalog)
Encode schema eve.pixelworld.material-catalog version 1 canonically.
double sample(const Heightmap &map, double u, double v)
Sample.
ModelData parse(std::span< const uint8_t > bytes)
Parse.
constexpr std::string_view statusCodeName(StatusCode code) noexcept
Return the stable protocol spelling of a status code.
Definition Status.h:45