2#include "gaia/config/config.h"
6#include "gaia/ser/ser_json.h"
11 inline bool runtime_field_json_layout(
12 const ComponentCacheItem& item,
const RuntimeField& field, ser::serialization_type_id& type,
13 uint32_t& fieldSize)
noexcept {
14 if (!runtime_primitive_serialization_type(field.type, type))
17 const auto elemCount = ComponentCacheItem::field_element_count(field);
18 if (field.type != Char8 && elemCount != 1)
21 const auto elemSize = ComponentCacheItem::primitive_type_size(field.type);
22 const auto fieldSize64 = (uint64_t)elemSize * (uint64_t)elemCount;
23 const auto end = (uint64_t)field.offset + fieldSize64;
24 if (elemSize == 0 || fieldSize64 > UINT32_MAX || end > item.comp.size())
27 fieldSize = (uint32_t)fieldSize64;
34 inline bool component_to_json(
const ComponentCacheItem& item,
const void* pComponentData, ser::ser_json& writer) {
35 GAIA_ASSERT(pComponentData !=
nullptr);
36 if (pComponentData ==
nullptr)
40 const auto* pBase =
reinterpret_cast<const uint8_t*
>(pComponentData);
42 writer.begin_object();
43 for (
const auto& field: item.fields) {
44 writer.key(field.name);
45 ser::serialization_type_id type = ser::serialization_type_id::ignore;
46 uint32_t fieldSize = 0;
47 if (!detail::runtime_field_json_layout(item, field, type, fieldSize)) {
52 const auto* pFieldData = pBase + field.offset;
53 ok = ser::detail::write_runtime_field_json(writer, pFieldData, type, fieldSize) && ok;
61 inline ser::json_str component_to_json(
const ComponentCacheItem& item,
const void* pComponentData,
bool& ok) {
63 ok = component_to_json(item, pComponentData, writer);
75 inline bool json_to_component(
76 const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader, ser::JsonDiagnostics& diagnostics,
77 ser::json_str_view componentPath = {}) {
78 GAIA_ASSERT(pComponentData !=
nullptr);
79 if (pComponentData ==
nullptr)
82 auto make_field_path = [&](ser::json_str_view fieldName) {
83 if (componentPath.empty())
84 return ser::json_str(fieldName);
85 if (fieldName.empty())
86 return ser::json_str(componentPath);
89 path.reserve(componentPath.size() + 1 + fieldName.size());
90 path.append(componentPath.data(), componentPath.size());
92 path.append(fieldName.data(), fieldName.size());
95 auto warn = [&](ser::JsonDiagReason reason,
const ser::json_str& path,
const char* message) {
96 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
99 if (reader.parse_null()) {
100 warn(ser::JsonDiagReason::NullComponentPayload, make_field_path(
""),
"Component payload is null.");
104 if (!reader.expect(
'{'))
107 bool rawFound =
false;
108 bool fieldFound =
false;
109 ser::ser_buffer_binary rawPayload;
110 auto* pBase =
reinterpret_cast<uint8_t*
>(pComponentData);
113 if (reader.consume(
'}')) {
115 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(
""),
116 "Component object is empty and contains no runtime fields or $raw payload.");
121 ser::json_str_view key;
122 bool keyFromScratch =
false;
123 if (!reader.parse_string_view(key, &keyFromScratch))
125 ser::json_str keyStorage;
126 if (keyFromScratch) {
127 keyStorage.assign(key.data(), key.size());
130 if (!reader.expect(
':'))
135 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
137 }
else if (item.field_count() != 0 && item.comp.soa() == 0) {
138 const RuntimeField* pField =
nullptr;
139 for (
const auto& field: item.fields) {
140 const auto fieldNameLen = (size_t)GAIA_STRLEN(field.name, ComponentCacheItem::MaxNameLength);
141 if (key.size() == fieldNameLen && memcmp(key.data(), field.name, key.size()) == 0) {
147 if (pField ==
nullptr) {
148 warn(ser::JsonDiagReason::UnknownField, make_field_path(key),
"Unknown runtime field.");
149 if (!reader.skip_value())
152 ser::serialization_type_id type = ser::serialization_type_id::ignore;
153 uint32_t fieldSize = 0;
154 if (!detail::runtime_field_json_layout(item, *pField, type, fieldSize)) {
156 ser::JsonDiagReason::FieldOutOfBounds, make_field_path(key),
157 "Runtime field points outside component size or uses an unsupported type.");
158 if (!reader.skip_value())
162 auto* pFieldData = pBase + pField->offset;
164 if (!ser::detail::read_runtime_field_json(reader, pFieldData, type, fieldSize, fieldOk))
168 ser::JsonDiagReason::FieldValueAdjusted, make_field_path(key),
169 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
175 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(key),
176 "Runtime field metadata is unavailable for keyed field payloads.");
177 if (!reader.skip_value())
182 if (reader.consume(
','))
184 if (reader.consume(
'}'))
190 if (item.comp.soa() != 0) {
192 ser::JsonDiagReason::SoaRawUnsupported, make_field_path(
"$raw"),
193 "$raw payload is not supported for SoA components.");
197 auto s = ser::make_serializer(rawPayload);
199 item.load(s, pBase, 0, 1, 1);
202 if (!rawFound && !fieldFound)
204 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(
""),
205 "Component payload contains neither recognized runtime fields nor $raw data.");
212 json_to_component(
const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader,
bool& ok) {
213 ser::JsonDiagnostics diagnostics;
214 const bool parsed = json_to_component(item, pComponentData, reader, diagnostics);
215 ok = !diagnostics.has_issues();
224 auto write_raw_component = [&](
const ComponentCacheItem& item,
const uint8_t* pData, uint32_t from, uint32_t to,
227 auto s = ser::make_serializer(raw);
228 item.save(s, pData, from, to, cap);
230 writer.begin_object();
232 writer.begin_array();
233 const auto* pRaw = raw.
data();
234 GAIA_FOR(raw.
bytes()) writer.value_int(pRaw[i]);
240 const bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
241 const bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
243 if (includeBinarySnapshot) {
244 auto s = ser::make_serializer(binarySnapshot);
250 writer.begin_object();
251 writer.key(
"format");
252 writer.value_int(WorldSerializerJSONVersion);
253 writer.key(
"worldVersion");
254 writer.value_int(m_worldVersion);
255 if (includeBinarySnapshot) {
256 writer.key(
"binary");
257 writer.begin_array();
259 const auto* pData = (
const uint8_t*)binarySnapshot.
data();
260 GAIA_FOR(binarySnapshot.
bytes()) writer.value_int(pData[i]);
264 writer.key(
"archetypes");
265 writer.begin_array();
267 for (
const auto* pArchetype: m_archetypes) {
268 if (pArchetype ==
nullptr || pArchetype->chunks().empty())
271 writer.begin_object();
273 writer.value_int((uint32_t)pArchetype->id());
275 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
277 writer.key(
"components");
278 writer.begin_array();
280 for (
const auto entity: pArchetype->ids_view()) {
281 const auto itemName =
name(entity);
282 if (!itemName.empty())
283 writer.value_string(itemName.data(), itemName.size());
285 writer.value_string(
"<unnamed>");
290 writer.key(
"entities");
291 writer.begin_array();
293 for (
const auto* pChunk: pArchetype->chunks()) {
294 if (pChunk ==
nullptr || pChunk->empty())
297 const auto ents = pChunk->entity_view();
298 const auto recs = pChunk->comp_rec_view();
299 GAIA_FOR((uint32_t)ents.size()) {
300 const auto entity = ents[i];
302 writer.begin_object();
304 writer.key(
"entity");
306 writer.begin_object();
308 writer.value_int(entity.id());
310 writer.value_int(entity.gen());
312 writer.value_bool(entity.pair());
314 writer.value_string(EntityKindString[entity.kind()]);
315 const auto entityName =
name(entity);
316 if (!entityName.empty()) {
318 writer.value_string(entityName.data(), entityName.size());
323 writer.key(
"components");
324 writer.begin_object();
326 GAIA_FOR_((uint32_t)recs.size(), j) {
327 const auto& rec = recs[j];
328 const auto& item = *rec.pItem;
330 writer.key(
name.data(),
name.size());
333 if (rec.comp.size() == 0) {
334 writer.value_bool(
true);
338 const auto row = item.
entity.kind() == EntityKind::EK_Uni ? 0U : i;
340 if (item.field_count() != 0 && rec.comp.soa() == 0) {
341 const auto* pCompData = pChunk->comp_ptr(j, row);
342 ok = ecs::component_to_json(item, pCompData, writer) && ok;
344 if (allowRawFallback)
345 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
383 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson,
"$",
384 "Input JSON length must be provided and non-zero.");
388 const auto dataLen = len;
389 const auto* p = json;
390 const auto* end = json + dataLen;
392 diagnostics.add(ser::JsonDiagSeverity::Warning, reason,
path, message);
395 diagnostics.add(ser::JsonDiagSeverity::Error, reason,
path, message);
401 if (!header.expect(
'{')) {
402 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
406 bool hasFormat =
false;
407 uint32_t formatValue = 0;
410 if (!header.consume(
'}')) {
413 if (!header.parse_string_view(key))
415 if (!header.expect(
':'))
418 if (key ==
"format") {
420 if (!header.parse_number(d))
422 if (d < 0.0 || d > 4294967295.0)
424 const auto v = (uint32_t)d;
430 if (!header.skip_value())
435 if (header.consume(
','))
437 if (header.consume(
'}'))
448 error(ser::JsonDiagReason::MissingFormatField,
"$.format",
"Missing required 'format' field.");
452 if (formatValue != WorldSerializerJSONVersion) {
454 ser::JsonDiagReason::UnsupportedFormatVersion,
"$.format",
455 "Unsupported format version. Expected numeric value 1.");
462 const char key[] =
"\"binary\"";
463 const uint32_t keyLen = (uint32_t)(
sizeof(key) - 1);
464 const char* keyPos =
nullptr;
465 for (
const char* it = p; it + keyLen <= end; ++it) {
466 if (memcmp(it, key, keyLen) == 0) {
471 if (keyPos !=
nullptr) {
472 const char* arr =
nullptr;
473 for (
const char* it = keyPos + keyLen; it < end; ++it) {
479 if (arr !=
nullptr) {
482 if (!ser::detail::parse_json_byte_array(binaryReader, serializer))
485 return load(serializer);
494 uint8_t* pBase =
nullptr;
505 auto& ec =
fetch(entity);
506 const auto compIdx = core::get_index(ec.pChunk->ids_view(), item.entity);
510 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
511 loc.row = item.entity.kind() == EntityKind::EK_Uni ? 0U : ec.row;
512 loc.cap = ec.pChunk->capacity();
517 auto has_direct_component = [&](
Entity entity,
Entity componentEntity) ->
bool {
520 const auto& ec =
fetch(entity);
521 return core::get_index(ec.pChunk->ids_view(), componentEntity) !=
BadIndex;
531 const char next = jp.
peek();
532 if (next ==
't' || next ==
'f') {
533 bool tagValue =
false;
534 if (!jp.parse_bool(tagValue))
537 ser::JsonDiagReason::TagValueIgnored, compPath,
538 "Tag-like boolean component payload is ignored in semantic mode.");
542 if (jp.parse_null()) {
544 ser::JsonDiagReason::NullComponentPayload, compPath,
545 "Null component payload is ignored in semantic mode.");
549 if (!has_direct_component(entity, item.entity))
550 add(entity, item.entity);
552 auto loc = locate_component_data(entity, item);
555 ser::JsonDiagReason::MissingComponentStorage, compPath,
556 "Component storage is unavailable on the target entity.");
557 return jp.skip_value();
560 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
561 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, compPath))
566 auto parse_entity_meta = [&](
bool& isPair,
ser::json_str& nameOut) ->
bool {
576 if (!jp.parse_string_view(key))
582 if (!jp.parse_bool(isPair))
584 }
else if (key ==
"name") {
585 if (!jp.parse_string(nameOut))
588 if (!jp.skip_value())
603 auto parse_components_for_entity = [&](
Entity& entity,
bool& created,
bool isPair,
614 bool compNameFromScratch =
false;
615 if (!jp.parse_string_view(compName, &compNameFromScratch))
618 if (compNameFromScratch) {
620 compName = compNameStorage;
625 const bool isInternalComp = compName.
size() >= 10 && memcmp(compName.
data(),
"gaia::ecs::", 10) == 0;
626 if (isPair || isInternalComp) {
627 if (!jp.skip_value())
630 const auto componentEntity =
get(compName.
data(), (uint32_t)compName.
size());
631 const auto* pItem = componentEntity != EntityBad ?
comp_cache().
find(componentEntity) :
nullptr;
632 if (pItem ==
nullptr) {
634 ser::JsonDiagReason::UnknownComponent, compName,
635 "Component is not registered in the component cache.");
636 if (!jp.skip_value())
638 }
else if (pItem->comp.size() == 0) {
641 ser::JsonDiagReason::TagComponentUnsupported, compName,
642 "Tag-only component semantic JSON loading is currently unsupported.");
643 if (!jp.skip_value())
649 if (!entityName.empty()) {
650 const auto existing =
get(entityName.data(), (uint32_t)entityName.size());
651 if (existing == EntityBad)
652 name(entity, entityName.data(), (uint32_t)entityName.size());
655 ser::JsonDiagReason::DuplicateEntityName,
"entity.name",
656 "Entity name already exists; keeping existing mapping.");
660 if (!parse_and_apply_component_value(entity, *pItem, compName))
676 auto parse_entity_entry = [&]() ->
bool {
677 if (!jp.expect(
'{')) {
678 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
684 Entity entity = EntityBad;
685 bool created =
false;
693 if (!jp.parse_string_view(key))
698 if (key ==
"entity") {
699 if (!parse_entity_meta(isPair, entityName))
701 }
else if (key ==
"components") {
702 if (!parse_components_for_entity(entity, created, isPair, entityName))
705 if (!jp.skip_value())
720 auto parse_archetypes = [&]() ->
bool {
733 if (!jp.consume(
'}')) {
736 if (!jp.parse_string_view(key))
741 if (key ==
"entities") {
746 if (!jp.consume(
']')) {
748 if (!parse_entity_entry())
760 if (!jp.skip_value())
787 bool hasArchetypes =
false;
789 if (!jp.consume(
'}')) {
792 if (!jp.parse_string_view(key))
797 if (key ==
"archetypes") {
798 hasArchetypes =
true;
799 if (!parse_archetypes())
802 if (!jp.skip_value())
818 if (!hasArchetypes) {
819 error(ser::JsonDiagReason::MissingArchetypesSection,
"$.archetypes",
"Missing required 'archetypes' section.");
828 const bool parsed =
load_json(json, len, diagnostics);
829 return parsed && !diagnostics.has_issues();
839 return parsed && !diagnostics.has_issues();
GAIA_NODISCARD const ComponentCacheItem * find(Entity entity) const noexcept
Searches for the component cache item.
Definition component_cache.h:389
bool save_json(ser::ser_json &writer, ser::JsonSaveFlags flags=ser::JsonSaveFlags::Default) const
Serializes world state into a JSON document. Components with runtime fields are emitted as structured...
Definition world_json.h:223
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:2807
bool load_json(const char *json, uint32_t len, ser::JsonDiagnostics &diagnostics)
Loads world state from JSON previously emitted by save_json(). Returns true when JSON shape is valid ...
Definition world_json.h:377
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:2817
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:3300
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:3231
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:938
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:5752
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:5431
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:2837
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:8058
Default in-memory binary backend used by ECS world/runtime serialization. Provides aligned raw read/w...
Definition ser_binary.h:12
uint32_t bytes() const
Returns total buffered byte count.
Definition ser_binary.h:53
const char * data() const
Returns pointer to serialized bytes.
Definition ser_binary.h:38
GAIA_NODISCARD uint32_t bytes() const
Returns the number of bytes written in the buffer.
Definition ser_buffer_binary.h:35
GAIA_NODISCARD const auto * data() const
Returns the pointer to the data in the buffer.
Definition ser_buffer_binary.h:45
Minimal in-memory binary serializer. It stores raw bytes only (no schema, versioning,...
Definition ser_buffer_binary.h:155
Lightweight JSON serializer/deserializer.
Definition ser_json.h:101
GAIA_NODISCARD const json_str & str() const
Returns currently emitted output text.
Definition ser_json.h:197
void ws()
Skips whitespace in parser input.
Definition ser_json.h:213
GAIA_NODISCARD bool eof() const
Returns true if parser has reached end of input.
Definition ser_json.h:202
void clear()
Clears output JSON text and writer context.
Definition ser_json.h:190
GAIA_NODISCARD char peek() const
Returns next non-consumed character.
Definition ser_json.h:207
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
constexpr uint32_t BadIndex
Sentinel index value returned by helpers when a lookup fails.
Definition utility.h:20
Definition component_cache_item.h:26
Entity entity
Component entity.
Definition component_cache_item.h:84
Lightweight non-owning string view over a character sequence.
Definition str.h:13
GAIA_NODISCARD constexpr uint32_t size() const
Returns the number of characters in the view.
Definition str.h:41
GAIA_NODISCARD constexpr const char * data() const
Returns the underlying character pointer.
Definition str.h:35
Lightweight owning string container with explicit length semantics (no implicit null terminator).
Definition str.h:331
void assign(const char *data, uint32_t size)
Replaces contents with size characters from data.
Definition str.h:365