Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
world_schema_json.h
1#pragma once
2#include "gaia/config/config.h"
3
4#if GAIA_JSON_ENABLED
5
6 #include <cstdio>
7 #include <cstring>
8
9namespace gaia {
10 namespace ecs {
12 namespace detail {
16 inline const char* runtime_schema_kind_name(RuntimeTypeKind kind) noexcept {
17 switch (kind) {
18 case RuntimeTypeKind::None:
19 return "none";
20 case RuntimeTypeKind::Primitive:
21 return "primitive";
22 case RuntimeTypeKind::Struct:
23 return "struct";
24 case RuntimeTypeKind::Enum:
25 return "enum";
26 case RuntimeTypeKind::Bitmask:
27 return "bitmask";
28 case RuntimeTypeKind::Array:
29 return "array";
30 case RuntimeTypeKind::Vector:
31 return "vector";
32 case RuntimeTypeKind::Opaque:
33 return "opaque";
34 }
35 return "none";
36 }
37
41 inline const char* runtime_schema_json_encoding_name(RuntimeJsonEncoding encoding) noexcept {
42 return encoding == RuntimeJsonEncoding::Utf8String ? "utf8-string" : "default";
43 }
44
48 inline const char* runtime_schema_storage_name(DataStorageType storage) noexcept {
49 return storage == DataStorageType::Sparse ? "sparse" : "table";
50 }
51
57 inline bool
58 runtime_schema_type_editable(const ComponentCache& cache, const ComponentCacheItem& item, uint32_t depth = 0) {
59 if (depth >= RuntimeJsonMaxDepth)
60 return false;
61
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);
65 };
66
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))
77 return true;
78 }
79 return false;
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)
87 return false;
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);
93 }
94 case RuntimeTypeKind::None:
95 return false;
96 }
97 return false;
98 }
99
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) {
107 writer.value_null();
108 return;
109 }
110 writer.begin_object();
111 writer.key("symbol");
112 const auto typeSymbol = pType->symbol_name();
113 writer.value_string(typeSymbol.data(), typeSymbol.size());
114 char stableId[17]{};
115 (void)snprintf(stableId, sizeof(stableId), "%016llx", (unsigned long long)pType->hashLookup.hash);
116 writer.key("stableId");
117 writer.value_string(stableId);
118 writer.end_object();
119 }
120
127 GAIA_NODISCARD inline bool
128 runtime_schema_semantic_path(const World& world, Entity semantic, util::str& out, uint64_t& stableHash) {
129 out.clear();
130 stableHash = 0;
131 if (semantic == EntityBad || semantic.pair())
132 return false;
133
134 cnt::darray_ext<util::str_view, 16> segments;
135 auto current = semantic;
136 while (current != EntityBad) {
137 if (!world.valid(current) || current.pair())
138 return false;
139 const auto name = world.name(current);
140 if (name.empty())
141 return false;
142 segments.push_back(name);
143 current = world.target(current, ChildOf);
144 }
145 if (segments.empty())
146 return false;
147
148 uint32_t size = (uint32_t)segments.size() - 1;
149 for (const auto segment: segments)
150 size += segment.size();
151 out.reserve(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];
155 if (!out.empty())
156 out.append('.');
157 out.append(segment);
158 stableHash = core::hash_combine(
159 stableHash, (uint64_t)segment.size(), core::calculate_hash64(segment.data(), segment.size()));
160 }
161 return true;
162 }
163
168 inline void runtime_schema_write_semantic_ref(ser::ser_json& writer, const World& world, Entity semantic) {
169 util::str path;
170 uint64_t stableHash = 0;
171 if (!runtime_schema_semantic_path(world, semantic, path, stableHash)) {
172 writer.value_null();
173 return;
174 }
175 const auto name = world.name(semantic);
176 writer.begin_object();
177 writer.key("name");
178 writer.value_string(name.data(), name.size());
179 writer.key("path");
180 writer.value_string(path.data(), path.size());
181 char stableId[17]{};
182 (void)snprintf(stableId, sizeof(stableId), "%016llx", (unsigned long long)stableHash);
183 writer.key("stableId");
184 writer.value_string(stableId);
185 writer.end_object();
186 }
187
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();
198 writer.key("name");
199 const auto fieldName = item.field_name(field);
200 writer.value_string(fieldName.data(), fieldName.size());
201 writer.key("type");
202 runtime_schema_write_type_ref(writer, cache, field.type);
203 const auto* pFieldType = cache.find(field.type);
204 writer.key("editable");
205 writer.value_bool(
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);
210 writer.key("count");
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);
223 if (!unit.empty()) {
224 writer.key("unit");
225 writer.value_string(unit.data(), unit.size());
226 }
227 if ((field.flags & RuntimeFieldFlag_HasMinimum) != 0) {
228 writer.key("minimum");
229 writer.value_float(field.minimum);
230 }
231 if ((field.flags & RuntimeFieldFlag_HasMaximum) != 0) {
232 writer.key("maximum");
233 writer.value_float(field.maximum);
234 }
235 if ((field.flags & RuntimeFieldFlag_HasStep) != 0) {
236 writer.key("step");
237 writer.value_float(field.step);
238 }
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);
247 }
248 writer.end_object();
249 }
250
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());
264 writer.key("path");
265 writer.value_string(item.path.empty() ? "" : item.path.data(), item.path.size());
266 if (includeRuntimeEntity) {
267 writer.key("entity");
268 writer.begin_object();
269 writer.key("id");
270 writer.value_int(item.entity.id());
271 writer.key("generation");
272 writer.value_int(item.entity.gen());
273 writer.end_object();
274 }
275 char stableId[17]{};
276 (void)snprintf(stableId, sizeof(stableId), "%016llx", (unsigned long long)item.hashLookup.hash);
277 writer.key("stableId");
278 writer.value_string(stableId);
279 writer.key("kind");
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));
285 writer.key("size");
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");
298 }
299 if (item.underlyingType != EntityBad) {
300 writer.key("underlyingType");
301 runtime_schema_write_type_ref(writer, cache, item.underlyingType);
302 }
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);
308 }
309 if (item.opaqueAsType != EntityBad) {
310 writer.key("opaqueAsType");
311 runtime_schema_write_type_ref(writer, cache, item.opaqueAsType);
312 }
313 if (item.typeKind == RuntimeTypeKind::Vector) {
314 writer.key("readable");
315 writer.value_bool(
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);
320 }
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);
326 }
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]);
331 writer.end_array();
332 }
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));
336 writer.end_array();
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();
343 writer.key("name");
344 const auto constantName = item.constant_name(constant);
345 writer.value_string(constantName.data(), constantName.size());
346 writer.key("value");
347 writer.value_int(constant.value);
348 writer.end_object();
349 }
350 writer.end_array();
351 }
352 writer.end_object();
353 }
354
355 } // namespace detail
357
358 inline bool
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) {
363 (void)entityId;
364 items.push_back(pItem);
365 }
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());
368 if (nameOrder != 0)
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();
373 });
374 writer.clear();
375 writer.begin_object();
376 writer.key("format");
377 writer.value_string("gaia.ecs.schema");
378 writer.key("version");
379 writer.value_int(2);
380 if (schemaHash != nullptr) {
381 writer.key("hash");
382 writer.value_string(schemaHash);
383 }
384 writer.key("types");
385 writer.begin_array();
386 for (const auto* pItem: items)
387 detail::runtime_schema_write_item(writer, *this, m_compCache, *pItem, includeRuntimeEntities);
388 writer.end_array();
389 writer.end_object();
390 return true;
391 }
392
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());
398 }
399
400 inline bool World::save_runtime_schema_json(ser::ser_json& writer) const {
401 char hash[17]{};
402 (void)snprintf(hash, sizeof(hash), "%016llx", (unsigned long long)runtime_schema_hash());
403 return write_runtime_schema_json(writer, hash, true);
404 }
405
406 inline ser::json_str World::save_runtime_schema_json() const {
407 ser::ser_json writer;
408 (void)save_runtime_schema_json(writer);
409 return writer.str();
410 }
411
412 } // namespace ecs
413} // namespace gaia
414
415#endif