载入中...
搜索中...
未找到
PixelWorldGenerationCodec.cpp
浏览该文件的文档.
2
3#if defined(__EMSCRIPTEN__)
4
5namespace eve::pixelworld {
6namespace {
7
8eve::Diagnostic webCodecUnavailable() {
10 "generation request JSON codec is unavailable on the Web build", "codec",
11 {}, "pixelworld.generation-codec");
12}
13
14} // namespace
15
17 const PixelWorldGenerationRequest&) {
18 return eve::Result<std::string>::failure(webCodecUnavailable());
19}
20
22 return eve::Result<PixelWorldGenerationRequest>::failure(webCodecUnavailable());
23}
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 <charconv>
36#include <limits>
37#include <sstream>
38#include <type_traits>
39#include <unordered_set>
40
41namespace eve::pixelworld {
42namespace {
43
44using Object = Poco::JSON::Object;
45using Array = Poco::JSON::Array;
46
48 std::string path = "document") {
51 "pixelworld.generation-codec"));
52}
53
54bool exactFields(const Object& object, std::initializer_list<std::string_view> allowed) {
55 std::unordered_set<std::string> names;
56 for (const auto name : allowed) names.emplace(name);
57 if (object.size() != names.size()) return false;
58 for (const auto& name : object.getNames())
59 if (!names.contains(name)) return false;
60 for (const auto& name : names)
61 if (!object.has(name)) return false;
62 return true;
63}
64
65template <class T>
66bool readNumber(const Object& object, const std::string& key, T& result) {
67 static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool>);
68 if (!object.has(key) || object.isNull(key)) return false;
69 try {
70 const auto wide = object.get(key).convert<std::int64_t>();
71 if constexpr (std::is_unsigned_v<T>) {
72 if (wide < 0 || std::uint64_t(wide) > std::numeric_limits<T>::max()) return false;
73 } else if (wide < std::numeric_limits<T>::min() ||
74 wide > std::numeric_limits<T>::max()) {
75 return false;
76 }
77 result = static_cast<T>(wide);
78 return true;
79 } catch (...) {
80 return false;
81 }
82}
83
84bool readSeed(const Object& object, std::uint64_t& seed) {
85 if (!object.has("seed") || object.isNull("seed")) return false;
86 try {
87 const std::string text = object.getValue<std::string>("seed");
88 const char* begin = text.data();
89 const char* end = begin + text.size();
90 const auto parsed = std::from_chars(begin, end, seed);
91 return parsed.ec == std::errc{} && parsed.ptr == end;
92 } catch (...) {
93 return false;
94 }
95}
96
97Object::Ptr cellJson(const PixelCell& cell) {
98 Object::Ptr out(new Object);
99 out->set("material", std::uint16_t(cell.material));
100 out->set("temperature", cell.temperature);
101 out->set("lifetime", cell.lifetime);
102 out->set("thermalRemainder", cell.thermalRemainder);
103 return out;
104}
105
106} // namespace
107
110 Object::Ptr root(new Object);
111 root->set("schema", "eve.pixelworld.generation-request");
112 root->set("version", request.schemaVersion);
113 root->set("seed", std::to_string(request.seed));
114 Object::Ptr region(new Object);
115 region->set("minX", request.region.minX);
116 region->set("minY", request.region.minY);
117 region->set("maxX", request.region.maxX);
118 region->set("maxY", request.region.maxY);
119 root->set("region", region);
120 root->set("surfaceY", request.surfaceY);
121 root->set("terrainAmplitude", request.terrainAmplitude);
122 root->set("waterLevel", request.waterLevel);
123 root->set("caveThreshold", request.caveThreshold);
124 Array::Ptr stamps(new Array);
125 for (const PixelMaterialStamp& stamp : request.stamps) {
126 Object::Ptr value(new Object);
127 value->set("originX", stamp.originX);
128 value->set("originY", stamp.originY);
129 value->set("width", stamp.width);
130 value->set("height", stamp.height);
131 Array::Ptr cells(new Array);
132 for (const PixelCell& cell : stamp.cells) cells->add(cellJson(cell));
133 value->set("cells", cells);
134 stamps->add(value);
135 }
136 root->set("stamps", stamps);
137 std::ostringstream text;
138 Poco::JSON::Stringifier::stringify(Poco::Dynamic::Var(root), text, 2, -1);
140}
141
143 std::string_view json) {
144 Object::Ptr root;
145 try {
146 root = Poco::JSON::Parser().parse(std::string(json)).extract<Object::Ptr>();
147 } catch (...) {
148 return malformed("generation request JSON is malformed");
149 }
150 if (!root || !exactFields(*root, {"schema", "version", "seed", "region", "surfaceY",
151 "terrainAmplitude", "waterLevel", "caveThreshold", "stamps"}))
152 return malformed("root contains missing or unknown fields");
153 if (root->optValue<std::string>("schema", "") != "eve.pixelworld.generation-request")
154 return malformed("unknown generation request schema", "schema");
155
157 if (!readNumber(*root, "version", request.schemaVersion) ||
159 return malformed("unknown generation request version", "version");
160 if (!readSeed(*root, request.seed)) return malformed("seed is not a canonical uint64", "seed");
161 Object::Ptr region;
162 Array::Ptr stamps;
163 try {
164 region = root->getObject("region");
165 stamps = root->getArray("stamps");
166 } catch (...) {
167 return malformed("region or stamps has an invalid type");
168 }
169 if (!region || !exactFields(*region, {"minX", "minY", "maxX", "maxY"}) || !stamps ||
170 stamps->size() > 65'536U)
171 return malformed("region fields or stamp budget are invalid");
172 if (!readNumber(*region, "minX", request.region.minX) ||
173 !readNumber(*region, "minY", request.region.minY) ||
174 !readNumber(*region, "maxX", request.region.maxX) ||
175 !readNumber(*region, "maxY", request.region.maxY) ||
176 !readNumber(*root, "surfaceY", request.surfaceY) ||
177 !readNumber(*root, "terrainAmplitude", request.terrainAmplitude) ||
178 !readNumber(*root, "waterLevel", request.waterLevel) ||
179 !readNumber(*root, "caveThreshold", request.caveThreshold))
180 return malformed("generation request contains an invalid number");
181
182 std::uint64_t totalCells = 0;
183 request.stamps.reserve(stamps->size());
184 for (std::size_t index = 0; index < stamps->size(); ++index) {
185 Object::Ptr value;
186 Array::Ptr cells;
187 try {
188 value = stamps->getObject(unsigned(index));
189 if (value) cells = value->getArray("cells");
190 } catch (...) {
191 return malformed("stamp is not an object", "stamps[" + std::to_string(index) + "]");
192 }
193 if (!value || !exactFields(*value, {"originX", "originY", "width", "height", "cells"}) ||
194 !cells)
195 return malformed("stamp contains missing or unknown fields",
196 "stamps[" + std::to_string(index) + "]");
198 if (!readNumber(*value, "originX", stamp.originX) ||
199 !readNumber(*value, "originY", stamp.originY) ||
200 !readNumber(*value, "width", stamp.width) ||
201 !readNumber(*value, "height", stamp.height) || stamp.width <= 0 || stamp.height <= 0 ||
202 std::uint64_t(stamp.width) * std::uint64_t(stamp.height) != cells->size())
203 return malformed("stamp dimensions are invalid", "stamps[" + std::to_string(index) + "]");
204 totalCells += cells->size();
205 if (totalCells > 4'194'304ULL) return malformed("stamp cell budget is exceeded", "stamps");
206 stamp.cells.reserve(cells->size());
207 for (std::size_t cellIndex = 0; cellIndex < cells->size(); ++cellIndex) {
208 Object::Ptr item;
209 try {
210 item = cells->getObject(unsigned(cellIndex));
211 } catch (...) {
212 return malformed("stamp cell is not an object", "stamps.cells");
213 }
215 std::uint16_t material = 0;
216 if (!item || !exactFields(*item, {"material", "temperature", "lifetime", "thermalRemainder"}) ||
217 !readNumber(*item, "material", material) ||
218 !readNumber(*item, "temperature", cell.temperature) ||
219 !readNumber(*item, "lifetime", cell.lifetime) ||
220 !readNumber(*item, "thermalRemainder", cell.thermalRemainder))
221 return malformed("stamp cell contains invalid fields", "stamps.cells");
222 cell.material = MaterialId(material);
223 stamp.cells.push_back(cell);
224 }
225 request.stamps.push_back(std::move(stamp));
226 }
228}
229
230} // namespace eve::pixelworld
231
232#endif // defined(__EMSCRIPTEN__)
double value
int root
Definition AnimSmr.cpp:119
std::string message
const GltfImportRequest & request
std::uint32_t key
std::string text
std::string name
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
std::string path
Definition PlayHost.cpp:110
std::uint32_t seed
Definition PointSet.cpp:807
float begin
Material * material
Cell cell
Heightmap stamp
Json object
float size
Definition TreeMesh.cpp:156
uint32_t index
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
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
eve::Result< std::string > encodePixelWorldGenerationRequestJson(const PixelWorldGenerationRequest &request)
Encode schema eve.pixelworld.generation-request version 1 canonically.
MaterialId
Compact stable material identifier stored in each authoritative cell.
eve::Result< PixelWorldGenerationRequest > decodePixelWorldGenerationRequestJson(std::string_view json)
Decode schema eve.pixelworld.generation-request version 1.
bool readNumber(const EditorValue &value, const char *name, float &output)
enum EVENGINE_API_FOUNDATION Array
OT_ARRAY (editable via Runtime array APIs / ReflectedPropertyModel).
Definition Runtime.h:120
Compact authoritative state for one simulated world pixel.
Definition PixelWorld.h:24
Owning row-major material stamp applied after terrain and cave generation.
Versioned bounded request for deterministic biome and cave generation.