2#include "gaia/config/config.h"
6#include "gaia/ser/ser_json.h"
13 inline bool component_to_json(
const ComponentCacheItem& item,
const void* pComponentData, ser::ser_json& writer) {
14 GAIA_ASSERT(pComponentData !=
nullptr);
15 if (pComponentData ==
nullptr)
19 const auto* pBase =
reinterpret_cast<const uint8_t*
>(pComponentData);
21 writer.begin_object();
22 for (
const auto& field: item.schema) {
23 writer.key(field.name);
24 if (field.offset + field.size > item.comp.size()) {
29 const auto* pFieldData = pBase + field.offset;
30 ok = ser::detail::write_schema_field_json(writer, pFieldData, field.type, field.size) && ok;
38 inline ser::json_str component_to_json(
const ComponentCacheItem& item,
const void* pComponentData,
bool& ok) {
40 ok = component_to_json(item, pComponentData, writer);
52 inline bool json_to_component(
53 const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader, ser::JsonDiagnostics& diagnostics,
54 ser::json_str_view componentPath = {}) {
55 GAIA_ASSERT(pComponentData !=
nullptr);
56 if (pComponentData ==
nullptr)
59 auto make_field_path = [&](ser::json_str_view fieldName) {
60 if (componentPath.empty())
61 return ser::json_str(fieldName);
62 if (fieldName.empty())
63 return ser::json_str(componentPath);
66 path.reserve(componentPath.size() + 1 + fieldName.size());
67 path.append(componentPath.data(), componentPath.size());
69 path.append(fieldName.data(), fieldName.size());
72 auto warn = [&](ser::JsonDiagReason reason,
const ser::json_str& path,
const char* message) {
73 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
76 if (reader.parse_null()) {
77 warn(ser::JsonDiagReason::NullComponentPayload, make_field_path(
""),
"Component payload is null.");
81 if (!reader.expect(
'{'))
84 bool rawFound =
false;
85 bool schemaFound =
false;
86 ser::ser_buffer_binary rawPayload;
87 auto* pBase =
reinterpret_cast<uint8_t*
>(pComponentData);
90 if (reader.consume(
'}')) {
92 ser::JsonDiagReason::MissingSchemaOrRawPayload, make_field_path(
""),
93 "Component object is empty and contains no schema fields or $raw payload.");
98 ser::json_str_view key;
99 bool keyFromScratch =
false;
100 if (!reader.parse_string_view(key, &keyFromScratch))
102 ser::json_str keyStorage;
103 if (keyFromScratch) {
104 keyStorage.assign(key.data(), key.size());
107 if (!reader.expect(
':'))
112 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
114 }
else if (item.has_fields() && item.comp.soa() == 0) {
115 const ComponentCacheItem::SchemaField* pField =
nullptr;
116 for (
const auto& field: item.schema) {
117 const auto fieldNameLen = (size_t)GAIA_STRLEN(field.name, ComponentCacheItem::MaxNameLength);
118 if (key.size() == fieldNameLen && memcmp(key.data(), field.name, key.size()) == 0) {
124 if (pField ==
nullptr) {
125 warn(ser::JsonDiagReason::UnknownField, make_field_path(key),
"Unknown schema field.");
126 if (!reader.skip_value())
128 }
else if (pField->offset + pField->size > item.comp.size()) {
130 ser::JsonDiagReason::FieldOutOfBounds, make_field_path(key),
131 "Schema field points outside component size.");
132 if (!reader.skip_value())
136 auto* pFieldData = pBase + pField->offset;
138 if (!ser::detail::read_schema_field_json(reader, pFieldData, pField->type, pField->size, fieldOk))
142 ser::JsonDiagReason::FieldValueAdjusted, make_field_path(key),
143 "Field value was lossy, truncated, or unsupported for the target schema type.");
148 ser::JsonDiagReason::MissingSchemaOrRawPayload, make_field_path(key),
149 "Component schema is unavailable for keyed field payloads.");
150 if (!reader.skip_value())
155 if (reader.consume(
','))
157 if (reader.consume(
'}'))
163 if (item.comp.soa() != 0) {
165 ser::JsonDiagReason::SoaRawUnsupported, make_field_path(
"$raw"),
166 "$raw payload is not supported for SoA components.");
170 auto s = ser::make_serializer(rawPayload);
172 item.load(s, pBase, 0, 1, 1);
175 if (!rawFound && !schemaFound)
177 ser::JsonDiagReason::MissingSchemaOrRawPayload, make_field_path(
""),
178 "Component payload contains neither recognized schema fields nor $raw data.");
185 json_to_component(
const ComponentCacheItem& item,
void* pComponentData, ser::ser_json& reader,
bool& ok) {
186 ser::JsonDiagnostics diagnostics;
187 const bool parsed = json_to_component(item, pComponentData, reader, diagnostics);
188 ok = !diagnostics.has_issues();
197 auto write_raw_component = [&](
const ComponentCacheItem& item,
const uint8_t* pData, uint32_t from, uint32_t to,
200 auto s = ser::make_serializer(raw);
201 item.save(s, pData, from, to, cap);
203 writer.begin_object();
205 writer.begin_array();
206 const auto* pRaw = raw.
data();
207 GAIA_FOR(raw.
bytes()) writer.value_int(pRaw[i]);
213 const bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
214 const bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
216 if (includeBinarySnapshot) {
217 auto s = ser::make_serializer(binarySnapshot);
223 writer.begin_object();
224 writer.key(
"format");
225 writer.value_int(WorldSerializerJSONVersion);
226 writer.key(
"worldVersion");
227 writer.value_int(m_worldVersion);
228 if (includeBinarySnapshot) {
229 writer.key(
"binary");
230 writer.begin_array();
232 const auto* pData = (
const uint8_t*)binarySnapshot.
data();
233 GAIA_FOR(binarySnapshot.
bytes()) writer.value_int(pData[i]);
237 writer.key(
"archetypes");
238 writer.begin_array();
240 for (
const auto* pArchetype: m_archetypes) {
241 if (pArchetype ==
nullptr || pArchetype->chunks().empty())
244 writer.begin_object();
246 writer.value_int((uint32_t)pArchetype->id());
248 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
250 writer.key(
"components");
251 writer.begin_array();
253 for (
const auto entity: pArchetype->ids_view()) {
254 const auto itemName =
name(entity);
255 if (!itemName.empty())
256 writer.value_string(itemName.data(), itemName.size());
258 writer.value_string(
"<unnamed>");
263 writer.key(
"entities");
264 writer.begin_array();
266 for (
const auto* pChunk: pArchetype->chunks()) {
267 if (pChunk ==
nullptr || pChunk->empty())
270 const auto ents = pChunk->entity_view();
271 const auto recs = pChunk->comp_rec_view();
272 GAIA_FOR((uint32_t)ents.size()) {
273 const auto entity = ents[i];
275 writer.begin_object();
277 writer.key(
"entity");
279 writer.begin_object();
281 writer.value_int(entity.id());
283 writer.value_int(entity.gen());
285 writer.value_bool(entity.pair());
287 writer.value_string(EntityKindString[entity.kind()]);
288 const auto entityName =
name(entity);
289 if (!entityName.empty()) {
291 writer.value_string(entityName.data(), entityName.size());
296 writer.key(
"components");
297 writer.begin_object();
299 GAIA_FOR_((uint32_t)recs.size(), j) {
300 const auto& rec = recs[j];
301 const auto& item = *rec.pItem;
303 writer.key(
name.data(),
name.size());
306 if (rec.comp.size() == 0) {
307 writer.value_bool(
true);
311 const auto row = item.
entity.kind() == EntityKind::EK_Uni ? 0U : i;
313 if (item.has_fields() && rec.comp.soa() == 0) {
314 const auto* pCompData = pChunk->comp_ptr(j, row);
315 ok = ecs::component_to_json(item, pCompData, writer) && ok;
317 if (allowRawFallback)
318 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
356 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson,
"$",
357 "Input JSON length must be provided and non-zero.");
361 const auto dataLen = len;
362 const auto* p = json;
363 const auto* end = json + dataLen;
365 diagnostics.add(ser::JsonDiagSeverity::Warning, reason,
path, message);
368 diagnostics.add(ser::JsonDiagSeverity::Error, reason,
path, message);
374 if (!header.expect(
'{')) {
375 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
379 bool hasFormat =
false;
380 uint32_t formatValue = 0;
383 if (!header.consume(
'}')) {
386 if (!header.parse_string_view(key))
388 if (!header.expect(
':'))
391 if (key ==
"format") {
393 if (!header.parse_number(d))
395 if (d < 0.0 || d > 4294967295.0)
397 const auto v = (uint32_t)d;
403 if (!header.skip_value())
408 if (header.consume(
','))
410 if (header.consume(
'}'))
421 error(ser::JsonDiagReason::MissingFormatField,
"$.format",
"Missing required 'format' field.");
425 if (formatValue != WorldSerializerJSONVersion) {
427 ser::JsonDiagReason::UnsupportedFormatVersion,
"$.format",
428 "Unsupported format version. Expected numeric value 1.");
435 const char key[] =
"\"binary\"";
436 const uint32_t keyLen = (uint32_t)(
sizeof(key) - 1);
437 const char* keyPos =
nullptr;
438 for (
const char* it = p; it + keyLen <= end; ++it) {
439 if (memcmp(it, key, keyLen) == 0) {
444 if (keyPos !=
nullptr) {
445 const char* arr =
nullptr;
446 for (
const char* it = keyPos + keyLen; it < end; ++it) {
452 if (arr !=
nullptr) {
455 if (!ser::detail::parse_json_byte_array(binaryReader, serializer))
458 return load(serializer);
467 uint8_t* pBase =
nullptr;
478 auto& ec =
fetch(entity);
479 const auto compIdx = core::get_index(ec.pChunk->ids_view(), item.entity);
480 if (compIdx == BadIndex)
483 loc.pBase = ec.pChunk->comp_ptr_mut(compIdx, 0);
484 loc.row = item.entity.kind() == EntityKind::EK_Uni ? 0U : ec.row;
485 loc.cap = ec.pChunk->capacity();
490 auto has_direct_component = [&](
Entity entity,
Entity componentEntity) ->
bool {
493 const auto& ec =
fetch(entity);
494 return core::get_index(ec.pChunk->ids_view(), componentEntity) != BadIndex;
504 const char next = jp.
peek();
505 if (next ==
't' || next ==
'f') {
506 bool tagValue =
false;
507 if (!jp.parse_bool(tagValue))
510 ser::JsonDiagReason::TagValueIgnored, compPath,
511 "Tag-like boolean component payload is ignored in semantic mode.");
515 if (jp.parse_null()) {
517 ser::JsonDiagReason::NullComponentPayload, compPath,
518 "Null component payload is ignored in semantic mode.");
522 if (!has_direct_component(entity, item.entity))
523 add(entity, item.entity);
525 auto loc = locate_component_data(entity, item);
528 ser::JsonDiagReason::MissingComponentStorage, compPath,
529 "Component storage is unavailable on the target entity.");
530 return jp.skip_value();
533 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
534 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, compPath))
539 auto parse_entity_meta = [&](
bool& isPair,
ser::json_str& nameOut) ->
bool {
549 if (!jp.parse_string_view(key))
555 if (!jp.parse_bool(isPair))
557 }
else if (key ==
"name") {
558 if (!jp.parse_string(nameOut))
561 if (!jp.skip_value())
576 auto parse_components_for_entity = [&](
Entity& entity,
bool& created,
bool isPair,
587 bool compNameFromScratch =
false;
588 if (!jp.parse_string_view(compName, &compNameFromScratch))
591 if (compNameFromScratch) {
593 compName = compNameStorage;
598 const bool isInternalComp = compName.
size() >= 10 && memcmp(compName.
data(),
"gaia::ecs::", 10) == 0;
599 if (isPair || isInternalComp) {
600 if (!jp.skip_value())
603 const auto componentEntity =
get(compName.
data(), (uint32_t)compName.
size());
604 const auto* pItem = componentEntity != EntityBad ?
comp_cache().
find(componentEntity) :
nullptr;
605 if (pItem ==
nullptr) {
607 ser::JsonDiagReason::UnknownComponent, compName,
608 "Component is not registered in the component cache.");
609 if (!jp.skip_value())
611 }
else if (pItem->comp.size() == 0) {
614 ser::JsonDiagReason::TagComponentUnsupported, compName,
615 "Tag-only component semantic JSON loading is currently unsupported.");
616 if (!jp.skip_value())
622 if (!entityName.empty()) {
623 const auto existing =
get(entityName.data(), (uint32_t)entityName.size());
624 if (existing == EntityBad)
625 name(entity, entityName.data(), (uint32_t)entityName.size());
628 ser::JsonDiagReason::DuplicateEntityName,
"entity.name",
629 "Entity name already exists; keeping existing mapping.");
633 if (!parse_and_apply_component_value(entity, *pItem, compName))
649 auto parse_entity_entry = [&]() ->
bool {
650 if (!jp.expect(
'{')) {
651 error(ser::JsonDiagReason::InvalidJson,
"$",
"Root JSON value must be an object.");
657 Entity entity = EntityBad;
658 bool created =
false;
666 if (!jp.parse_string_view(key))
671 if (key ==
"entity") {
672 if (!parse_entity_meta(isPair, entityName))
674 }
else if (key ==
"components") {
675 if (!parse_components_for_entity(entity, created, isPair, entityName))
678 if (!jp.skip_value())
693 auto parse_archetypes = [&]() ->
bool {
706 if (!jp.consume(
'}')) {
709 if (!jp.parse_string_view(key))
714 if (key ==
"entities") {
719 if (!jp.consume(
']')) {
721 if (!parse_entity_entry())
733 if (!jp.skip_value())
760 bool hasArchetypes =
false;
762 if (!jp.consume(
'}')) {
765 if (!jp.parse_string_view(key))
770 if (key ==
"archetypes") {
771 hasArchetypes =
true;
772 if (!parse_archetypes())
775 if (!jp.skip_value())
791 if (!hasArchetypes) {
792 error(ser::JsonDiagReason::MissingArchetypesSection,
"$.archetypes",
"Missing required 'archetypes' section.");
801 const bool parsed =
load_json(json, len, diagnostics);
802 return parsed && !diagnostics.has_issues();
812 return parsed && !diagnostics.has_issues();
GAIA_NODISCARD const ComponentCacheItem * find(detail::ComponentDescId compDescId) const noexcept
Searches for the component cache item given the compDescId.
Definition component_cache.h:355
bool save_json(ser::ser_json &writer, ser::JsonSaveFlags flags=ser::JsonSaveFlags::Default) const
Serializes world state into a JSON document. Components with runtime schema are emitted as structured...
Definition world_json.h:196
GAIA_NODISCARD const ComponentCache & comp_cache() const
Returns read-only access to the world component cache.
Definition world.h:4036
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:350
GAIA_NODISCARD Entity symbol(const char *symbol, uint32_t len=0) const
Finds a component entity by its exact registered symbol.
Definition world.h:4046
GAIA_NODISCARD const ComponentCacheItem & add()
Creates a new component if not found already.
Definition world.h:4450
GAIA_NODISCARD Entity get() const
Returns the entity registered for component type T.
Definition world.h:4381
GAIA_NODISCARD EntityContainer & fetch(Entity entity)
Returns the internal record for entity.
Definition world.h:2275
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:6803
GAIA_NODISCARD bool has(Entity entity) const
Checks if entity is currently used by the world.
Definition world.h:6477
GAIA_NODISCARD Entity path(const char *path, uint32_t len=0) const
Finds a component entity by its exact scoped path.
Definition world.h:4066
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:9083
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
Definition component_cache_item.h:25
Entity entity
Component entity.
Definition component_cache_item.h:75
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