2#include "gaia/config/config.h"
9 #include "gaia/ser/ser_json.h"
16 static constexpr uint32_t RuntimeJsonMaxDepth = 32;
22 GAIA_NODISCARD
inline bool
23 runtime_type_json_type(
const ComponentCacheItem& typeItem, ser::serialization_type_id& out)
noexcept {
24 const auto primitiveType = typeItem.primitive_type();
25 if (primitiveType != EntityBad)
26 return runtime_primitive_serialization_type(primitiveType, out);
27 out = ser::serialization_type_id::ignore;
35 GAIA_NODISCARD
inline bool
36 runtime_json_is_char8_type(
const ComponentCacheItem* pType, Entity typeEntity)
noexcept {
37 ser::serialization_type_id type = ser::serialization_type_id::ignore;
39 return runtime_type_json_type(*pType, type) && type == ser::serialization_type_id::c8;
40 return runtime_primitive_serialization_type(typeEntity, type) && type == ser::serialization_type_id::c8;
47 GAIA_NODISCARD
inline const ComponentCacheItem*
48 find_runtime_json_type(
const ComponentCache* pCache, Entity typeEntity)
noexcept {
49 return pCache !=
nullptr ? pCache->find(typeEntity) :
nullptr;
57 GAIA_NODISCARD
inline bool
58 runtime_json_type_size(
const ComponentCacheItem* pType, Entity typeEntity, uint32_t& outSize)
noexcept {
59 if (pType !=
nullptr) {
60 outSize = pType->comp.size();
63 outSize = ComponentCacheItem::primitive_type_size(typeEntity);
68 struct RuntimeJsonFieldLayout final {
70 const ComponentCacheItem* pType =
nullptr;
72 Entity type = EntityBad;
74 uint32_t elemSize = 0;
76 uint32_t elemCount = 0;
84 GAIA_NODISCARD
inline bool resolve_runtime_json_field_layout(
85 const ComponentCache* pCache,
const RuntimeFieldDesc& field, RuntimeJsonFieldLayout& out)
noexcept {
87 const auto* pFieldType = find_runtime_json_type(pCache, field.type);
88 out.type = field.type;
89 out.pType = pFieldType;
90 out.elemCount = ComponentCacheItem::field_element_count(field);
92 if (pFieldType !=
nullptr && pFieldType->typeKind == RuntimeTypeKind::Array) {
95 out.type = pFieldType->element_type();
96 out.elemCount = pFieldType->element_count();
97 out.pType = find_runtime_json_type(pCache, out.type);
100 return out.elemCount != 0 && runtime_json_type_size(out.pType, out.type, out.elemSize);
109 GAIA_NODISCARD
inline bool runtime_json_integer_bits(
110 const uint8_t* pData, ser::serialization_type_id type, uint32_t valueSize, uint64_t& out)
noexcept {
112 case ser::serialization_type_id::s8:
113 case ser::serialization_type_id::u8: {
114 if (valueSize !=
sizeof(uint8_t))
117 memcpy(&value, pData,
sizeof(value));
121 case ser::serialization_type_id::s16:
122 case ser::serialization_type_id::u16: {
123 if (valueSize !=
sizeof(uint16_t))
126 memcpy(&value, pData,
sizeof(value));
130 case ser::serialization_type_id::s32:
131 case ser::serialization_type_id::u32: {
132 if (valueSize !=
sizeof(uint32_t))
135 memcpy(&value, pData,
sizeof(value));
139 case ser::serialization_type_id::s64:
140 case ser::serialization_type_id::u64: {
141 if (valueSize !=
sizeof(uint64_t))
143 memcpy(&out, pData,
sizeof(out));
156 GAIA_NODISCARD
inline bool
157 runtime_json_constant_bits(ser::serialization_type_id type, int64_t value, uint64_t& out)
noexcept {
159 case ser::serialization_type_id::s8:
160 if (value < INT8_MIN || value > INT8_MAX)
162 out = (uint8_t)value;
164 case ser::serialization_type_id::u8:
165 if (value < 0 || value > UINT8_MAX)
167 out = (uint8_t)value;
169 case ser::serialization_type_id::s16:
170 if (value < INT16_MIN || value > INT16_MAX)
172 out = (uint16_t)value;
174 case ser::serialization_type_id::u16:
175 if (value < 0 || value > UINT16_MAX)
177 out = (uint16_t)value;
179 case ser::serialization_type_id::s32:
180 if (value < INT32_MIN || value > INT32_MAX)
182 out = (uint32_t)value;
184 case ser::serialization_type_id::u32:
185 if (value < 0 || (uint64_t)value > UINT32_MAX)
187 out = (uint32_t)value;
189 case ser::serialization_type_id::s64:
190 out = (uint64_t)value;
192 case ser::serialization_type_id::u64:
195 out = (uint64_t)value;
208 GAIA_NODISCARD
inline bool runtime_json_write_integer_bits(
209 uint8_t* pData, ser::serialization_type_id type, uint32_t valueSize, uint64_t value)
noexcept {
211 case ser::serialization_type_id::s8:
212 case ser::serialization_type_id::u8: {
213 if (valueSize !=
sizeof(uint8_t))
215 const auto narrowed = (uint8_t)value;
216 memcpy(pData, &narrowed,
sizeof(narrowed));
219 case ser::serialization_type_id::s16:
220 case ser::serialization_type_id::u16: {
221 if (valueSize !=
sizeof(uint16_t))
223 const auto narrowed = (uint16_t)value;
224 memcpy(pData, &narrowed,
sizeof(narrowed));
227 case ser::serialization_type_id::s32:
228 case ser::serialization_type_id::u32: {
229 if (valueSize !=
sizeof(uint32_t))
231 const auto narrowed = (uint32_t)value;
232 memcpy(pData, &narrowed,
sizeof(narrowed));
235 case ser::serialization_type_id::s64:
236 case ser::serialization_type_id::u64:
237 if (valueSize !=
sizeof(uint64_t))
239 memcpy(pData, &value,
sizeof(value));
249 GAIA_NODISCARD
inline bool runtime_json_is_direct_value(
const ComponentCacheItem& item)
noexcept {
250 switch (item.typeKind) {
251 case RuntimeTypeKind::Primitive:
252 case RuntimeTypeKind::Enum:
253 case RuntimeTypeKind::Bitmask:
254 case RuntimeTypeKind::Array:
255 case RuntimeTypeKind::Vector:
257 case RuntimeTypeKind::Opaque:
258 return item.opaque_adapter() !=
nullptr;
267 GAIA_NODISCARD
inline bool runtime_json_leaf_editable(
const ComponentCacheItem& item)
noexcept {
268 return item.typeKind == RuntimeTypeKind::Primitive || item.typeKind == RuntimeTypeKind::Enum ||
269 item.typeKind == RuntimeTypeKind::Bitmask;
276 GAIA_NODISCARD
inline ser::json_str
277 make_runtime_json_child_path(ser::json_str_view parent, ser::json_str_view child) {
279 return ser::json_str(child);
281 return ser::json_str(parent);
284 path.reserve(parent.size() + 1 + child.size());
285 path.append(parent.data(), parent.size());
287 path.append(child.data(), child.size());
295 GAIA_NODISCARD
inline ser::json_str make_runtime_json_element_path(ser::json_str_view parent, uint32_t index) {
296 ser::json_str path(parent);
299 const int len = snprintf(idx,
sizeof(idx),
"%u", index);
301 path.append(idx, (uint32_t)len);
310 GAIA_NODISCARD
inline bool count_runtime_json_array_elements(ser::ser_json& reader, uint32_t& outCount) {
313 const auto* start = reader.pos();
314 const auto* end = reader.end();
315 if (start ==
nullptr || end ==
nullptr || start > end)
318 ser::ser_json counter(start, (uint32_t)(end - start));
319 if (!counter.expect(
'['))
322 if (counter.consume(
']'))
326 if (!counter.skip_value())
329 if (counter.consume(
','))
331 return counter.consume(
']');
345 inline bool write_runtime_json_value(
346 const ComponentCache* pCache,
const ComponentCacheItem* pType, Entity typeEntity,
const uint8_t* pData,
347 uint32_t valueSize, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth);
358 inline bool write_runtime_json_field_data(
359 const ComponentCache* pCache,
const RuntimeFieldDesc& field,
const RuntimeJsonFieldLayout& layout,
360 const uint8_t* pFieldData, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
361 if (layout.elemCount == 1 || field.jsonEncoding == RuntimeJsonEncoding::Utf8String ||
362 runtime_json_is_char8_type(layout.pType, layout.type))
363 return write_runtime_json_value(
364 pCache, layout.pType, layout.type, pFieldData, layout.elemSize * layout.elemCount, writer, policy,
368 writer.begin_array();
369 GAIA_FOR(layout.elemCount) {
370 const auto* pElemData = pFieldData + (uintptr_t)layout.elemSize * i;
371 ok = write_runtime_json_value(
372 pCache, layout.pType, layout.type, pElemData, layout.elemSize, writer, policy, depth + 1) &&
388 inline bool write_runtime_json_field(
389 const ComponentCache* pCache,
const ComponentCacheItem& owner,
const RuntimeFieldDesc& field,
390 const uint8_t* pBase, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
391 RuntimeJsonFieldLayout layout{};
392 if (!resolve_runtime_json_field_layout(pCache, field, layout)) {
397 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
398 const auto end = (uint64_t)field.offset + fieldSize64;
399 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
404 return write_runtime_json_field_data(pCache, field, layout, pBase + field.offset, writer, policy, depth);
415 inline bool write_runtime_json_struct(
416 const ComponentCache* pCache,
const ComponentCacheItem& item,
const uint8_t* pData, ser::ser_json& writer,
417 const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
418 if (depth >= RuntimeJsonMaxDepth) {
424 writer.begin_object();
425 GAIA_FOR(item.field_count()) {
426 const auto* pField = item.field(i);
427 GAIA_ASSERT(pField !=
nullptr);
428 const auto& field = *pField;
429 const auto fieldName = item.field_name(field);
430 writer.key(fieldName.data(), fieldName.size());
431 ok = write_runtime_json_field(pCache, item, field, pData, writer, policy, depth + 1) && ok;
437 inline bool write_runtime_json_value(
438 const ComponentCache* pCache,
const ComponentCacheItem* pType, Entity typeEntity,
const uint8_t* pData,
439 uint32_t valueSize, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
440 if (depth >= RuntimeJsonMaxDepth) {
445 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Array) {
446 const auto elemCount = pType->element_count();
447 const auto elementType = pType->element_type();
448 const auto* pElementType = find_runtime_json_type(pCache, elementType);
449 uint32_t elemSize = 0;
450 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
451 (uint64_t)elemSize * elemCount != valueSize) {
457 writer.begin_array();
458 GAIA_FOR(elemCount) {
459 const auto* pElemData = pData + (uintptr_t)elemSize * i;
460 ok = write_runtime_json_value(
461 pCache, pElementType, elementType, pElemData, elemSize, writer, policy, depth + 1) &&
468 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Vector) {
469 const auto* adapter = pType->sequence_adapter();
470 const auto elementType = pType->element_type();
471 const auto* pElementType = find_runtime_json_type(pCache, elementType);
472 if (adapter ==
nullptr || adapter->count ==
nullptr || adapter->element ==
nullptr) {
476 RuntimeSequenceScope sequence{typeEntity, pData,
nullptr, valueSize};
477 uint32_t elemCount = 0;
478 if (!adapter->count(adapter->ctx, sequence, elemCount)) {
482 if (pType->jsonEncoding == RuntimeJsonEncoding::Utf8String) {
483 if (elementType != Char8) {
489 text.reserve(elemCount);
490 GAIA_FOR(elemCount) {
491 RuntimeSequenceElement element{};
492 element.type = elementType;
493 if (!adapter->element(adapter->ctx, sequence, i, element) || element.data ==
nullptr ||
494 element.size !=
sizeof(
char) || (element.type != EntityBad && element.type != Char8)) {
498 text.append(*(
const char*)element.data);
500 writer.value_string(text.empty() ?
"" : text.data(), text.size());
505 writer.begin_array();
506 GAIA_FOR(elemCount) {
507 RuntimeSequenceElement element{};
508 element.type = elementType;
509 if (!adapter->element(adapter->ctx, sequence, i, element)) {
514 if (element.type == EntityBad)
515 element.type = elementType;
516 ok = write_runtime_json_value(
517 pCache, pElementType, element.type, (
const uint8_t*)element.data, element.size, writer, policy,
525 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Opaque) {
526 const auto* adapter = pType->opaque_adapter();
527 const auto semanticType = pType->opaque_as_type();
528 const auto* pSemanticType = find_runtime_json_type(pCache, semanticType);
529 if (adapter ==
nullptr || adapter->project ==
nullptr || pSemanticType ==
nullptr) {
533 RuntimeOpaqueScope opaque{typeEntity, pData,
nullptr, valueSize};
534 RuntimeOpaqueValue projected{};
535 projected.type = semanticType;
536 if (!adapter->project(adapter->ctx, opaque, projected)) {
540 if (projected.type == EntityBad)
541 projected.type = semanticType;
542 if (projected.type != semanticType || projected.data ==
nullptr) {
546 return write_runtime_json_value(
547 pCache, pSemanticType, projected.type, (
const uint8_t*)projected.data, projected.size, writer, policy,
551 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Struct)
552 return write_runtime_json_struct(pCache, *pType, pData, writer, policy, depth + 1);
554 ser::serialization_type_id type = ser::serialization_type_id::ignore;
555 if (pType !=
nullptr) {
556 if (!runtime_type_json_type(*pType, type)) {
560 }
else if (!runtime_primitive_serialization_type(typeEntity, type)) {
565 uint64_t valueBits = 0;
566 if (pType !=
nullptr && policy.symbolicEnums && pType->typeKind == RuntimeTypeKind::Enum &&
567 runtime_json_integer_bits(pData, type, valueSize, valueBits)) {
568 GAIA_FOR(pType->constant_count()) {
569 const auto* pConstant = pType->constant(i);
570 GAIA_ASSERT(pConstant !=
nullptr);
571 uint64_t constantBits = 0;
572 if (runtime_json_constant_bits(type, pConstant->value, constantBits) && constantBits == valueBits) {
573 const auto constantName = pType->constant_name(*pConstant);
574 writer.value_string(constantName.data(), constantName.size());
580 if (pType !=
nullptr && policy.symbolicBitmasks && pType->typeKind == RuntimeTypeKind::Bitmask &&
581 runtime_json_integer_bits(pData, type, valueSize, valueBits)) {
582 uint64_t remaining = valueBits;
583 GAIA_FOR(pType->constant_count()) {
584 const auto* pConstant = pType->constant(i);
585 GAIA_ASSERT(pConstant !=
nullptr);
586 uint64_t flagBits = 0;
587 if (runtime_json_constant_bits(type, pConstant->value, flagBits) && flagBits != 0 &&
588 (flagBits & (flagBits - 1)) == 0 && (remaining & flagBits) == flagBits)
589 remaining &= ~flagBits;
592 if (remaining == 0) {
593 writer.begin_array();
594 remaining = valueBits;
595 GAIA_FOR(pType->constant_count()) {
596 const auto* pConstant = pType->constant(i);
597 GAIA_ASSERT(pConstant !=
nullptr);
598 uint64_t flagBits = 0;
599 if (runtime_json_constant_bits(type, pConstant->value, flagBits) && flagBits != 0 &&
600 (flagBits & (flagBits - 1)) == 0 && (remaining & flagBits) == flagBits) {
601 const auto constantName = pType->constant_name(*pConstant);
602 writer.value_string(constantName.data(), constantName.size());
603 remaining &= ~flagBits;
611 return ser::detail::write_runtime_field_json(writer, pData, type, valueSize);
615 struct RuntimeJsonReadContext final {
617 const ComponentCache* pCache;
619 ser::ser_json& reader;
621 ser::JsonDiagnostics& diagnostics;
623 const ser::RuntimeJsonPolicy& policy;
637 inline bool read_runtime_json_value(
638 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
639 uint32_t valueSize, ser::json_str_view path, uint32_t depth);
651 inline bool read_runtime_json_elements(
652 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
653 uint32_t elemSize, uint32_t elemCount, ser::json_str_view path, uint32_t depth) {
654 auto& reader = ctx.reader;
655 if (!reader.expect(
'['))
658 GAIA_FOR(elemCount) {
659 if (i > 0 && !reader.expect(
','))
662 const auto elemPath = make_runtime_json_element_path(path, i);
663 auto* pElemData = pData + (uintptr_t)elemSize * i;
665 if (!read_runtime_json_value(ctx, pType, typeEntity, pElemData, elemSize, elemPath, depth))
669 return reader.expect(
']');
680 template <
typename T>
682 runtime_patch_numeric_value_as(Entity type, Entity expectedType,
const void* data, uint32_t size,
double& value) {
683 if (type != expectedType || data ==
nullptr || size !=
sizeof(T))
687 memcpy(&result, data,
sizeof(result));
688 value = (double)result;
699 inline bool runtime_patch_numeric_value(
700 const ComponentCacheItem* pType, Entity type,
const void* data, uint32_t size,
double& value) {
701 if (pType !=
nullptr &&
702 (pType->typeKind == RuntimeTypeKind::Enum || pType->typeKind == RuntimeTypeKind::Bitmask))
703 type = pType->underlyingType;
705 return runtime_patch_numeric_value_as<int8_t>(type, S8, data, size, value) ||
706 runtime_patch_numeric_value_as<uint8_t>(type, U8, data, size, value) ||
707 runtime_patch_numeric_value_as<int16_t>(type, S16, data, size, value) ||
708 runtime_patch_numeric_value_as<uint16_t>(type, U16, data, size, value) ||
709 runtime_patch_numeric_value_as<int32_t>(type, S32, data, size, value) ||
710 runtime_patch_numeric_value_as<uint32_t>(type, U32, data, size, value) ||
711 runtime_patch_numeric_value_as<int64_t>(type, S64, data, size, value) ||
712 runtime_patch_numeric_value_as<uint64_t>(type, U64, data, size, value) ||
713 runtime_patch_numeric_value_as<float>(type, F32, data, size, value) ||
714 runtime_patch_numeric_value_as<double>(type, F64, data, size, value);
722 inline void fail_runtime_json(
723 ser::JsonDiagnostics& diagnostics, ser::JsonDiagReason reason, ser::json_str_view path,
const char* message) {
724 diagnostics.add(ser::JsonDiagSeverity::Error, reason, path, message);
732 inline void warn_runtime_json(
733 ser::JsonDiagnostics& diagnostics, ser::JsonDiagReason reason, ser::json_str_view path,
const char* message) {
734 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
745 inline bool read_runtime_json_field(
746 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& owner,
const RuntimeFieldDesc& field, uint8_t* pBase,
747 ser::json_str_view path, uint32_t depth) {
748 auto& reader = ctx.reader;
749 RuntimeJsonFieldLayout layout{};
751 if (!resolve_runtime_json_field_layout(ctx.pCache, field, layout)) {
754 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
755 "Runtime field uses an unknown reflected type.");
756 return reader.skip_value();
759 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
760 const auto end = (uint64_t)field.offset + fieldSize64;
762 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
765 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
766 "Runtime field points outside component size or uses an unsupported type.");
767 return reader.skip_value();
770 auto* pFieldData = pBase + field.offset;
771 const bool ranged = ctx.policy.enforceRanges &&
772 (field.flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) != 0;
774 cnt::darray_ext<uint8_t, 64> staged;
775 auto* pTarget = pFieldData;
777 staged.resize((uint32_t)fieldSize64);
778 memcpy(staged.data(), pFieldData, (
size_t)fieldSize64);
779 pTarget = staged.data();
783 layout.elemCount == 1 || field.jsonEncoding == RuntimeJsonEncoding::Utf8String ||
784 runtime_json_is_char8_type(layout.pType, layout.type)
785 ? read_runtime_json_value(
786 ctx, layout.pType, layout.type, pTarget, (uint32_t)fieldSize64, path, depth + 1)
787 : read_runtime_json_elements(
788 ctx, layout.pType, layout.type, pTarget, layout.elemSize, layout.elemCount, path, depth + 1);
789 if (!parsed || !ranged)
792 GAIA_FOR(layout.elemCount) {
794 if (!runtime_patch_numeric_value(
795 layout.pType, layout.type, pTarget + (uintptr_t)layout.elemSize * i, layout.elemSize, number)) {
798 ctx.diagnostics, ser::JsonDiagReason::UnsupportedPatchValue, path,
799 "Runtime field range applies to a non-numeric field.");
802 if (((field.flags & RuntimeFieldFlag_HasMinimum) != 0 && number < field.minimum) ||
803 ((field.flags & RuntimeFieldFlag_HasMaximum) != 0 && number > field.maximum)) {
806 ctx.diagnostics, ser::JsonDiagReason::RangeViolation, path,
807 "Runtime field value is outside the authored field range.");
811 memcpy(pFieldData, pTarget, (
size_t)fieldSize64);
820 inline void report_missing_runtime_json_fields(
821 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item,
const cnt::darray_ext<uint8_t, 64>& seen,
822 ser::json_str_view path) {
823 GAIA_FOR(item.field_count()) {
826 const auto fieldName = item.field_name(*item.field(i));
827 const auto fieldPath =
828 make_runtime_json_child_path(path, ser::json_str_view(fieldName.data(), fieldName.size()));
830 fail_runtime_json(ctx.diagnostics, ser::JsonDiagReason::MissingField, fieldPath,
"Runtime field is missing.");
841 inline bool read_runtime_json_struct(
842 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, uint8_t* pData, ser::json_str_view path,
844 auto& reader = ctx.reader;
846 if (depth >= RuntimeJsonMaxDepth) {
849 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime JSON nesting is too deep.");
850 return reader.skip_value();
853 if (reader.parse_null()) {
856 ctx.diagnostics, ser::JsonDiagReason::NullComponentPayload, path,
"Runtime object payload is null.");
860 if (!reader.expect(
'{'))
864 cnt::darray_ext<uint8_t, 64> seen;
865 if (ctx.policy.requireAllFields)
866 seen.resize(item.field_count(), 0);
869 if (reader.consume(
'}')) {
870 if (ctx.policy.requireAllFields)
871 report_missing_runtime_json_fields(ctx, item, seen, path);
876 ser::json_str_view key;
877 bool keyFromScratch =
false;
878 if (!reader.parse_string_view(key, &keyFromScratch))
882 ser::json_str keyStorage;
883 if (keyFromScratch) {
884 keyStorage.assign(key.data(), key.size());
888 if (!reader.expect(
':'))
891 const auto* pField = item.field(util::str_view(key.data(), (uint32_t)key.size()));
892 const auto fieldPath = make_runtime_json_child_path(path, key);
894 if (pField ==
nullptr) {
896 warn_runtime_json(ctx.diagnostics, ser::JsonDiagReason::UnknownField, fieldPath,
"Unknown runtime field.");
897 if (!reader.skip_value())
900 if (ctx.policy.requireAllFields)
901 seen[(uint32_t)(pField - item.field(0))] = 1;
902 if (!read_runtime_json_field(ctx, item, *pField, pData, fieldPath, depth + 1))
907 if (reader.consume(
','))
909 if (reader.consume(
'}'))
914 if (ctx.policy.requireAllFields)
915 report_missing_runtime_json_fields(ctx, item, seen, path);
927 inline bool read_runtime_json_array(
928 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, uint8_t* pData, uint32_t valueSize,
929 ser::json_str_view path, uint32_t depth) {
930 const auto elemCount = item.element_count();
931 const auto elementType = item.element_type();
932 const auto* pElementType = find_runtime_json_type(ctx.pCache, elementType);
933 uint32_t elemSize = 0;
935 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
936 (uint64_t)elemSize * elemCount != valueSize) {
939 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
940 "Runtime array payload uses an invalid reflected element type.");
941 return ctx.reader.skip_value();
944 return read_runtime_json_elements(ctx, pElementType, elementType, pData, elemSize, elemCount, path, depth + 1);
955 inline bool read_runtime_json_utf8_vector(
956 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
957 uint32_t valueSize, ser::json_str_view path) {
958 const auto* adapter = item.sequence_adapter();
959 ser::json_str_view text;
961 if (item.element_type() != Char8 || adapter ==
nullptr || adapter->resize ==
nullptr ||
962 adapter->element ==
nullptr || !ctx.reader.parse_string_view(text)) {
965 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
966 "Runtime UTF-8 string cannot be resized or traversed.");
970 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
972 if (!adapter->resize(adapter->ctx, sequence, text.size())) {
975 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
976 "Runtime UTF-8 string adapter rejected the requested byte count.");
982 GAIA_FOR(text.size()) {
983 RuntimeSequenceElement element{};
984 element.type = Char8;
986 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData ==
nullptr ||
987 element.size !=
sizeof(
char) || (element.type != EntityBad && element.type != Char8)) {
990 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
991 "Runtime UTF-8 string adapter rejected an element.");
995 *(
char*)element.mutData = text.data()[i];
997 if (adapter->commitElement !=
nullptr && !adapter->commitElement(adapter->ctx, sequence, element)) {
1000 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1001 "Runtime UTF-8 string adapter rejected element commit.");
1018 inline bool read_runtime_json_vector(
1019 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
1020 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
1021 if (item.jsonEncoding == RuntimeJsonEncoding::Utf8String)
1022 return read_runtime_json_utf8_vector(ctx, item, typeEntity, pData, valueSize, path);
1024 const auto* adapter = item.sequence_adapter();
1025 const auto elementType = item.element_type();
1026 const auto* pElementType = find_runtime_json_type(ctx.pCache, elementType);
1027 uint32_t elemCount = 0;
1030 if (adapter ==
nullptr || adapter->resize ==
nullptr || adapter->element ==
nullptr ||
1031 !count_runtime_json_array_elements(ctx.reader, elemCount)) {
1034 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1035 "Runtime vector payload cannot be resized or traversed.");
1036 return ctx.reader.skip_value();
1039 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
1041 if (!adapter->resize(adapter->ctx, sequence, elemCount)) {
1044 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1045 "Runtime vector adapter rejected the requested element count.");
1046 return ctx.reader.skip_value();
1049 if (!ctx.reader.expect(
'['))
1052 GAIA_FOR(elemCount) {
1053 if (i > 0 && !ctx.reader.expect(
','))
1056 RuntimeSequenceElement element{};
1057 element.type = elementType;
1059 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData ==
nullptr) {
1062 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1063 "Runtime vector adapter rejected an element.");
1064 return ctx.reader.skip_value();
1067 if (element.type == EntityBad)
1068 element.type = elementType;
1070 const auto elemPath = make_runtime_json_element_path(path, i);
1072 if (!read_runtime_json_value(
1073 ctx, pElementType, element.type, (uint8_t*)element.mutData, element.size, elemPath, depth + 1))
1077 return ctx.reader.expect(
']');
1089 inline bool read_runtime_json_opaque(
1090 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
1091 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
1092 const auto* adapter = item.opaque_adapter();
1093 const auto semanticType = item.opaque_as_type();
1094 const auto* pSemanticType = find_runtime_json_type(ctx.pCache, semanticType);
1096 if (adapter ==
nullptr || adapter->project ==
nullptr || pSemanticType ==
nullptr) {
1099 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1100 "Runtime opaque payload cannot be projected.");
1101 return ctx.reader.skip_value();
1104 RuntimeOpaqueScope opaque{typeEntity, pData, pData, valueSize};
1105 RuntimeOpaqueValue projected{};
1106 projected.type = semanticType;
1108 if (!adapter->project(adapter->ctx, opaque, projected) || projected.mutData ==
nullptr) {
1111 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1112 "Runtime opaque adapter rejected projection.");
1113 return ctx.reader.skip_value();
1117 if (projected.type == EntityBad)
1118 projected.type = semanticType;
1120 if (projected.type != semanticType) {
1123 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1124 "Runtime opaque adapter projected an unexpected semantic type.");
1125 return ctx.reader.skip_value();
1128 const bool parsed = read_runtime_json_value(
1129 ctx, pSemanticType, projected.type, (uint8_t*)projected.mutData, projected.size, path, depth + 1);
1132 if (parsed && adapter->commit !=
nullptr && ctx.ok && !adapter->commit(adapter->ctx, opaque, projected)) {
1135 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime opaque adapter rejected commit.");
1149 inline bool read_runtime_json_enum_symbol(
1150 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, ser::serialization_type_id type, uint8_t* pData,
1151 uint32_t valueSize, ser::json_str_view path) {
1152 ser::json_str_view symbol;
1153 if (!ctx.reader.parse_string_view(symbol))
1156 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1157 uint64_t constantBits = 0;
1159 if (pConstant ==
nullptr || !runtime_json_constant_bits(type, pConstant->value, constantBits) ||
1160 !runtime_json_write_integer_bits(pData, type, valueSize, constantBits)) {
1163 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1164 "Runtime enum symbol is unknown or incompatible with its underlying type.");
1178 inline bool read_runtime_json_bitmask_symbols(
1179 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, ser::serialization_type_id type, uint8_t* pData,
1180 uint32_t valueSize, ser::json_str_view path) {
1181 auto& reader = ctx.reader;
1183 if (!reader.expect(
'['))
1186 uint64_t valueBits = 0;
1187 bool symbolsOk =
true;
1190 if (!reader.consume(
']')) {
1192 ser::json_str_view symbol;
1193 if (!reader.parse_string_view(symbol))
1198 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1199 uint64_t flagBits = 0;
1201 if (pConstant ==
nullptr) {
1205 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1206 "Runtime bitmask symbol is unknown.");
1208 !runtime_json_constant_bits(type, pConstant->value, flagBits) || flagBits == 0 ||
1209 (flagBits & (flagBits - 1)) != 0 || (valueBits & flagBits) != 0) {
1213 ctx.diagnostics, ser::JsonDiagReason::InvalidRuntimeConstant, path,
1214 "Runtime bitmask symbol is not a distinct one-bit flag.");
1216 valueBits |= flagBits;
1221 if (reader.consume(
','))
1223 if (reader.consume(
']'))
1230 if (symbolsOk && !runtime_json_write_integer_bits(pData, type, valueSize, valueBits)) {
1233 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1234 "Runtime bitmask symbols are incompatible with the underlying field size.");
1248 inline bool read_runtime_json_scalar(
1249 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
1250 uint32_t valueSize, ser::json_str_view path) {
1251 ser::serialization_type_id type = ser::serialization_type_id::ignore;
1253 if (pType !=
nullptr) {
1254 if (!runtime_type_json_type(*pType, type)) {
1257 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1258 "Runtime field uses an unsupported reflected type.");
1259 return ctx.reader.skip_value();
1261 }
else if (!runtime_primitive_serialization_type(typeEntity, type)) {
1264 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime field type is unknown.");
1265 return ctx.reader.skip_value();
1270 if (pType !=
nullptr && ctx.policy.symbolicEnums && pType->typeKind == RuntimeTypeKind::Enum &&
1271 !ctx.reader.eof() && ctx.reader.peek() ==
'"')
1272 return read_runtime_json_enum_symbol(ctx, *pType, type, pData, valueSize, path);
1273 if (pType !=
nullptr && ctx.policy.symbolicBitmasks && pType->typeKind == RuntimeTypeKind::Bitmask &&
1274 !ctx.reader.eof() && ctx.reader.peek() ==
'[')
1275 return read_runtime_json_bitmask_symbols(ctx, *pType, type, pData, valueSize, path);
1278 bool fieldOk =
true;
1279 if (!ser::detail::read_runtime_field_json(ctx.reader, pData, type, valueSize, fieldOk)) {
1282 ctx.diagnostics, ser::JsonDiagReason::InvalidJson, path,
1283 "Runtime field value is not the JSON kind its type needs.");
1290 ctx.diagnostics, ser::JsonDiagReason::FieldValueAdjusted, path,
1291 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
1297 inline bool read_runtime_json_value(
1298 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
1299 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
1300 if (depth >= RuntimeJsonMaxDepth) {
1303 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime JSON nesting is too deep.");
1304 return ctx.reader.skip_value();
1307 if (pType !=
nullptr) {
1308 switch (pType->typeKind) {
1309 case RuntimeTypeKind::Array:
1310 return read_runtime_json_array(ctx, *pType, pData, valueSize, path, depth);
1311 case RuntimeTypeKind::Vector:
1312 return read_runtime_json_vector(ctx, *pType, typeEntity, pData, valueSize, path, depth);
1313 case RuntimeTypeKind::Opaque:
1314 return read_runtime_json_opaque(ctx, *pType, typeEntity, pData, valueSize, path, depth);
1315 case RuntimeTypeKind::Struct:
1316 return read_runtime_json_struct(ctx, *pType, pData, path, depth + 1);
1322 return read_runtime_json_scalar(ctx, pType, typeEntity, pData, valueSize, path);
1333 inline bool component_to_json(
1334 const ComponentCacheItem& item,
const void* pComponentData, ser::ser_json& writer,
1335 const ser::RuntimeJsonPolicy& policy = {}) {
1336 GAIA_ASSERT(pComponentData !=
nullptr);
1337 if (pComponentData ==
nullptr)
1340 return detail::write_runtime_json_value(
1341 item.owner_cache(), &item, item.entity,
reinterpret_cast<const uint8_t*
>(pComponentData), item.comp.size(),
1350 inline ser::json_str component_to_json(
const ComponentCacheItem& item,
const void* pComponentData,
bool& ok) {
1351 ser::ser_json writer;
1352 ok = component_to_json(item, pComponentData, writer);
1353 return writer.str();
1365 inline bool json_to_component(
1366 const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader, ser::JsonDiagnostics& diagnostics,
1367 const ser::RuntimeJsonPolicy& policy = {}, ser::json_str_view componentPath = {}) {
1368 GAIA_ASSERT(pComponentData !=
nullptr);
1369 if (pComponentData ==
nullptr)
1372 if (reader.parse_null()) {
1373 detail::warn_runtime_json(
1374 diagnostics, ser::JsonDiagReason::NullComponentPayload, componentPath,
"Component payload is null.");
1378 if (detail::runtime_json_is_direct_value(item)) {
1380 detail::RuntimeJsonReadContext ctx{item.owner_cache(), reader, diagnostics, policy, ok};
1381 return detail::read_runtime_json_value(
1382 ctx, &item, item.entity,
reinterpret_cast<uint8_t*
>(pComponentData), item.comp.size(), componentPath, 0);
1385 bool rawFound =
false;
1386 bool fieldFound =
false;
1389 detail::RuntimeJsonReadContext ctx{item.owner_cache(), reader, diagnostics, policy, ok};
1390 ser::ser_buffer_binary rawPayload;
1391 auto* pBase =
reinterpret_cast<uint8_t*
>(pComponentData);
1393 if (!reader.expect(
'{'))
1397 const bool requireAllFields = policy.requireAllFields && item.field_count() != 0 && item.comp.soa() == 0;
1398 cnt::darray_ext<uint8_t, 64> seen;
1399 if (requireAllFields)
1400 seen.resize(item.field_count(), 0);
1403 if (reader.consume(
'}')) {
1404 detail::warn_runtime_json(
1405 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, componentPath,
1406 "Component object is empty and contains no runtime fields or $raw payload.");
1407 if (requireAllFields)
1408 detail::report_missing_runtime_json_fields(ctx, item, seen, componentPath);
1413 ser::json_str_view key;
1414 bool keyFromScratch =
false;
1415 if (!reader.parse_string_view(key, &keyFromScratch))
1419 ser::json_str keyStorage;
1420 if (keyFromScratch) {
1421 keyStorage.assign(key.data(), key.size());
1425 if (!reader.expect(
':'))
1428 const auto fieldPath = detail::make_runtime_json_child_path(componentPath, key);
1430 if (key ==
"$raw") {
1432 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
1434 }
else if (item.field_count() != 0 && item.comp.soa() == 0) {
1435 const auto* pField = item.field(util::str_view(key.data(), (uint32_t)key.size()));
1436 if (pField ==
nullptr) {
1438 detail::warn_runtime_json(
1439 diagnostics, ser::JsonDiagReason::UnknownField, fieldPath,
"Unknown runtime field.");
1440 if (!reader.skip_value())
1444 if (requireAllFields)
1445 seen[(uint32_t)(pField - item.field(0))] = 1;
1446 if (!detail::read_runtime_json_field(ctx, item, *pField, pBase, fieldPath, 0))
1451 detail::warn_runtime_json(
1452 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, fieldPath,
1453 "Runtime field metadata is unavailable for keyed field payloads.");
1454 if (!reader.skip_value())
1459 if (reader.consume(
','))
1461 if (reader.consume(
'}'))
1467 if (item.comp.soa() != 0) {
1468 detail::warn_runtime_json(
1469 diagnostics, ser::JsonDiagReason::SoaRawUnsupported,
1470 detail::make_runtime_json_child_path(componentPath,
"$raw"),
1471 "$raw payload is not supported for SoA components.");
1476 auto s = ser::make_serializer(rawPayload);
1478 item.load(s, pBase, 0, 1, 1);
1481 if (!rawFound && !fieldFound)
1482 detail::warn_runtime_json(
1483 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, componentPath,
1484 "Component payload contains neither recognized runtime fields nor $raw data.");
1486 if (requireAllFields && !rawFound)
1487 detail::report_missing_runtime_json_fields(ctx, item, seen, componentPath);
1499 json_to_component(
const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader,
bool& ok) {
1500 ser::JsonDiagnostics diagnostics;
1501 const bool parsed = json_to_component(item, pComponentData, reader, diagnostics);
1503 ok = !diagnostics.has_issues();
1508 World::save_json(ser::ser_json& writer, ser::JsonSaveFlags flags,
const ser::RuntimeJsonPolicy& policy)
const {
1510 const bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
1511 const bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
1513 auto write_component_key = [&](Entity component,
const ComponentCacheItem* pItem) {
1514 if (!component.pair()) {
1516 const auto componentName = pItem !=
nullptr ?
comp_cache().symbol_name(*pItem) :
name(component);
1517 if (componentName.empty())
1519 writer.key(componentName.data(), componentName.size());
1523 const auto relation = pair_rel(*
this, component);
1525 if (relationName.empty())
1527 const auto target = pair_tgt(*
this, component);
1529 if (targetName.empty())
1531 if (relationName.empty() || targetName.empty())
1535 pairName.append(
"(");
1536 pairName.append(relationName.data(), relationName.size());
1537 pairName.append(
",");
1538 pairName.append(targetName.data(), targetName.size());
1539 pairName.append(
")");
1540 writer.key(pairName.data(), pairName.size());
1544 auto write_raw_component = [&](
const ComponentCacheItem& item,
const uint8_t* pData, uint32_t from, uint32_t to,
1546 ser::ser_buffer_binary raw;
1547 auto s = ser::make_serializer(raw);
1548 item.save(s, pData, from, to, cap);
1550 writer.begin_object();
1552 writer.begin_array();
1553 const auto* pRaw = raw.data();
1554 GAIA_FOR(raw.bytes()) writer.value_int(pRaw[i]);
1556 writer.end_object();
1559 auto write_sparse_component_value = [&](Entity owner, Entity component, const ComponentCacheItem& item) {
1561 const auto* pData = pStore !=
nullptr ? (
const uint8_t*)pStore->func_get(pStore->pStore, owner) :
nullptr;
1562 GAIA_ASSERT(pData !=
nullptr);
1563 if (pData ==
nullptr) {
1564 writer.value_null();
1569 if (item.field_count() != 0 || detail::runtime_json_is_direct_value(item))
1570 ok = ecs::component_to_json(item, pData, writer, policy) && ok;
1571 else if (allowRawFallback)
1572 write_raw_component(item, pData, 0, 1, 1);
1574 writer.value_null();
1581 EntityArrayMap nonFragmentingSparseByOwner;
1582 for (
const auto& [compKey, store]: m_sparseComponentsByComp) {
1583 const auto component = compKey.entity();
1587 cnt::darray<Entity> owners;
1588 store.func_collect_entities(store.pStore, owners);
1589 for (
auto owner: owners) {
1591 nonFragmentingSparseByOwner[EntityLookupKey(owner)].push_back(component);
1594 for (
auto& [owner, components]: nonFragmentingSparseByOwner) {
1596 core::sort(components, core::is_smaller<Entity>{});
1599 ser::bin_stream binarySnapshot;
1600 if (includeBinarySnapshot) {
1601 auto s = ser::make_serializer(binarySnapshot);
1607 writer.begin_object();
1608 writer.key(
"format");
1609 writer.value_int(WorldSerializerJSONVersion);
1610 writer.key(
"worldVersion");
1611 writer.value_int(m_worldVersion);
1612 if (includeBinarySnapshot) {
1613 writer.key(
"binary");
1614 writer.begin_array();
1616 const auto* pData = (
const uint8_t*)binarySnapshot.data();
1617 GAIA_FOR(binarySnapshot.bytes()) writer.value_int(pData[i]);
1621 writer.key("archetypes");
1622 writer.begin_array();
1624 for (const auto* pArchetype: m_archetypes) {
1625 if (pArchetype ==
nullptr || pArchetype->chunks().empty())
1628 writer.begin_object();
1630 writer.value_int((uint32_t)pArchetype->id());
1632 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
1634 writer.key(
"components");
1635 writer.begin_array();
1637 for (
const auto entity: pArchetype->ids_view()) {
1638 const auto itemName =
name(entity);
1639 if (!itemName.empty())
1640 writer.value_string(itemName.data(), itemName.size());
1642 writer.value_string(
"<unnamed>");
1647 writer.key(
"entities");
1648 writer.begin_array();
1650 for (
const auto* pChunk: pArchetype->chunks()) {
1651 if (pChunk ==
nullptr || pChunk->empty())
1654 const auto ents = pChunk->entity_view();
1655 const auto recs = pChunk->comp_rec_view();
1656 GAIA_FOR((uint32_t)ents.size()) {
1657 const auto entity = ents[i];
1659 writer.begin_object();
1661 writer.key(
"entity");
1663 writer.begin_object();
1665 writer.value_int(entity.id());
1667 writer.value_int(entity.gen());
1669 writer.value_bool(entity.pair());
1670 const auto entityName =
name(entity);
1671 if (!entityName.empty()) {
1673 writer.value_string(entityName.data(), entityName.size());
1675 writer.end_object();
1678 writer.key(
"components");
1679 writer.begin_object();
1681 GAIA_FOR_((uint32_t)recs.size(), j) {
1682 const auto& rec = recs[j];
1683 const auto component = pChunk->ids_view()[j];
1684 if (!write_component_key(component, rec.pItem)) {
1685 writer.key(
"<unnamed>");
1686 if (component.pair() && !includeBinarySnapshot)
1691 if (rec.comp.size() == 0) {
1692 writer.value_bool(
true);
1697 GAIA_ASSERT(rec.pItem !=
nullptr);
1698 const auto& item = *rec.pItem;
1699 if (gaia::ecs::component_uses_sparse_storage(rec.comp)) {
1700 write_sparse_component_value(entity, component, item);
1706 if ((item.field_count() != 0 || detail::runtime_json_is_direct_value(item)) &&
1707 rec.comp.soa() == 0) {
1708 const auto* pCompData = pChunk->comp_ptr(j, row);
1709 ok = ecs::component_to_json(item, pCompData, writer, policy) && ok;
1711 if (allowRawFallback)
1712 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
1714 writer.value_null();
1720 const auto sparseIt = nonFragmentingSparseByOwner.find(EntityLookupKey(entity));
1721 if (sparseIt != nonFragmentingSparseByOwner.end()) {
1722 for (
auto component: sparseIt->second) {
1725 GAIA_ASSERT(pItem !=
nullptr);
1726 if (pItem ==
nullptr || !write_component_key(component, pItem)) {
1727 writer.key(
"<unnamed>");
1730 if (pItem !=
nullptr)
1731 write_sparse_component_value(entity, component, *pItem);
1733 writer.value_null();
1736 writer.end_object();
1739 writer.end_object();
1745 writer.end_object();
1749 writer.end_object();
1753 inline ser::json_str World::save_json(
bool& ok, ser::JsonSaveFlags flags)
const {
1754 ser::ser_json writer;
1755 ok = save_json(writer, flags);
1756 return writer.str();
1759 inline bool World::load_json(
1760 const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy) {
1761 diagnostics.clear();
1762 if (json ==
nullptr)
1766 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson,
"$",
1767 "Input JSON length must be provided and non-zero.");
1771 const auto dataLen = len;
1772 auto warn = [&](ser::JsonDiagReason reason, ser::json_str_view
path,
const char* message) {
1773 diagnostics.add(ser::JsonDiagSeverity::Warning, reason,
path, message);
1775 auto error = [&](ser::JsonDiagReason reason, ser::json_str_view
path,
const char* message) {
1776 diagnostics.add(ser::JsonDiagSeverity::Error, reason,
path, message);
1779 ser::bin_stream binarySnapshot;
1780 bool hasBinarySnapshot =
false;
1784 ser::ser_json header(json, dataLen);
1785 if (!header.expect(
'{')) {
1786 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
1790 bool hasFormat =
false;
1791 uint32_t formatValue = 0;
1794 if (!header.consume(
'}')) {
1796 ser::json_str_view key;
1797 if (!header.parse_string_view(key))
1799 if (!header.expect(
':'))
1802 if (key ==
"format") {
1804 if (!header.parse_number(d))
1806 if (d < 0.0 || d > 4294967295.0)
1808 const auto v = (uint32_t)d;
1813 }
else if (key ==
"binary") {
1814 if (hasBinarySnapshot)
1816 hasBinarySnapshot =
true;
1817 if (!ser::detail::parse_json_byte_array(header, binarySnapshot))
1820 if (!header.skip_value())
1825 if (header.consume(
','))
1827 if (header.consume(
'}'))
1838 error(ser::JsonDiagReason::MissingFormatField,
"$.format",
"Missing required 'format' field.");
1842 if (formatValue != WorldSerializerJSONVersion) {
1844 ser::JsonDiagReason::UnsupportedFormatVersion,
"$.format",
1845 "Unsupported format version. Expected numeric value 1.");
1850 if (hasBinarySnapshot)
1851 return load(binarySnapshot);
1854 ser::ser_json jp(json, dataLen);
1856 struct CompDataLoc {
1857 uint8_t* pBase =
nullptr;
1861 auto locate_component_data = [&](Entity entity, Entity component) {
1863 const auto* pItem = component.pair() ?
comp_cache().find_pair_payload(component) :
comp_cache().find(component);
1864 if (pItem !=
nullptr && gaia::ecs::component_uses_sparse_storage(pItem->comp)) {
1866 loc.pBase = (uint8_t*)store.func_mut(store.pStore, entity);
1870 auto& ec =
fetch(entity);
1871 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
1872 if (compIdx == BadIndex)
1875 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
1880 auto parse_and_apply_component_value = [&](Entity entity, Entity component,
const ComponentCacheItem& item,
1881 ser::json_str_view compPath) ->
bool {
1886 if (jp.parse_null()) {
1888 ser::JsonDiagReason::NullComponentPayload, compPath,
1889 "Null component payload is ignored in semantic mode.");
1894 if (gaia::ecs::component_uses_sparse_storage(item.comp)) {
1896 (void)store.func_add(store.pStore, entity);
1898 }
else if (component.pair())
1899 add(entity, Pair(pair_rel(*
this, component), pair_tgt(*
this, component)));
1901 add(entity, component);
1904 const auto loc = locate_component_data(entity, component);
1905 if (loc.pBase ==
nullptr) {
1907 ser::JsonDiagReason::MissingComponentStorage, compPath,
1908 "Component storage is unavailable on the target entity.");
1909 return jp.skip_value();
1912 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
1914 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, policy, compPath))
1920 auto parse_entity_meta = [&](
bool& isPair, ser::json_str& nameOut) ->
bool {
1921 if (!jp.expect(
'{'))
1925 if (jp.consume(
'}'))
1929 ser::json_str_view key;
1930 if (!jp.parse_string_view(key))
1932 if (!jp.expect(
':'))
1935 if (key ==
"pair") {
1936 if (!jp.parse_bool(isPair))
1938 }
else if (key ==
"name") {
1939 if (!jp.parse_string(nameOut))
1942 if (!jp.skip_value())
1947 if (jp.consume(
','))
1949 if (jp.consume(
'}'))
1957 auto parse_components_for_entity = [&](Entity& entity,
bool& created,
bool isPair,
1958 const ser::json_str& entityName) ->
bool {
1959 if (!jp.expect(
'{'))
1963 if (jp.consume(
'}'))
1967 ser::json_str_view compName;
1968 bool compNameFromScratch =
false;
1969 if (!jp.parse_string_view(compName, &compNameFromScratch))
1973 ser::json_str compNameStorage;
1974 if (compNameFromScratch) {
1975 compNameStorage.assign(compName.data(), compName.size());
1976 compName = compNameStorage;
1979 if (!jp.expect(
':'))
1982 const auto componentName = util::str_view(compName.data(), compName.size());
1983 const bool nameIsInternal = ComponentCache::is_internal_symbol(componentName);
1984 const auto componentEntity = nameIsInternal ? EntityBad :
name_to_entity({compName.data(), compName.size()});
1986 const ComponentCacheItem* pItem =
nullptr;
1987 if (componentEntity.pair())
1988 pItem =
comp_cache().find_pair_payload(componentEntity);
1989 else if (componentEntity != EntityBad)
1992 const auto itemName = pItem !=
nullptr ?
comp_cache().symbol_name(*pItem) : util::str_view{};
1993 const bool itemIsInternal = ComponentCache::is_internal_symbol(itemName);
1994 const auto relationName =
1995 componentEntity.pair() ?
symbol(pair_rel(*
this, componentEntity)) : util::str_view{};
1996 const bool relationIsInternal = ComponentCache::is_internal_symbol(relationName);
1998 if (isPair || nameIsInternal || itemIsInternal || relationIsInternal) {
1999 if (!jp.skip_value())
2002 if (pItem ==
nullptr) {
2004 ser::JsonDiagReason::UnknownComponent, compName,
2005 "Component is not registered in the component cache.");
2006 if (!jp.skip_value())
2008 }
else if (pItem->comp.size() == 0) {
2011 ser::JsonDiagReason::TagComponentUnsupported, compName,
2012 "Tag-only component semantic JSON loading is currently unsupported.");
2013 if (!jp.skip_value())
2020 if (!entityName.empty()) {
2021 const auto existing =
get(entityName.data(), (uint32_t)entityName.size());
2022 if (existing == EntityBad)
2023 name(entity, entityName.data(), (uint32_t)entityName.size());
2026 ser::JsonDiagReason::DuplicateEntityName,
"entity.name",
2027 "Entity name already exists; keeping existing mapping.");
2031 if (!parse_and_apply_component_value(entity, componentEntity, *pItem, compName))
2037 if (jp.consume(
','))
2039 if (jp.consume(
'}'))
2047 auto parse_entity_entry = [&]() ->
bool {
2048 if (!jp.expect(
'{')) {
2049 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
2053 bool isPair =
false;
2054 ser::json_str entityName;
2055 Entity entity = EntityBad;
2056 bool created =
false;
2059 if (jp.consume(
'}'))
2063 ser::json_str_view key;
2064 if (!jp.parse_string_view(key))
2066 if (!jp.expect(
':'))
2069 if (key ==
"entity") {
2070 if (!parse_entity_meta(isPair, entityName))
2072 }
else if (key ==
"components") {
2073 if (!parse_components_for_entity(entity, created, isPair, entityName))
2076 if (!jp.skip_value())
2081 if (jp.consume(
','))
2083 if (jp.consume(
'}'))
2091 auto parse_archetypes = [&]() ->
bool {
2092 if (!jp.expect(
'['))
2096 if (jp.consume(
']'))
2100 if (!jp.expect(
'{'))
2104 if (!jp.consume(
'}')) {
2106 ser::json_str_view key;
2107 if (!jp.parse_string_view(key))
2109 if (!jp.expect(
':'))
2112 if (key ==
"entities") {
2113 if (!jp.expect(
'['))
2117 if (!jp.consume(
']')) {
2119 if (!parse_entity_entry())
2123 if (jp.consume(
','))
2125 if (jp.consume(
']'))
2131 if (!jp.skip_value())
2136 if (jp.consume(
','))
2138 if (jp.consume(
'}'))
2145 if (jp.consume(
','))
2147 if (jp.consume(
']'))
2155 if (!jp.expect(
'{'))
2158 bool hasArchetypes =
false;
2160 if (!jp.consume(
'}')) {
2162 ser::json_str_view key;
2163 if (!jp.parse_string_view(key))
2165 if (!jp.expect(
':'))
2168 if (key ==
"archetypes") {
2169 hasArchetypes =
true;
2170 if (!parse_archetypes())
2173 if (!jp.skip_value())
2178 if (jp.consume(
','))
2180 if (jp.consume(
'}'))
2189 if (!hasArchetypes) {
2190 error(ser::JsonDiagReason::MissingArchetypesSection,
"$.archetypes",
"Missing required 'archetypes' section.");
2197 inline bool World::load_json(
const char* json, uint32_t len) {
2198 ser::JsonDiagnostics diagnostics;
2199 const bool parsed = load_json(json, len, diagnostics);
2200 return parsed && !diagnostics.has_issues();
2204 World::load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy) {
2205 return load_json(json.data(), json.size(), diagnostics, policy);
2208 inline bool World::load_json(ser::json_str_view json) {
2209 ser::JsonDiagnostics diagnostics;
2210 const bool parsed = load_json(json.data(), json.size(), diagnostics);
2211 return parsed && !diagnostics.has_issues();
GAIA_NODISCARD const SparseComponentStoreErased * sparse_component_store_erased(Entity component) const
Returns the erased sparse store for component, or nullptr when absent.
Definition world.h:1701
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:3841
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:3851
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:4308
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:1052
GAIA_NODISCARD SparseStorageMode sparse_storage_mode(Entity component) const
Returns the sparse storage mode used by a component.
Definition world.h:1340
void name(Entity entity, const char *name, uint32_t len=0)
Assigns a name to entity. Ignored if used with pair. The string is copied and kept internally.
Definition world.h:8070
GAIA_NODISCARD bool valid(Entity entity) const
Checks if entity is valid.
Definition world.h:4276
GAIA_NODISCARD bool has_direct(Entity entity, Entity object) const
Checks if entity directly contains the entity object, without semantic inheritance expansion.
Definition world.h:7695
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:3871
GAIA_NODISCARD SparseComponentStoreErased & sparse_component_store_erased_mut(Entity component, const ComponentCacheItem &item)
Returns the erased sparse store for component, creating runtime-sized storage when absent.
Definition world.h:1711
GAIA_NODISCARD Entity add()
Creates a new empty entity.
Definition world.h:4336
bool load(ser::serializer inputSerializer={})
Loads a world state from a buffer. The buffer is sought to 0 before any loading happens....
Definition world.h:10962
Entity name_to_entity(std::span< const char > exprRaw) const
Resolves a textual id expression to an entity. Supports names, aliases, wildcard *,...
Definition world.h:14970
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:8312
void finish_sparse_component_add_inter(Entity entity, Entity object, SparseStorageMode mode)
Finishes adding a sparse component after its payload has been created. Fragmenting components also ad...
Definition world.h:1730
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:8788
GAIA_NODISCARD bool component_is_non_fragmenting(Entity component) const
Returns whether component is non-fragmenting. Non-fragmenting component ids do not participate in arc...
Definition world.h:1325