2#include "gaia/config/config.h"
17 inline bool runtime_patch_decode_token(ser::json_str_view encoded, util::str& token) {
20 GAIA_FOR(encoded.size()) {
21 const auto ch = encoded.data()[i];
27 if (i + 1 >= encoded.size())
30 const auto escaped = encoded.data()[++i];
33 else if (escaped ==
'1')
46 inline bool runtime_patch_parse_index(util::str_view token, uint32_t& index) {
51 GAIA_FOR(token.size()) {
52 const auto ch = token.data()[i];
53 if (ch <
'0' || ch >
'9')
55 value = value * 10 + (uint32_t)(ch -
'0');
56 if (value > UINT32_MAX)
60 index = (uint32_t)value;
65 struct RuntimeJsonPointerTarget final {
67 const ComponentCacheItem* pType =
nullptr;
69 const RuntimeFieldDesc* pField =
nullptr;
71 bool endsAtField =
false;
73 ser::JsonDiagReason reason = ser::JsonDiagReason::None;
75 const char* message =
nullptr;
86 inline bool walk_runtime_json_pointer(
87 const ComponentCache& cache,
const ComponentCacheItem& root, ComponentCursor& cursor,
88 ser::json_str_view pointer,
bool forWrite, RuntimeJsonPointerTarget& target) {
89 auto fail = [&](ser::JsonDiagReason reason,
const char* message) {
90 target.reason = reason;
91 target.message = message;
95 if (!pointer.empty() && pointer.data()[0] !=
'/')
96 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path must be an RFC 6901 JSON Pointer.");
97 if (!pointer.empty() && (pointer.size() == 1 || pointer.data()[pointer.size() - 1] ==
'/'))
98 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path contains an empty token.");
100 target.pType = &root;
101 uint32_t pos = pointer.empty() ? pointer.size() : 1;
102 while (pos < pointer.size()) {
104 while (end < pointer.size() && pointer.data()[end] !=
'/')
108 if (!runtime_patch_decode_token(ser::json_str_view(pointer.data() + pos, end - pos), token) || token.empty())
109 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path contains an invalid token.");
111 const ComponentCacheItem* pFieldOwner = target.pType;
112 if (pFieldOwner !=
nullptr && pFieldOwner->typeKind == RuntimeTypeKind::Opaque)
113 pFieldOwner = cache.find(pFieldOwner->opaque_as_type());
115 if (pFieldOwner !=
nullptr && pFieldOwner->typeKind == RuntimeTypeKind::Struct) {
116 const auto* pField = pFieldOwner->field(util::str_view(token.data(), token.size()));
117 if (pField ==
nullptr)
118 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path field does not exist.");
119 if (forWrite && (pField->flags & RuntimeFieldFlag_ReadOnly) != 0)
120 return fail(ser::JsonDiagReason::ReadOnlyField,
"Component path field is read-only.");
121 if (forWrite && (pField->flags & RuntimeFieldFlag_Hidden) != 0)
122 return fail(ser::JsonDiagReason::HiddenField,
"Component path field is hidden.");
123 if (!cursor.field(util::str_view(token.data(), token.size())))
124 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path field cannot be traversed.");
126 target.pField = pField;
127 target.endsAtField =
true;
128 target.pType = cache.find(pField->type);
131 if (!runtime_patch_parse_index(util::str_view(token.data(), token.size()), index) || !cursor.elem(index))
132 return fail(ser::JsonDiagReason::InvalidPatchPath,
"Component path sequence index is invalid.");
134 target.endsAtField =
false;
135 target.pType = cache.find(cursor.type());
158 inline bool component_to_json_at(
159 const ComponentCacheItem& item,
const void* pComponentData, ser::json_str_view pointer, ser::ser_json& writer,
160 ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy = {}) {
161 auto error = [&](ser::JsonDiagReason reason,
const char* message) {
162 diagnostics.add(ser::JsonDiagSeverity::Error, reason, pointer, message);
166 const auto* pCache = item.owner_cache();
167 if (pComponentData ==
nullptr || pCache ==
nullptr || item.comp.size() == 0)
168 return error(ser::JsonDiagReason::MissingComponentStorage,
"Component value is unavailable.");
171 *pCache, item.entity, ComponentRawView{pComponentData, item.comp.size(), ComponentRawViewFlag_Valid});
172 detail::RuntimeJsonPointerTarget target;
173 if (!detail::walk_runtime_json_pointer(*pCache, item, cursor, pointer,
false, target))
174 return error(target.reason, target.message);
177 const auto* pData = (
const uint8_t*)cursor.ptr();
178 const auto size = cursor.size();
179 if (pData ==
nullptr || size == 0)
180 return error(ser::JsonDiagReason::UnsupportedPatchValue,
"Component path endpoint cannot be read.");
183 if (target.endsAtField) {
185 detail::RuntimeJsonFieldLayout layout{};
186 if (!detail::resolve_runtime_json_field_layout(pCache, *target.pField, layout) ||
187 (uint64_t)layout.elemSize * layout.elemCount != size)
188 return error(ser::JsonDiagReason::UnsupportedPatchValue,
"Component path field layout is invalid.");
189 ok = detail::write_runtime_json_field_data(pCache, *target.pField, layout, pData, writer, policy, 0);
191 ok = detail::write_runtime_json_value(pCache, target.pType, cursor.type(), pData, size, writer, policy, 0);
194 return error(ser::JsonDiagReason::UnsupportedPatchValue,
"Component path endpoint cannot be written as JSON.");
198 inline bool World::patch_comp_json(
199 Entity entity, Entity component, ser::json_str_view pointer, ser::json_str_view value,
200 ser::JsonDiagnostics& diagnostics,
const ser::RuntimeJsonPolicy& policy, uint64_t expectedRuntimeSchemaHash) {
201 auto error = [&](ser::JsonDiagReason reason,
const char* message) {
202 diagnostics.add(ser::JsonDiagSeverity::Error, reason, pointer, message);
206 if (expectedRuntimeSchemaHash != 0 && expectedRuntimeSchemaHash != runtime_schema_hash())
207 return error(ser::JsonDiagReason::StaleSchema,
"Runtime schema hash does not match the current manifest.");
209 const auto* pRoot = component.pair() ? m_compCache.find_pair_payload(component) : m_compCache.find(component);
210 if (pRoot ==
nullptr)
211 return error(ser::JsonDiagReason::UnknownComponent,
"Component patch target is not registered.");
215 return error(ser::JsonDiagReason::MissingComponentStorage,
"Component patch payload is unavailable.");
217 detail::RuntimeJsonPointerTarget
target;
218 if (!detail::walk_runtime_json_pointer(m_compCache, *pRoot,
cursor, pointer,
true,
target))
221 const ComponentCacheItem* pCurrent =
target.pType;
222 const RuntimeFieldDesc* pSelectedField =
target.pField;
225 const bool charBuffer = pSelectedField !=
nullptr &&
cursor.
type() == Char8 && count.ok() && count.
value > 1;
227 if (pCurrent ==
nullptr || !detail::runtime_json_leaf_editable(*pCurrent) ||
228 (count.ok() && count.value > 1 && !charBuffer))
230 ser::JsonDiagReason::UnsupportedPatchValue,
"Component patch endpoint must be a supported reflected leaf.");
233 cnt::darray<uint8_t> original;
235 if (!
cursor.
get_raw(original.data(), (uint32_t)original.size()))
236 return error(ser::JsonDiagReason::UnsupportedPatchValue,
"Component patch endpoint cannot be read.");
238 cnt::darray<uint8_t>
bytes;
239 bytes.resize(original.size());
240 memcpy(
bytes.data(), original.data(), original.size());
242 ser::ser_json reader(value.data(), value.size());
244 detail::RuntimeJsonReadContext ctx{&m_compCache, reader, diagnostics, policy, valueOk};
248 ser::json_str_view text;
249 if (!reader.parse_string_view(text) || text.size() != 1)
250 return error(ser::JsonDiagReason::InvalidJson,
"Component patch character value must contain one character.");
252 bytes[0] = (uint8_t)text.data()[0];
253 }
else if (!detail::read_runtime_json_value(
256 ser::JsonDiagReason::InvalidJson,
"Component patch value is not valid JSON for the selected field.");
261 return error(ser::JsonDiagReason::InvalidJson,
"Component patch value contains trailing JSON.");
265 ser::JsonDiagReason::UnsupportedPatchValue,
266 "Component patch value is incompatible with the selected field.");
268 if (pSelectedField !=
nullptr &&
269 (pSelectedField->flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) != 0) {
273 ser::JsonDiagReason::UnsupportedPatchValue,
"Component patch range applies to a non-numeric field.");
275 if (((pSelectedField->flags & RuntimeFieldFlag_HasMinimum) != 0 && number < pSelectedField->minimum) ||
276 ((pSelectedField->flags & RuntimeFieldFlag_HasMaximum) != 0 && number > pSelectedField->maximum))
278 ser::JsonDiagReason::RangeViolation,
"Component patch value is outside the authored field range.");
283 (void)
cursor.
set_raw(original.data(), (uint32_t)original.size(),
false);
285 ser::JsonDiagReason::UnsupportedPatchValue,
"Component patch could not commit the selected field.");
GAIA_NODISCARD ComponentCursor cursor(Entity entity, Entity component) const
Creates a read-only cursor over a runtime component on entity. Inherited ids resolve like get_raw()....
Definition world.h:15096
GAIA_NODISCARD ComponentCursor cursor_mut(Entity entity, Entity component)
Creates a mutable cursor over a directly owned runtime component on entity. Direct writes through mut...
Definition world.h:15112
GAIA_NODISCARD Entity target(Entity entity, Entity relation) const
Returns the first relationship target for the relation entity on entity.
Definition world.h:8788
GAIA_NODISCARD uint32_t bytes(const T &data)
Calculates how many bytes data would need when serialized via ser::save. Useful when a destination st...
Definition ser_ct.h:88
GAIA_NODISCARD CursorResult< uint32_t > get_raw(void *data, uint32_t byteCount) const noexcept
Copies exact bytes from the current cursor position.
Definition component_cursor.h:798
static GAIA_NODISCARD ComponentCursor from_raw(const ComponentCache &components, Entity component, ComponentRawView view)
Creates a read-only cursor from a raw component view.
Definition component_cursor.h:198
GAIA_NODISCARD uint32_t size() const noexcept
Returns the current payload or field size in bytes.
Definition component_cursor.h:317
GAIA_NODISCARD bool valid() const noexcept
Definition component_cursor.h:280
GAIA_NODISCARD Entity type() const noexcept
Definition component_cursor.h:290
GAIA_NODISCARD CursorResult< uint32_t > count() const noexcept
Returns the element count for the current fixed or adapted sequence scope.
Definition component_cursor.h:347
CursorResult< void > set_raw(const void *data, uint32_t byteCount) noexcept
Writes exact bytes to the current cursor position.
Definition component_cursor.h:828
GAIA_NODISCARD constexpr auto value() const noexcept
Raw identifier value.
Definition id.h:325