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 if ((field.flags & RuntimeFieldFlag_HasMinimum) != 0) {
228 writer.key(
"minimum");
229 writer.value_float(field.minimum);
231 if ((field.flags & RuntimeFieldFlag_HasMaximum) != 0) {
232 writer.key(
"maximum");
233 writer.value_float(field.maximum);
235 if ((field.flags & RuntimeFieldFlag_HasStep) != 0) {
237 writer.value_float(field.step);
239 RuntimeJsonFieldLayout layout{};
240 const bool derivedUtf8 = resolve_runtime_json_field_layout(&cache, field, layout) && layout.elemCount > 1 &&
241 runtime_json_is_char8_type(layout.pType, layout.type);
242 if (field.jsonEncoding == RuntimeJsonEncoding::Utf8String || derivedUtf8) {
243 writer.key(
"encoding");
244 writer.value_string(
"utf-8");
245 writer.key(
"capacity");
246 writer.value_int(layout.elemCount);
257 inline void runtime_schema_write_item(
258 ser::ser_json& writer,
const World& world,
const ComponentCache& cache,
const ComponentCacheItem& item,
259 bool includeRuntimeEntity) {
260 writer.begin_object();
261 writer.key(
"symbol");
262 const auto itemSymbol = item.symbol_name();
263 writer.value_string(itemSymbol.data(), itemSymbol.size());
265 writer.value_string(item.path.empty() ?
"" : item.path.data(), item.path.size());
266 if (includeRuntimeEntity) {
267 writer.key(
"entity");
268 writer.begin_object();
270 writer.value_int(item.entity.id());
271 writer.key(
"generation");
272 writer.value_int(item.entity.gen());
276 (void)snprintf(stableId,
sizeof(stableId),
"%016llx", (
unsigned long long)item.hashLookup.hash);
277 writer.key(
"stableId");
278 writer.value_string(stableId);
280 writer.value_string(runtime_schema_kind_name(item.typeKind));
281 writer.key(
"semantic");
282 runtime_schema_write_semantic_ref(writer, world, item.semantic);
283 writer.key(
"jsonEncoding");
284 writer.value_string(runtime_schema_json_encoding_name(item.jsonEncoding));
286 writer.value_int(item.comp.size());
287 writer.key(
"alignment");
288 writer.value_int(item.comp.alig());
289 writer.key(
"storage");
290 writer.value_string(runtime_schema_storage_name(item.comp.storage_type()));
291 writer.key(
"layout");
292 writer.value_string(item.comp.soa() == 0 ?
"aos" :
"soa");
293 writer.key(
"editable");
294 writer.value_bool(runtime_schema_type_editable(cache, item));
295 if (item.jsonEncoding == RuntimeJsonEncoding::Utf8String) {
296 writer.key(
"encoding");
297 writer.value_string(
"utf-8");
299 if (item.underlyingType != EntityBad) {
300 writer.key(
"underlyingType");
301 runtime_schema_write_type_ref(writer, cache, item.underlyingType);
303 if (item.elementType != EntityBad) {
304 writer.key(
"elementType");
305 runtime_schema_write_type_ref(writer, cache, item.elementType);
306 writer.key(
"elementCount");
307 writer.value_int(item.elementCount);
309 if (item.opaqueAsType != EntityBad) {
310 writer.key(
"opaqueAsType");
311 runtime_schema_write_type_ref(writer, cache, item.opaqueAsType);
313 if (item.typeKind == RuntimeTypeKind::Vector) {
314 writer.key(
"readable");
316 item.sequenceAdapter !=
nullptr && item.sequenceAdapter->count !=
nullptr &&
317 item.sequenceAdapter->element !=
nullptr);
318 writer.key(
"resizable");
319 writer.value_bool(item.sequenceAdapter !=
nullptr && item.sequenceAdapter->resize !=
nullptr);
321 if (item.typeKind == RuntimeTypeKind::Opaque) {
322 writer.key(
"readable");
323 writer.value_bool(item.opaqueAdapter !=
nullptr && item.opaqueAdapter->project !=
nullptr);
324 writer.key(
"committable");
325 writer.value_bool(item.opaqueAdapter !=
nullptr && item.opaqueAdapter->commit !=
nullptr);
327 if (item.comp.soa() != 0) {
328 writer.key(
"soaElementSizes");
329 writer.begin_array();
330 GAIA_FOR(item.comp.soa()) writer.value_int(item.soaSizes[i]);
333 writer.key("fields");
334 writer.begin_array();
335 GAIA_FOR(item.field_count()) runtime_schema_write_field(writer, world, cache, item, *item.field(i));
337 if (item.constant_count() != 0) {
338 writer.key(
"constants");
339 writer.begin_array();
340 GAIA_FOR(item.constant_count()) {
341 const auto& constant = *item.constant(i);
342 writer.begin_object();
344 const auto constantName = item.constant_name(constant);
345 writer.value_string(constantName.data(), constantName.size());
347 writer.value_int(constant.value);
359 World::write_runtime_schema_json(ser::ser_json& writer,
const char* schemaHash,
bool includeRuntimeEntities)
const {
360 cnt::darray<const ComponentCacheItem*> items;
361 items.reserve(m_compCache.m_compByEntityId.size());
362 for (
const auto& [entityId, pItem]: m_compCache.m_compByEntityId) {
364 items.push_back(pItem);
366 core::sort(items.begin(), items.end(), [](
const ComponentCacheItem* a,
const ComponentCacheItem* b) {
367 const auto nameOrder = strcmp(a->symbol_name().data(), b->symbol_name().data());
369 return nameOrder < 0;
370 const auto pathSize = a->path.size() < b->path.size() ? a->path.size() : b->path.size();
371 const auto pathOrder = pathSize != 0 ? memcmp(a->path.data(), b->path.data(), pathSize) : 0;
372 return pathOrder != 0 ? pathOrder < 0 : a->path.size() < b->path.size();
375 writer.begin_object();
376 writer.key(
"format");
377 writer.value_string(
"gaia.ecs.schema");
378 writer.key(
"version");
380 if (schemaHash !=
nullptr) {
382 writer.value_string(schemaHash);
385 writer.begin_array();
386 for (
const auto* pItem: items)
387 detail::runtime_schema_write_item(writer, *this, m_compCache, *pItem, includeRuntimeEntities);
393 inline uint64_t World::runtime_schema_hash()
const {
394 ser::ser_json writer;
395 (void)write_runtime_schema_json(writer,
nullptr,
false);
396 const auto& canonical = writer.str();
397 return core::calculate_hash64(canonical.data(), canonical.size());
400 inline bool World::save_runtime_schema_json(ser::ser_json& writer)
const {
402 (void)snprintf(hash,
sizeof(hash),
"%016llx", (
unsigned long long)runtime_schema_hash());
403 return write_runtime_schema_json(writer, hash,
true);
406 inline ser::json_str World::save_runtime_schema_json()
const {
407 ser::ser_json writer;
408 (void)save_runtime_schema_json(writer);