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 auto len = (uint32_t)snprintf(idx, sizeof(idx), "%u", index);
300 path.append(idx, len);
301 path.append(']');
302 return path;
303 }
304
309 GAIA_NODISCARD inline bool count_runtime_json_array_elements(ser::ser_json& reader, uint32_t& outCount) {
310 outCount = 0;
311 reader.ws();
312 const auto* start = reader.pos();
313 const auto* end = reader.end();
314 if (start == nullptr || end == nullptr || start > end)
315 return false;
316
317 ser::ser_json counter(start, (uint32_t)(end - start));
318 if (!counter.expect('['))
319 return false;
320 counter.ws();
321 if (counter.consume(']'))
322 return true;
323
324 while (true) {
325 if (!counter.skip_value())
326 return false;
327 ++outCount;
328 if (counter.consume(','))
329 continue;
330 return counter.consume(']');
331 }
332 }
333
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);
347
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)) {
362 writer.value_null();
363 return false;
364 }
365
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()) {
369 writer.value_null();
370 return false;
371 }
372
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);
378
379 bool ok = true;
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) &&
385 ok;
386 }
387 writer.end_array();
388 return ok;
389 }
390
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) {
403 writer.value_null();
404 return false;
405 }
406
407 bool ok = true;
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;
416 }
417 writer.end_object();
418 return ok;
419 }
420
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) {
425 writer.value_null();
426 return false;
427 }
428
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) {
436 writer.value_null();
437 return false;
438 }
439
440 bool ok = true;
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) &&
446 ok;
447 }
448 writer.end_array();
449 return ok;
450 }
451
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) {
457 writer.value_null();
458 return false;
459 }
460 RuntimeSequenceScope sequence{typeEntity, pData, nullptr, valueSize};
461 uint32_t elemCount = 0;
462 if (!adapter->count(adapter->ctx, sequence, elemCount)) {
463 writer.value_null();
464 return false;
465 }
466 if (pType->jsonEncoding == RuntimeJsonEncoding::Utf8String) {
467 if (elementType != Char8) {
468 writer.value_null();
469 return false;
470 }
471
472 ser::json_str text;
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)) {
479 writer.value_null();
480 return false;
481 }
482 text.append(*(const char*)element.data);
483 }
484 writer.value_string(text.empty() ? "" : text.data(), text.size());
485 return true;
486 }
487
488 bool ok = true;
489 writer.begin_array();
490 GAIA_FOR(elemCount) {
491 RuntimeSequenceElement element{};
492 element.type = elementType;
493 if (!adapter->element(adapter->ctx, sequence, i, element)) {
494 writer.value_null();
495 ok = false;
496 continue;
497 }
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,
502 depth + 1) &&
503 ok;
504 }
505 writer.end_array();
506 return ok;
507 }
508
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) {
514 writer.value_null();
515 return false;
516 }
517 RuntimeOpaqueScope opaque{typeEntity, pData, nullptr, valueSize};
518 RuntimeOpaqueValue projected{};
519 projected.type = semanticType;
520 if (!adapter->project(adapter->ctx, opaque, projected)) {
521 writer.value_null();
522 return false;
523 }
524 if (projected.type == EntityBad)
525 projected.type = semanticType;
526 if (projected.type != semanticType || projected.data == nullptr) {
527 writer.value_null();
528 return false;
529 }
530 return write_runtime_json_value(
531 pCache, pSemanticType, projected.type, (const uint8_t*)projected.data, projected.size, writer, policy,
532 depth + 1);
533 }
534
535 if (pType != nullptr && pType->typeKind == RuntimeTypeKind::Struct)
536 return write_runtime_json_struct(pCache, *pType, pData, writer, policy, depth + 1);
537
538 ser::serialization_type_id type = ser::serialization_type_id::ignore;
539 if (pType != nullptr) {
540 if (!runtime_type_json_type(*pType, type)) {
541 writer.value_null();
542 return false;
543 }
544 } else if (!runtime_primitive_serialization_type(typeEntity, type)) {
545 writer.value_null();
546 return false;
547 }
548
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());
559 return true;
560 }
561 }
562 }
563
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;
574 }
575
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;
588 }
589 }
590 writer.end_array();
591 return true;
592 }
593 }
594
595 return ser::detail::write_runtime_field_json(writer, pData, type, valueSize);
596 }
597
599 struct RuntimeJsonReadContext final {
601 const ComponentCache* pCache;
603 ser::ser_json& reader;
605 ser::JsonDiagnostics& diagnostics;
607 const ser::RuntimeJsonPolicy& policy;
609 bool& ok;
610 };
611
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);
624
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('['))
640 return false;
641
642 GAIA_FOR(elemCount) {
643 if (i > 0 && !reader.expect(','))
644 return false;
645
646 const auto elemPath = make_runtime_json_element_path(path, i);
647 auto* pElemData = pData + (uintptr_t)elemSize * i;
648
649 if (!read_runtime_json_value(ctx, pType, typeEntity, pElemData, elemSize, elemPath, depth))
650 return false;
651 }
652
653 return reader.expect(']');
654 }
655
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);
664 }
665
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{};
679
680 if (!resolve_runtime_json_field_layout(ctx.pCache, field, layout)) {
681 ctx.ok = false;
682 warn_runtime_json(
683 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
684 "Runtime field uses an unknown reflected type.");
685 return reader.skip_value();
686 }
687
688 const auto fieldSize64 = (uint64_t)layout.elemSize * (uint64_t)layout.elemCount;
689 const auto end = (uint64_t)field.offset + fieldSize64;
690
691 if (layout.elemSize == 0 || fieldSize64 > UINT32_MAX || end > owner.comp.size()) {
692 ctx.ok = false;
693 warn_runtime_json(
694 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
695 "Runtime field points outside component size or uses an unsupported type.");
696 return reader.skip_value();
697 }
698
699 auto* pFieldData = pBase + field.offset;
700
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);
705
706 return read_runtime_json_elements(
707 ctx, layout.pType, layout.type, pFieldData, layout.elemSize, layout.elemCount, path, depth + 1);
708 }
709
717 inline bool read_runtime_json_struct(
718 RuntimeJsonReadContext& ctx, const ComponentCacheItem& item, uint8_t* pData, ser::json_str_view path,
719 uint32_t depth) {
720 auto& reader = ctx.reader;
721
722 if (depth >= RuntimeJsonMaxDepth) {
723 ctx.ok = false;
724 warn_runtime_json(
725 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime JSON nesting is too deep.");
726 return reader.skip_value();
727 }
728
729 if (reader.parse_null()) {
730 ctx.ok = false;
731 warn_runtime_json(
732 ctx.diagnostics, ser::JsonDiagReason::NullComponentPayload, path, "Runtime object payload is null.");
733 return true;
734 }
735
736 if (!reader.expect('{'))
737 return false;
738
739 reader.ws();
740 if (reader.consume('}'))
741 return true;
742
743 while (true) {
744 ser::json_str_view key;
745 bool keyFromScratch = false;
746 if (!reader.parse_string_view(key, &keyFromScratch))
747 return false;
748
749 // Escaped names are temporary, so keep a copy while reading the field value.
750 ser::json_str keyStorage;
751 if (keyFromScratch) {
752 keyStorage.assign(key.data(), key.size());
753 key = keyStorage;
754 }
755
756 if (!reader.expect(':'))
757 return false;
758
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);
761
762 if (pField == nullptr) {
763 ctx.ok = false;
764 warn_runtime_json(ctx.diagnostics, ser::JsonDiagReason::UnknownField, fieldPath, "Unknown runtime field.");
765 if (!reader.skip_value())
766 return false;
767 } else if (!read_runtime_json_field(ctx, item, *pField, pData, fieldPath, depth + 1))
768 return false;
769
770 reader.ws();
771 if (reader.consume(','))
772 continue;
773 if (reader.consume('}'))
774 break;
775 return false;
776 }
777
778 return true;
779 }
780
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;
796
797 if (elemCount == 0 || !runtime_json_type_size(pElementType, elementType, elemSize) ||
798 (uint64_t)elemSize * elemCount != valueSize) {
799 ctx.ok = false;
800 warn_runtime_json(
801 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
802 "Runtime array payload uses an invalid reflected element type.");
803 return ctx.reader.skip_value();
804 }
805
806 return read_runtime_json_elements(ctx, pElementType, elementType, pData, elemSize, elemCount, path, depth + 1);
807 }
808
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;
822
823 if (item.element_type() != Char8 || adapter == nullptr || adapter->resize == nullptr ||
824 adapter->element == nullptr || !ctx.reader.parse_string_view(text)) {
825 ctx.ok = false;
826 warn_runtime_json(
827 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
828 "Runtime UTF-8 string cannot be resized or traversed.");
829 return false;
830 }
831
832 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
833
834 if (!adapter->resize(adapter->ctx, sequence, text.size())) {
835 ctx.ok = false;
836 warn_runtime_json(
837 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
838 "Runtime UTF-8 string adapter rejected the requested byte count.");
839 return true;
840 }
841
842 // Escaped text is temporary, so copy its bytes before reading anything else.
843 // Each byte is stored as one Char8 element.
844 GAIA_FOR(text.size()) {
845 RuntimeSequenceElement element{};
846 element.type = Char8;
847
848 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData == nullptr ||
849 element.size != sizeof(char) || (element.type != EntityBad && element.type != Char8)) {
850 ctx.ok = false;
851 warn_runtime_json(
852 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
853 "Runtime UTF-8 string adapter rejected an element.");
854 return true;
855 }
856
857 *(char*)element.mutData = text.data()[i];
858
859 if (adapter->commitElement != nullptr && !adapter->commitElement(adapter->ctx, sequence, element)) {
860 ctx.ok = false;
861 warn_runtime_json(
862 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
863 "Runtime UTF-8 string adapter rejected element commit.");
864 return true;
865 }
866 }
867
868 return true;
869 }
870
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);
885
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;
890
891 // Count first because the adapter must resize the sequence before it can return its elements.
892 if (adapter == nullptr || adapter->resize == nullptr || adapter->element == nullptr ||
893 !count_runtime_json_array_elements(ctx.reader, elemCount)) {
894 ctx.ok = false;
895 warn_runtime_json(
896 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
897 "Runtime vector payload cannot be resized or traversed.");
898 return ctx.reader.skip_value();
899 }
900
901 RuntimeSequenceScope sequence{typeEntity, pData, pData, valueSize};
902
903 if (!adapter->resize(adapter->ctx, sequence, elemCount)) {
904 ctx.ok = false;
905 warn_runtime_json(
906 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
907 "Runtime vector adapter rejected the requested element count.");
908 return ctx.reader.skip_value();
909 }
910
911 if (!ctx.reader.expect('['))
912 return false;
913
914 GAIA_FOR(elemCount) {
915 if (i > 0 && !ctx.reader.expect(','))
916 return false;
917
918 RuntimeSequenceElement element{};
919 element.type = elementType;
920
921 if (!adapter->element(adapter->ctx, sequence, i, element) || element.mutData == nullptr) {
922 ctx.ok = false;
923 warn_runtime_json(
924 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
925 "Runtime vector adapter rejected an element.");
926 return ctx.reader.skip_value();
927 }
928
929 if (element.type == EntityBad)
930 element.type = elementType;
931
932 const auto elemPath = make_runtime_json_element_path(path, i);
933
934 if (!read_runtime_json_value(
935 ctx, pElementType, element.type, (uint8_t*)element.mutData, element.size, elemPath, depth + 1))
936 return false;
937 }
938
939 return ctx.reader.expect(']');
940 }
941
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);
957
958 if (adapter == nullptr || adapter->project == nullptr || pSemanticType == nullptr) {
959 ctx.ok = false;
960 warn_runtime_json(
961 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
962 "Runtime opaque payload cannot be projected.");
963 return ctx.reader.skip_value();
964 }
965
966 RuntimeOpaqueScope opaque{typeEntity, pData, pData, valueSize};
967 RuntimeOpaqueValue projected{};
968 projected.type = semanticType;
969
970 if (!adapter->project(adapter->ctx, opaque, projected) || projected.mutData == nullptr) {
971 ctx.ok = false;
972 warn_runtime_json(
973 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
974 "Runtime opaque adapter rejected projection.");
975 return ctx.reader.skip_value();
976 }
977
978 // EntityBad means "use the type from the schema."
979 if (projected.type == EntityBad)
980 projected.type = semanticType;
981
982 if (projected.type != semanticType) {
983 ctx.ok = false;
984 warn_runtime_json(
985 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
986 "Runtime opaque adapter projected an unexpected semantic type.");
987 return ctx.reader.skip_value();
988 }
989
990 const bool parsed = read_runtime_json_value(
991 ctx, pSemanticType, projected.type, (uint8_t*)projected.mutData, projected.size, path, depth + 1);
992
993 // The adapter may be using a temporary value. Commit it only if the whole read succeeded.
994 if (parsed && adapter->commit != nullptr && ctx.ok && !adapter->commit(adapter->ctx, opaque, projected)) {
995 ctx.ok = false;
996 warn_runtime_json(
997 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime opaque adapter rejected commit.");
998 }
999
1000 return parsed;
1001 }
1002
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))
1016 return false;
1017
1018 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1019 uint64_t constantBits = 0;
1020
1021 if (pConstant == nullptr || !runtime_json_constant_bits(type, pConstant->value, constantBits) ||
1022 !runtime_json_write_integer_bits(pData, type, valueSize, constantBits)) {
1023 ctx.ok = false;
1024 warn_runtime_json(
1025 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1026 "Runtime enum symbol is unknown or incompatible with its underlying type.");
1027 }
1028
1029 return true;
1030 }
1031
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;
1044
1045 if (!reader.expect('['))
1046 return false;
1047
1048 uint64_t valueBits = 0;
1049 bool symbolsOk = true;
1050
1051 reader.ws();
1052 if (!reader.consume(']')) {
1053 while (true) {
1054 ser::json_str_view symbol;
1055 if (!reader.parse_string_view(symbol))
1056 return false;
1057
1058 // Keep reading after a bad name so the next JSON value starts in the right place.
1059 if (symbolsOk) {
1060 const auto* pConstant = item.constant(util::str_view(symbol.data(), (uint32_t)symbol.size()));
1061 uint64_t flagBits = 0;
1062
1063 if (pConstant == nullptr) {
1064 symbolsOk = false;
1065 ctx.ok = false;
1066 warn_runtime_json(
1067 ctx.diagnostics, ser::JsonDiagReason::UnknownRuntimeConstant, path,
1068 "Runtime bitmask symbol is unknown.");
1069 } else if (
1070 !runtime_json_constant_bits(type, pConstant->value, flagBits) || flagBits == 0 ||
1071 (flagBits & (flagBits - 1)) != 0 || (valueBits & flagBits) != 0) {
1072 symbolsOk = false;
1073 ctx.ok = false;
1074 warn_runtime_json(
1075 ctx.diagnostics, ser::JsonDiagReason::InvalidRuntimeConstant, path,
1076 "Runtime bitmask symbol is not a distinct one-bit flag.");
1077 } else {
1078 valueBits |= flagBits;
1079 }
1080 }
1081
1082 reader.ws();
1083 if (reader.consume(','))
1084 continue;
1085 if (reader.consume(']'))
1086 break;
1087 return false;
1088 }
1089 }
1090
1091 // Leave the old value alone if any name is invalid.
1092 if (symbolsOk && !runtime_json_write_integer_bits(pData, type, valueSize, valueBits)) {
1093 ctx.ok = false;
1094 warn_runtime_json(
1095 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1096 "Runtime bitmask symbols are incompatible with the underlying field size.");
1097 }
1098
1099 return true;
1100 }
1101
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;
1114
1115 if (pType != nullptr) {
1116 if (!runtime_type_json_type(*pType, type)) {
1117 ctx.ok = false;
1118 warn_runtime_json(
1119 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path,
1120 "Runtime field uses an unsupported reflected type.");
1121 return ctx.reader.skip_value();
1122 }
1123 } else if (!runtime_primitive_serialization_type(typeEntity, type)) {
1124 ctx.ok = false;
1125 warn_runtime_json(
1126 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime field type is unknown.");
1127 return ctx.reader.skip_value();
1128 }
1129
1130 ctx.reader.ws();
1131 // Names are optional. Numbers still work for enum and bitmask values.
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);
1138
1139 // Valid JSON can still lose data when it is converted to the field's type.
1140 bool fieldOk = true;
1141 if (!ser::detail::read_runtime_field_json(ctx.reader, pData, type, valueSize, fieldOk))
1142 return false;
1143
1144 if (!fieldOk) {
1145 ctx.ok = false;
1146 warn_runtime_json(
1147 ctx.diagnostics, ser::JsonDiagReason::FieldValueAdjusted, path,
1148 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
1149 }
1150
1151 return true;
1152 }
1153
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) {
1158 ctx.ok = false;
1159 warn_runtime_json(
1160 ctx.diagnostics, ser::JsonDiagReason::FieldOutOfBounds, path, "Runtime JSON nesting is too deep.");
1161 return ctx.reader.skip_value();
1162 }
1163
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);
1174 default:
1175 break;
1176 }
1177 }
1178
1179 return read_runtime_json_scalar(ctx, pType, typeEntity, pData, valueSize, path);
1180 }
1181 } // namespace detail
1183
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)
1195 return false;
1196
1197 return detail::write_runtime_json_value(
1198 item.owner_cache(), &item, item.entity, reinterpret_cast<const uint8_t*>(pComponentData), item.comp.size(),
1199 writer, policy, 0);
1200 }
1201
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();
1211 }
1212
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)
1227 return false;
1228
1229 if (reader.parse_null()) {
1230 detail::warn_runtime_json(
1231 diagnostics, ser::JsonDiagReason::NullComponentPayload, componentPath, "Component payload is null.");
1232 return true;
1233 }
1234
1235 if (detail::runtime_json_is_direct_value(item)) {
1236 bool ok = true;
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);
1240 }
1241
1242 bool rawFound = false;
1243 bool fieldFound = false;
1244 bool ok = true;
1245
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);
1249
1250 if (!reader.expect('{'))
1251 return false;
1252
1253 reader.ws();
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.");
1258 return true;
1259 }
1260
1261 while (true) {
1262 ser::json_str_view key;
1263 bool keyFromScratch = false;
1264 if (!reader.parse_string_view(key, &keyFromScratch))
1265 return false;
1266
1267 // Escaped names are temporary, so keep a copy while reading the component value.
1268 ser::json_str keyStorage;
1269 if (keyFromScratch) {
1270 keyStorage.assign(key.data(), key.size());
1271 key = keyStorage;
1272 }
1273
1274 if (!reader.expect(':'))
1275 return false;
1276
1277 const auto fieldPath = detail::make_runtime_json_child_path(componentPath, key);
1278
1279 if (key == "$raw") {
1280 rawFound = true;
1281 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
1282 return false;
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) {
1286 ok = false;
1287 detail::warn_runtime_json(
1288 diagnostics, ser::JsonDiagReason::UnknownField, fieldPath, "Unknown runtime field.");
1289 if (!reader.skip_value())
1290 return false;
1291 } else {
1292 fieldFound = true;
1293 if (!detail::read_runtime_json_field(ctx, item, *pField, pBase, fieldPath, 0))
1294 return false;
1295 }
1296 } else {
1297 ok = false;
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())
1302 return false;
1303 }
1304
1305 reader.ws();
1306 if (reader.consume(','))
1307 continue;
1308 if (reader.consume('}'))
1309 break;
1310 return false;
1311 }
1312
1313 if (rawFound) {
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.");
1319 return true;
1320 }
1321
1322 // Apply $raw last. If both forms are present, $raw wins regardless of key order.
1323 auto s = ser::make_serializer(rawPayload);
1324 s.seek(0);
1325 item.load(s, pBase, 0, 1, 1);
1326 }
1327
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.");
1332
1333 return true;
1334 }
1335
1342 inline bool
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);
1346
1347 ok = !diagnostics.has_issues();
1348 return parsed;
1349 }
1350
1351 inline bool
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())
1357 return false;
1358 writer.key(componentName.data(), componentName.size());
1359 return true;
1360 }
1361
1362 const auto relation = pair_rel(*this, component);
1363 auto relationName = symbol(relation);
1364 if (relationName.empty())
1365 relationName = name(relation);
1366 const auto target = pair_tgt(*this, component);
1367 auto targetName = symbol(target);
1368 if (targetName.empty())
1369 targetName = name(target);
1370 if (relationName.empty() || targetName.empty())
1371 return false;
1372
1373 util::str pairName;
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());
1380 return true;
1381 };
1382
1383 auto write_raw_component = [&](const ComponentCacheItem& item, const uint8_t* pData, uint32_t from, uint32_t to,
1384 uint32_t cap) {
1385 ser::ser_buffer_binary raw;
1386 auto s = ser::make_serializer(raw);
1387 item.save(s, pData, from, to, cap);
1388
1389 writer.begin_object();
1390 writer.key("$raw");
1391 writer.begin_array();
1392 const auto* pRaw = raw.data();
1393 GAIA_FOR(raw.bytes()) writer.value_int(pRaw[i]);
1394 writer.end_array();
1395 writer.end_object();
1396 };
1397
1398 bool ok = true;
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);
1404 s.reset();
1405 save_to(s);
1406 }
1407
1408 writer.clear();
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();
1417 {
1418 const auto* pData = (const uint8_t*)binarySnapshot.data();
1419 GAIA_FOR(binarySnapshot.bytes()) writer.value_int(pData[i]);
1420 }
1421 writer.end_array();
1422 }
1423 writer.key("archetypes");
1424 writer.begin_array();
1425
1426 for (const auto* pArchetype: m_archetypes) {
1427 if (pArchetype == nullptr || pArchetype->chunks().empty())
1428 continue;
1429
1430 writer.begin_object();
1431 writer.key("id");
1432 writer.value_int((uint32_t)pArchetype->id());
1433 writer.key("hash");
1434 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
1435
1436 writer.key("components");
1437 writer.begin_array();
1438 {
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());
1443 else
1444 writer.value_string("<unnamed>");
1445 }
1446 }
1447 writer.end_array();
1448
1449 writer.key("entities");
1450 writer.begin_array();
1451 {
1452 for (const auto* pChunk: pArchetype->chunks()) {
1453 if (pChunk == nullptr || pChunk->empty())
1454 continue;
1455
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];
1460
1461 writer.begin_object();
1462 {
1463 writer.key("entity");
1464 {
1465 writer.begin_object();
1466 writer.key("id");
1467 writer.value_int(entity.id());
1468 writer.key("gen");
1469 writer.value_int(entity.gen());
1470 writer.key("pair");
1471 writer.value_bool(entity.pair());
1472 writer.key("kind");
1473 writer.value_string(EntityKindString[entity.kind()]);
1474 const auto entityName = name(entity);
1475 if (!entityName.empty()) {
1476 writer.key("name");
1477 writer.value_string(entityName.data(), entityName.size());
1478 }
1479 writer.end_object();
1480 }
1481
1482 writer.key("components");
1483 writer.begin_object();
1484 {
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)
1492 ok = false;
1493 }
1494
1495 // Tags have no associated payload.
1496 if (rec.comp.size() == 0) {
1497 writer.value_bool(true);
1498 continue;
1499 }
1500
1501 const auto row = component.kind() == EntityKind::EK_Uni ? 0U : i;
1502
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;
1507 } else {
1508 if (allowRawFallback)
1509 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
1510 else {
1511 writer.value_null();
1512 ok = false;
1513 }
1514 }
1515 }
1516 writer.end_object();
1517 }
1518 }
1519 writer.end_object();
1520 }
1521 }
1522 }
1523
1524 writer.end_array();
1525 writer.end_object();
1526 }
1527
1528 writer.end_array();
1529 writer.end_object();
1530 return ok;
1531 }
1532
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();
1537 }
1538
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)
1543 return false;
1544 if (len == 0) {
1545 diagnostics.add(
1546 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson, "$",
1547 "Input JSON length must be provided and non-zero.");
1548 return false;
1549 }
1550
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);
1556 };
1557 auto error = [&](ser::JsonDiagReason reason, ser::json_str_view path, const char* message) {
1558 diagnostics.add(ser::JsonDiagSeverity::Error, reason, path, message);
1559 };
1560
1561 // Validate top-level format version first.
1562 {
1563 ser::ser_json header(json, dataLen);
1564 if (!header.expect('{')) {
1565 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
1566 return false;
1567 }
1568
1569 bool hasFormat = false;
1570 uint32_t formatValue = 0;
1571
1572 header.ws();
1573 if (!header.consume('}')) {
1574 while (true) {
1575 ser::json_str_view key;
1576 if (!header.parse_string_view(key))
1577 return false;
1578 if (!header.expect(':'))
1579 return false;
1580
1581 if (key == "format") {
1582 double d = 0.0;
1583 if (!header.parse_number(d))
1584 return false;
1585 if (d < 0.0 || d > 4294967295.0)
1586 return false;
1587 const auto v = (uint32_t)d;
1588 if ((double)v != d)
1589 return false;
1590 formatValue = v;
1591 hasFormat = true;
1592 } else {
1593 if (!header.skip_value())
1594 return false;
1595 }
1596
1597 header.ws();
1598 if (header.consume(','))
1599 continue;
1600 if (header.consume('}'))
1601 break;
1602 return false;
1603 }
1604 }
1605
1606 header.ws();
1607 if (!header.eof())
1608 return false;
1609
1610 if (!hasFormat) {
1611 error(ser::JsonDiagReason::MissingFormatField, "$.format", "Missing required 'format' field.");
1612 return false;
1613 }
1614
1615 if (formatValue != WorldSerializerJSONVersion) {
1616 error(
1617 ser::JsonDiagReason::UnsupportedFormatVersion, "$.format",
1618 "Unsupported format version. Expected numeric value 1.");
1619 return false;
1620 }
1621 }
1622
1623 // Prefer fast-path: binary snapshot payload.
1624 {
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) {
1630 keyPos = it;
1631 break;
1632 }
1633 }
1634 if (keyPos != nullptr) {
1635 const char* arr = nullptr;
1636 for (const char* it = keyPos + keyLen; it < end; ++it) {
1637 if (*it == '[') {
1638 arr = it;
1639 break;
1640 }
1641 }
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))
1646 return false;
1647
1648 return load(serializer);
1649 }
1650 }
1651 }
1652
1653 // Fallback: semantic world JSON parser.
1654 ser::ser_json jp(json, dataLen);
1655
1656 struct CompDataLoc {
1657 uint8_t* pBase = nullptr;
1658 uint32_t row = 0;
1659 };
1660
1661 auto locate_component_data = [&](Entity entity, Entity component) {
1662 CompDataLoc loc{};
1663 auto& ec = fetch(entity);
1664 const auto compIdx = core::get_index(ec.pChunk->ids_view(), component);
1665 if (compIdx == BadIndex)
1666 return loc;
1667
1668 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
1669 loc.row = component.kind() == EntityKind::EK_Uni ? 0U : ec.row;
1670 return loc;
1671 };
1672
1673 auto parse_and_apply_component_value = [&](Entity entity, Entity component, const ComponentCacheItem& item,
1674 ser::json_str_view compPath) -> bool {
1675 jp.ws();
1676 if (jp.eof())
1677 return false;
1678
1679 if (jp.parse_null()) {
1680 warn(
1681 ser::JsonDiagReason::NullComponentPayload, compPath,
1682 "Null component payload is ignored in semantic mode.");
1683 return true;
1684 }
1685
1686 if (!has_direct(entity, component)) {
1687 if (component.pair())
1688 add(entity, Pair(pair_rel(*this, component), pair_tgt(*this, component)));
1689 else
1690 add(entity, component);
1691 }
1692
1693 const auto loc = locate_component_data(entity, component);
1694 if (loc.pBase == nullptr) {
1695 warn(
1696 ser::JsonDiagReason::MissingComponentStorage, compPath,
1697 "Component storage is unavailable on the target entity.");
1698 return jp.skip_value();
1699 }
1700
1701 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
1702
1703 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, policy, compPath))
1704 return false;
1705
1706 return true;
1707 };
1708
1709 auto parse_entity_meta = [&](bool& isPair, ser::json_str& nameOut) -> bool {
1710 if (!jp.expect('{'))
1711 return false;
1712
1713 jp.ws();
1714 if (jp.consume('}'))
1715 return true;
1716
1717 while (true) {
1718 ser::json_str_view key;
1719 if (!jp.parse_string_view(key))
1720 return false;
1721 if (!jp.expect(':'))
1722 return false;
1723
1724 if (key == "pair") {
1725 if (!jp.parse_bool(isPair))
1726 return false;
1727 } else if (key == "name") {
1728 if (!jp.parse_string(nameOut))
1729 return false;
1730 } else {
1731 if (!jp.skip_value())
1732 return false;
1733 }
1734
1735 jp.ws();
1736 if (jp.consume(','))
1737 continue;
1738 if (jp.consume('}'))
1739 break;
1740 return false;
1741 }
1742
1743 return true;
1744 };
1745
1746 auto parse_components_for_entity = [&](Entity& entity, bool& created, bool isPair,
1747 const ser::json_str& entityName) -> bool {
1748 if (!jp.expect('{'))
1749 return false;
1750
1751 jp.ws();
1752 if (jp.consume('}'))
1753 return true;
1754
1755 while (true) {
1756 ser::json_str_view compName;
1757 bool compNameFromScratch = false;
1758 if (!jp.parse_string_view(compName, &compNameFromScratch))
1759 return false;
1760
1761 // Escaped names are temporary, so keep a copy while reading the component value.
1762 ser::json_str compNameStorage;
1763 if (compNameFromScratch) {
1764 compNameStorage.assign(compName.data(), compName.size());
1765 compName = compNameStorage;
1766 }
1767
1768 if (!jp.expect(':'))
1769 return false;
1770
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()});
1774
1775 const ComponentCacheItem* pItem = nullptr;
1776 if (componentEntity.pair())
1777 pItem = comp_cache().find_pair_payload(componentEntity);
1778 else if (componentEntity != EntityBad)
1779 pItem = comp_cache().find(componentEntity);
1780
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);
1786
1787 if (isPair || nameIsInternal || itemIsInternal || relationIsInternal) {
1788 if (!jp.skip_value())
1789 return false;
1790 } else {
1791 if (pItem == nullptr) {
1792 warn(
1793 ser::JsonDiagReason::UnknownComponent, compName,
1794 "Component is not registered in the component cache.");
1795 if (!jp.skip_value())
1796 return false;
1797 } else if (pItem->comp.size() == 0) {
1798 // Ignore tag-only components in semantic mode for now.
1799 warn(
1800 ser::JsonDiagReason::TagComponentUnsupported, compName,
1801 "Tag-only component semantic JSON loading is currently unsupported.");
1802 if (!jp.skip_value())
1803 return false;
1804 } else {
1805 if (!created) {
1806 entity = add();
1807 created = true;
1808
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());
1813 else
1814 warn(
1815 ser::JsonDiagReason::DuplicateEntityName, "entity.name",
1816 "Entity name already exists; keeping existing mapping.");
1817 }
1818 }
1819
1820 if (!parse_and_apply_component_value(entity, componentEntity, *pItem, compName))
1821 return false;
1822 }
1823 }
1824
1825 jp.ws();
1826 if (jp.consume(','))
1827 continue;
1828 if (jp.consume('}'))
1829 break;
1830 return false;
1831 }
1832
1833 return true;
1834 };
1835
1836 auto parse_entity_entry = [&]() -> bool {
1837 if (!jp.expect('{')) {
1838 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
1839 return false;
1840 }
1841
1842 bool isPair = false;
1843 ser::json_str entityName;
1844 Entity entity = EntityBad;
1845 bool created = false;
1846
1847 jp.ws();
1848 if (jp.consume('}'))
1849 return true;
1850
1851 while (true) {
1852 ser::json_str_view key;
1853 if (!jp.parse_string_view(key))
1854 return false;
1855 if (!jp.expect(':'))
1856 return false;
1857
1858 if (key == "entity") {
1859 if (!parse_entity_meta(isPair, entityName))
1860 return false;
1861 } else if (key == "components") {
1862 if (!parse_components_for_entity(entity, created, isPair, entityName))
1863 return false;
1864 } else {
1865 if (!jp.skip_value())
1866 return false;
1867 }
1868
1869 jp.ws();
1870 if (jp.consume(','))
1871 continue;
1872 if (jp.consume('}'))
1873 break;
1874 return false;
1875 }
1876
1877 return true;
1878 };
1879
1880 auto parse_archetypes = [&]() -> bool {
1881 if (!jp.expect('['))
1882 return false;
1883
1884 jp.ws();
1885 if (jp.consume(']'))
1886 return true;
1887
1888 while (true) {
1889 if (!jp.expect('{'))
1890 return false;
1891
1892 jp.ws();
1893 if (!jp.consume('}')) {
1894 while (true) {
1895 ser::json_str_view key;
1896 if (!jp.parse_string_view(key))
1897 return false;
1898 if (!jp.expect(':'))
1899 return false;
1900
1901 if (key == "entities") {
1902 if (!jp.expect('['))
1903 return false;
1904
1905 jp.ws();
1906 if (!jp.consume(']')) {
1907 while (true) {
1908 if (!parse_entity_entry())
1909 return false;
1910
1911 jp.ws();
1912 if (jp.consume(','))
1913 continue;
1914 if (jp.consume(']'))
1915 break;
1916 return false;
1917 }
1918 }
1919 } else {
1920 if (!jp.skip_value())
1921 return false;
1922 }
1923
1924 jp.ws();
1925 if (jp.consume(','))
1926 continue;
1927 if (jp.consume('}'))
1928 break;
1929 return false;
1930 }
1931 }
1932
1933 jp.ws();
1934 if (jp.consume(','))
1935 continue;
1936 if (jp.consume(']'))
1937 break;
1938 return false;
1939 }
1940
1941 return true;
1942 };
1943
1944 if (!jp.expect('{'))
1945 return false;
1946
1947 bool hasArchetypes = false;
1948 jp.ws();
1949 if (!jp.consume('}')) {
1950 while (true) {
1951 ser::json_str_view key;
1952 if (!jp.parse_string_view(key))
1953 return false;
1954 if (!jp.expect(':'))
1955 return false;
1956
1957 if (key == "archetypes") {
1958 hasArchetypes = true;
1959 if (!parse_archetypes())
1960 return false;
1961 } else {
1962 if (!jp.skip_value())
1963 return false;
1964 }
1965
1966 jp.ws();
1967 if (jp.consume(','))
1968 continue;
1969 if (jp.consume('}'))
1970 break;
1971 return false;
1972 }
1973 }
1974
1975 jp.ws();
1976 if (!jp.eof())
1977 return false;
1978 if (!hasArchetypes) {
1979 error(ser::JsonDiagReason::MissingArchetypesSection, "$.archetypes", "Missing required 'archetypes' section.");
1980 return false;
1981 }
1982
1983 return true;
1984 }
1985
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();
1990 }
1991
1992 inline bool
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);
1995 }
1996
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();
2001 }
2002 } // namespace ecs
2003} // namespace gaia
2004
2005#endif
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