Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
world_json.h
1#pragma once
2#include "gaia/config/config.h"
3
4#if GAIA_JSON_ENABLED
5
6 #include <cstdio>
7 #include <cstring>
8
9 #include "gaia/ser/ser_json.h"
10
11namespace gaia {
12 namespace ecs {
14 namespace detail {
16 static constexpr uint32_t RuntimeJsonMaxDepth = 32;
17
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;
28 return false;
29 }
30
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;
38 if (pType != nullptr)
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;
41 }
42
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;
50 }
51
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();
61 return true;
62 }
63 outSize = ComponentCacheItem::primitive_type_size(typeEntity);
64 return outSize != 0;
65 }
66
68 struct RuntimeJsonFieldLayout final {
70 const ComponentCacheItem* pType = nullptr;
72 Entity type = EntityBad;
74 uint32_t elemSize = 0;
76 uint32_t elemCount = 0;
77 };
78
84 GAIA_NODISCARD inline bool resolve_runtime_json_field_layout(
85 const ComponentCache* pCache, const RuntimeFieldDesc& field, RuntimeJsonFieldLayout& out) noexcept {
86 out = {};
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);
91
92 if (pFieldType != nullptr && pFieldType->typeKind == RuntimeTypeKind::Array) {
93 if (field.count != 0)
94 return false;
95 out.type = pFieldType->element_type();
96 out.elemCount = pFieldType->element_count();
97 out.pType = find_runtime_json_type(pCache, out.type);
98 }
99
100 return out.elemCount != 0 && runtime_json_type_size(out.pType, out.type, out.elemSize);
101 }
102
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 {
111 switch (type) {
112 case ser::serialization_type_id::s8:
113 case ser::serialization_type_id::u8: {
114 if (valueSize != sizeof(uint8_t))
115 return false;
116 uint8_t value = 0;
117 memcpy(&value, pData, sizeof(value));
118 out = value;
119 return true;
120 }
121 case ser::serialization_type_id::s16:
122 case ser::serialization_type_id::u16: {
123 if (valueSize != sizeof(uint16_t))
124 return false;
125 uint16_t value = 0;
126 memcpy(&value, pData, sizeof(value));
127 out = value;
128 return true;
129 }
130 case ser::serialization_type_id::s32:
131 case ser::serialization_type_id::u32: {
132 if (valueSize != sizeof(uint32_t))
133 return false;
134 uint32_t value = 0;
135 memcpy(&value, pData, sizeof(value));
136 out = value;
137 return true;
138 }
139 case ser::serialization_type_id::s64:
140 case ser::serialization_type_id::u64: {
141 if (valueSize != sizeof(uint64_t))
142 return false;
143 memcpy(&out, pData, sizeof(out));
144 return true;
145 }
146 default:
147 return false;
148 }
149 }
150
156 GAIA_NODISCARD inline bool
157 runtime_json_constant_bits(ser::serialization_type_id type, int64_t value, uint64_t& out) noexcept {
158 switch (type) {
159 case ser::serialization_type_id::s8:
160 if (value < INT8_MIN || value > INT8_MAX)
161 return false;
162 out = (uint8_t)value;
163 return true;
164 case ser::serialization_type_id::u8:
165 if (value < 0 || value > UINT8_MAX)
166 return false;
167 out = (uint8_t)value;
168 return true;
169 case ser::serialization_type_id::s16:
170 if (value < INT16_MIN || value > INT16_MAX)
171 return false;
172 out = (uint16_t)value;
173 return true;
174 case ser::serialization_type_id::u16:
175 if (value < 0 || value > UINT16_MAX)
176 return false;
177 out = (uint16_t)value;
178 return true;
179 case ser::serialization_type_id::s32:
180 if (value < INT32_MIN || value > INT32_MAX)
181 return false;
182 out = (uint32_t)value;
183 return true;
184 case ser::serialization_type_id::u32:
185 if (value < 0 || (uint64_t)value > UINT32_MAX)
186 return false;
187 out = (uint32_t)value;
188 return true;
189 case ser::serialization_type_id::s64:
190 out = (uint64_t)value;
191 return true;
192 case ser::serialization_type_id::u64:
193 if (value < 0)
194 return false;
195 out = (uint64_t)value;
196 return true;
197 default:
198 return false;
199 }
200 }
201
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 {
210 switch (type) {
211 case ser::serialization_type_id::s8:
212 case ser::serialization_type_id::u8: {
213 if (valueSize != sizeof(uint8_t))
214 return false;
215 const auto narrowed = (uint8_t)value;
216 memcpy(pData, &narrowed, sizeof(narrowed));
217 return true;
218 }
219 case ser::serialization_type_id::s16:
220 case ser::serialization_type_id::u16: {
221 if (valueSize != sizeof(uint16_t))
222 return false;
223 const auto narrowed = (uint16_t)value;
224 memcpy(pData, &narrowed, sizeof(narrowed));
225 return true;
226 }
227 case ser::serialization_type_id::s32:
228 case ser::serialization_type_id::u32: {
229 if (valueSize != sizeof(uint32_t))
230 return false;
231 const auto narrowed = (uint32_t)value;
232 memcpy(pData, &narrowed, sizeof(narrowed));
233 return true;
234 }
235 case ser::serialization_type_id::s64:
236 case ser::serialization_type_id::u64:
237 if (valueSize != sizeof(uint64_t))
238 return false;
239 memcpy(pData, &value, sizeof(value));
240 return true;
241 default:
242 return false;
243 }
244 }
245
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:
256 return true;
257 case RuntimeTypeKind::Opaque:
258 return item.opaque_adapter() != nullptr;
259 default:
260 return false;
261 }
262 }
263
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;
270 }
271
276 GAIA_NODISCARD inline ser::json_str
277 make_runtime_json_child_path(ser::json_str_view parent, ser::json_str_view child) {
278 if (parent.empty())
279 return ser::json_str(child);
280 if (child.empty())
281 return ser::json_str(parent);
282
283 ser::json_str path;
284 path.reserve(parent.size() + 1 + child.size());
285 path.append(parent.data(), parent.size());
286 path.append('.');
287 path.append(child.data(), child.size());
288 return path;
289 }
290
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);
297 path.append('[');
298 char idx[16]{};
299 const int len = snprintf(idx, sizeof(idx), "%u", index);
300 if (len > 0)
301 path.append(idx, (uint32_t)len);
302 path.append(']');
303 return path;
304 }
305
310 GAIA_NODISCARD inline bool count_runtime_json_array_elements(ser::ser_json& reader, uint32_t& outCount) {
311 outCount = 0;
312 reader.ws();
313 const auto* start = reader.pos();
314 const auto* end = reader.end();
315 if (start == nullptr || end == nullptr || start > end)
316 return false;
317
318 ser::ser_json counter(start, (uint32_t)(end - start));
319 if (!counter.expect('['))
320 return false;
321 counter.ws();
322 if (counter.consume(']'))
323 return true;
324
325 while (true) {
326 if (!counter.skip_value())
327 return false;
328 ++outCount;
329 if (counter.consume(','))
330 continue;
331 return counter.consume(']');
332 }
333 }
334
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);
348
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,
365 depth + 1);
366
367 bool ok = true;
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) &&
373 ok;
374 }
375 writer.end_array();
376 return ok;
377 }
378
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)) {
393 writer.value_null();
394 return false;
395 }
396
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()) {
400 writer.value_null();
401 return false;
402 }
403
404 return write_runtime_json_field_data(pCache, field, layout, pBase + field.offset, writer, policy, depth);
405 }
406
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) {
419 writer.value_null();
420 return false;
421 }
422
423 bool ok = true;
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;
432 }
433 writer.end_object();
434 return ok;
435 }
436
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) {
441 writer.value_null();
442 return false;
443 }
444
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) {
452 writer.value_null();
453 return false;
454 }
455
456 bool ok = true;
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) &&
462 ok;
463 }
464 writer.end_array();
465 return ok;
466 }
467
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) {
473 writer.value_null();
474 return false;
475 }
476 RuntimeSequenceScope sequence{typeEntity, pData, nullptr, valueSize};
477 uint32_t elemCount = 0;
478 if (!adapter->count(adapter->ctx, sequence, elemCount)) {
479 writer.value_null();
480 return false;
481 }
482 if (pType->jsonEncoding == RuntimeJsonEncoding::Utf8String) {
483 if (elementType != Char8) {
484 writer.value_null();
485 return false;
486 }
487
488 ser::json_str text;
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)) {
495 writer.value_null();
496 return false;
497 }
498 text.append(*(const char*)element.data);
499 }
500 writer.value_string(text.empty() ? "" : text.data(), text.size());
501 return true;
502 }
503
504 bool ok = true;
505 writer.begin_array();
506 GAIA_FOR(elemCount) {
507 RuntimeSequenceElement element{};
508 element.type = elementType;
509 if (!adapter->element(adapter->ctx, sequence, i, element)) {
510 writer.value_null();
511 ok = false;
512 continue;
513 }
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,
518 depth + 1) &&
519 ok;
520 }
521 writer.end_array();
522 return ok;
523 }
524
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) {
530 writer.value_null();
531 return false;
532 }
533 RuntimeOpaqueScope opaque{typeEntity, pData, nullptr, valueSize};
534 RuntimeOpaqueValue projected{};
535 projected.type = semanticType;
536 if (!adapter->project(adapter->ctx, opaque, projected)) {
537 writer.value_null();
538 return false;
539 }
540 if (projected.type == EntityBad)
541 projected.type = semanticType;
542 if (projected.type != semanticType || projected.data == nullptr) {
543 writer.value_null();
544 return false;
545 }
546 return write_runtime_json_value(
547 pCache, pSemanticType, projected.type, (const uint8_t*)projected.data, projected.size, writer, policy,
548 depth + 1);
549 }
550
551 if (pType != nullptr && pType->typeKind == RuntimeTypeKind::Struct)
552 return write_runtime_json_struct(pCache, *pType, pData, writer, policy, depth + 1);
553
554 ser::serialization_type_id type = ser::serialization_type_id::ignore;
555 if (pType != nullptr) {
556 if (!runtime_type_json_type(*pType, type)) {
557 writer.value_null();
558 return false;
559 }
560 } else if (!runtime_primitive_serialization_type(typeEntity, type)) {
561 writer.value_null();
562 return false;
563 }
564
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());
575 return true;
576 }
577 }
578 }
579
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;
590 }
591
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;
604 }
605 }
606 writer.end_array();
607 return true;
608 }
609 }
610
611 return ser::detail::write_runtime_field_json(writer, pData, type, valueSize);
612 }
613
615 struct RuntimeJsonReadContext final {
617 const ComponentCache* pCache;
619 ser::ser_json& reader;
621 ser::JsonDiagnostics& diagnostics;
623 const ser::RuntimeJsonPolicy& policy;
625 bool& ok;
626 };
627
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);
640
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('['))
656 return false;
657
658 GAIA_FOR(elemCount) {
659 if (i > 0 && !reader.expect(','))
660 return false;
661
662 const auto elemPath = make_runtime_json_element_path(path, i);
663 auto* pElemData = pData + (uintptr_t)elemSize * i;
664
665 if (!read_runtime_json_value(ctx, pType, typeEntity, pElemData, elemSize, elemPath, depth))
666 return false;
667 }
668
669 return reader.expect(']');
670 }
671
680 template <typename T>
681 inline bool
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))
684 return false;
685
686 T result{};
687 memcpy(&result, data, sizeof(result));
688 value = (double)result;
689 return true;
690 }
691
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;
704
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);
715 }
716
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);
725 }
726
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);
735 }
736
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{};
750
751 if (!resolve_runtime_json_field_layout(ctx.pCache, field, layout)) {
752 ctx.ok = false;
753 warn_runtime_json(
754 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
755 "Runtime field uses an unknown reflected type.");
756 return reader.skip_value();
757 }
758
759 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
760 const auto end = (uint64_t)field.offset + fieldSize64;
761
762 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
763 ctx.ok = false;
764 warn_runtime_json(
765 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
766 "Runtime field points outside component size or uses an unsupported type.");
767 return reader.skip_value();
768 }
769
770 auto* pFieldData = pBase + field.offset;
771 const bool ranged = ctx.policy.enforceRanges &&
772 (field.flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) != 0;
773 // A ranged field is read into a copy first, so a value out of range never reaches storage.
774 cnt::darray_ext<uint8_t, 64> staged;
775 auto* pTarget = pFieldData;
776 if (ranged) {
777 staged.resize((uint32_t)fieldSize64);
778 memcpy(staged.data(), pFieldData, (size_t)fieldSize64);
779 pTarget = staged.data();
780 }
781
782 const bool parsed =
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)
790 return parsed;
791
792 GAIA_FOR(layout.elemCount) {
793 double number = 0.0;
794 if (!runtime_patch_numeric_value(
795 layout.pType, layout.type, pTarget + (uintptr_t)layout.elemSize * i, layout.elemSize, number)) {
796 ctx.ok = false;
797 fail_runtime_json(
798 ctx.diagnostics, ser::JsonDiagReason::UnsupportedPatchValue, path,
799 "Runtime field range applies to a non-numeric field.");
800 return true;
801 }
802 if (((field.flags & RuntimeFieldFlag_HasMinimum) != 0 && number < field.minimum) ||
803 ((field.flags & RuntimeFieldFlag_HasMaximum) != 0 && number > field.maximum)) {
804 ctx.ok = false;
805 fail_runtime_json(
806 ctx.diagnostics, ser::JsonDiagReason::RangeViolation, path,
807 "Runtime field value is outside the authored field range.");
808 return true;
809 }
810 }
811 memcpy(pFieldData, pTarget, (size_t)fieldSize64);
812 return true;
813 }
814
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()) {
824 if (seen[i] != 0)
825 continue;
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()));
829 ctx.ok = false;
830 fail_runtime_json(ctx.diagnostics, ser::JsonDiagReason::MissingField, fieldPath, "Runtime field is missing.");
831 }
832 }
833
841 inline bool read_runtime_json_struct(
842 RuntimeJsonReadContext& ctx, const ComponentCacheItem& item, uint8_t* pData, ser::json_str_view path,
843 uint32_t depth) {
844 auto& reader = ctx.reader;
845
846 if (depth >= RuntimeJsonMaxDepth) {
847 ctx.ok = false;
848 warn_runtime_json(
849 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime JSON nesting is too deep.");
850 return reader.skip_value();
851 }
852
853 if (reader.parse_null()) {
854 ctx.ok = false;
855 warn_runtime_json(
856 ctx.diagnostics, ser::JsonDiagReason::NullComponentPayload, path, "Runtime object payload is null.");
857 return true;
858 }
859
860 if (!reader.expect('{'))
861 return false;
862
863 // Only a strict import pays for tracking which fields the object names.
864 cnt::darray_ext<uint8_t, 64> seen;
865 if (ctx.policy.requireAllFields)
866 seen.resize(item.field_count(), 0);
867
868 reader.ws();
869 if (reader.consume('}')) {
870 if (ctx.policy.requireAllFields)
871 report_missing_runtime_json_fields(ctx, item, seen, path);
872 return true;
873 }
874
875 while (true) {
876 ser::json_str_view key;
877 bool keyFromScratch = false;
878 if (!reader.parse_string_view(key, &keyFromScratch))
879 return false;
880
881 // Escaped names are temporary, so keep a copy while reading the field value.
882 ser::json_str keyStorage;
883 if (keyFromScratch) {
884 keyStorage.assign(key.data(), key.size());
885 key = keyStorage;
886 }
887
888 if (!reader.expect(':'))
889 return false;
890
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);
893
894 if (pField == nullptr) {
895 ctx.ok = false;
896 warn_runtime_json(ctx.diagnostics, ser::JsonDiagReason::UnknownField, fieldPath, "Unknown runtime field.");
897 if (!reader.skip_value())
898 return false;
899 } else {
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))
903 return false;
904 }
905
906 reader.ws();
907 if (reader.consume(','))
908 continue;
909 if (reader.consume('}'))
910 break;
911 return false;
912 }
913
914 if (ctx.policy.requireAllFields)
915 report_missing_runtime_json_fields(ctx, item, seen, path);
916 return true;
917 }
918
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;
934
935 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
936 (uint64_t)elemSize * elemCount != valueSize) {
937 ctx.ok = false;
938 warn_runtime_json(
939 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
940 "Runtime array payload uses an invalid reflected element type.");
941 return ctx.reader.skip_value();
942 }
943
944 return read_runtime_json_elements(ctx, pElementType, elementType, pData, elemSize, elemCount, path, depth + 1);
945 }
946
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;
960
961 if (item.element_type() != Char8 || adapter == nullptr || adapter->resize == nullptr ||
962 adapter->element == nullptr || !ctx.reader.parse_string_view(text)) {
963 ctx.ok = false;
964 warn_runtime_json(
965 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
966 "Runtime UTF-8 string cannot be resized or traversed.");
967 return false;
968 }
969
970 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
971
972 if (!adapter->resize(adapter->ctx, sequence, text.size())) {
973 ctx.ok = false;
974 warn_runtime_json(
975 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
976 "Runtime UTF-8 string adapter rejected the requested byte count.");
977 return true;
978 }
979
980 // Escaped text is temporary, so copy its bytes before reading anything else.
981 // Each byte is stored as one Char8 element.
982 GAIA_FOR(text.size()) {
983 RuntimeSequenceElement element{};
984 element.type = Char8;
985
986 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData == nullptr ||
987 element.size != sizeof(char) || (element.type != EntityBad && element.type != Char8)) {
988 ctx.ok = false;
989 warn_runtime_json(
990 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
991 "Runtime UTF-8 string adapter rejected an element.");
992 return true;
993 }
994
995 *(char*)element.mutData = text.data()[i];
996
997 if (adapter->commitElement != nullptr && !adapter->commitElement(adapter->ctx, sequence, element)) {
998 ctx.ok = false;
999 warn_runtime_json(
1000 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1001 "Runtime UTF-8 string adapter rejected element commit.");
1002 return true;
1003 }
1004 }
1005
1006 return true;
1007 }
1008
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);
1023
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;
1028
1029 // Count first because the adapter must resize the sequence before it can return its elements.
1030 if (adapter == nullptr || adapter->resize == nullptr || adapter->element == nullptr ||
1031 !count_runtime_json_array_elements(ctx.reader, elemCount)) {
1032 ctx.ok = false;
1033 warn_runtime_json(
1034 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1035 "Runtime vector payload cannot be resized or traversed.");
1036 return ctx.reader.skip_value();
1037 }
1038
1039 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
1040
1041 if (!adapter->resize(adapter->ctx, sequence, elemCount)) {
1042 ctx.ok = false;
1043 warn_runtime_json(
1044 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1045 "Runtime vector adapter rejected the requested element count.");
1046 return ctx.reader.skip_value();
1047 }
1048
1049 if (!ctx.reader.expect('['))
1050 return false;
1051
1052 GAIA_FOR(elemCount) {
1053 if (i > 0 && !ctx.reader.expect(','))
1054 return false;
1055
1056 RuntimeSequenceElement element{};
1057 element.type = elementType;
1058
1059 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData == nullptr) {
1060 ctx.ok = false;
1061 warn_runtime_json(
1062 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1063 "Runtime vector adapter rejected an element.");
1064 return ctx.reader.skip_value();
1065 }
1066
1067 if (element.type == EntityBad)
1068 element.type = elementType;
1069
1070 const auto elemPath = make_runtime_json_element_path(path, i);
1071
1072 if (!read_runtime_json_value(
1073 ctx, pElementType, element.type, (uint8_t*)element.mutData, element.size, elemPath, depth + 1))
1074 return false;
1075 }
1076
1077 return ctx.reader.expect(']');
1078 }
1079
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);
1095
1096 if (adapter == nullptr || adapter->project == nullptr || pSemanticType == nullptr) {
1097 ctx.ok = false;
1098 warn_runtime_json(
1099 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1100 "Runtime opaque payload cannot be projected.");
1101 return ctx.reader.skip_value();
1102 }
1103
1104 RuntimeOpaqueScope opaque{typeEntity, pData, pData, valueSize};
1105 RuntimeOpaqueValue projected{};
1106 projected.type = semanticType;
1107
1108 if (!adapter->project(adapter->ctx, opaque, projected) || projected.mutData == nullptr) {
1109 ctx.ok = false;
1110 warn_runtime_json(
1111 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1112 "Runtime opaque adapter rejected projection.");
1113 return ctx.reader.skip_value();
1114 }
1115
1116 // EntityBad means "use the type from the schema."
1117 if (projected.type == EntityBad)
1118 projected.type = semanticType;
1119
1120 if (projected.type != semanticType) {
1121 ctx.ok = false;
1122 warn_runtime_json(
1123 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1124 "Runtime opaque adapter projected an unexpected semantic type.");
1125 return ctx.reader.skip_value();
1126 }
1127
1128 const bool parsed = read_runtime_json_value(
1129 ctx, pSemanticType, projected.type, (uint8_t*)projected.mutData, projected.size, path, depth + 1);
1130
1131 // The adapter may be using a temporary value. Commit it only if the whole read succeeded.
1132 if (parsed && adapter->commit != nullptr && ctx.ok && !adapter->commit(adapter->ctx, opaque, projected)) {
1133 ctx.ok = false;
1134 warn_runtime_json(
1135 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime opaque adapter rejected commit.");
1136 }
1137
1138 return parsed;
1139 }
1140
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))
1154 return false;
1155
1156 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1157 uint64_t constantBits = 0;
1158
1159 if (pConstant == nullptr || !runtime_json_constant_bits(type, pConstant->value, constantBits) ||
1160 !runtime_json_write_integer_bits(pData, type, valueSize, constantBits)) {
1161 ctx.ok = false;
1162 warn_runtime_json(
1163 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1164 "Runtime enum symbol is unknown or incompatible with its underlying type.");
1165 }
1166
1167 return true;
1168 }
1169
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;
1182
1183 if (!reader.expect('['))
1184 return false;
1185
1186 uint64_t valueBits = 0;
1187 bool symbolsOk = true;
1188
1189 reader.ws();
1190 if (!reader.consume(']')) {
1191 while (true) {
1192 ser::json_str_view symbol;
1193 if (!reader.parse_string_view(symbol))
1194 return false;
1195
1196 // Keep reading after a bad name so the next JSON value starts in the right place.
1197 if (symbolsOk) {
1198 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1199 uint64_t flagBits = 0;
1200
1201 if (pConstant == nullptr) {
1202 symbolsOk = false;
1203 ctx.ok = false;
1204 warn_runtime_json(
1205 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1206 "Runtime bitmask symbol is unknown.");
1207 } else if (
1208 !runtime_json_constant_bits(type, pConstant->value, flagBits) || flagBits == 0 ||
1209 (flagBits & (flagBits - 1)) != 0 || (valueBits & flagBits) != 0) {
1210 symbolsOk = false;
1211 ctx.ok = false;
1212 warn_runtime_json(
1213 ctx.diagnostics, ser::JsonDiagReason::InvalidRuntimeConstant, path,
1214 "Runtime bitmask symbol is not a distinct one-bit flag.");
1215 } else {
1216 valueBits |= flagBits;
1217 }
1218 }
1219
1220 reader.ws();
1221 if (reader.consume(','))
1222 continue;
1223 if (reader.consume(']'))
1224 break;
1225 return false;
1226 }
1227 }
1228
1229 // Leave the old value alone if any name is invalid.
1230 if (symbolsOk && !runtime_json_write_integer_bits(pData, type, valueSize, valueBits)) {
1231 ctx.ok = false;
1232 warn_runtime_json(
1233 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1234 "Runtime bitmask symbols are incompatible with the underlying field size.");
1235 }
1236
1237 return true;
1238 }
1239
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;
1252
1253 if (pType != nullptr) {
1254 if (!runtime_type_json_type(*pType, type)) {
1255 ctx.ok = false;
1256 warn_runtime_json(
1257 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1258 "Runtime field uses an unsupported reflected type.");
1259 return ctx.reader.skip_value();
1260 }
1261 } else if (!runtime_primitive_serialization_type(typeEntity, type)) {
1262 ctx.ok = false;
1263 warn_runtime_json(
1264 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime field type is unknown.");
1265 return ctx.reader.skip_value();
1266 }
1267
1268 ctx.reader.ws();
1269 // Names are optional. Numbers still work for enum and bitmask values.
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);
1276
1277 // Valid JSON can still lose data when it is converted to the field's type.
1278 bool fieldOk = true;
1279 if (!ser::detail::read_runtime_field_json(ctx.reader, pData, type, valueSize, fieldOk)) {
1280 // The import stops here, so name the field the author has to fix.
1281 fail_runtime_json(
1282 ctx.diagnostics, ser::JsonDiagReason::InvalidJson, path,
1283 "Runtime field value is not the JSON kind its type needs.");
1284 return false;
1285 }
1286
1287 if (!fieldOk) {
1288 ctx.ok = false;
1289 warn_runtime_json(
1290 ctx.diagnostics, ser::JsonDiagReason::FieldValueAdjusted, path,
1291 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
1292 }
1293
1294 return true;
1295 }
1296
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) {
1301 ctx.ok = false;
1302 warn_runtime_json(
1303 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime JSON nesting is too deep.");
1304 return ctx.reader.skip_value();
1305 }
1306
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);
1317 default:
1318 break;
1319 }
1320 }
1321
1322 return read_runtime_json_scalar(ctx, pType, typeEntity, pData, valueSize, path);
1323 }
1324 } // namespace detail
1326
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)
1338 return false;
1339
1340 return detail::write_runtime_json_value(
1341 item.owner_cache(), &item, item.entity, reinterpret_cast<const uint8_t*>(pComponentData), item.comp.size(),
1342 writer, policy, 0);
1343 }
1344
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();
1354 }
1355
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)
1370 return false;
1371
1372 if (reader.parse_null()) {
1373 detail::warn_runtime_json(
1374 diagnostics, ser::JsonDiagReason::NullComponentPayload, componentPath, "Component payload is null.");
1375 return true;
1376 }
1377
1378 if (detail::runtime_json_is_direct_value(item)) {
1379 bool ok = true;
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);
1383 }
1384
1385 bool rawFound = false;
1386 bool fieldFound = false;
1387 bool ok = true;
1388
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);
1392
1393 if (!reader.expect('{'))
1394 return false;
1395
1396 // Only a strict import pays for tracking which fields the object names.
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);
1401
1402 reader.ws();
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);
1409 return true;
1410 }
1411
1412 while (true) {
1413 ser::json_str_view key;
1414 bool keyFromScratch = false;
1415 if (!reader.parse_string_view(key, &keyFromScratch))
1416 return false;
1417
1418 // Escaped names are temporary, so keep a copy while reading the component value.
1419 ser::json_str keyStorage;
1420 if (keyFromScratch) {
1421 keyStorage.assign(key.data(), key.size());
1422 key = keyStorage;
1423 }
1424
1425 if (!reader.expect(':'))
1426 return false;
1427
1428 const auto fieldPath = detail::make_runtime_json_child_path(componentPath, key);
1429
1430 if (key == "$raw") {
1431 rawFound = true;
1432 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
1433 return false;
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) {
1437 ok = false;
1438 detail::warn_runtime_json(
1439 diagnostics, ser::JsonDiagReason::UnknownField, fieldPath, "Unknown runtime field.");
1440 if (!reader.skip_value())
1441 return false;
1442 } else {
1443 fieldFound = true;
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))
1447 return false;
1448 }
1449 } else {
1450 ok = false;
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())
1455 return false;
1456 }
1457
1458 reader.ws();
1459 if (reader.consume(','))
1460 continue;
1461 if (reader.consume('}'))
1462 break;
1463 return false;
1464 }
1465
1466 if (rawFound) {
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.");
1472 return true;
1473 }
1474
1475 // Apply $raw last. If both forms are present, $raw wins regardless of key order.
1476 auto s = ser::make_serializer(rawPayload);
1477 s.seek(0);
1478 item.load(s, pBase, 0, 1, 1);
1479 }
1480
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.");
1485 // A $raw payload sets every byte, so it leaves no field missing.
1486 if (requireAllFields && !rawFound)
1487 detail::report_missing_runtime_json_fields(ctx, item, seen, componentPath);
1488
1489 return true;
1490 }
1491
1498 inline bool
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);
1502
1503 ok = !diagnostics.has_issues();
1504 return parsed;
1505 }
1506
1507 inline bool
1508 World::save_json(ser::ser_json& writer, ser::JsonSaveFlags flags, const ser::RuntimeJsonPolicy& policy) const {
1509 bool ok = true;
1510 const bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
1511 const bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
1512
1513 auto write_component_key = [&](Entity component, const ComponentCacheItem* pItem) {
1514 if (!component.pair()) {
1515 // A plain entity used as a tag has no component cache item, so it goes by its entity name
1516 const auto componentName = pItem != nullptr ? comp_cache().symbol_name(*pItem) : name(component);
1517 if (componentName.empty())
1518 return false;
1519 writer.key(componentName.data(), componentName.size());
1520 return true;
1521 }
1522
1523 const auto relation = pair_rel(*this, component);
1524 auto relationName = symbol(relation);
1525 if (relationName.empty())
1526 relationName = name(relation);
1527 const auto target = pair_tgt(*this, component);
1528 auto targetName = symbol(target);
1529 if (targetName.empty())
1530 targetName = name(target);
1531 if (relationName.empty() || targetName.empty())
1532 return false;
1533
1534 util::str pairName;
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());
1541 return true;
1542 };
1543
1544 auto write_raw_component = [&](const ComponentCacheItem& item, const uint8_t* pData, uint32_t from, uint32_t to,
1545 uint32_t cap) {
1546 ser::ser_buffer_binary raw;
1547 auto s = ser::make_serializer(raw);
1548 item.save(s, pData, from, to, cap);
1549
1550 writer.begin_object();
1551 writer.key("$raw");
1552 writer.begin_array();
1553 const auto* pRaw = raw.data();
1554 GAIA_FOR(raw.bytes()) writer.value_int(pRaw[i]);
1555 writer.end_array();
1556 writer.end_object();
1557 };
1558
1559 auto write_sparse_component_value = [&](Entity owner, Entity component, const ComponentCacheItem& item) {
1560 const auto* pStore = sparse_component_store_erased(component);
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();
1565 ok = false;
1566 return;
1567 }
1568
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);
1573 else {
1574 writer.value_null();
1575 ok = false;
1576 }
1577 };
1578
1579 // Non-fragmenting ids are absent from archetype component lists. Index their owners once so
1580 // each value can still be emitted inside its owner's normal semantic JSON entry.
1581 EntityArrayMap nonFragmentingSparseByOwner;
1582 for (const auto& [compKey, store]: m_sparseComponentsByComp) {
1583 const auto component = compKey.entity();
1584 if (!component_is_non_fragmenting(component) || store.func_count(store.pStore) == 0)
1585 continue;
1586
1587 cnt::darray<Entity> owners;
1588 store.func_collect_entities(store.pStore, owners);
1589 for (auto owner: owners) {
1590 if (valid(owner))
1591 nonFragmentingSparseByOwner[EntityLookupKey(owner)].push_back(component);
1592 }
1593 }
1594 for (auto& [owner, components]: nonFragmentingSparseByOwner) {
1595 (void)owner;
1596 core::sort(components, core::is_smaller<Entity>{});
1597 }
1598
1599 ser::bin_stream binarySnapshot;
1600 if (includeBinarySnapshot) {
1601 auto s = ser::make_serializer(binarySnapshot);
1602 s.reset();
1603 save_to(s);
1604 }
1605
1606 writer.clear();
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();
1615 {
1616 const auto* pData = (const uint8_t*)binarySnapshot.data();
1617 GAIA_FOR(binarySnapshot.bytes()) writer.value_int(pData[i]);
1618 }
1619 writer.end_array();
1620 }
1621 writer.key("archetypes");
1622 writer.begin_array();
1623
1624 for (const auto* pArchetype: m_archetypes) {
1625 if (pArchetype == nullptr || pArchetype->chunks().empty())
1626 continue;
1627
1628 writer.begin_object();
1629 writer.key("id");
1630 writer.value_int((uint32_t)pArchetype->id());
1631 writer.key("hash");
1632 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
1633
1634 writer.key("components");
1635 writer.begin_array();
1636 {
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());
1641 else
1642 writer.value_string("<unnamed>");
1643 }
1644 }
1645 writer.end_array();
1646
1647 writer.key("entities");
1648 writer.begin_array();
1649 {
1650 for (const auto* pChunk: pArchetype->chunks()) {
1651 if (pChunk == nullptr || pChunk->empty())
1652 continue;
1653
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];
1658
1659 writer.begin_object();
1660 {
1661 writer.key("entity");
1662 {
1663 writer.begin_object();
1664 writer.key("id");
1665 writer.value_int(entity.id());
1666 writer.key("gen");
1667 writer.value_int(entity.gen());
1668 writer.key("pair");
1669 writer.value_bool(entity.pair());
1670 const auto entityName = name(entity);
1671 if (!entityName.empty()) {
1672 writer.key("name");
1673 writer.value_string(entityName.data(), entityName.size());
1674 }
1675 writer.end_object();
1676 }
1677
1678 writer.key("components");
1679 writer.begin_object();
1680 {
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)
1687 ok = false;
1688 }
1689
1690 // Tags have no associated payload.
1691 if (rec.comp.size() == 0) {
1692 writer.value_bool(true);
1693 continue;
1694 }
1695
1696 // Components with a payload are always registered in the component cache
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);
1701 continue;
1702 }
1703
1704 const auto row = i;
1705
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;
1710 } else {
1711 if (allowRawFallback)
1712 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
1713 else {
1714 writer.value_null();
1715 ok = false;
1716 }
1717 }
1718 }
1719
1720 const auto sparseIt = nonFragmentingSparseByOwner.find(EntityLookupKey(entity));
1721 if (sparseIt != nonFragmentingSparseByOwner.end()) {
1722 for (auto component: sparseIt->second) {
1723 const auto* pItem =
1724 component.pair() ? comp_cache().find_pair_payload(component) : comp_cache().find(component);
1725 GAIA_ASSERT(pItem != nullptr);
1726 if (pItem == nullptr || !write_component_key(component, pItem)) {
1727 writer.key("<unnamed>");
1728 ok = false;
1729 }
1730 if (pItem != nullptr)
1731 write_sparse_component_value(entity, component, *pItem);
1732 else
1733 writer.value_null();
1734 }
1735 }
1736 writer.end_object();
1737 }
1738 }
1739 writer.end_object();
1740 }
1741 }
1742 }
1743
1744 writer.end_array();
1745 writer.end_object();
1746 }
1747
1748 writer.end_array();
1749 writer.end_object();
1750 return ok;
1751 }
1752
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();
1757 }
1758
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)
1763 return false;
1764 if (len == 0) {
1765 diagnostics.add(
1766 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson, "$",
1767 "Input JSON length must be provided and non-zero.");
1768 return false;
1769 }
1770
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);
1774 };
1775 auto error = [&](ser::JsonDiagReason reason, ser::json_str_view path, const char* message) {
1776 diagnostics.add(ser::JsonDiagSeverity::Error, reason, path, message);
1777 };
1778
1779 ser::bin_stream binarySnapshot;
1780 bool hasBinarySnapshot = false;
1781
1782 // Validate top-level metadata and collect the optional binary snapshot in one pass.
1783 {
1784 ser::ser_json header(json, dataLen);
1785 if (!header.expect('{')) {
1786 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
1787 return false;
1788 }
1789
1790 bool hasFormat = false;
1791 uint32_t formatValue = 0;
1792
1793 header.ws();
1794 if (!header.consume('}')) {
1795 while (true) {
1796 ser::json_str_view key;
1797 if (!header.parse_string_view(key))
1798 return false;
1799 if (!header.expect(':'))
1800 return false;
1801
1802 if (key == "format") {
1803 double d = 0.0;
1804 if (!header.parse_number(d))
1805 return false;
1806 if (d < 0.0 || d > 4294967295.0)
1807 return false;
1808 const auto v = (uint32_t)d;
1809 if ((double)v != d)
1810 return false;
1811 formatValue = v;
1812 hasFormat = true;
1813 } else if (key == "binary") {
1814 if (hasBinarySnapshot)
1815 return false;
1816 hasBinarySnapshot = true;
1817 if (!ser::detail::parse_json_byte_array(header, binarySnapshot))
1818 return false;
1819 } else {
1820 if (!header.skip_value())
1821 return false;
1822 }
1823
1824 header.ws();
1825 if (header.consume(','))
1826 continue;
1827 if (header.consume('}'))
1828 break;
1829 return false;
1830 }
1831 }
1832
1833 header.ws();
1834 if (!header.eof())
1835 return false;
1836
1837 if (!hasFormat) {
1838 error(ser::JsonDiagReason::MissingFormatField, "$.format", "Missing required 'format' field.");
1839 return false;
1840 }
1841
1842 if (formatValue != WorldSerializerJSONVersion) {
1843 error(
1844 ser::JsonDiagReason::UnsupportedFormatVersion, "$.format",
1845 "Unsupported format version. Expected numeric value 1.");
1846 return false;
1847 }
1848 }
1849
1850 if (hasBinarySnapshot)
1851 return load(binarySnapshot);
1852
1853 // Fallback: semantic world JSON parser.
1854 ser::ser_json jp(json, dataLen);
1855
1856 struct CompDataLoc {
1857 uint8_t* pBase = nullptr;
1858 uint32_t row = 0;
1859 };
1860
1861 auto locate_component_data = [&](Entity entity, Entity component) {
1862 CompDataLoc loc{};
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)) {
1865 auto& store = sparse_component_store_erased_mut(component, *pItem);
1866 loc.pBase = (uint8_t*)store.func_mut(store.pStore, entity);
1867 return loc;
1868 }
1869
1870 auto& ec = fetch(entity);
1871 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
1872 if (compIdx == BadIndex)
1873 return loc;
1874
1875 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
1876 loc.row = ec.row;
1877 return loc;
1878 };
1879
1880 auto parse_and_apply_component_value = [&](Entity entity, Entity component, const ComponentCacheItem& item,
1881 ser::json_str_view compPath) -> bool {
1882 jp.ws();
1883 if (jp.eof())
1884 return false;
1885
1886 if (jp.parse_null()) {
1887 warn(
1888 ser::JsonDiagReason::NullComponentPayload, compPath,
1889 "Null component payload is ignored in semantic mode.");
1890 return true;
1891 }
1892
1893 if (!has_direct(entity, component)) {
1894 if (gaia::ecs::component_uses_sparse_storage(item.comp)) {
1895 auto& store = sparse_component_store_erased_mut(component, item);
1896 (void)store.func_add(store.pStore, entity);
1897 finish_sparse_component_add_inter(entity, component, sparse_storage_mode(component));
1898 } else if (component.pair())
1899 add(entity, Pair(pair_rel(*this, component), pair_tgt(*this, component)));
1900 else
1901 add(entity, component);
1902 }
1903
1904 const auto loc = locate_component_data(entity, component);
1905 if (loc.pBase == nullptr) {
1906 warn(
1907 ser::JsonDiagReason::MissingComponentStorage, compPath,
1908 "Component storage is unavailable on the target entity.");
1909 return jp.skip_value();
1910 }
1911
1912 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
1913
1914 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, policy, compPath))
1915 return false;
1916
1917 return true;
1918 };
1919
1920 auto parse_entity_meta = [&](bool& isPair, ser::json_str& nameOut) -> bool {
1921 if (!jp.expect('{'))
1922 return false;
1923
1924 jp.ws();
1925 if (jp.consume('}'))
1926 return true;
1927
1928 while (true) {
1929 ser::json_str_view key;
1930 if (!jp.parse_string_view(key))
1931 return false;
1932 if (!jp.expect(':'))
1933 return false;
1934
1935 if (key == "pair") {
1936 if (!jp.parse_bool(isPair))
1937 return false;
1938 } else if (key == "name") {
1939 if (!jp.parse_string(nameOut))
1940 return false;
1941 } else {
1942 if (!jp.skip_value())
1943 return false;
1944 }
1945
1946 jp.ws();
1947 if (jp.consume(','))
1948 continue;
1949 if (jp.consume('}'))
1950 break;
1951 return false;
1952 }
1953
1954 return true;
1955 };
1956
1957 auto parse_components_for_entity = [&](Entity& entity, bool& created, bool isPair,
1958 const ser::json_str& entityName) -> bool {
1959 if (!jp.expect('{'))
1960 return false;
1961
1962 jp.ws();
1963 if (jp.consume('}'))
1964 return true;
1965
1966 while (true) {
1967 ser::json_str_view compName;
1968 bool compNameFromScratch = false;
1969 if (!jp.parse_string_view(compName, &compNameFromScratch))
1970 return false;
1971
1972 // Escaped names are temporary, so keep a copy while reading the component value.
1973 ser::json_str compNameStorage;
1974 if (compNameFromScratch) {
1975 compNameStorage.assign(compName.data(), compName.size());
1976 compName = compNameStorage;
1977 }
1978
1979 if (!jp.expect(':'))
1980 return false;
1981
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()});
1985
1986 const ComponentCacheItem* pItem = nullptr;
1987 if (componentEntity.pair())
1988 pItem = comp_cache().find_pair_payload(componentEntity);
1989 else if (componentEntity != EntityBad)
1990 pItem = comp_cache().find(componentEntity);
1991
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);
1997
1998 if (isPair || nameIsInternal || itemIsInternal || relationIsInternal) {
1999 if (!jp.skip_value())
2000 return false;
2001 } else {
2002 if (pItem == nullptr) {
2003 warn(
2004 ser::JsonDiagReason::UnknownComponent, compName,
2005 "Component is not registered in the component cache.");
2006 if (!jp.skip_value())
2007 return false;
2008 } else if (pItem->comp.size() == 0) {
2009 // Ignore tag-only components in semantic mode for now.
2010 warn(
2011 ser::JsonDiagReason::TagComponentUnsupported, compName,
2012 "Tag-only component semantic JSON loading is currently unsupported.");
2013 if (!jp.skip_value())
2014 return false;
2015 } else {
2016 if (!created) {
2017 entity = add();
2018 created = true;
2019
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());
2024 else
2025 warn(
2026 ser::JsonDiagReason::DuplicateEntityName, "entity.name",
2027 "Entity name already exists; keeping existing mapping.");
2028 }
2029 }
2030
2031 if (!parse_and_apply_component_value(entity, componentEntity, *pItem, compName))
2032 return false;
2033 }
2034 }
2035
2036 jp.ws();
2037 if (jp.consume(','))
2038 continue;
2039 if (jp.consume('}'))
2040 break;
2041 return false;
2042 }
2043
2044 return true;
2045 };
2046
2047 auto parse_entity_entry = [&]() -> bool {
2048 if (!jp.expect('{')) {
2049 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
2050 return false;
2051 }
2052
2053 bool isPair = false;
2054 ser::json_str entityName;
2055 Entity entity = EntityBad;
2056 bool created = false;
2057
2058 jp.ws();
2059 if (jp.consume('}'))
2060 return true;
2061
2062 while (true) {
2063 ser::json_str_view key;
2064 if (!jp.parse_string_view(key))
2065 return false;
2066 if (!jp.expect(':'))
2067 return false;
2068
2069 if (key == "entity") {
2070 if (!parse_entity_meta(isPair, entityName))
2071 return false;
2072 } else if (key == "components") {
2073 if (!parse_components_for_entity(entity, created, isPair, entityName))
2074 return false;
2075 } else {
2076 if (!jp.skip_value())
2077 return false;
2078 }
2079
2080 jp.ws();
2081 if (jp.consume(','))
2082 continue;
2083 if (jp.consume('}'))
2084 break;
2085 return false;
2086 }
2087
2088 return true;
2089 };
2090
2091 auto parse_archetypes = [&]() -> bool {
2092 if (!jp.expect('['))
2093 return false;
2094
2095 jp.ws();
2096 if (jp.consume(']'))
2097 return true;
2098
2099 while (true) {
2100 if (!jp.expect('{'))
2101 return false;
2102
2103 jp.ws();
2104 if (!jp.consume('}')) {
2105 while (true) {
2106 ser::json_str_view key;
2107 if (!jp.parse_string_view(key))
2108 return false;
2109 if (!jp.expect(':'))
2110 return false;
2111
2112 if (key == "entities") {
2113 if (!jp.expect('['))
2114 return false;
2115
2116 jp.ws();
2117 if (!jp.consume(']')) {
2118 while (true) {
2119 if (!parse_entity_entry())
2120 return false;
2121
2122 jp.ws();
2123 if (jp.consume(','))
2124 continue;
2125 if (jp.consume(']'))
2126 break;
2127 return false;
2128 }
2129 }
2130 } else {
2131 if (!jp.skip_value())
2132 return false;
2133 }
2134
2135 jp.ws();
2136 if (jp.consume(','))
2137 continue;
2138 if (jp.consume('}'))
2139 break;
2140 return false;
2141 }
2142 }
2143
2144 jp.ws();
2145 if (jp.consume(','))
2146 continue;
2147 if (jp.consume(']'))
2148 break;
2149 return false;
2150 }
2151
2152 return true;
2153 };
2154
2155 if (!jp.expect('{'))
2156 return false;
2157
2158 bool hasArchetypes = false;
2159 jp.ws();
2160 if (!jp.consume('}')) {
2161 while (true) {
2162 ser::json_str_view key;
2163 if (!jp.parse_string_view(key))
2164 return false;
2165 if (!jp.expect(':'))
2166 return false;
2167
2168 if (key == "archetypes") {
2169 hasArchetypes = true;
2170 if (!parse_archetypes())
2171 return false;
2172 } else {
2173 if (!jp.skip_value())
2174 return false;
2175 }
2176
2177 jp.ws();
2178 if (jp.consume(','))
2179 continue;
2180 if (jp.consume('}'))
2181 break;
2182 return false;
2183 }
2184 }
2185
2186 jp.ws();
2187 if (!jp.eof())
2188 return false;
2189 if (!hasArchetypes) {
2190 error(ser::JsonDiagReason::MissingArchetypesSection, "$.archetypes", "Missing required 'archetypes' section.");
2191 return false;
2192 }
2193
2194 return true;
2195 }
2196
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();
2201 }
2202
2203 inline bool
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);
2206 }
2207
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();
2212 }
2213 } // namespace ecs
2214} // namespace gaia
2215
2216#endif
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