Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
world_json_patch.h
1#pragma once
2#include "gaia/config/config.h"
3
4#if GAIA_JSON_ENABLED
5
6 #include <cstdint>
7 #include <cstring>
8
9namespace gaia {
10 namespace ecs {
12 namespace detail {
17 inline bool runtime_patch_decode_token(ser::json_str_view encoded, util::str& token) {
18 token.clear();
19
20 GAIA_FOR(encoded.size()) {
21 const auto ch = encoded.data()[i];
22 if (ch != '~') {
23 token.append(ch);
24 continue;
25 }
26
27 if (i + 1 >= encoded.size())
28 return false;
29
30 const auto escaped = encoded.data()[++i];
31 if (escaped == '0')
32 token.append('~');
33 else if (escaped == '1')
34 token.append('/');
35 else
36 return false;
37 }
38
39 return true;
40 }
41
46 inline bool runtime_patch_parse_index(util::str_view token, uint32_t& index) {
47 if (token.empty())
48 return false;
49
50 uint64_t value = 0;
51 GAIA_FOR(token.size()) {
52 const auto ch = token.data()[i];
53 if (ch < '0' || ch > '9')
54 return false;
55 value = value * 10 + (uint32_t)(ch - '0');
56 if (value > UINT32_MAX)
57 return false;
58 }
59
60 index = (uint32_t)value;
61 return true;
62 }
63
72 template <typename T>
73 inline bool
74 runtime_patch_numeric_value_as(Entity type, Entity expectedType, const void* data, uint32_t size, double& value) {
75 if (type != expectedType || data == nullptr || size != sizeof(T))
76 return false;
77
78 T result{};
79 memcpy(&result, data, sizeof(result));
80 value = (double)result;
81 return true;
82 }
83
91 inline bool runtime_patch_numeric_value(
92 const ComponentCacheItem* pType, Entity type, const void* data, uint32_t size, double& value) {
93 if (pType != nullptr &&
94 (pType->typeKind == RuntimeTypeKind::Enum || pType->typeKind == RuntimeTypeKind::Bitmask))
95 type = pType->underlyingType;
96
97 return runtime_patch_numeric_value_as<int8_t>(type, S8, data, size, value) ||
98 runtime_patch_numeric_value_as<uint8_t>(type, U8, data, size, value) ||
99 runtime_patch_numeric_value_as<int16_t>(type, S16, data, size, value) ||
100 runtime_patch_numeric_value_as<uint16_t>(type, U16, data, size, value) ||
101 runtime_patch_numeric_value_as<int32_t>(type, S32, data, size, value) ||
102 runtime_patch_numeric_value_as<uint32_t>(type, U32, data, size, value) ||
103 runtime_patch_numeric_value_as<int64_t>(type, S64, data, size, value) ||
104 runtime_patch_numeric_value_as<uint64_t>(type, U64, data, size, value) ||
105 runtime_patch_numeric_value_as<float>(type, F32, data, size, value) ||
106 runtime_patch_numeric_value_as<double>(type, F64, data, size, value);
107 }
108 } // namespace detail
110
111 inline bool World::patch_comp_json(
112 Entity entity, Entity component, ser::json_str_view pointer, ser::json_str_view value,
113 ser::JsonDiagnostics& diagnostics, const ser::RuntimeJsonPolicy& policy, uint64_t expectedRuntimeSchemaHash) {
114 auto error = [&](ser::JsonDiagReason reason, const char* message) {
115 diagnostics.add(ser::JsonDiagSeverity::Error, reason, pointer, message);
116 return false;
117 };
118
119 if (expectedRuntimeSchemaHash != 0 && expectedRuntimeSchemaHash != runtime_schema_hash())
120 return error(ser::JsonDiagReason::StaleSchema, "Runtime schema hash does not match the current manifest.");
121
122 const auto* pRoot = component.pair() ? m_compCache.find_pair_payload(component) : m_compCache.find(component);
123 if (pRoot == nullptr)
124 return error(ser::JsonDiagReason::UnknownComponent, "Component patch target is not registered.");
125
126 auto cursor = cursor_mut(entity, component);
127 if (!cursor.valid() || cursor.size() == 0)
128 return error(ser::JsonDiagReason::MissingComponentStorage, "Component patch payload is unavailable.");
129 if (!pointer.empty() && pointer.data()[0] != '/')
130 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch path must be an RFC 6901 JSON Pointer.");
131 if (!pointer.empty() && (pointer.size() == 1 || pointer.data()[pointer.size() - 1] == '/'))
132 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch path contains an empty token.");
133
134 const ComponentCacheItem* pCurrent = pRoot;
135 const RuntimeFieldDesc* pSelectedField = nullptr;
136 uint32_t pos = pointer.empty() ? pointer.size() : 1;
137 while (pos < pointer.size()) {
138 uint32_t end = pos;
139 while (end < pointer.size() && pointer.data()[end] != '/')
140 ++end;
141
142 util::str token;
143 if (!detail::runtime_patch_decode_token(ser::json_str_view(pointer.data() + pos, end - pos), token) ||
144 token.empty())
145 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch path contains an invalid token.");
146
147 const ComponentCacheItem* pFieldOwner = pCurrent;
148 if (pFieldOwner != nullptr && pFieldOwner->typeKind == RuntimeTypeKind::Opaque)
149 pFieldOwner = m_compCache.find(pFieldOwner->opaque_as_type());
150
151 if (pFieldOwner != nullptr && pFieldOwner->typeKind == RuntimeTypeKind::Struct) {
152 const auto* pField = pFieldOwner->field(util::str_view(token.data(), token.size()));
153 if (pField == nullptr)
154 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch field does not exist.");
155 if ((pField->flags & RuntimeFieldFlag_ReadOnly) != 0)
156 return error(ser::JsonDiagReason::ReadOnlyField, "Component patch field is read-only.");
157 if ((pField->flags & RuntimeFieldFlag_Hidden) != 0)
158 return error(ser::JsonDiagReason::HiddenField, "Component patch field is hidden.");
159 if (!cursor.field(util::str_view(token.data(), token.size())))
160 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch field cannot be traversed.");
161
162 pSelectedField = pField;
163 pCurrent = m_compCache.find(pField->type);
164 } else {
165 uint32_t index = 0;
166 if (!detail::runtime_patch_parse_index(util::str_view(token.data(), token.size()), index) ||
167 !cursor.elem(index))
168 return error(ser::JsonDiagReason::InvalidPatchPath, "Component patch sequence index is invalid.");
169
170 pCurrent = m_compCache.find(cursor.type());
171 }
172
173 pos = end + 1;
174 }
175
176 const auto count = cursor.count();
177 const bool charBuffer = pSelectedField != nullptr && cursor.type() == Char8 && count.ok() && count.value > 1;
178
179 if (pCurrent == nullptr || !detail::runtime_json_leaf_editable(*pCurrent) ||
180 (count.ok() && count.value > 1 && !charBuffer))
181 return error(
182 ser::JsonDiagReason::UnsupportedPatchValue, "Component patch endpoint must be a supported reflected leaf.");
183
184 // Work on a copy so a failed patch does not change the component.
185 cnt::darray<uint8_t> original;
186 original.resize(cursor.size());
187 if (!cursor.get_raw(original.data(), (uint32_t)original.size()))
188 return error(ser::JsonDiagReason::UnsupportedPatchValue, "Component patch endpoint cannot be read.");
189
190 cnt::darray<uint8_t> bytes;
191 bytes.resize(original.size());
192 memcpy(bytes.data(), original.data(), original.size());
193
194 ser::ser_json reader(value.data(), value.size());
195 bool valueOk = true;
196 detail::RuntimeJsonReadContext ctx{&m_compCache, reader, diagnostics, policy, valueOk};
197
198 if (cursor.type() == Char8 && cursor.size() == sizeof(char)) {
199 // One Char8 value is written as a one-character JSON string.
200 ser::json_str_view text;
201 if (!reader.parse_string_view(text) || text.size() != 1)
202 return error(ser::JsonDiagReason::InvalidJson, "Component patch character value must contain one character.");
203
204 bytes[0] = (uint8_t)text.data()[0];
205 } else if (!detail::read_runtime_json_value(
206 ctx, pCurrent, cursor.type(), bytes.data(), (uint32_t)bytes.size(), pointer, 0)) {
207 return error(
208 ser::JsonDiagReason::InvalidJson, "Component patch value is not valid JSON for the selected field.");
209 }
210
211 reader.ws();
212 if (!reader.eof())
213 return error(ser::JsonDiagReason::InvalidJson, "Component patch value contains trailing JSON.");
214
215 if (!valueOk)
216 return error(
217 ser::JsonDiagReason::UnsupportedPatchValue,
218 "Component patch value is incompatible with the selected field.");
219
220 if (pSelectedField != nullptr &&
221 (pSelectedField->flags & (RuntimeFieldFlag_HasMinimum | RuntimeFieldFlag_HasMaximum)) != 0) {
222 double number = 0.0;
223 if (!detail::runtime_patch_numeric_value(pCurrent, cursor.type(), bytes.data(), cursor.size(), number))
224 return error(
225 ser::JsonDiagReason::UnsupportedPatchValue, "Component patch range applies to a non-numeric field.");
226
227 if (((pSelectedField->flags & RuntimeFieldFlag_HasMinimum) != 0 && number < pSelectedField->minimum) ||
228 ((pSelectedField->flags & RuntimeFieldFlag_HasMaximum) != 0 && number > pSelectedField->maximum))
229 return error(
230 ser::JsonDiagReason::RangeViolation, "Component patch value is outside the authored field range.");
231 }
232
233 if (!cursor.set_raw(bytes.data(), (uint32_t)bytes.size())) {
234 // The write may be incomplete, so put the original value back.
235 (void)cursor.set_raw(original.data(), (uint32_t)original.size(), false);
236 return error(
237 ser::JsonDiagReason::UnsupportedPatchValue, "Component patch could not commit the selected field.");
238 }
239
240 return true;
241 }
242
243 } // namespace ecs
244} // namespace gaia
245
246#endif
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:13051
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:13067
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
bool field(uint32_t index)
Descends into the reflected field at index.
Definition component_cursor.h:394
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
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
bool elem(uint32_t index) noexcept
Descends into element index of the current fixed inline or named array scope.
Definition component_cursor.h:435
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:395