Gaia-ECS v0.9.3
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#include <cstring>
5
6#include "gaia/ser/ser_json.h"
7
8namespace gaia {
9 namespace ecs {
10 namespace detail {
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))
15 return false;
16
17 const auto elemCount = ComponentCacheItem::field_element_count(field);
18 if (field.type != Char8 && elemCount != 1)
19 return false;
20
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())
25 return false;
26
27 fieldSize = (uint32_t)fieldSize64;
28 return true;
29 }
30 } // namespace detail
31
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)
37 return false;
38
39 bool ok = true;
40 const auto* pBase = reinterpret_cast<const uint8_t*>(pComponentData);
41
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)) {
48 writer.value_null();
49 ok = false;
50 continue;
51 }
52 const auto* pFieldData = pBase + field.offset;
53 ok = ser::detail::write_runtime_field_json(writer, pFieldData, type, fieldSize) && ok;
54 }
55 writer.end_object();
56
57 return ok;
58 }
59
61 inline ser::json_str component_to_json(const ComponentCacheItem& item, const void* pComponentData, bool& ok) {
62 ser::ser_json writer;
63 ok = component_to_json(item, pComponentData, writer);
64 return writer.str();
65 }
66
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)
80 return false;
81
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);
87
88 ser::json_str path;
89 path.reserve(componentPath.size() + 1 + fieldName.size());
90 path.append(componentPath.data(), componentPath.size());
91 path.append('.');
92 path.append(fieldName.data(), fieldName.size());
93 return path;
94 };
95 auto warn = [&](ser::JsonDiagReason reason, const ser::json_str& path, const char* message) {
96 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
97 };
98
99 if (reader.parse_null()) {
100 warn(ser::JsonDiagReason::NullComponentPayload, make_field_path(""), "Component payload is null.");
101 return true;
102 }
103
104 if (!reader.expect('{'))
105 return false;
106
107 bool rawFound = false;
108 bool fieldFound = false;
109 ser::ser_buffer_binary rawPayload;
110 auto* pBase = reinterpret_cast<uint8_t*>(pComponentData);
111
112 reader.ws();
113 if (reader.consume('}')) {
114 warn(
115 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(""),
116 "Component object is empty and contains no runtime fields or $raw payload.");
117 return true;
118 }
119
120 while (true) {
121 ser::json_str_view key;
122 bool keyFromScratch = false;
123 if (!reader.parse_string_view(key, &keyFromScratch))
124 return false;
125 ser::json_str keyStorage;
126 if (keyFromScratch) {
127 keyStorage.assign(key.data(), key.size());
128 key = keyStorage;
129 }
130 if (!reader.expect(':'))
131 return false;
132
133 if (key == "$raw") {
134 rawFound = true;
135 if (!ser::detail::parse_json_byte_array(reader, rawPayload))
136 return false;
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) {
142 pField = &field;
143 break;
144 }
145 }
146
147 if (pField == nullptr) {
148 warn(ser::JsonDiagReason::UnknownField, make_field_path(key), "Unknown runtime field.");
149 if (!reader.skip_value())
150 return false;
151 } else {
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)) {
155 warn(
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())
159 return false;
160 } else {
161 fieldFound = true;
162 auto* pFieldData = pBase + pField->offset;
163 bool fieldOk = true;
164 if (!ser::detail::read_runtime_field_json(reader, pFieldData, type, fieldSize, fieldOk))
165 return false;
166 if (!fieldOk) {
167 warn(
168 ser::JsonDiagReason::FieldValueAdjusted, make_field_path(key),
169 "Field value was lossy, truncated, or unsupported for the target runtime field type.");
170 }
171 }
172 }
173 } else {
174 warn(
175 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(key),
176 "Runtime field metadata is unavailable for keyed field payloads.");
177 if (!reader.skip_value())
178 return false;
179 }
180
181 reader.ws();
182 if (reader.consume(','))
183 continue;
184 if (reader.consume('}'))
185 break;
186 return false;
187 }
188
189 if (rawFound) {
190 if (item.comp.soa() != 0) {
191 warn(
192 ser::JsonDiagReason::SoaRawUnsupported, make_field_path("$raw"),
193 "$raw payload is not supported for SoA components.");
194 return true;
195 }
196
197 auto s = ser::make_serializer(rawPayload);
198 s.seek(0);
199 item.load(s, pBase, 0, 1, 1);
200 }
201
202 if (!rawFound && !fieldFound)
203 warn(
204 ser::JsonDiagReason::MissingRuntimeFieldsOrRawPayload, make_field_path(""),
205 "Component payload contains neither recognized runtime fields nor $raw data.");
206
207 return true;
208 }
209
211 inline bool
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();
216 return parsed;
217 }
218
223 inline bool World::save_json(ser::ser_json& writer, ser::JsonSaveFlags flags) const {
224 auto write_raw_component = [&](const ComponentCacheItem& item, const uint8_t* pData, uint32_t from, uint32_t to,
225 uint32_t cap) {
227 auto s = ser::make_serializer(raw);
228 item.save(s, pData, from, to, cap);
229
230 writer.begin_object();
231 writer.key("$raw");
232 writer.begin_array();
233 const auto* pRaw = raw.data();
234 GAIA_FOR(raw.bytes()) writer.value_int(pRaw[i]);
235 writer.end_array();
236 writer.end_object();
237 };
238
239 bool ok = true;
240 const bool includeBinarySnapshot = (flags & ser::JsonSaveFlags::BinarySnapshot) != 0;
241 const bool allowRawFallback = (flags & ser::JsonSaveFlags::RawFallback) != 0;
242 ser::bin_stream binarySnapshot;
243 if (includeBinarySnapshot) {
244 auto s = ser::make_serializer(binarySnapshot);
245 s.reset();
246 save_to(s);
247 }
248
249 writer.clear();
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();
258 {
259 const auto* pData = (const uint8_t*)binarySnapshot.data();
260 GAIA_FOR(binarySnapshot.bytes()) writer.value_int(pData[i]);
261 }
262 writer.end_array();
263 }
264 writer.key("archetypes");
265 writer.begin_array();
266
267 for (const auto* pArchetype: m_archetypes) {
268 if (pArchetype == nullptr || pArchetype->chunks().empty())
269 continue;
270
271 writer.begin_object();
272 writer.key("id");
273 writer.value_int((uint32_t)pArchetype->id());
274 writer.key("hash");
275 writer.value_int((uint64_t)pArchetype->lookup_hash().hash);
276
277 writer.key("components");
278 writer.begin_array();
279 {
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());
284 else
285 writer.value_string("<unnamed>");
286 }
287 }
288 writer.end_array();
289
290 writer.key("entities");
291 writer.begin_array();
292 {
293 for (const auto* pChunk: pArchetype->chunks()) {
294 if (pChunk == nullptr || pChunk->empty())
295 continue;
296
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];
301
302 writer.begin_object();
303 {
304 writer.key("entity");
305 {
306 writer.begin_object();
307 writer.key("id");
308 writer.value_int(entity.id());
309 writer.key("gen");
310 writer.value_int(entity.gen());
311 writer.key("pair");
312 writer.value_bool(entity.pair());
313 writer.key("kind");
314 writer.value_string(EntityKindString[entity.kind()]);
315 const auto entityName = name(entity);
316 if (!entityName.empty()) {
317 writer.key("name");
318 writer.value_string(entityName.data(), entityName.size());
319 }
320 writer.end_object();
321 }
322
323 writer.key("components");
324 writer.begin_object();
325 {
326 GAIA_FOR_((uint32_t)recs.size(), j) {
327 const auto& rec = recs[j];
328 const auto& item = *rec.pItem;
329 const auto name = symbol(item.entity);
330 writer.key(name.data(), name.size());
331
332 // Tags have no associated payload.
333 if (rec.comp.size() == 0) {
334 writer.value_bool(true);
335 continue;
336 }
337
338 const auto row = item.entity.kind() == EntityKind::EK_Uni ? 0U : i;
339
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;
343 } else {
344 if (allowRawFallback)
345 write_raw_component(item, rec.pData, row, row + 1, pChunk->capacity());
346 else {
347 writer.value_null();
348 ok = false;
349 }
350 }
351 }
352 writer.end_object();
353 }
354 }
355 writer.end_object();
356 }
357 }
358 }
359
360 writer.end_array();
361 writer.end_object();
362 }
363
364 writer.end_array();
365 writer.end_object();
366 return ok;
367 }
368
370 inline ser::json_str World::save_json(bool& ok, ser::JsonSaveFlags flags) const {
371 ser::ser_json writer;
372 ok = save_json(writer, flags);
373 return writer.str();
374 }
375
377 inline bool World::load_json(const char* json, uint32_t len, ser::JsonDiagnostics& diagnostics) {
378 diagnostics.clear();
379 if (json == nullptr)
380 return false;
381 if (len == 0) {
382 diagnostics.add(
383 ser::JsonDiagSeverity::Error, ser::JsonDiagReason::InvalidJson, "$",
384 "Input JSON length must be provided and non-zero.");
385 return false;
386 }
387
388 const auto dataLen = len;
389 const auto* p = json;
390 const auto* end = json + dataLen;
391 auto warn = [&](ser::JsonDiagReason reason, ser::json_str_view path, const char* message) {
392 diagnostics.add(ser::JsonDiagSeverity::Warning, reason, path, message);
393 };
394 auto error = [&](ser::JsonDiagReason reason, ser::json_str_view path, const char* message) {
395 diagnostics.add(ser::JsonDiagSeverity::Error, reason, path, message);
396 };
397
398 // Validate top-level format version first.
399 {
400 ser::ser_json header(json, dataLen);
401 if (!header.expect('{')) {
402 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
403 return false;
404 }
405
406 bool hasFormat = false;
407 uint32_t formatValue = 0;
408
409 header.ws();
410 if (!header.consume('}')) {
411 while (true) {
413 if (!header.parse_string_view(key))
414 return false;
415 if (!header.expect(':'))
416 return false;
417
418 if (key == "format") {
419 double d = 0.0;
420 if (!header.parse_number(d))
421 return false;
422 if (d < 0.0 || d > 4294967295.0)
423 return false;
424 const auto v = (uint32_t)d;
425 if ((double)v != d)
426 return false;
427 formatValue = v;
428 hasFormat = true;
429 } else {
430 if (!header.skip_value())
431 return false;
432 }
433
434 header.ws();
435 if (header.consume(','))
436 continue;
437 if (header.consume('}'))
438 break;
439 return false;
440 }
441 }
442
443 header.ws();
444 if (!header.eof())
445 return false;
446
447 if (!hasFormat) {
448 error(ser::JsonDiagReason::MissingFormatField, "$.format", "Missing required 'format' field.");
449 return false;
450 }
451
452 if (formatValue != WorldSerializerJSONVersion) {
453 error(
454 ser::JsonDiagReason::UnsupportedFormatVersion, "$.format",
455 "Unsupported format version. Expected numeric value 1.");
456 return false;
457 }
458 }
459
460 // Prefer fast-path: binary snapshot payload.
461 {
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) {
467 keyPos = it;
468 break;
469 }
470 }
471 if (keyPos != nullptr) {
472 const char* arr = nullptr;
473 for (const char* it = keyPos + keyLen; it < end; ++it) {
474 if (*it == '[') {
475 arr = it;
476 break;
477 }
478 }
479 if (arr != nullptr) {
480 ser::bin_stream serializer;
481 ser::ser_json binaryReader(arr, (uint32_t)(end - arr));
482 if (!ser::detail::parse_json_byte_array(binaryReader, serializer))
483 return false;
484
485 return load(serializer);
486 }
487 }
488 }
489
490 // Fallback: semantic world JSON parser.
491 ser::ser_json jp(json, dataLen);
492
493 struct CompDataLoc {
494 uint8_t* pBase = nullptr;
495 uint32_t row = 0;
496 uint32_t cap = 0;
497 bool valid = false;
498 };
499
500 auto locate_component_data = [&](Entity entity, const ComponentCacheItem& item) -> CompDataLoc {
501 CompDataLoc loc{};
502 if (!has(entity))
503 return loc;
504
505 auto& ec = fetch(entity);
506 const auto compIdx = core::get_index(ec.pChunk->ids_view(), item.entity);
507 if (compIdx == BadIndex)
508 return loc;
509
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();
513 loc.valid = true;
514 return loc;
515 };
516
517 auto has_direct_component = [&](Entity entity, Entity componentEntity) -> bool {
518 if (!has(entity))
519 return false;
520 const auto& ec = fetch(entity);
521 return core::get_index(ec.pChunk->ids_view(), componentEntity) != BadIndex;
522 };
523
524 auto parse_and_apply_component_value = [&](Entity entity, const ComponentCacheItem& item,
525 ser::json_str_view compPath) -> bool {
526 jp.ws();
527 if (jp.eof())
528 return false;
529
530 // Tag values are currently ignored by semantic loader.
531 const char next = jp.peek();
532 if (next == 't' || next == 'f') {
533 bool tagValue = false;
534 if (!jp.parse_bool(tagValue))
535 return false;
536 warn(
537 ser::JsonDiagReason::TagValueIgnored, compPath,
538 "Tag-like boolean component payload is ignored in semantic mode.");
539 return true;
540 }
541
542 if (jp.parse_null()) {
543 warn(
544 ser::JsonDiagReason::NullComponentPayload, compPath,
545 "Null component payload is ignored in semantic mode.");
546 return true;
547 }
548
549 if (!has_direct_component(entity, item.entity))
550 add(entity, item.entity);
551
552 auto loc = locate_component_data(entity, item);
553 if (!loc.valid) {
554 warn(
555 ser::JsonDiagReason::MissingComponentStorage, compPath,
556 "Component storage is unavailable on the target entity.");
557 return jp.skip_value();
558 }
559
560 auto* pRowData = loc.pBase + (uintptr_t)item.comp.size() * loc.row;
561 if (!ecs::json_to_component(item, pRowData, jp, diagnostics, compPath))
562 return false;
563 return true;
564 };
565
566 auto parse_entity_meta = [&](bool& isPair, ser::json_str& nameOut) -> bool {
567 if (!jp.expect('{'))
568 return false;
569
570 jp.ws();
571 if (jp.consume('}'))
572 return true;
573
574 while (true) {
576 if (!jp.parse_string_view(key))
577 return false;
578 if (!jp.expect(':'))
579 return false;
580
581 if (key == "pair") {
582 if (!jp.parse_bool(isPair))
583 return false;
584 } else if (key == "name") {
585 if (!jp.parse_string(nameOut))
586 return false;
587 } else {
588 if (!jp.skip_value())
589 return false;
590 }
591
592 jp.ws();
593 if (jp.consume(','))
594 continue;
595 if (jp.consume('}'))
596 break;
597 return false;
598 }
599
600 return true;
601 };
602
603 auto parse_components_for_entity = [&](Entity& entity, bool& created, bool isPair,
604 const ser::json_str& entityName) -> bool {
605 if (!jp.expect('{'))
606 return false;
607
608 jp.ws();
609 if (jp.consume('}'))
610 return true;
611
612 while (true) {
613 ser::json_str_view compName;
614 bool compNameFromScratch = false;
615 if (!jp.parse_string_view(compName, &compNameFromScratch))
616 return false;
617 ser::json_str compNameStorage;
618 if (compNameFromScratch) {
619 compNameStorage.assign(compName.data(), compName.size());
620 compName = compNameStorage;
621 }
622 if (!jp.expect(':'))
623 return false;
624
625 const bool isInternalComp = compName.size() >= 10 && memcmp(compName.data(), "gaia::ecs::", 10) == 0;
626 if (isPair || isInternalComp) {
627 if (!jp.skip_value())
628 return false;
629 } else {
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) {
633 warn(
634 ser::JsonDiagReason::UnknownComponent, compName,
635 "Component is not registered in the component cache.");
636 if (!jp.skip_value())
637 return false;
638 } else if (pItem->comp.size() == 0) {
639 // Ignore tag-only components in semantic mode for now.
640 warn(
641 ser::JsonDiagReason::TagComponentUnsupported, compName,
642 "Tag-only component semantic JSON loading is currently unsupported.");
643 if (!jp.skip_value())
644 return false;
645 } else {
646 if (!created) {
647 entity = add();
648 created = true;
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());
653 else
654 warn(
655 ser::JsonDiagReason::DuplicateEntityName, "entity.name",
656 "Entity name already exists; keeping existing mapping.");
657 }
658 }
659
660 if (!parse_and_apply_component_value(entity, *pItem, compName))
661 return false;
662 }
663 }
664
665 jp.ws();
666 if (jp.consume(','))
667 continue;
668 if (jp.consume('}'))
669 break;
670 return false;
671 }
672
673 return true;
674 };
675
676 auto parse_entity_entry = [&]() -> bool {
677 if (!jp.expect('{')) {
678 error(ser::JsonDiagReason::InvalidJson, "$", "Root JSON value must be an object.");
679 return false;
680 }
681
682 bool isPair = false;
683 ser::json_str entityName;
684 Entity entity = EntityBad;
685 bool created = false;
686
687 jp.ws();
688 if (jp.consume('}'))
689 return true;
690
691 while (true) {
693 if (!jp.parse_string_view(key))
694 return false;
695 if (!jp.expect(':'))
696 return false;
697
698 if (key == "entity") {
699 if (!parse_entity_meta(isPair, entityName))
700 return false;
701 } else if (key == "components") {
702 if (!parse_components_for_entity(entity, created, isPair, entityName))
703 return false;
704 } else {
705 if (!jp.skip_value())
706 return false;
707 }
708
709 jp.ws();
710 if (jp.consume(','))
711 continue;
712 if (jp.consume('}'))
713 break;
714 return false;
715 }
716
717 return true;
718 };
719
720 auto parse_archetypes = [&]() -> bool {
721 if (!jp.expect('['))
722 return false;
723
724 jp.ws();
725 if (jp.consume(']'))
726 return true;
727
728 while (true) {
729 if (!jp.expect('{'))
730 return false;
731
732 jp.ws();
733 if (!jp.consume('}')) {
734 while (true) {
736 if (!jp.parse_string_view(key))
737 return false;
738 if (!jp.expect(':'))
739 return false;
740
741 if (key == "entities") {
742 if (!jp.expect('['))
743 return false;
744
745 jp.ws();
746 if (!jp.consume(']')) {
747 while (true) {
748 if (!parse_entity_entry())
749 return false;
750
751 jp.ws();
752 if (jp.consume(','))
753 continue;
754 if (jp.consume(']'))
755 break;
756 return false;
757 }
758 }
759 } else {
760 if (!jp.skip_value())
761 return false;
762 }
763
764 jp.ws();
765 if (jp.consume(','))
766 continue;
767 if (jp.consume('}'))
768 break;
769 return false;
770 }
771 }
772
773 jp.ws();
774 if (jp.consume(','))
775 continue;
776 if (jp.consume(']'))
777 break;
778 return false;
779 }
780
781 return true;
782 };
783
784 if (!jp.expect('{'))
785 return false;
786
787 bool hasArchetypes = false;
788 jp.ws();
789 if (!jp.consume('}')) {
790 while (true) {
792 if (!jp.parse_string_view(key))
793 return false;
794 if (!jp.expect(':'))
795 return false;
796
797 if (key == "archetypes") {
798 hasArchetypes = true;
799 if (!parse_archetypes())
800 return false;
801 } else {
802 if (!jp.skip_value())
803 return false;
804 }
805
806 jp.ws();
807 if (jp.consume(','))
808 continue;
809 if (jp.consume('}'))
810 break;
811 return false;
812 }
813 }
814
815 jp.ws();
816 if (!jp.eof())
817 return false;
818 if (!hasArchetypes) {
819 error(ser::JsonDiagReason::MissingArchetypesSection, "$.archetypes", "Missing required 'archetypes' section.");
820 return false;
821 }
822
823 return true;
824 }
825
826 inline bool World::load_json(const char* json, uint32_t len) {
827 ser::JsonDiagnostics diagnostics;
828 const bool parsed = load_json(json, len, diagnostics);
829 return parsed && !diagnostics.has_issues();
830 }
831
833 return load_json(json.data(), json.size(), diagnostics);
834 }
835
837 ser::JsonDiagnostics diagnostics;
838 const bool parsed = load_json(json.data(), json.size(), diagnostics);
839 return parsed && !diagnostics.has_issues();
840 }
841 } // namespace ecs
842} // namespace gaia
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
Definition id.h:247
Definition ser_json.h:57
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