载入中...
搜索中...
未找到
PixelMaterialCatalogCodec.cpp
浏览该文件的文档.
2
3#if defined(__EMSCRIPTEN__)
4
5namespace eve::pixelworld {
6namespace {
7
8eve::Diagnostic webCodecUnavailable() {
10 "material Catalog JSON codec is unavailable on the Web build", "codec",
11 {}, "pixelworld.catalog-codec");
12}
13
14} // namespace
15
17 return eve::Result<std::string>::failure(webCodecUnavailable());
18}
19
21 return eve::Result<MaterialCatalog>::failure(webCodecUnavailable());
22}
23
24} // namespace eve::pixelworld
25
26#else
27
28#include <Poco/Dynamic/Var.h>
29#include <Poco/JSON/Array.h>
30#include <Poco/JSON/Object.h>
31#include <Poco/JSON/Parser.h>
32#include <Poco/JSON/Stringifier.h>
33
34#include <algorithm>
35#include <limits>
36#include <sstream>
37#include <stdexcept>
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
47eve::Result<MaterialCatalog> malformed(std::string message, std::string path = "document") {
50 "pixelworld.catalog-codec"));
51}
52
53bool exactFields(const Object& object, std::initializer_list<std::string_view> allowed) {
54 std::unordered_set<std::string> names;
55 for (const auto name : allowed) names.emplace(name);
56 if (object.size() != names.size()) return false;
57 for (const auto& name : object.getNames())
58 if (!names.contains(name)) return false;
59 for (const auto& name : names)
60 if (!object.has(name)) return false;
61 return true;
62}
63
64std::string stateName(MaterialState state) {
65 switch (state) {
66 case MaterialState::Empty: return "empty";
67 case MaterialState::Solid: return "solid";
68 case MaterialState::Powder: return "powder";
69 case MaterialState::Liquid: return "liquid";
70 case MaterialState::Gas: return "gas";
71 case MaterialState::Energy: return "energy";
72 }
73 return "empty";
74}
75
76bool parseState(const std::string& name, MaterialState& state) {
77 if (name == "empty") state = MaterialState::Empty;
78 else if (name == "solid") state = MaterialState::Solid;
79 else if (name == "powder") state = MaterialState::Powder;
80 else if (name == "liquid") state = MaterialState::Liquid;
81 else if (name == "gas") state = MaterialState::Gas;
82 else if (name == "energy") state = MaterialState::Energy;
83 else return false;
84 return true;
85}
86
87template <class T>
88bool readNumber(const Object& object, const std::string& key, T& result) {
89 static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool>);
90 if (!object.has(key) || object.isNull(key)) return false;
91 try {
92 const auto wide = object.get(key).convert<std::int64_t>();
93 if constexpr (std::is_unsigned_v<T>) {
94 if (wide < 0 || std::uint64_t(wide) > std::numeric_limits<T>::max()) return false;
95 } else if (wide < std::numeric_limits<T>::min() ||
96 wide > std::numeric_limits<T>::max()) {
97 return false;
98 }
99 result = static_cast<T>(wide);
100 return true;
101 } catch (...) {
102 return false;
103 }
104}
105
106Object::Ptr materialJson(const MaterialDefinition& material) {
107 Object::Ptr out(new Object);
108 out->set("id", std::uint16_t(material.id));
109 out->set("name", material.name);
110 out->set("state", stateName(material.state));
111 out->set("density", material.density);
112 out->set("viscosity", material.viscosity);
113 out->set("thermalConductivity", material.thermalConductivity);
114 out->set("heatCapacity", material.heatCapacity);
115 out->set("ignitionTemperature", material.ignitionTemperature);
116 out->set("meltingTemperature", material.meltingTemperature);
117 out->set("boilingTemperature", material.boilingTemperature);
118 out->set("defaultLifetime", material.defaultLifetime);
119 out->set("flammable", material.flammable);
120 out->set("defaultTemperature", material.defaultTemperature);
121 out->set("blastResistance", material.blastResistance);
122 out->set("displayRgba", material.displayRgba);
123 Array::Ptr tags(new Array);
124 for (const auto& tag : material.tags) tags->add(tag);
125 out->set("tags", tags);
126 return out;
127}
128
129} // namespace
130
132 Object::Ptr root(new Object);
133 root->set("schema", "eve.pixelworld.material-catalog");
134 root->set("version", 1);
135 Array::Ptr materials(new Array);
136 for (const auto& material : catalog.definitions()) materials->add(materialJson(material));
137 root->set("materials", materials);
138 Array::Ptr reactions(new Array);
139 for (const auto& reaction : catalog.reactions()) {
140 Object::Ptr value(new Object);
141 value->set("id", reaction.id);
142 value->set("first", std::uint16_t(reaction.first));
143 value->set("second", std::uint16_t(reaction.second));
144 value->set("firstResult", std::uint16_t(reaction.firstResult));
145 value->set("secondResult", std::uint16_t(reaction.secondResult));
146 value->set("minimumTemperature", reaction.minimumTemperature);
147 value->set("heatDelta", reaction.heatDelta);
148 value->set("priority", reaction.priority);
149 reactions->add(value);
150 }
151 root->set("reactions", reactions);
152 Array::Ptr phases(new Array);
153 for (const auto& phase : catalog.phaseRules()) {
154 Object::Ptr value(new Object);
155 value->set("id", phase.id);
156 value->set("source", std::uint16_t(phase.source));
157 value->set("result", std::uint16_t(phase.result));
158 value->set("direction", phase.direction == TemperatureDirection::AtOrBelow
159 ? "at_or_below"
160 : "at_or_above");
161 value->set("threshold", phase.threshold);
162 value->set("temperatureDelta", phase.temperatureDelta);
163 value->set("priority", phase.priority);
164 phases->add(value);
165 }
166 root->set("phaseRules", phases);
167 std::ostringstream text;
168 Poco::JSON::Stringifier::stringify(Poco::Dynamic::Var(root), text, 2, -1);
170}
171
173 Object::Ptr root;
174 try {
175 root = Poco::JSON::Parser().parse(std::string(json)).extract<Object::Ptr>();
176 } catch (...) {
177 return malformed("material Catalog JSON is malformed");
178 }
179 if (!root || !exactFields(*root, {"schema", "version", "materials", "reactions", "phaseRules"}))
180 return malformed("root contains missing or unknown fields");
181 if (root->optValue<std::string>("schema", "") != "eve.pixelworld.material-catalog")
182 return malformed("unknown material Catalog schema", "schema");
183 int version = 0;
184 if (!readNumber(*root, "version", version) || version != 1)
185 return malformed("unknown material Catalog version", "version");
186 Array::Ptr materials;
187 Array::Ptr reactions;
188 Array::Ptr phases;
189 try {
190 materials = root->getArray("materials");
191 reactions = root->getArray("reactions");
192 phases = root->getArray("phaseRules");
193 } catch (...) {
194 return malformed("material Catalog arrays are invalid");
195 }
196 if (!materials || !reactions || !phases || materials->size() > 65535U ||
197 reactions->size() > 1'000'000U || phases->size() > 1'000'000U)
198 return malformed("material Catalog arrays are missing or exceed their budgets");
199
200 std::vector<MaterialDefinition> definitions;
201 definitions.reserve(materials->size());
202 for (std::size_t index = 0; index < materials->size(); ++index) {
203 Object::Ptr value;
204 try {
205 value = materials->getObject(unsigned(index));
206 } catch (...) {
207 return malformed("material entry is not an object",
208 "materials[" + std::to_string(index) + "]");
209 }
210 if (!value || !exactFields(*value, {"id", "name", "state", "density", "viscosity",
211 "thermalConductivity", "heatCapacity", "ignitionTemperature",
212 "meltingTemperature", "boilingTemperature", "defaultLifetime",
213 "flammable", "defaultTemperature", "blastResistance",
214 "displayRgba", "tags"}))
215 return malformed("material contains missing or unknown fields",
216 "materials[" + std::to_string(index) + "]");
218 std::uint16_t id = 0;
219 if (!readNumber(*value, "id", id) || !readNumber(*value, "density", material.density) ||
220 !readNumber(*value, "viscosity", material.viscosity) ||
221 !readNumber(*value, "thermalConductivity", material.thermalConductivity) ||
222 !readNumber(*value, "heatCapacity", material.heatCapacity) ||
223 !readNumber(*value, "ignitionTemperature", material.ignitionTemperature) ||
224 !readNumber(*value, "meltingTemperature", material.meltingTemperature) ||
225 !readNumber(*value, "boilingTemperature", material.boilingTemperature) ||
226 !readNumber(*value, "defaultLifetime", material.defaultLifetime) ||
227 !readNumber(*value, "defaultTemperature", material.defaultTemperature) ||
228 !readNumber(*value, "blastResistance", material.blastResistance) ||
229 !readNumber(*value, "displayRgba", material.displayRgba))
230 return malformed("material contains an invalid number",
231 "materials[" + std::to_string(index) + "]");
232 material.id = MaterialId(id);
233 material.name = value->optValue<std::string>("name", "");
234 if (!parseState(value->optValue<std::string>("state", ""), material.state))
235 return malformed("material state is unknown",
236 "materials[" + std::to_string(index) + "].state");
237 if (!value->has("flammable") || !value->has("tags"))
238 return malformed("material fields are missing",
239 "materials[" + std::to_string(index) + "]");
240 try {
241 material.flammable = value->getValue<bool>("flammable");
242 Array::Ptr tags = value->getArray("tags");
243 if (!tags || tags->size() > 1024U) throw std::runtime_error("tags");
244 for (std::size_t tag = 0; tag < tags->size(); ++tag)
245 material.tags.push_back(tags->getElement<std::string>(unsigned(tag)));
246 } catch (...) {
247 return malformed("material tags or flammable value are invalid",
248 "materials[" + std::to_string(index) + "]");
249 }
250 definitions.push_back(std::move(material));
251 }
252
253 std::vector<MaterialReactionRule> reactionRules;
254 reactionRules.reserve(reactions->size());
255 for (std::size_t index = 0; index < reactions->size(); ++index) {
256 Object::Ptr value;
257 try {
258 value = reactions->getObject(unsigned(index));
259 } catch (...) {
260 return malformed("reaction entry is not an object",
261 "reactions[" + std::to_string(index) + "]");
262 }
263 if (!value || !exactFields(*value, {"id", "first", "second", "firstResult", "secondResult",
264 "minimumTemperature", "heatDelta", "priority"}))
265 return malformed("reaction contains missing or unknown fields",
266 "reactions[" + std::to_string(index) + "]");
268 std::uint16_t first = 0, second = 0, firstResult = 0, secondResult = 0;
269 rule.id = value->optValue<std::string>("id", "");
270 if (!readNumber(*value, "first", first) || !readNumber(*value, "second", second) ||
271 !readNumber(*value, "firstResult", firstResult) ||
272 !readNumber(*value, "secondResult", secondResult) ||
273 !readNumber(*value, "minimumTemperature", rule.minimumTemperature) ||
274 !readNumber(*value, "heatDelta", rule.heatDelta) ||
275 !readNumber(*value, "priority", rule.priority))
276 return malformed("reaction contains an invalid number",
277 "reactions[" + std::to_string(index) + "]");
278 rule.first = MaterialId(first);
279 rule.second = MaterialId(second);
280 rule.firstResult = MaterialId(firstResult);
281 rule.secondResult = MaterialId(secondResult);
282 reactionRules.push_back(std::move(rule));
283 }
284
285 std::vector<MaterialPhaseRule> phaseRules;
286 phaseRules.reserve(phases->size());
287 for (std::size_t index = 0; index < phases->size(); ++index) {
288 Object::Ptr value;
289 try {
290 value = phases->getObject(unsigned(index));
291 } catch (...) {
292 return malformed("phase rule entry is not an object",
293 "phaseRules[" + std::to_string(index) + "]");
294 }
295 if (!value || !exactFields(*value, {"id", "source", "result", "direction", "threshold",
296 "temperatureDelta", "priority"}))
297 return malformed("phase rule contains missing or unknown fields",
298 "phaseRules[" + std::to_string(index) + "]");
300 std::uint16_t source = 0, result = 0;
301 rule.id = value->optValue<std::string>("id", "");
302 const std::string direction = value->optValue<std::string>("direction", "");
303 if (direction == "at_or_below") rule.direction = TemperatureDirection::AtOrBelow;
304 else if (direction == "at_or_above") rule.direction = TemperatureDirection::AtOrAbove;
305 else return malformed("phase direction is unknown",
306 "phaseRules[" + std::to_string(index) + "].direction");
307 if (!readNumber(*value, "source", source) || !readNumber(*value, "result", result) ||
308 !readNumber(*value, "threshold", rule.threshold) ||
309 !readNumber(*value, "temperatureDelta", rule.temperatureDelta) ||
310 !readNumber(*value, "priority", rule.priority))
311 return malformed("phase rule contains an invalid number",
312 "phaseRules[" + std::to_string(index) + "]");
313 rule.source = MaterialId(source);
314 rule.result = MaterialId(result);
315 phaseRules.push_back(std::move(rule));
316 }
317 return MaterialCatalog::create(std::move(definitions), std::move(reactionRules),
318 std::move(phaseRules));
319}
320
321} // namespace eve::pixelworld
322
323#endif // defined(__EMSCRIPTEN__)
double value
int root
Definition AnimSmr.cpp:119
float phase
Definition CaveMesh.cpp:58
std::string message
std::uint32_t key
std::int32_t second
std::int32_t first
std::string text
std::string name
const std::string * tag
std::string path
Definition PlayHost.cpp:110
Material * material
RoadLaneDirection direction
Battle::Reactions reactions
Json object
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
const std::map< std::string, Value > & definitions
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
Owning immutable material and reaction table used by one PixelWorld.
std::span< const MaterialReactionRule > reactions() const noexcept
Borrow immutable canonical-priority reaction rules.
std::span< const MaterialDefinition > definitions() const noexcept
Borrow immutable definitions until this catalog is destroyed.
std::span< const MaterialPhaseRule > phaseRules() const noexcept
Borrow immutable canonical-priority phase rules.
static eve::Result< MaterialCatalog > create(std::vector< MaterialDefinition > definitions, std::vector< MaterialReactionRule > reactions={}, std::vector< MaterialPhaseRule > phaseRules={})
Validate and own a complete catalog candidate transactionally.
std::string stateName(OrderState state)
Returns the stable lowercase name of a state.
eve::Result< MaterialCatalog > decodeMaterialCatalogJson(std::string_view json)
Decode and validate schema eve.pixelworld.material-catalog version 1.
MaterialId
Compact stable material identifier stored in each authoritative cell.
MaterialState
Broad movement class for one pixel material.
eve::Result< std::string > encodeMaterialCatalogJson(const MaterialCatalog &catalog)
Encode schema eve.pixelworld.material-catalog version 1 canonically.
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
Immutable validated properties for one catalog material.
Data-driven phase transition evaluated after heat diffusion.
Deterministically ordered binary material reaction.