2#include "gaia/config/config.h"
16 inline const char* runtime_schema_kind_name(RuntimeTypeKind kind)
noexcept {
18 case RuntimeTypeKind::None:
20 case RuntimeTypeKind::Primitive:
22 case RuntimeTypeKind::Struct:
24 case RuntimeTypeKind::Enum:
26 case RuntimeTypeKind::Bitmask:
28 case RuntimeTypeKind::Array:
30 case RuntimeTypeKind::Vector:
32 case RuntimeTypeKind::Opaque:
41 inline const char* runtime_schema_json_encoding_name(RuntimeJsonEncoding encoding)
noexcept {
42 return encoding == RuntimeJsonEncoding::Utf8String ?
"utf8-string" :
"default";
48 inline const char* runtime_schema_storage_name(DataStorageType storage)
noexcept {
49 return storage == DataStorageType::Sparse ?
"sparse" :
"table";
58 runtime_schema_type_editable(
const ComponentCache& cache,
const ComponentCacheItem& item, uint32_t depth = 0) {
59 if (depth >= RuntimeJsonMaxDepth)
62 auto referenced_type_editable = [&](Entity type) {
63 const auto* pType = cache.find(type);
64 return pType !=
nullptr && runtime_schema_type_editable(cache, *pType, depth + 1);
67 switch (item.typeKind) {
68 case RuntimeTypeKind::Primitive:
69 case RuntimeTypeKind::Enum:
70 case RuntimeTypeKind::Bitmask:
71 return runtime_json_leaf_editable(item);
72 case RuntimeTypeKind::Struct:
73 GAIA_FOR(item.field_count()) {
74 const auto& field = *item.field(i);
75 if ((field.flags & (RuntimeFieldFlag_ReadOnly | RuntimeFieldFlag_Hidden)) == 0 &&
76 referenced_type_editable(field.type))
80 case RuntimeTypeKind::Array:
81 return item.elementCount > 0 && referenced_type_editable(item.elementType);
82 case RuntimeTypeKind::Vector:
83 return item.sequenceAdapter !=
nullptr && item.sequenceAdapter->count !=
nullptr &&
84 item.sequenceAdapter->element !=
nullptr && referenced_type_editable(item.elementType);
85 case RuntimeTypeKind::Opaque: {
86 if (item.opaqueAdapter ==
nullptr || item.opaqueAdapter->project ==
nullptr)
88 const auto* pProjected = cache.find(item.opaqueAsType);
89 return pProjected !=
nullptr &&
90 (pProjected->typeKind == RuntimeTypeKind::Struct || pProjected->typeKind == RuntimeTypeKind::Array ||
91 pProjected->typeKind == RuntimeTypeKind::Vector) &&
92 runtime_schema_type_editable(cache, *pProjected, depth + 1);
94 case RuntimeTypeKind::None:
104 inline void runtime_schema_write_type_ref(ser::ser_json& writer,
const ComponentCache& cache, Entity type) {
105 const auto* pType = cache.find(type);
106 if (pType ==
nullptr) {
110 writer.begin_object();
111 writer.key(
"symbol");
112 const auto typeSymbol = pType->symbol_name();
113 writer.value_string(typeSymbol.data(), typeSymbol.size());
115 (void)snprintf(stableId,
sizeof(stableId),
"%016llx", (
unsigned long long)pType->hashLookup.hash);
116 writer.key(
"stableId");
117 writer.value_string(stableId);
127 GAIA_NODISCARD
inline bool
128 runtime_schema_semantic_path(
const World& world, Entity semantic, util::str& out, uint64_t& stableHash) {
131 if (semantic == EntityBad || semantic.pair())
134 cnt::darray_ext<util::str_view, 16> segments;
135 auto current = semantic;
136 while (current != EntityBad) {
137 if (!world.valid(current) || current.pair())
139 const auto name = world.name(current);
142 segments.push_back(name);
143 current = world.target(current, ChildOf);
145 if (segments.empty())
148 uint32_t size = (uint32_t)segments.size() - 1;
149 for (
const auto segment: segments)
150 size += segment.size();
152 stableHash = core::calculate_hash64(
"gaia.ecs.semantic.path.v1");
153 for (uint32_t i = (uint32_t)segments.size(); i > 0; --i) {
154 const auto segment = segments[i - 1];
158 stableHash = core::hash_combine(
159 stableHash, (uint64_t)segment.size(), core::calculate_hash64(segment.data(), segment.size()));
168 inline void runtime_schema_write_semantic_ref(ser::ser_json& writer,
const World& world, Entity semantic) {
170 uint64_t stableHash = 0;
171 if (!runtime_schema_semantic_path(world, semantic, path, stableHash)) {
175 const auto name = world.name(semantic);
176 writer.begin_object();
178 writer.value_string(name.data(), name.size());
180 writer.value_string(path.data(), path.size());
182 (void)snprintf(stableId,
sizeof(stableId),
"%016llx", (
unsigned long long)stableHash);
183 writer.key(
"stableId");
184 writer.value_string(stableId);
194 inline void runtime_schema_write_field(
195 ser::ser_json& writer,
const World& world,
const ComponentCache& cache,
const ComponentCacheItem& item,
196 const RuntimeFieldDesc& field) {
197 writer.begin_object();
199 const auto fieldName = item.field_name(field);
200 writer.value_string(fieldName.data(), fieldName.size());
202 runtime_schema_write_type_ref(writer, cache, field.type);
203 const auto* pFieldType = cache.find(field.type);
204 writer.key(
"editable");
206 (field.flags & (RuntimeFieldFlag_ReadOnly | RuntimeFieldFlag_Hidden)) == 0 && pFieldType !=
nullptr &&
207 runtime_schema_type_editable(cache, *pFieldType));
208 writer.key(
"offset");
209 writer.value_int(field.offset);
211 writer.value_int(ComponentCacheItem::field_element_count(field));
212 writer.key(
"semantic");
213 runtime_schema_write_semantic_ref(writer, world, field.semantic);
214 writer.key(
"jsonEncoding");
215 writer.value_string(runtime_schema_json_encoding_name(field.jsonEncoding));
216 writer.key(
"readOnly");
217 writer.value_bool((field.flags & RuntimeFieldFlag_ReadOnly) != 0);
218 writer.key(
"hidden");
219 writer.value_bool((field.flags & RuntimeFieldFlag_Hidden) != 0);
220 writer.key(
"multiline");
221 writer.value_bool((field.flags & RuntimeFieldFlag_Multiline) != 0);
222 const auto unit = item.field_unit(field);
225 writer.value_string(unit.data(), unit.size());
227 const auto fieldDescription = item.field_description(field);
228 if (!fieldDescription.empty()) {
229 writer.key(
"description");
230 writer.value_string(fieldDescription.data(), fieldDescription.size());
232 if ((field.flags & RuntimeFieldFlag_HasMinimum) != 0) {
233 writer.key(
"minimum");
234 writer.value_float(field.minimum);
236 if ((field.flags & RuntimeFieldFlag_HasMaximum) != 0) {
237 writer.key(
"maximum");
238 writer.value_float(field.maximum);
240 if ((field.flags & RuntimeFieldFlag_HasStep) != 0) {
242 writer.value_float(field.step);
244 RuntimeJsonFieldLayout layout{};
245 const bool derivedUtf8 = resolve_runtime_json_field_layout(&cache, field, layout) && layout.elemCount > 1 &&
246 runtime_json_is_char8_type(layout.pType, layout.type);
247 if (field.jsonEncoding == RuntimeJsonEncoding::Utf8String || derivedUtf8) {
248 writer.key(
"encoding");
249 writer.value_string(
"utf-8");
250 writer.key(
"capacity");
251 writer.value_int(layout.elemCount);
262 inline void runtime_schema_write_item(
263 ser::ser_json& writer,
const World& world,
const ComponentCache& cache,
const ComponentCacheItem& item,
264 bool includeRuntimeEntity) {
265 writer.begin_object();
266 writer.key(
"symbol");
267 const auto itemSymbol = item.symbol_name();
268 writer.value_string(itemSymbol.data(), itemSymbol.size());
270 writer.value_string(item.path.empty() ?
"" : item.path.data(), item.path.size());
271 if (includeRuntimeEntity) {
272 writer.key(
"entity");
273 writer.begin_object();
275 writer.value_int(item.entity.id());
276 writer.key(
"generation");
277 writer.value_int(item.entity.gen());
281 (void)snprintf(stableId,
sizeof(stableId),
"%016llx", (
unsigned long long)item.hashLookup.hash);
282 writer.key(
"stableId");
283 writer.value_string(stableId);
285 writer.value_string(runtime_schema_kind_name(item.typeKind));
286 const auto typeDescription = item.type_description();
287 if (!typeDescription.empty()) {
288 writer.key(
"description");
289 writer.value_string(typeDescription.data(), typeDescription.size());
291 writer.key(
"semantic");
292 runtime_schema_write_semantic_ref(writer, world, item.semantic);
293 writer.key(
"jsonEncoding");
294 writer.value_string(runtime_schema_json_encoding_name(item.jsonEncoding));
296 writer.value_int(item.comp.size());
297 writer.key(
"alignment");
298 writer.value_int(item.comp.alig());
299 writer.key(
"storage");
300 writer.value_string(runtime_schema_storage_name(item.comp.storage_type()));
301 writer.key(
"layout");
302 writer.value_string(item.comp.soa() == 0 ?
"aos" :
"soa");
303 writer.key(
"editable");
304 writer.value_bool(runtime_schema_type_editable(cache, item));
305 if (item.jsonEncoding == RuntimeJsonEncoding::Utf8String) {
306 writer.key(
"encoding");
307 writer.value_string(
"utf-8");
309 if (item.underlyingType != EntityBad) {
310 writer.key(
"underlyingType");
311 runtime_schema_write_type_ref(writer, cache, item.underlyingType);
313 if (item.elementType != EntityBad) {
314 writer.key(
"elementType");
315 runtime_schema_write_type_ref(writer, cache, item.elementType);
316 writer.key(
"elementCount");
317 writer.value_int(item.elementCount);
319 if (item.opaqueAsType != EntityBad) {
320 writer.key(
"opaqueAsType");
321 runtime_schema_write_type_ref(writer, cache, item.opaqueAsType);
323 if (item.typeKind == RuntimeTypeKind::Vector) {
324 writer.key(
"readable");
326 item.sequenceAdapter !=
nullptr && item.sequenceAdapter->count !=
nullptr &&
327 item.sequenceAdapter->element !=
nullptr);
328 writer.key(
"resizable");
329 writer.value_bool(item.sequenceAdapter !=
nullptr && item.sequenceAdapter->resize !=
nullptr);
331 if (item.typeKind == RuntimeTypeKind::Opaque) {
332 writer.key(
"readable");
333 writer.value_bool(item.opaqueAdapter !=
nullptr && item.opaqueAdapter->project !=
nullptr);
334 writer.key(
"committable");
335 writer.value_bool(item.opaqueAdapter !=
nullptr && item.opaqueAdapter->commit !=
nullptr);
337 if (item.comp.soa() != 0) {
338 writer.key(
"soaElementSizes");
339 writer.begin_array();
340 GAIA_FOR(item.comp.soa()) writer.value_int(item.soaSizes[i]);
343 writer.key("fields");
344 writer.begin_array();
345 GAIA_FOR(item.field_count()) runtime_schema_write_field(writer, world, cache, item, *item.field(i));
347 if (item.constant_count() != 0) {
348 writer.key(
"constants");
349 writer.begin_array();
350 GAIA_FOR(item.constant_count()) {
351 const auto& constant = *item.constant(i);
352 writer.begin_object();
354 const auto constantName = item.constant_name(constant);
355 writer.value_string(constantName.data(), constantName.size());
357 writer.value_int(constant.value);
358 const auto constantDescription = item.constant_description(constant);
359 if (!constantDescription.empty()) {
360 writer.key(
"description");
361 writer.value_string(constantDescription.data(), constantDescription.size());
374 World::write_runtime_schema_json(ser::ser_json& writer,
const char* schemaHash,
bool includeRuntimeEntities)
const {
375 cnt::darray<const ComponentCacheItem*> items;
376 items.reserve((uint32_t)m_compCache.m_compByEntityId.size());
377 for (
const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
379 items.push_back(pItem);
381 core::sort(items.begin(), items.end(), [](
const ComponentCacheItem* a,
const ComponentCacheItem* b) {
382 const auto nameOrder = strcmp(a->symbol_name().data(), b->symbol_name().data());
384 return nameOrder < 0;
385 const auto pathSize = a->path.size() < b->path.size() ? a->path.size() : b->path.size();
386 const auto pathOrder = pathSize != 0 ? memcmp(a->path.data(), b->path.data(), pathSize) : 0;
387 return pathOrder != 0 ? pathOrder < 0 : a->path.size() < b->path.size();
390 writer.begin_object();
391 writer.key(
"format");
392 writer.value_string(
"gaia.ecs.schema");
393 writer.key(
"version");
395 if (schemaHash !=
nullptr) {
397 writer.value_string(schemaHash);
400 writer.begin_array();
401 for (
const auto* pItem: items)
402 detail::runtime_schema_write_item(writer, *this, m_compCache, *pItem, includeRuntimeEntities);
408 inline uint64_t World::runtime_schema_hash()
const {
409 ser::ser_json writer;
410 (void)write_runtime_schema_json(writer,
nullptr,
false);
411 const auto& canonical = writer.str();
412 return core::calculate_hash64(canonical.data(), canonical.size());
415 inline bool World::save_runtime_schema_json(ser::ser_json& writer)
const {
417 (void)snprintf(hash,
sizeof(hash),
"%016llx", (
unsigned long long)runtime_schema_hash());
418 return write_runtime_schema_json(writer, hash,
true);
421 inline ser::json_str World::save_runtime_schema_json()
const {
422 ser::ser_json writer;
423 (void)save_runtime_schema_json(writer);