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 auto len = (uint32_t)snprintf(idx,
sizeof(idx),
"%u", index);
300 path.append(idx, len);
309 GAIA_NODISCARD
inline bool count_runtime_json_array_elements(ser::ser_json& reader, uint32_t& outCount) {
312 const auto* start = reader.pos();
313 const auto* end = reader.end();
314 if (start ==
nullptr || end ==
nullptr || start > end)
317 ser::ser_json counter(start, (uint32_t)(end - start));
318 if (!counter.expect(
'['))
321 if (counter.consume(
']'))
325 if (!counter.skip_value())
328 if (counter.consume(
','))
330 return counter.consume(
']');
344 inline bool write_runtime_json_value(
345 const ComponentCache* pCache,
const ComponentCacheItem* pType, Entity typeEntity,
const uint8_t* pData,
346 uint32_t valueSize, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth);
357 inline bool write_runtime_json_field(
358 const ComponentCache* pCache,
const ComponentCacheItem& owner,
const RuntimeFieldDesc& field,
359 const uint8_t* pBase, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
360 RuntimeJsonFieldLayout layout{};
361 if (!resolve_runtime_json_field_layout(pCache, field, layout)) {
366 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
367 const auto end = (uint64_t)field.offset + fieldSize64;
368 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
373 const auto* pFieldData = pBase + field.offset;
374 if (layout.elemCount == 1 || field.jsonEncoding == RuntimeJsonEncoding::Utf8String ||
375 runtime_json_is_char8_type(layout.pType, layout.type))
376 return write_runtime_json_value(
377 pCache, layout.pType, layout.type, pFieldData, (uint32_t)fieldSize64, writer, policy, depth + 1);
380 writer.begin_array();
381 GAIA_FOR(layout.elemCount) {
382 const auto* pElemData = pFieldData + (uintptr_t)layout.elemSize * i;
383 ok = write_runtime_json_value(
384 pCache, layout.pType, layout.type, pElemData, layout.elemSize, writer, policy, depth + 1) &&
399 inline bool write_runtime_json_struct(
400 const ComponentCache* pCache,
const ComponentCacheItem& item,
const uint8_t* pData, ser::ser_json& writer,
401 const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
402 if (depth >= RuntimeJsonMaxDepth) {
408 writer.begin_object();
409 GAIA_FOR(item.field_count()) {
410 const auto* pField = item.field(i);
411 GAIA_ASSERT(pField !=
nullptr);
412 const auto& field = *pField;
413 const auto fieldName = item.field_name(field);
414 writer.key(fieldName.data(), fieldName.size());
415 ok = write_runtime_json_field(pCache, item, field, pData, writer, policy, depth + 1) && ok;
421 inline bool write_runtime_json_value(
422 const ComponentCache* pCache,
const ComponentCacheItem* pType, Entity typeEntity,
const uint8_t* pData,
423 uint32_t valueSize, ser::ser_json& writer,
const ser::RuntimeJsonPolicy& policy, uint32_t depth) {
424 if (depth >= RuntimeJsonMaxDepth) {
429 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Array) {
430 const auto elemCount = pType->element_count();
431 const auto elementType = pType->element_type();
432 const auto* pElementType = find_runtime_json_type(pCache, elementType);
433 uint32_t elemSize = 0;
434 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
435 (uint64_t)elemSize * elemCount != valueSize) {
441 writer.begin_array();
442 GAIA_FOR(elemCount) {
443 const auto* pElemData = pData + (uintptr_t)elemSize * i;
444 ok = write_runtime_json_value(
445 pCache, pElementType, elementType, pElemData, elemSize, writer, policy, depth + 1) &&
452 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Vector) {
453 const auto* adapter = pType->sequence_adapter();
454 const auto elementType = pType->element_type();
455 const auto* pElementType = find_runtime_json_type(pCache, elementType);
456 if (adapter ==
nullptr || adapter->count ==
nullptr || adapter->element ==
nullptr) {
460 RuntimeSequenceScope sequence{typeEntity, pData,
nullptr, valueSize};
461 uint32_t elemCount = 0;
462 if (!adapter->count(adapter->ctx, sequence, elemCount)) {
466 if (pType->jsonEncoding == RuntimeJsonEncoding::Utf8String) {
467 if (elementType != Char8) {
473 text.reserve(elemCount);
474 GAIA_FOR(elemCount) {
475 RuntimeSequenceElement element{};
476 element.type = elementType;
477 if (!adapter->element(adapter->ctx, sequence, i, element) || element.data ==
nullptr ||
478 element.size !=
sizeof(
char) || (element.type != EntityBad && element.type != Char8)) {
482 text.append(*(
const char*)element.data);
484 writer.value_string(text.empty() ?
"" : text.data(), text.size());
489 writer.begin_array();
490 GAIA_FOR(elemCount) {
491 RuntimeSequenceElement element{};
492 element.type = elementType;
493 if (!adapter->element(adapter->ctx, sequence, i, element)) {
498 if (element.type == EntityBad)
499 element.type = elementType;
500 ok = write_runtime_json_value(
501 pCache, pElementType, element.type, (
const uint8_t*)element.data, element.size, writer, policy,
509 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Opaque) {
510 const auto* adapter = pType->opaque_adapter();
511 const auto semanticType = pType->opaque_as_type();
512 const auto* pSemanticType = find_runtime_json_type(pCache, semanticType);
513 if (adapter ==
nullptr || adapter->project ==
nullptr || pSemanticType ==
nullptr) {
517 RuntimeOpaqueScope opaque{typeEntity, pData,
nullptr, valueSize};
518 RuntimeOpaqueValue projected{};
519 projected.type = semanticType;
520 if (!adapter->project(adapter->ctx, opaque, projected)) {
524 if (projected.type == EntityBad)
525 projected.type = semanticType;
526 if (projected.type != semanticType || projected.data ==
nullptr) {
530 return write_runtime_json_value(
531 pCache, pSemanticType, projected.type, (
const uint8_t*)projected.data, projected.size, writer, policy,
535 if (pType !=
nullptr && pType->typeKind == RuntimeTypeKind::Struct)
536 return write_runtime_json_struct(pCache, *pType, pData, writer, policy, depth + 1);
538 ser::serialization_type_id type = ser::serialization_type_id::ignore;
539 if (pType !=
nullptr) {
540 if (!runtime_type_json_type(*pType, type)) {
544 }
else if (!runtime_primitive_serialization_type(typeEntity, type)) {
549 uint64_t valueBits = 0;
550 if (pType !=
nullptr && policy.symbolicEnums && pType->typeKind == RuntimeTypeKind::Enum &&
551 runtime_json_integer_bits(pData, type, valueSize, valueBits)) {
552 GAIA_FOR(pType->constant_count()) {
553 const auto* pConstant = pType->constant(i);
554 GAIA_ASSERT(pConstant !=
nullptr);
555 uint64_t constantBits = 0;
556 if (runtime_json_constant_bits(type, pConstant->value, constantBits) && constantBits == valueBits) {
557 const auto constantName = pType->constant_name(*pConstant);
558 writer.value_string(constantName.data(), constantName.size());
564 if (pType !=
nullptr && policy.symbolicBitmasks && pType->typeKind == RuntimeTypeKind::Bitmask &&
565 runtime_json_integer_bits(pData, type, valueSize, valueBits)) {
566 uint64_t remaining = valueBits;
567 GAIA_FOR(pType->constant_count()) {
568 const auto* pConstant = pType->constant(i);
569 GAIA_ASSERT(pConstant !=
nullptr);
570 uint64_t flagBits = 0;
571 if (runtime_json_constant_bits(type, pConstant->value, flagBits) && flagBits != 0 &&
572 (flagBits & (flagBits - 1)) == 0 && (remaining & flagBits) == flagBits)
573 remaining &= ~flagBits;
576 if (remaining == 0) {
577 writer.begin_array();
578 remaining = valueBits;
579 GAIA_FOR(pType->constant_count()) {
580 const auto* pConstant = pType->constant(i);
581 GAIA_ASSERT(pConstant !=
nullptr);
582 uint64_t flagBits = 0;
583 if (runtime_json_constant_bits(type, pConstant->value, flagBits) && flagBits != 0 &&
584 (flagBits & (flagBits - 1)) == 0 && (remaining & flagBits) == flagBits) {
585 const auto constantName = pType->constant_name(*pConstant);
586 writer.value_string(constantName.data(), constantName.size());
587 remaining &= ~flagBits;
595 return ser::detail::write_runtime_field_json(writer, pData, type, valueSize);
599 struct RuntimeJsonReadContext final {
601 const ComponentCache* pCache;
603 ser::ser_json& reader;
605 ser::JsonDiagnostics& diagnostics;
607 const ser::RuntimeJsonPolicy& policy;
621 inline bool read_runtime_json_value(
622 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
623 uint32_t valueSize, ser::json_str_view path, uint32_t depth);
635 inline bool read_runtime_json_elements(
636 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
637 uint32_t elemSize, uint32_t elemCount, ser::json_str_view path, uint32_t depth) {
638 auto& reader = ctx.reader;
639 if (!reader.expect(
'['))
642 GAIA_FOR(elemCount) {
643 if (i > 0 && !reader.expect(
','))
646 const auto elemPath = make_runtime_json_element_path(path, i);
647 auto* pElemData = pData + (uintptr_t)elemSize * i;
649 if (!read_runtime_json_value(ctx, pType, typeEntity, pElemData, elemSize, elemPath, depth))
653 return reader.expect(
']');
661 inline void warn_runtime_json(
662 ser::JsonDiagnostics& diagnostics, ser::JsonDiagReason reason, ser::json_str_view path,
const char* message) {
663 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
674 inline bool read_runtime_json_field(
675 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& owner,
const RuntimeFieldDesc& field, uint8_t* pBase,
676 ser::json_str_view path, uint32_t depth) {
677 auto& reader = ctx.reader;
678 RuntimeJsonFieldLayout layout{};
680 if (!resolve_runtime_json_field_layout(ctx.pCache, field, layout)) {
683 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
684 "Runtime field uses an unknown reflected type.");
685 return reader.skip_value();
688 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
689 const auto end = (uint64_t)field.offset + fieldSize64;
691 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
694 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
695 "Runtime field points outside component size or uses an unsupported type.");
696 return reader.skip_value();
699 auto* pFieldData = pBase + field.offset;
701 if (layout.elemCount == 1 || field.jsonEncoding == RuntimeJsonEncoding::Utf8String ||
702 runtime_json_is_char8_type(layout.pType, layout.type))
703 return read_runtime_json_value(
704 ctx, layout.pType, layout.type, pFieldData, (uint32_t)fieldSize64, path, depth + 1);
706 return read_runtime_json_elements(
707 ctx, layout.pType, layout.type, pFieldData, layout.elemSize, layout.elemCount, path, depth + 1);
717 inline bool read_runtime_json_struct(
718 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, uint8_t* pData, ser::json_str_view path,
720 auto& reader = ctx.reader;
722 if (depth >= RuntimeJsonMaxDepth) {
725 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime JSON nesting is too deep.");
726 return reader.skip_value();
729 if (reader.parse_null()) {
732 ctx.diagnostics, ser::JsonDiagReason::NullComponentPayload, path,
"Runtime object payload is null.");
736 if (!reader.expect(
'{'))
740 if (reader.consume(
'}'))
744 ser::json_str_view key;
745 bool keyFromScratch =
false;
746 if (!reader.parse_string_view(key, &keyFromScratch))
750 ser::json_str keyStorage;
751 if (keyFromScratch) {
752 keyStorage.assign(key.data(), key.size());
756 if (!reader.expect(
':'))
759 const auto* pField = item.field(util::str_view(key.data(), (uint32_t)key.size()));
760 const auto fieldPath = make_runtime_json_child_path(path, key);
762 if (pField ==
nullptr) {
764 warn_runtime_json(ctx.diagnostics, ser::JsonDiagReason::UnknownField, fieldPath,
"Unknown runtime field.");
765 if (!reader.skip_value())
767 }
else if (!read_runtime_json_field(ctx, item, *pField, pData, fieldPath, depth + 1))
771 if (reader.consume(
','))
773 if (reader.consume(
'}'))
789 inline bool read_runtime_json_array(
790 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, uint8_t* pData, uint32_t valueSize,
791 ser::json_str_view path, uint32_t depth) {
792 const auto elemCount = item.element_count();
793 const auto elementType = item.element_type();
794 const auto* pElementType = find_runtime_json_type(ctx.pCache, elementType);
795 uint32_t elemSize = 0;
797 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
798 (uint64_t)elemSize * elemCount != valueSize) {
801 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
802 "Runtime array payload uses an invalid reflected element type.");
803 return ctx.reader.skip_value();
806 return read_runtime_json_elements(ctx, pElementType, elementType, pData, elemSize, elemCount, path, depth + 1);
817 inline bool read_runtime_json_utf8_vector(
818 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
819 uint32_t valueSize, ser::json_str_view path) {
820 const auto* adapter = item.sequence_adapter();
821 ser::json_str_view text;
823 if (item.element_type() != Char8 || adapter ==
nullptr || adapter->resize ==
nullptr ||
824 adapter->element ==
nullptr || !ctx.reader.parse_string_view(text)) {
827 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
828 "Runtime UTF-8 string cannot be resized or traversed.");
832 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
834 if (!adapter->resize(adapter->ctx, sequence, text.size())) {
837 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
838 "Runtime UTF-8 string adapter rejected the requested byte count.");
844 GAIA_FOR(text.size()) {
845 RuntimeSequenceElement element{};
846 element.type = Char8;
848 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData ==
nullptr ||
849 element.size !=
sizeof(
char) || (element.type != EntityBad && element.type != Char8)) {
852 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
853 "Runtime UTF-8 string adapter rejected an element.");
857 *(
char*)element.mutData = text.data()[i];
859 if (adapter->commitElement !=
nullptr && !adapter->commitElement(adapter->ctx, sequence, element)) {
862 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
863 "Runtime UTF-8 string adapter rejected element commit.");
880 inline bool read_runtime_json_vector(
881 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
882 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
883 if (item.jsonEncoding == RuntimeJsonEncoding::Utf8String)
884 return read_runtime_json_utf8_vector(ctx, item, typeEntity, pData, valueSize, path);
886 const auto* adapter = item.sequence_adapter();
887 const auto elementType = item.element_type();
888 const auto* pElementType = find_runtime_json_type(ctx.pCache, elementType);
889 uint32_t elemCount = 0;
892 if (adapter ==
nullptr || adapter->resize ==
nullptr || adapter->element ==
nullptr ||
893 !count_runtime_json_array_elements(ctx.reader, elemCount)) {
896 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
897 "Runtime vector payload cannot be resized or traversed.");
898 return ctx.reader.skip_value();
901 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
903 if (!adapter->resize(adapter->ctx, sequence, elemCount)) {
906 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
907 "Runtime vector adapter rejected the requested element count.");
908 return ctx.reader.skip_value();
911 if (!ctx.reader.expect(
'['))
914 GAIA_FOR(elemCount) {
915 if (i > 0 && !ctx.reader.expect(
','))
918 RuntimeSequenceElement element{};
919 element.type = elementType;
921 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData ==
nullptr) {
924 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
925 "Runtime vector adapter rejected an element.");
926 return ctx.reader.skip_value();
929 if (element.type == EntityBad)
930 element.type = elementType;
932 const auto elemPath = make_runtime_json_element_path(path, i);
934 if (!read_runtime_json_value(
935 ctx, pElementType, element.type, (uint8_t*)element.mutData, element.size, elemPath, depth + 1))
939 return ctx.reader.expect(
']');
951 inline bool read_runtime_json_opaque(
952 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, Entity typeEntity, uint8_t* pData,
953 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
954 const auto* adapter = item.opaque_adapter();
955 const auto semanticType = item.opaque_as_type();
956 const auto* pSemanticType = find_runtime_json_type(ctx.pCache, semanticType);
958 if (adapter ==
nullptr || adapter->project ==
nullptr || pSemanticType ==
nullptr) {
961 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
962 "Runtime opaque payload cannot be projected.");
963 return ctx.reader.skip_value();
966 RuntimeOpaqueScope opaque{typeEntity, pData, pData, valueSize};
967 RuntimeOpaqueValue projected{};
968 projected.type = semanticType;
970 if (!adapter->project(adapter->ctx, opaque, projected) || projected.mutData ==
nullptr) {
973 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
974 "Runtime opaque adapter rejected projection.");
975 return ctx.reader.skip_value();
979 if (projected.type == EntityBad)
980 projected.type = semanticType;
982 if (projected.type != semanticType) {
985 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
986 "Runtime opaque adapter projected an unexpected semantic type.");
987 return ctx.reader.skip_value();
990 const bool parsed = read_runtime_json_value(
991 ctx, pSemanticType, projected.type, (uint8_t*)projected.mutData, projected.size, path, depth + 1);
994 if (parsed && adapter->commit !=
nullptr && ctx.ok && !adapter->commit(adapter->ctx, opaque, projected)) {
997 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime opaque adapter rejected commit.");
1011 inline bool read_runtime_json_enum_symbol(
1012 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, ser::serialization_type_id type, uint8_t* pData,
1013 uint32_t valueSize, ser::json_str_view path) {
1014 ser::json_str_view symbol;
1015 if (!ctx.reader.parse_string_view(symbol))
1018 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1019 uint64_t constantBits = 0;
1021 if (pConstant ==
nullptr || !runtime_json_constant_bits(type, pConstant->value, constantBits) ||
1022 !runtime_json_write_integer_bits(pData, type, valueSize, constantBits)) {
1025 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1026 "Runtime enum symbol is unknown or incompatible with its underlying type.");
1040 inline bool read_runtime_json_bitmask_symbols(
1041 RuntimeJsonReadContext& ctx,
const ComponentCacheItem& item, ser::serialization_type_id type, uint8_t* pData,
1042 uint32_t valueSize, ser::json_str_view path) {
1043 auto& reader = ctx.reader;
1045 if (!reader.expect(
'['))
1048 uint64_t valueBits = 0;
1049 bool symbolsOk =
true;
1052 if (!reader.consume(
']')) {
1054 ser::json_str_view symbol;
1055 if (!reader.parse_string_view(symbol))
1060 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1061 uint64_t flagBits = 0;
1063 if (pConstant ==
nullptr) {
1067 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1068 "Runtime bitmask symbol is unknown.");
1070 !runtime_json_constant_bits(type, pConstant->value, flagBits) || flagBits == 0 ||
1071 (flagBits & (flagBits - 1)) != 0 || (valueBits & flagBits) != 0) {
1075 ctx.diagnostics, ser::JsonDiagReason::InvalidRuntimeConstant, path,
1076 "Runtime bitmask symbol is not a distinct one-bit flag.");
1078 valueBits |= flagBits;
1083 if (reader.consume(
','))
1085 if (reader.consume(
']'))
1092 if (symbolsOk && !runtime_json_write_integer_bits(pData, type, valueSize, valueBits)) {
1095 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1096 "Runtime bitmask symbols are incompatible with the underlying field size.");
1110 inline bool read_runtime_json_scalar(
1111 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
1112 uint32_t valueSize, ser::json_str_view path) {
1113 ser::serialization_type_id type = ser::serialization_type_id::ignore;
1115 if (pType !=
nullptr) {
1116 if (!runtime_type_json_type(*pType, type)) {
1119 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1120 "Runtime field uses an unsupported reflected type.");
1121 return ctx.reader.skip_value();
1123 }
else if (!runtime_primitive_serialization_type(typeEntity, type)) {
1126 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime field type is unknown.");
1127 return ctx.reader.skip_value();
1132 if (pType !=
nullptr && ctx.policy.symbolicEnums && pType->typeKind == RuntimeTypeKind::Enum &&
1133 !ctx.reader.eof() && ctx.reader.peek() ==
'"')
1134 return read_runtime_json_enum_symbol(ctx, *pType, type, pData, valueSize, path);
1135 if (pType !=
nullptr && ctx.policy.symbolicBitmasks && pType->typeKind == RuntimeTypeKind::Bitmask &&
1136 !ctx.reader.eof() && ctx.reader.peek() ==
'[')
1137 return read_runtime_json_bitmask_symbols(ctx, *pType, type, pData, valueSize, path);
1140 bool fieldOk =
true;
1141 if (!ser::detail::read_runtime_field_json(ctx.reader, pData, type, valueSize, fieldOk))
1147 ctx.diagnostics, ser::JsonDiagReason::FieldValueAdjusted, path,
1148 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
1154 inline bool read_runtime_json_value(
1155 RuntimeJsonReadContext& ctx,
const ComponentCacheItem* pType, Entity typeEntity, uint8_t* pData,
1156 uint32_t valueSize, ser::json_str_view path, uint32_t depth) {
1157 if (depth >= RuntimeJsonMaxDepth) {
1160 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
"Runtime JSON nesting is too deep.");
1161 return ctx.reader.skip_value();
1164 if (pType !=
nullptr) {
1165 switch (pType->typeKind) {
1166 case RuntimeTypeKind::Array:
1167 return read_runtime_json_array(ctx, *pType, pData, valueSize, path, depth);
1168 case RuntimeTypeKind::Vector:
1169 return read_runtime_json_vector(ctx, *pType, typeEntity, pData, valueSize, path, depth);
1170 case RuntimeTypeKind::Opaque:
1171 return read_runtime_json_opaque(ctx, *pType, typeEntity, pData, valueSize, path, depth);
1172 case RuntimeTypeKind::Struct:
1173 return read_runtime_json_struct(ctx, *pType, pData, path, depth + 1);
1179 return read_runtime_json_scalar(ctx, pType, typeEntity, pData, valueSize, path);
1190 inline bool component_to_json(
1191 const ComponentCacheItem& item,
const void* pComponentData, ser::ser_json& writer,
1192 const ser::RuntimeJsonPolicy& policy = {}) {
1193 GAIA_ASSERT(pComponentData !=
nullptr);
1194 if (pComponentData ==
nullptr)
1197 return detail::write_runtime_json_value(
1198 item.owner_cache(), &item, item.entity,
reinterpret_cast<const uint8_t*
>(pComponentData), item.comp.size(),
1207 inline ser::json_str component_to_json(
const ComponentCacheItem& item,
const void* pComponentData,
bool& ok) {
1208 ser::ser_json writer;
1209 ok = component_to_json(item, pComponentData, writer);
1210 return writer.str();
1222 inline bool json_to_component(
1223 const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader, ser::JsonDiagnostics& diagnostics,
1224 const ser::RuntimeJsonPolicy& policy = {}, ser::json_str_view componentPath = {}) {
1225 GAIA_ASSERT(pComponentData !=
nullptr);
1226 if (pComponentData ==
nullptr)
1229 if (reader.parse_null()) {
1230 detail::warn_runtime_json(
1231 diagnostics, ser::JsonDiagReason::NullComponentPayload, componentPath,
"Component payload is null.");
1235 if (detail::runtime_json_is_direct_value(item)) {
1237 detail::RuntimeJsonReadContext ctx{item.owner_cache(), reader, diagnostics, policy, ok};
1238 return detail::read_runtime_json_value(
1239 ctx, &item, item.entity,
reinterpret_cast<uint8_t*
>(pComponentData), item.comp.size(), componentPath, 0);
1242 bool rawFound =
false;
1243 bool fieldFound =
false;
1246 detail::RuntimeJsonReadContext ctx{item.owner_cache(), reader, diagnostics, policy, ok};
1247 ser::ser_buffer_binary rawPayload;
1248 auto* pBase =
reinterpret_cast<uint8_t*
>(pComponentData);
1250 if (!reader.expect(
'{'))
1254 if (reader.consume(
'}')) {
1255 detail::warn_runtime_json(
1256 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, componentPath,
1257 "Component object is empty and contains no runtime fields or $raw payload.");
1262 ser::json_str_view key;
1263 bool keyFromScratch =
false;
1264 if (!reader.parse_string_view(key, &keyFromScratch))
1268 ser::json_str keyStorage;
1269 if (keyFromScratch) {
1270 keyStorage.assign(key.data(), key.size());
1274 if (!reader.expect(
':'))
1277 const auto fieldPath = detail::make_runtime_json_child_path(componentPath, key);
1279 if (key ==
"$raw") {
1281 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
1283 }
else if (item.field_count() != 0 && item.comp.soa() == 0) {
1284 const auto* pField = item.field(util::str_view(key.data(), (uint32_t)key.size()));
1285 if (pField ==
nullptr) {
1287 detail::warn_runtime_json(
1288 diagnostics, ser::JsonDiagReason::UnknownField, fieldPath,
"Unknown runtime field.");
1289 if (!reader.skip_value())
1293 if (!detail::read_runtime_json_field(ctx, item, *pField, pBase, fieldPath, 0))
1298 detail::warn_runtime_json(
1299 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, fieldPath,
1300 "Runtime field metadata is unavailable for keyed field payloads.");
1301 if (!reader.skip_value())
1306 if (reader.consume(
','))
1308 if (reader.consume(
'}'))
1314 if (item.comp.soa() != 0) {
1315 detail::warn_runtime_json(
1316 diagnostics, ser::JsonDiagReason::SoaRawUnsupported,
1317 detail::make_runtime_json_child_path(componentPath,
"$raw"),
1318 "$raw payload is not supported for SoA components.");
1323 auto s = ser::make_serializer(rawPayload);
1325 item.load(s, pBase, 0, 1, 1);
1328 if (!rawFound && !fieldFound)
1329 detail::warn_runtime_json(
1330 diagnostics, ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, componentPath,
1331 "Component payload contains neither recognized runtime fields nor $raw data.");
1343 json_to_component(
const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader,
bool& ok) {
1344 ser::JsonDiagnostics diagnostics;
1345 const bool parsed = json_to_component(item, pComponentData, reader, diagnostics);
1347 ok = !diagnostics.has_issues();
1352 World::save_json(ser::ser_json& writer, ser::JsonSaveFlags flags,
const ser::RuntimeJsonPolicy& policy)
const {
1353 auto write_component_key = [&](Entity component,
const ComponentCacheItem& item) {
1354 if (!component.pair()) {
1355 const auto componentName =
comp_cache().symbol_name(item);
1356 if (componentName.empty())
1358 writer.key(componentName.data(), componentName.size());
1362 const auto relation = pair_rel(*
this, component);
1364 if (relationName.empty())
1366 const auto target = pair_tgt(*
this, component);
1368 if (targetName.empty())
1370 if (relationName.empty() || targetName.empty())
1374 pairName.append(
"(");
1375 pairName.append(relationName.data(), relationName.size());
1376 pairName.append(
",");
1377 pairName.append(targetName.data(), targetName.size());
1378 pairName.append(
")");
1379 writer.key(pairName.data(), pairName.size());
1383 auto write_raw_component = [&](
const ComponentCacheItem& item,
const uint8_t* pData, uint32_t from, uint32_t to,
1385 ser::ser_buffer_binary raw;
1386 auto s = ser::make_serializer(raw);
1387 item.save(s, pData, from, to, cap);
1389 writer.begin_object();
1391 writer.begin_array();
1392 const auto* pRaw = raw.data();
1393 GAIA_FOR(raw.bytes()) writer.value_int(pRaw[i]);
1395 writer.end_object();
1399 const
bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
1400 const
bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
1401 ser::bin_stream binarySnapshot;
1402 if (includeBinarySnapshot) {
1403 auto s = ser::make_serializer(binarySnapshot);
1409 writer.begin_object();
1410 writer.key(
"format");
1411 writer.value_int(WorldSerializerJSONVersion);
1412 writer.key(
"worldVersion");
1413 writer.value_int(m_worldVersion);
1414 if (includeBinarySnapshot) {
1415 writer.key(
"binary");
1416 writer.begin_array();
1418 const auto* pData = (
const uint8_t*)binarySnapshot.data();
1419 GAIA_FOR(binarySnapshot.bytes()) writer.value_int(pData[i]);
1423 writer.key("archetypes");
1424 writer.begin_array();
1426 for (const auto* pArchetype: m_archetypes) {
1427 if (pArchetype ==
nullptr || pArchetype->chunks().empty())
1430 writer.begin_object();
1432 writer.value_int((uint32_t)pArchetype->id());
1434 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
1436 writer.key(
"components");
1437 writer.begin_array();
1439 for (
const auto entity: pArchetype->ids_view()) {
1440 const auto itemName =
name(entity);
1441 if (!itemName.empty())
1442 writer.value_string(itemName.data(), itemName.size());
1444 writer.value_string(
"<unnamed>");
1449 writer.key(
"entities");
1450 writer.begin_array();
1452 for (
const auto* pChunk: pArchetype->chunks()) {
1453 if (pChunk ==
nullptr || pChunk->empty())
1456 const auto ents = pChunk->entity_view();
1457 const auto recs = pChunk->comp_rec_view();
1458 GAIA_FOR((uint32_t)ents.size()) {
1459 const auto entity = ents[i];
1461 writer.begin_object();
1463 writer.key(
"entity");
1465 writer.begin_object();
1467 writer.value_int(entity.id());
1469 writer.value_int(entity.gen());
1471 writer.value_bool(entity.pair());
1473 writer.value_string(EntityKindString[entity.kind()]);
1474 const auto entityName =
name(entity);
1475 if (!entityName.empty()) {
1477 writer.value_string(entityName.data(), entityName.size());
1479 writer.end_object();
1482 writer.key(
"components");
1483 writer.begin_object();
1485 GAIA_FOR_((uint32_t)recs.size(), j) {
1486 const auto& rec = recs[j];
1487 const auto& item = *rec.pItem;
1488 const auto component = pChunk->ids_view()[j];
1489 if (!write_component_key(component, item)) {
1490 writer.key(
"<unnamed>");
1491 if (component.pair() && !includeBinarySnapshot)
1496 if (rec.comp.size() == 0) {
1497 writer.value_bool(
true);
1501 const auto row = component.kind() == EntityKind::EK_Uni ? 0U : i;
1503 if ((item.field_count() != 0 || detail::runtime_json_is_direct_value(item)) &&
1504 rec.comp.soa() == 0) {
1505 const auto* pCompData = pChunk->comp_ptr(j, row);
1506 ok = ecs::component_to_json(item, pCompData, writer, policy) && ok;
1508 if (allowRawFallback)
1509 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
1511 writer.value_null();
1516 writer.end_object();
1519 writer.end_object();
1525 writer.end_object();
1529 writer.end_object();
1533 inline ser::json_str World::save_json(
bool& ok, ser::JsonSaveFlags flags)
const {
1534 ser::ser_json writer;
1535 ok = save_json(writer, flags);
1536 return writer.str();
1539 inline bool World::load_json(
1540 const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy) {
1541 diagnostics.clear();
1542 if (json ==
nullptr)
1546 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson,
"$",
1547 "Input JSON length must be provided and non-zero.");
1551 const auto dataLen = len;
1552 const auto* p = json;
1553 const auto* end = json + dataLen;
1554 auto warn = [&](ser::JsonDiagReason reason, ser::json_str_view
path,
const char* message) {
1555 diagnostics.add(ser::JsonDiagSeverity::Warning, reason,
path, message);
1557 auto error = [&](ser::JsonDiagReason reason, ser::json_str_view
path,
const char* message) {
1558 diagnostics.add(ser::JsonDiagSeverity::Error, reason,
path, message);
1563 ser::ser_json header(json, dataLen);
1564 if (!header.expect(
'{')) {
1565 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
1569 bool hasFormat =
false;
1570 uint32_t formatValue = 0;
1573 if (!header.consume(
'}')) {
1575 ser::json_str_view key;
1576 if (!header.parse_string_view(key))
1578 if (!header.expect(
':'))
1581 if (key ==
"format") {
1583 if (!header.parse_number(d))
1585 if (d < 0.0 || d > 4294967295.0)
1587 const auto v = (uint32_t)d;
1593 if (!header.skip_value())
1598 if (header.consume(
','))
1600 if (header.consume(
'}'))
1611 error(ser::JsonDiagReason::MissingFormatField,
"$.format",
"Missing required 'format' field.");
1615 if (formatValue != WorldSerializerJSONVersion) {
1617 ser::JsonDiagReason::UnsupportedFormatVersion,
"$.format",
1618 "Unsupported format version. Expected numeric value 1.");
1625 const char key[] =
"\"binary\"";
1626 const uint32_t keyLen = (uint32_t)(
sizeof(key) - 1);
1627 const char* keyPos =
nullptr;
1628 for (
const char* it = p; it + keyLen <= end; ++it) {
1629 if (memcmp(it, key, keyLen) == 0) {
1634 if (keyPos !=
nullptr) {
1635 const char* arr =
nullptr;
1636 for (
const char* it = keyPos + keyLen; it < end; ++it) {
1642 if (arr !=
nullptr) {
1643 ser::bin_stream serializer;
1644 ser::ser_json binaryReader(arr, (uint32_t)(end - arr));
1645 if (!ser::detail::parse_json_byte_array(binaryReader, serializer))
1648 return load(serializer);
1654 ser::ser_json jp(json, dataLen);
1656 struct CompDataLoc {
1657 uint8_t* pBase =
nullptr;
1661 auto locate_component_data = [&](Entity entity, Entity component) {
1663 auto& ec =
fetch(entity);
1664 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
1665 if (compIdx == BadIndex)
1668 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
1669 loc.row = component.kind() == EntityKind::EK_Uni ? 0U : ec.row;
1673 auto parse_and_apply_component_value = [&](Entity entity, Entity component,
const ComponentCacheItem& item,
1674 ser::json_str_view compPath) ->
bool {
1679 if (jp.parse_null()) {
1681 ser::JsonDiagReason::NullComponentPayload, compPath,
1682 "Null component payload is ignored in semantic mode.");
1687 if (component.pair())
1688 add(entity, Pair(pair_rel(*
this, component), pair_tgt(*
this, component)));
1690 add(entity, component);
1693 const auto loc = locate_component_data(entity, component);
1694 if (loc.pBase ==
nullptr) {
1696 ser::JsonDiagReason::MissingComponentStorage, compPath,
1697 "Component storage is unavailable on the target entity.");
1698 return jp.skip_value();
1701 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
1703 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, policy, compPath))
1709 auto parse_entity_meta = [&](
bool& isPair, ser::json_str& nameOut) ->
bool {
1710 if (!jp.expect(
'{'))
1714 if (jp.consume(
'}'))
1718 ser::json_str_view key;
1719 if (!jp.parse_string_view(key))
1721 if (!jp.expect(
':'))
1724 if (key ==
"pair") {
1725 if (!jp.parse_bool(isPair))
1727 }
else if (key ==
"name") {
1728 if (!jp.parse_string(nameOut))
1731 if (!jp.skip_value())
1736 if (jp.consume(
','))
1738 if (jp.consume(
'}'))
1746 auto parse_components_for_entity = [&](Entity& entity,
bool& created,
bool isPair,
1747 const ser::json_str& entityName) ->
bool {
1748 if (!jp.expect(
'{'))
1752 if (jp.consume(
'}'))
1756 ser::json_str_view compName;
1757 bool compNameFromScratch =
false;
1758 if (!jp.parse_string_view(compName, &compNameFromScratch))
1762 ser::json_str compNameStorage;
1763 if (compNameFromScratch) {
1764 compNameStorage.assign(compName.data(), compName.size());
1765 compName = compNameStorage;
1768 if (!jp.expect(
':'))
1771 const auto componentName = util::str_view(compName.data(), compName.size());
1772 const bool nameIsInternal = ComponentCache::is_internal_symbol(componentName);
1773 const auto componentEntity = nameIsInternal ? EntityBad :
name_to_entity({compName.data(), compName.size()});
1775 const ComponentCacheItem* pItem =
nullptr;
1776 if (componentEntity.pair())
1777 pItem =
comp_cache().find_pair_payload(componentEntity);
1778 else if (componentEntity != EntityBad)
1781 const auto itemName = pItem !=
nullptr ?
comp_cache().symbol_name(*pItem) : util::str_view{};
1782 const bool itemIsInternal = ComponentCache::is_internal_symbol(itemName);
1783 const auto relationName =
1784 componentEntity.pair() ?
symbol(pair_rel(*
this, componentEntity)) : util::str_view{};
1785 const bool relationIsInternal = ComponentCache::is_internal_symbol(relationName);
1787 if (isPair || nameIsInternal || itemIsInternal || relationIsInternal) {
1788 if (!jp.skip_value())
1791 if (pItem ==
nullptr) {
1793 ser::JsonDiagReason::UnknownComponent, compName,
1794 "Component is not registered in the component cache.");
1795 if (!jp.skip_value())
1797 }
else if (pItem->comp.size() == 0) {
1800 ser::JsonDiagReason::TagComponentUnsupported, compName,
1801 "Tag-only component semantic JSON loading is currently unsupported.");
1802 if (!jp.skip_value())
1809 if (!entityName.empty()) {
1810 const auto existing =
get(entityName.data(), (uint32_t)entityName.size());
1811 if (existing == EntityBad)
1812 name(entity, entityName.data(), (uint32_t)entityName.size());
1815 ser::JsonDiagReason::DuplicateEntityName,
"entity.name",
1816 "Entity name already exists; keeping existing mapping.");
1820 if (!parse_and_apply_component_value(entity, componentEntity, *pItem, compName))
1826 if (jp.consume(
','))
1828 if (jp.consume(
'}'))
1836 auto parse_entity_entry = [&]() ->
bool {
1837 if (!jp.expect(
'{')) {
1838 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
1842 bool isPair =
false;
1843 ser::json_str entityName;
1844 Entity entity = EntityBad;
1845 bool created =
false;
1848 if (jp.consume(
'}'))
1852 ser::json_str_view key;
1853 if (!jp.parse_string_view(key))
1855 if (!jp.expect(
':'))
1858 if (key ==
"entity") {
1859 if (!parse_entity_meta(isPair, entityName))
1861 }
else if (key ==
"components") {
1862 if (!parse_components_for_entity(entity, created, isPair, entityName))
1865 if (!jp.skip_value())
1870 if (jp.consume(
','))
1872 if (jp.consume(
'}'))
1880 auto parse_archetypes = [&]() ->
bool {
1881 if (!jp.expect(
'['))
1885 if (jp.consume(
']'))
1889 if (!jp.expect(
'{'))
1893 if (!jp.consume(
'}')) {
1895 ser::json_str_view key;
1896 if (!jp.parse_string_view(key))
1898 if (!jp.expect(
':'))
1901 if (key ==
"entities") {
1902 if (!jp.expect(
'['))
1906 if (!jp.consume(
']')) {
1908 if (!parse_entity_entry())
1912 if (jp.consume(
','))
1914 if (jp.consume(
']'))
1920 if (!jp.skip_value())
1925 if (jp.consume(
','))
1927 if (jp.consume(
'}'))
1934 if (jp.consume(
','))
1936 if (jp.consume(
']'))
1944 if (!jp.expect(
'{'))
1947 bool hasArchetypes =
false;
1949 if (!jp.consume(
'}')) {
1951 ser::json_str_view key;
1952 if (!jp.parse_string_view(key))
1954 if (!jp.expect(
':'))
1957 if (key ==
"archetypes") {
1958 hasArchetypes =
true;
1959 if (!parse_archetypes())
1962 if (!jp.skip_value())
1967 if (jp.consume(
','))
1969 if (jp.consume(
'}'))
1978 if (!hasArchetypes) {
1979 error(ser::JsonDiagReason::MissingArchetypesSection,
"$.archetypes",
"Missing required 'archetypes' section.");
1986 inline bool World::load_json(
const char* json, uint32_t len) {
1987 ser::JsonDiagnostics diagnostics;
1988 const bool parsed = load_json(json, len, diagnostics);
1989 return parsed && !diagnostics.has_issues();
1993 World::load_json(ser::json_str_view json, ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy) {
1994 return load_json(json.data(), json.size(), diagnostics, policy);
1997 inline bool World::load_json(ser::json_str_view json) {
1998 ser::JsonDiagnostics diagnostics;
1999 const bool parsed = load_json(json.data(), json.size(), diagnostics);
2000 return parsed && !diagnostics.has_issues();
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:3321
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:3331
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:3857
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:3788
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:892
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:6820
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:6571
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:3351
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:9357
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:12926
GAIA_NODISCARD Entity relation(Entity entity, Entity target) const
Returns the first relationship relation for the target entity on entity.
Definition world.h:7062
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:7543