载入中...
搜索中...
未找到
AssetMigration.cpp
浏览该文件的文档.
2
3#include "data/HashFunction.h"
4
5#include <algorithm>
6#include <map>
7#include <set>
8
9namespace eve::asset {
10namespace {
11
12std::string sha256(std::span<const std::uint8_t> bytes) {
13 data::HashFunction::Value digest{};
14 data::HashFunction::getHashFunction("sha256")->hash("sha256", reinterpret_cast<const char*>(bytes.data()),
15 bytes.size(), digest);
16 static constexpr char hex[] = "0123456789abcdef";
17 std::string result = "sha256:";
18 result.reserve(71);
19 for (std::size_t index = 0; index < 32; ++index) {
20 const auto byte = static_cast<std::uint8_t>(digest.data[index]);
21 result.push_back(hex[byte >> 4]);
22 result.push_back(hex[byte & 0x0f]);
23 }
24 return result;
25}
26
27Result<Value> migrateImageV2ToV3(const Value& input) {
28 const auto* object = input.getIf<Value::Object>();
29 if (!object)
31 DiagnosticCode::ParseError, "eve.image definition must be an object", {}, {}, "asset.migration"));
32 const auto schema = object->find("schema");
33 const auto version = object->find("schemaVersion");
34 const auto color = object->find("color");
35 const auto* colorObject = color == object->end() ? nullptr : color->second.getIf<Value::Object>();
36 if (schema == object->end() || !schema->second.isString() || schema->second.asString() != "eve.image" ||
37 version == object->end() || !version->second.isInt64() || version->second.asInt() != 2 ||
38 object->contains("mipCount") || !colorObject || !colorObject->contains("transfer") ||
39 !colorObject->at("transfer").isString() ||
40 (colorObject->at("transfer").asString() != "srgb" && colorObject->at("transfer").asString() != "linear"))
42 DiagnosticCode::ParseError, "eve.image/2 definition is malformed or contains unversioned mips", {}, {},
43 "asset.migration"));
44 Value::Object migrated = *object;
45 migrated["schemaVersion"] = Value(std::int64_t(3));
46 migrated["mipCount"] = Value(std::int64_t(1));
47 return Result<Value>::success(Value(std::move(migrated)));
48}
49
50Result<Value> migrateDefinition(std::string_view type, SchemaVersion from, SchemaVersion current,
51 const Value& definition) {
52 if (from == current) return Result<Value>::success(definition);
53 if (from.value() > current.value())
55 "asset definition is newer than this reader", std::string(type),
56 {}, "asset.migration"));
57 if (type == "eve.mesh" && current.value() == 3 && (from.value() == 1 || from.value() == 2)) {
58 const auto* object = definition.getIf<Value::Object>();
59 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
60 object->at("schema").asString() != "eve.mesh" || !object->contains("schemaVersion") ||
61 !object->at("schemaVersion").isInt64() ||
62 object->at("schemaVersion").asInt() != static_cast<std::int64_t>(from.value()))
64 DiagnosticCode::ParseError, "eve.mesh definition is malformed", {}, {}, "asset.migration"));
65 auto migrated = *object;
66 if (from.value() == 1) {
67 if (migrated.contains("texcoordSets") || migrated.contains("colors"))
69 DiagnosticCode::ParseError, "eve.mesh/1 contains reserved metadata", {}, {}, "asset.migration"));
70 if (auto uv = migrated.find("texcoord0"); uv != migrated.end()) {
71 if (!uv->second.isBool())
73 DiagnosticCode::ParseError, "invalid legacy UV metadata", {}, {}, "asset.migration"));
74 Value::Array sets;
75 if (uv->second.asBool()) sets.emplace_back(int64_t(0));
76 migrated.erase(uv);
77 migrated["texcoordSets"] = Value(std::move(sets));
78 }
79 } else if (migrated.contains("colors")) {
81 DiagnosticCode::ParseError, "eve.mesh/2 contains reserved color metadata", {}, {}, "asset.migration"));
82 }
83 migrated["colors"] = Value(false);
84 migrated["schemaVersion"] = Value(int64_t(3));
85 return Result<Value>::success(Value(std::move(migrated)));
86 }
87 if (from.value() + 1 != current.value() && type != "eve.instance-set")
89 "asset definition is older than the N-1 compatibility window",
90 std::string(type), {}, "asset.migration"));
91 if (type == "eve.instance-set" && from.value() == 1 && current.value() >= 2) {
92 const auto* object = definition.getIf<Value::Object>();
93 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
94 object->at("schema").asString() != "eve.instance-set" || !object->contains("schemaVersion") ||
95 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 1 ||
96 object->contains("prototypes"))
98 DiagnosticCode::ParseError, "eve.instance-set/1 is malformed", {}, {}, "asset.migration"));
99 auto migrated = *object;
100 migrated["schemaVersion"] = Value(int64_t(2));
101 migrated["prototypes"] = Value(Value::Array{});
102 Value next(std::move(migrated));
103 return current.value() == 2 ? Result<Value>::success(std::move(next))
104 : migrateDefinition(type, SchemaVersion(2), current, next);
105 }
106 if (type == "eve.instance-set" && from.value() == 2 && current.value() >= 3) {
107 const auto* object = definition.getIf<Value::Object>();
108 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
109 object->at("schema").asString() != "eve.instance-set" || !object->contains("schemaVersion") ||
110 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 2 ||
111 !object->contains("prototypes") || !object->at("prototypes").isArray())
113 DiagnosticCode::ParseError, "eve.instance-set/2 is malformed", {}, {}, "asset.migration"));
114 auto migrated = *object;
115 auto prototypes = *migrated["prototypes"].getIf<Value::Array>();
116 for (auto& value : prototypes) {
117 auto* prototype = value.getIf<Value::Object>();
118 if (!prototype || prototype->contains("resourceAsset"))
120 "eve.instance-set/2 prototype is malformed", {}, {},
121 "asset.migration"));
122 (*prototype)["resourceAsset"] = Value("");
123 }
124 migrated["prototypes"] = Value(std::move(prototypes));
125 migrated["schemaVersion"] = Value(int64_t(3));
126 Value next(std::move(migrated));
127 return current.value() == 3 ? Result<Value>::success(std::move(next))
128 : migrateDefinition(type, SchemaVersion(3), current, next);
129 }
130 if (type == "eve.instance-set" && from.value() == 3 && current.value() >= 4) {
131 const auto* object = definition.getIf<Value::Object>();
132 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
133 object->at("schema").asString() != "eve.instance-set" || !object->contains("schemaVersion") ||
134 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 3 ||
135 !object->contains("prototypes") || !object->at("prototypes").isArray())
137 DiagnosticCode::ParseError, "eve.instance-set/3 is malformed", {}, {}, "asset.migration"));
138 auto migrated = *object;
139 auto prototypes = *migrated["prototypes"].getIf<Value::Array>();
140 for (auto& value : prototypes) {
141 auto* prototype = value.getIf<Value::Object>();
142 if (!prototype || prototype->contains("healthyColor") || prototype->contains("dryColor") ||
143 prototype->contains("bendFactor") || prototype->contains("holeEdgePadding") ||
144 prototype->contains("useDensityScaling"))
146 "eve.instance-set/3 prototype is malformed", {}, {},
147 "asset.migration"));
148 (*prototype)["healthyColor"] = Value(Value::Array{Value(1.0), Value(1.0), Value(1.0), Value(1.0)});
149 (*prototype)["dryColor"] = Value(Value::Array{Value(1.0), Value(1.0), Value(1.0), Value(1.0)});
150 (*prototype)["bendFactor"] = Value(0.0);
151 (*prototype)["holeEdgePadding"] = Value(0.0);
152 (*prototype)["useDensityScaling"] = Value(true);
153 }
154 migrated["prototypes"] = Value(std::move(prototypes));
155 migrated["schemaVersion"] = Value(int64_t(4));
156 Value next(std::move(migrated));
157 return current.value() == 4 ? Result<Value>::success(std::move(next))
158 : migrateDefinition(type, SchemaVersion(4), current, next);
159 }
160 if (type == "eve.instance-set" && from.value() == 4 && current.value() == 5) {
161 const auto* object = definition.getIf<Value::Object>();
162 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
163 object->at("schema").asString() != "eve.instance-set" || !object->contains("schemaVersion") ||
164 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 4 ||
165 object->contains("wavingGrass"))
167 DiagnosticCode::ParseError, "eve.instance-set/4 is malformed", {}, {}, "asset.migration"));
168 auto migrated = *object;
169 migrated["wavingGrass"] = Value(Value::Object{
170 {"amount", Value(0.0)}, {"speed", Value(0.0)}, {"strength", Value(0.0)},
171 {"tint", Value(Value::Array{Value(1.0), Value(1.0), Value(1.0), Value(1.0)})}});
172 migrated["schemaVersion"] = Value(int64_t(5));
173 return Result<Value>::success(Value(std::move(migrated)));
174 }
175 if (type == "eve.material" && from.value() == 5 && current.value() == 6) {
176 const auto* object = definition.getIf<Value::Object>();
177 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
178 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
179 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 5 ||
180 object->contains("vegetationSurface"))
182 DiagnosticCode::ParseError, "eve.material/5 is malformed or has unversioned vegetation surface", {}, {},
183 "asset.migration"));
184 auto migrated = *object;
185 migrated["schemaVersion"] = Value(int64_t(6));
186 return Result<Value>::success(Value(std::move(migrated)));
187 }
188 if (type == "eve.material" && from.value() == 6 && current.value() == 7) {
189 const auto* object = definition.getIf<Value::Object>();
190 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
191 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
192 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 6)
193 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "eve.material/6 is malformed",
194 {}, {}, "asset.migration"));
195 auto migrated = *object;
196 migrated["schemaVersion"] = Value(int64_t(7));
197 if (auto surface = migrated.find("vegetationSurface"); surface != migrated.end()) {
198 auto* vegetation = surface->second.getIf<Value::Object>();
199 if (!vegetation || vegetation->contains("colors"))
201 "eve.material/6 has unversioned global colors", {}, {},
202 "asset.migration"));
203 (*vegetation)["colors"] = Value(1.0);
204 (*vegetation)["colorsIntensity"] = Value(1.0);
205 (*vegetation)["colorsMask"] = Value(1.0);
206 (*vegetation)["colorsVariation"] = Value(.5);
207 (*vegetation)["vertexOcclusionMinimum"] = Value(0.0);
208 (*vegetation)["vertexOcclusionMaximum"] = Value(1.0);
209 (*vegetation)["invertVertexOcclusionColors"] = Value(false);
210 }
211 return Result<Value>::success(Value(std::move(migrated)));
212 }
213 if (type == "eve.material" && from.value() == 7 && current.value() == 8) {
214 const auto* object = definition.getIf<Value::Object>();
215 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
216 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
217 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 7 ||
218 object->contains("vegetationDetail"))
220 DiagnosticCode::ParseError, "eve.material/7 is malformed or has unversioned vegetation detail", {}, {},
221 "asset.migration"));
222 auto migrated = *object;
223 migrated["schemaVersion"] = Value(int64_t(8));
224 return Result<Value>::success(Value(std::move(migrated)));
225 }
226 if (type == "eve.material" && from.value() == 8 && current.value() == 9) {
227 const auto* object = definition.getIf<Value::Object>();
228 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
229 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
230 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 8 ||
231 (object->contains("vegetationAlpha") || object->contains("vegetationFields")))
233 DiagnosticCode::ParseError, "eve.material/8 is malformed or has unversioned vegetation field data", {},
234 {}, "asset.migration"));
235 auto migrated = *object;
236 migrated["schemaVersion"] = Value(int64_t(9));
237 return Result<Value>::success(Value(std::move(migrated)));
238 }
239 if (type == "eve.material" && from.value() == 9 && current.value() == 10) {
240 const auto* object = definition.getIf<Value::Object>();
241 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
242 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
243 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 9)
244 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "eve.material/9 is malformed",
245 {}, {}, "asset.migration"));
246 auto migrated = *object;
247 if (auto at = migrated.find("vegetationFields"); at != migrated.end()) {
248 auto* fields = at->second.getIf<Value::Object>();
249 if (!fields || fields->size() != 4 || !fields->contains("colorsLayer") ||
250 !fields->contains("colorsUsePivotPosition") || !fields->contains("extrasLayer") ||
251 !fields->contains("extrasUsePivotPosition"))
253 DiagnosticCode::ParseError, "eve.material/9 has malformed or unversioned vegetation fields", {}, {},
254 "asset.migration"));
255 (*fields)["motionLayer"] = Value(int64_t(0));
256 (*fields)["vertexLayer"] = Value(int64_t(0));
257 (*fields)["globalSize"] = Value(1.0);
258 (*fields)["sizeFadeStart"] = Value(0.0);
259 (*fields)["sizeFadeEnd"] = Value(100.0);
260 }
261 migrated["schemaVersion"] = Value(int64_t(10));
262 return Result<Value>::success(Value(std::move(migrated)));
263 }
264 if (type == "eve.material" && from.value() == 10 && current.value() == 11) {
265 const auto* object = definition.getIf<Value::Object>();
266 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
267 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
268 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 10 ||
269 object->contains("vegetationMotion"))
271 DiagnosticCode::ParseError, "eve.material/10 is malformed or has unversioned vegetation motion", {}, {},
272 "asset.migration"));
273 auto migrated = *object;
274 migrated["schemaVersion"] = Value(int64_t(11));
275 return Result<Value>::success(Value(std::move(migrated)));
276 }
277 if (type == "eve.material" && from.value() == 11 && current.value() == 12) {
278 const auto* object = definition.getIf<Value::Object>();
279 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
280 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
281 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 11)
282 return Result<Value>::failure(Diagnostic::error(DiagnosticCode::ParseError, "eve.material/11 is malformed",
283 {}, {}, "asset.migration"));
284 auto migrated = *object;
285 if (auto at = migrated.find("vegetationAlpha"); at != migrated.end()) {
286 auto* alpha = at->second.getIf<Value::Object>();
287 if (!alpha || alpha->size() != 3 || !alpha->contains("global") || !alpha->contains("variation") ||
288 !alpha->contains("detailFade"))
290 DiagnosticCode::ParseError, "eve.material/11 has malformed or unversioned vegetation alpha fade",
291 {}, {}, "asset.migration"));
292 (*alpha)["glancing"] = Value(0.0);
293 (*alpha)["camera"] = Value(1.0);
294 (*alpha)["constant"] = Value(0.0);
295 }
296 migrated["schemaVersion"] = Value(int64_t(12));
297 return Result<Value>::success(Value(std::move(migrated)));
298 }
299 if (type == "eve.material" && from.value() == 12 && current.value() == 13) {
300 const auto* object = definition.getIf<Value::Object>();
301 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
302 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
303 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 12 ||
304 object->contains("vegetationEmission"))
306 DiagnosticCode::ParseError, "eve.material/12 is malformed or has unversioned vegetation emission", {},
307 {}, "asset.migration"));
308 auto migrated = *object;
309 migrated["schemaVersion"] = Value(int64_t(13));
310 return Result<Value>::success(Value(std::move(migrated)));
311 }
312 if (type == "eve.material" && from.value() == 13 && current.value() == 14) {
313 const auto* object = definition.getIf<Value::Object>();
314 const auto* vegetation = object && object->contains("vegetationSurface")
315 ? object->at("vegetationSurface").getIf<Value::Object>()
316 : nullptr;
317 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
318 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
319 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 13 ||
320 object->contains("vegetationGradient") || object->contains("cullMode") ||
321 (vegetation &&
322 (vegetation->contains("vertexOcclusionColor") || vegetation->contains("backfaceNormalMode"))))
325 "eve.material/13 is malformed or has unversioned vegetation gradient or occlusion color", {}, {},
326 "asset.migration"));
327 auto migrated = *object;
328 migrated["schemaVersion"] = Value(int64_t(14));
329 return Result<Value>::success(Value(std::move(migrated)));
330 }
331 if (type == "eve.material" && from.value() == 14 && current.value() == 15) {
332 const auto* object = definition.getIf<Value::Object>();
333 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
334 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
335 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 14 ||
336 object->contains("alphaToCoverage"))
338 DiagnosticCode::ParseError, "eve.material/14 is malformed or has unversioned alpha-to-coverage", {}, {},
339 "asset.migration"));
340 auto migrated = *object;
341 migrated["schemaVersion"] = Value(int64_t(15));
342 migrated["alphaToCoverage"] = Value(false);
343 return Result<Value>::success(Value(std::move(migrated)));
344 }
345 if (type == "eve.terrain-material" && from.value() == 2 && current.value() == 3) {
346 const auto* object = definition.getIf<Value::Object>();
347 const auto* layersValue = object && object->contains("layers") ? &object->at("layers") : nullptr;
348 const auto* layers = layersValue ? layersValue->getIf<Value::Array>() : nullptr;
349 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
350 object->at("schema").asString() != "eve.terrain-material" || !object->contains("schemaVersion") ||
351 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 2 || !layers ||
352 object->contains("holesAsset") || object->contains("controlAssets"))
354 DiagnosticCode::ParseError, "eve.terrain-material/2 is malformed or has unversioned AssetRefs", {}, {},
355 "asset.migration"));
356 auto migratedLayers = *layers;
357 for (auto& encoded : migratedLayers) {
358 auto* layer = encoded.getIf<Value::Object>();
359 if (!layer || layer->contains("diffuseAsset") || layer->contains("normalAsset") ||
360 layer->contains("weightAsset") || layer->contains("maskAsset"))
362 DiagnosticCode::ParseError, "eve.terrain-material/2 layer has unversioned AssetRefs", {}, {},
363 "asset.migration"));
364 (*layer)["diffuseAsset"] = Value("");
365 (*layer)["normalAsset"] = Value("");
366 (*layer)["weightAsset"] = Value("");
367 (*layer)["maskAsset"] = Value("");
368 }
369 auto migrated = *object;
370 migrated["schemaVersion"] = Value(int64_t(3));
371 migrated["layers"] = Value(std::move(migratedLayers));
372 migrated["holesAsset"] = Value("");
373 migrated["controlAssets"] = Value(Value::Array{"", "", "", ""});
374 return Result<Value>::success(Value(std::move(migrated)));
375 }
376 if (type == "eve.terrain-material" && from.value() == 1 && current.value() == 2) {
377 const auto* object = definition.getIf<Value::Object>();
378 const auto* layers =
379 object && object->contains("layers") ? object->at("layers").getIf<Value::Array>() : nullptr;
380 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
381 object->at("schema").asString() != "eve.terrain-material" || !object->contains("schemaVersion") ||
382 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 1 || !layers ||
383 object->contains("holesSource") || object->contains("controlSources") ||
384 object->contains("boundsMultiplier"))
386 DiagnosticCode::ParseError, "eve.terrain-material/1 is malformed or has unversioned TVE fields", {}, {},
387 "asset.migration"));
388 auto migratedLayers = *layers;
389 for (auto& encoded : migratedLayers) {
390 auto* layer = encoded.getIf<Value::Object>();
391 if (!layer || layer->contains("maskSource") || layer->contains("tileScaleMeters") ||
392 layer->contains("tileOffsetMeters") || layer->contains("maskRemapMinimum") ||
393 layer->contains("maskRemapMaximum") || layer->contains("specular") || layer->contains("metallic") ||
394 layer->contains("normalScale") || layer->contains("smoothness"))
396 DiagnosticCode::ParseError, "eve.terrain-material/1 layer has unversioned TVE fields", {}, {},
397 "asset.migration"));
398 double tileSize = 1;
399 if (auto found = layer->find("tileSizeMeters"); found != layer->end()) {
400 if (!found->second.isNumeric())
402 "eve.terrain-material/1 tile size is invalid", {},
403 {}, "asset.migration"));
404 tileSize = found->second.isInt64() ? double(found->second.asInt()) : found->second.asDouble();
405 }
406 (*layer)["maskSource"] = Value("");
407 (*layer)["tileScaleMeters"] = Value(Value::Array{tileSize, tileSize});
408 (*layer)["tileOffsetMeters"] = Value(Value::Array{0.0, 0.0});
409 (*layer)["maskRemapMinimum"] = Value(Value::Array{0.0, 0.0, 0.0, 0.0});
410 (*layer)["maskRemapMaximum"] = Value(Value::Array{1.0, 1.0, 1.0, 1.0});
411 (*layer)["specular"] = Value(Value::Array{0.0, 0.0, 0.0, 0.0});
412 (*layer)["metallic"] = Value(0.0);
413 (*layer)["normalScale"] = Value(1.0);
414 (*layer)["smoothness"] = Value(0.0);
415 }
416 auto migrated = *object;
417 migrated["schemaVersion"] = Value(int64_t(2));
418 migrated["layers"] = Value(std::move(migratedLayers));
419 migrated["holesSource"] = Value("");
420 migrated["controlSources"] = Value(Value::Array{"", "", "", ""});
421 migrated["boundsMultiplier"] = Value(1.0);
422 return Result<Value>::success(Value(std::move(migrated)));
423 }
424 if (type == "eve.material" && from.value() == 4 && current.value() == 5) {
425 const auto* object = definition.getIf<Value::Object>();
426 if (!object || !object->contains("schema") || !object->at("schema").isString() ||
427 object->at("schema").asString() != "eve.material" || !object->contains("schemaVersion") ||
428 !object->at("schemaVersion").isInt64() || object->at("schemaVersion").asInt() != 4 ||
429 object->contains("motionHighlightColor"))
431 DiagnosticCode::ParseError, "eve.material/4 is malformed or has unversioned motion highlight", {}, {},
432 "asset.migration"));
433 auto migrated = *object;
434 migrated["schemaVersion"] = Value(int64_t(5));
435 return Result<Value>::success(Value(std::move(migrated)));
436 }
437 if (type == "eve.image" && from.value() == 2 && current.value() == 3) return migrateImageV2ToV3(definition);
438 if (type == "eve.scene-template" && from.value() == 2 && current.value() == 3) {
439 const auto* object = definition.getIf<Value::Object>();
440 if (!object || !object->contains("nodes") || !object->contains("renderers") || !object->contains("schema") ||
441 !object->at("schema").isString() || object->at("schema").asString() != "eve.scene-template" ||
442 !object->contains("schemaVersion") || !object->at("schemaVersion").isInt64() ||
443 object->at("schemaVersion").asInt() != 2 || !object->at("renderers").isArray())
445 DiagnosticCode::ParseError, "scene-template/2 is malformed or has unversioned shadow state", {}, {},
446 "asset.migration"));
447 auto migrated = *object;
448 migrated["schemaVersion"] = Value(std::int64_t(3));
449 auto renderers = *migrated["renderers"].getIf<Value::Array>();
450 for (auto& renderer : renderers) {
451 auto* binding = renderer.getIf<Value::Object>();
452 if (!binding || binding->contains("castShadows") || binding->contains("receiveShadows"))
454 "scene-template/2 renderer shadow state is malformed",
455 {}, {}, "asset.migration"));
456 (*binding)["castShadows"] = Value(true);
457 (*binding)["receiveShadows"] = Value(true);
458 }
459 migrated["renderers"] = Value(std::move(renderers));
460 return Result<Value>::success(Value(std::move(migrated)));
461 }
463 "asset definition has no registered migration", std::string(type),
464 {}, "asset.migration"));
465}
466
467Result<void> refreshImportReport(EvaArchive& archive) {
468 const auto path = archive.manifest.provenance.find("path");
469 if (path == archive.manifest.provenance.end()) return Result<void>::success();
470 if (!path->second.isString() || path->second.asString() != "reports/import.json")
472 DiagnosticCode::ParseError, "migration found an invalid import report path", {}, {}, "asset.migration"));
473 const auto entry = std::lower_bound(
474 archive.entries.begin(), archive.entries.end(), path->second.asString(),
475 [](const EvaArchiveEntry& candidate, std::string_view value) { return candidate.path < value; });
476 if (entry == archive.entries.end() || entry->path != path->second.asString())
477 return Result<void>::failure(Diagnostic::error(DiagnosticCode::NotFound, "migration import report is missing",
478 path->second.asString(), {}, "asset.migration"));
479 auto parsed = Value::fromJson(std::string(entry->bytes.begin(), entry->bytes.end()));
480 if (!parsed) return Result<void>::failure(parsed.status());
481 auto* report = parsed.value().getIf<Value::Object>();
482 if (!report)
484 DiagnosticCode::ParseError, "migration import report root is invalid", entry->path, {}, "asset.migration"));
485 Value::Array assets;
486 for (const auto& asset : archive.manifest.assets)
487 assets.emplace_back(
488 Value::Object{{"asset", Value(asset.asset.format())},
489 {"type", Value(asset.type + "/" + std::to_string(asset.schemaVersion.value()))},
490 {"contentHash", Value(asset.contentHash)}});
491 (*report)["canonicalAssets"] = Value(std::move(assets));
492 report->erase("importKey");
493 auto facts = parsed.value().toJson();
494 if (!facts) return Result<void>::failure(facts.status());
495 const std::string key = sha256(std::span<const std::uint8_t>(
496 reinterpret_cast<const std::uint8_t*>(facts.value().data()), facts.value().size()));
497 (*report)["importKey"] = Value(key);
498 auto encoded = parsed.value().toJson();
499 if (!encoded) return Result<void>::failure(encoded.status());
500 entry->bytes.assign(encoded.value().begin(), encoded.value().end());
501 archive.manifest.provenance["importKey"] = Value(key);
502 return Result<void>::success();
503}
504
505} // namespace
506
508 static const std::map<std::string_view, std::uint64_t> versions = {
509 {"eve.image", 3},
510 {"eve.volume-texture", 1},
511 {"eve.texture", 1},
512 {"eve.mesh", 3},
513 {"eve.skeleton", 1},
514 {"eve.animation-clip", 1},
515 {"eve.skin", 1},
516 {"eve.material", 15},
517 {"eve.scene-template", 3},
518 {"eve.terrain", 1},
519 {"eve.terrain-material", 3},
520 {"eve.pcg-graph", 1},
521 {"eve.instance-set", 5},
522 {"eve.audio", 1},
523 {"eve.font", 1},
524 {"eve.sprite-animation", 1},
525 {"eve.shader", 1},
526 {"eve.stylize.mesh-vfx", 1},
527 {"eve.vegetation-conversion-preset", 1},
528 {"eve.vegetation-scene", 1},
529 };
530 const auto found = versions.find(type);
531 if (found == versions.end())
532 return Result<SchemaVersion>::failure(Diagnostic::error(
533 DiagnosticCode::Unsupported, "unknown canonical asset type", std::string(type), {}, "asset.migration"));
535}
536
538 for (auto& asset : source.manifest.assets) {
539 auto current = currentAssetSchemaVersion(asset.type);
540 if (!current) return Result<EvaArchive>::failure(current.status());
541 if (asset.schemaVersion.value() > current.value().value())
542 return Result<EvaArchive>::failure(Diagnostic::error(DiagnosticCode::UnknownVersion,
543 "asset definition is newer than this reader",
544 asset.type, {}, "asset.migration"));
545 const auto entry = std::lower_bound(
546 source.entries.begin(), source.entries.end(), asset.definition,
547 [](const EvaArchiveEntry& candidate, std::string_view path) { return candidate.path < path; });
548 if (entry == source.entries.end() || entry->path != asset.definition)
549 return Result<EvaArchive>::failure(Diagnostic::error(
550 DiagnosticCode::NotFound, "asset definition is missing", asset.definition, {}, "asset.migration"));
551 const std::string_view text(reinterpret_cast<const char*>(entry->bytes.data()), entry->bytes.size());
552 auto parsed = Value::fromJson(text);
553 if (!parsed) return Result<EvaArchive>::failure(parsed.status());
554 auto migrated = migrateDefinition(asset.type, asset.schemaVersion, current.value(), parsed.value());
555 if (!migrated) return Result<EvaArchive>::failure(migrated.status());
556 auto encoded = migrated.value().toJson();
557 if (!encoded) return Result<EvaArchive>::failure(encoded.status());
558 entry->bytes.assign(encoded.value().begin(), encoded.value().end());
559 if (asset.schemaVersion != current.value()) {
560 const std::string oldType = asset.type + "/" + std::to_string(asset.schemaVersion.value());
561 const std::string newType = asset.type + "/" + std::to_string(current.value().value());
562 for (auto& dependency : source.manifest.dependencies)
563 if (dependency.to.id() == asset.asset.id() && dependency.expectedType == oldType)
564 dependency.expectedType = newType;
565 }
566 asset.schemaVersion = current.value();
567 asset.contentHash = sha256(entry->bytes);
568 }
569 auto report = refreshImportReport(source);
570 if (!report) return Result<EvaArchive>::failure(report.status());
571 auto rebuilt = buildEvaArchive(source.manifest, std::move(source.entries), limits);
572 if (!rebuilt) return Result<EvaArchive>::failure(rebuilt.status());
573 return parseEvaArchive(rebuilt.value(), limits);
574}
575
576} // namespace eve::asset
double value
std::string from
Transactional migrations for canonical definitions stored in .eva.
float uv
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t key
std::int32_t second
std::string text
std::uint64_t bytes
eve::action::ActionVfxBinding binding
TileLayer * layer
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
bool found
double current
std::string digest
Json object
uint32_t index
const UnitySourceAsset & source
const AssetImportLimits & limits
std::size_t at
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 Result< Value > fromJson(std::string_view json)
Parse one strict JSON value into an owning Value.
Definition Value.cpp:57
std::map< std::string, Value > Object
Definition Value.h:34
std::vector< Value > Array
Definition Value.h:33
static HashFunction * getHashFunction(std::string function)
Get a HashFunction instance for the given function.
virtual void hash(std::string function, const char *input, uint64_t length, Value &output) const =0
Hash the input, producing an set of bytes as output.
std::string sha256(std::span< const std::uint8_t > bytes)
Sha 256.
Result< std::vector< std::uint8_t > > buildEvaArchive(const EvaManifest &manifest, std::vector< EvaArchiveEntry > entries, const EvaArchiveLimits &limits)
Build a deterministic ZIP64 .eva, selecting raw Deflate only when smaller than Store.
Result< EvaArchive > parseEvaArchive(std::span< const std::uint8_t > bytes, const EvaArchiveLimits &limits)
Parse and fully verify an untrusted .eva ZIP64 image.
Result< EvaArchive > migrateEvaArchive(EvaArchive source, const EvaArchiveLimits &limits)
Migrate every definition in an owning archive to its current schema.
Result< SchemaVersion > currentAssetSchemaVersion(std::string_view type)
Return the current supported definition version for a canonical asset type.
std::variant< std::monostate, std::int64_t, double, std::string, bool > Value
Definition Database.h:26
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
detail::StrongUint64< detail::SchemaVersionTag > SchemaVersion
Persistent data-format version; not a runtime replacement generation.
One owning regular-file entry inside an .eva archive.
Definition EvaArchive.h:31
Admission budgets applied before allocating decoded archive entries.
Definition EvaArchive.h:21
Fully admitted source archive with its typed manifest and payloads.
Definition EvaArchive.h:37
glm::vec4 color