MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Composite.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "NDData.hpp"
4
5namespace MayaFlux::Kakshya {
6
7/**
8 * @struct CompositeField
9 * @brief Description of one named value in a Composite.
10 *
11 * Numeric fields use their exact C++ scalar type. Text and binary fields
12 * store offsets and lengths into separate byte stores. Dynamic fields also
13 * store a value kind per element. Offsets are assigned when a CompositeArray
14 * finalizes its layout.
15 */
17 std::string name;
18 std::type_index type { typeid(void) };
19 size_t offset_bytes {};
20 size_t size_bytes {};
21};
22
23namespace detail {
25}
26
27/** @brief Borrowed value of a present dynamic field; monostate represents NULL. */
28using CompositeValue = std::variant<std::monostate, int64_t, double,
29 std::string_view, std::span<const uint8_t>>;
30
31/**
32 * @class CompositeLayout
33 * @brief Shared field description and byte layout for a CompositeArray.
34 *
35 * Declare fields before constructing an array. Construction finalizes the
36 * layout, placing a presence bitmap before the fields in each element.
37 * Field order determines byte offsets. A field may be an arithmetic type,
38 * std::string, binary bytes, or a dynamic value.
39 *
40 * Usage:
41 * @code
42 * CompositeLayout layout;
43 * layout.add_field<std::string>("name");
44 * layout.add_field<uint32_t>("age");
45 * layout.add_field<double>("score");
46 * CompositeArray items(std::move(layout));
47 * @endcode
48 *
49 * @note stride_bytes() and field offsets are final only after construction
50 * of a CompositeArray from this layout.
51 */
52class MAYAFLUX_API CompositeLayout {
53public:
54 /**
55 * @brief Declare a named arithmetic, text, or binary field.
56 * @tparam T Exact stored type; use std::string for text or
57 * std::vector<uint8_t> for binary bytes.
58 * @param name Nonempty unique field name.
59 * @return True if added; false if the name is invalid, duplicated, or
60 * the layout has already been finalized.
61 */
62 template <typename T>
63 requires(ArithmeticData<T> || std::same_as<std::remove_cvref_t<T>, std::string>
64 || std::same_as<std::remove_cvref_t<T>, std::vector<uint8_t>>)
65 bool add_field(std::string name)
66 {
67 if constexpr (std::same_as<std::remove_cvref_t<T>, std::string>
68 || std::same_as<std::remove_cvref_t<T>, std::vector<uint8_t>>) {
69 return add_field_impl(std::move(name), typeid(T), 2 * sizeof(uint64_t));
70 } else {
71 return add_field_impl(std::move(name), typeid(T), sizeof(T));
72 }
73 }
74
75 /** @brief Declare a field with a per-element NULL, INTEGER, REAL, TEXT, or BLOB kind. */
76 bool add_dynamic_field(std::string name)
77 {
78 return add_field_impl(std::move(name), typeid(detail::CompositeDynamic), 3 * sizeof(uint64_t));
79 }
80
81 /** @brief Declared fields in element order, with finalized byte offsets. */
82 [[nodiscard]] const std::vector<CompositeField>& fields() const noexcept { return m_fields; }
83 /** @brief Find a field index by name, or std::nullopt if absent. */
84 [[nodiscard]] std::optional<size_t> find_field(std::string_view name) const noexcept;
85 /** @brief Bytes per packed element after finalization. */
86 [[nodiscard]] size_t stride_bytes() const noexcept { return m_stride_bytes; }
87 /** @brief Bytes reserved for the per-element presence bitmap. */
88 [[nodiscard]] size_t presence_bytes() const noexcept { return m_presence_bytes; }
89
90private:
91 friend class CompositeArray;
92
93 std::vector<CompositeField> m_fields;
94 size_t m_stride_bytes {};
95 size_t m_presence_bytes {};
96 bool m_finalized {};
97
98 bool add_field_impl(std::string name, std::type_index type, size_t size_bytes);
99 void finalize();
100};
101
102class CompositeArray;
103class CompositeAccess;
104class CompositeSlice;
105class CompositeInsertion;
106struct CompositeProjection;
107struct Region;
108
109/**
110 * @class Composite
111 * @brief Read view of one value in a CompositeArray.
112 *
113 * Borrows a layout and three DataVariant stores. Arithmetic access copies one
114 * value from packed bytes, so the field need not be naturally aligned.
115 * Numeric getters require an exact declared type, except dynamic INTEGER
116 * and REAL fields which accept int64_t and double respectively. Missing
117 * and mismatched fields return std::nullopt.
118 *
119 * Usage:
120 * @code
121 * auto item = items.at(0);
122 * auto name = item->text("name");
123 * auto score = item->get<double>("score");
124 * bool has_age = item->has("age");
125 * @endcode
126 *
127 * @note The backing layout and DataVariant objects must outlive this view.
128 * Text and byte views may be invalidated by later writes to their stores.
129 */
130class MAYAFLUX_API Composite {
131public:
132 /** @brief Layout used to interpret this element's packed bytes. */
133 [[nodiscard]] const CompositeLayout& layout() const noexcept;
134 /**
135 * @brief Whether a named field exists and is present in this element.
136 * @param field_name Field to inspect.
137 */
138 [[nodiscard]] bool has(std::string_view field_name) const noexcept;
139
140 /**
141 * @brief Read a present arithmetic field by exact value type.
142 * @tparam T Declared arithmetic type, or int64_t/double for a dynamic field.
143 * @param field_name Field to read.
144 * @return Copied value, or std::nullopt for an absent field or type mismatch.
145 */
146 template <typename T>
147 requires ArithmeticData<T>
148 [[nodiscard]] std::optional<T> get(std::string_view field_name) const noexcept;
149
150 /**
151 * @brief Read a present std::string field as borrowed UTF-8 bytes.
152 * @param field_name Field to read.
153 * @return Text view, or std::nullopt for an absent field, type mismatch,
154 * or invalid stored text bounds. An empty string is a present value.
155 */
156 [[nodiscard]] std::optional<std::string_view> text(std::string_view field_name) const noexcept;
157 /** @brief Borrow a dynamic value; an absent field returns std::nullopt. */
158 [[nodiscard]] std::optional<CompositeValue> value(std::string_view field_name) const noexcept;
159 /** @brief Borrow the bytes of a fixed or dynamic BLOB field. */
160 [[nodiscard]] std::optional<std::span<const uint8_t>> blob(std::string_view field_name) const noexcept;
161
162private:
163 friend class CompositeArray;
164 friend class CompositeAccess;
165 friend class CompositeInsertion;
166
168 const DataVariant& text, const DataVariant& blob, size_t index) noexcept
169 : m_layout(&layout)
170 , m_rows(&rows)
171 , m_text(&text)
172 , m_blob(&blob)
173 , m_index(index)
174 {
175 }
176
181 size_t m_index;
182};
183
184/**
185 * @class CompositeArray
186 * @brief Owns a sequence of values that share a CompositeLayout.
187 *
188 * Owns three uint8_t DataVariants: fixed-stride packed elements, variable-
189 * length UTF-8 text, and binary bytes. The layout carries field names, types, offsets, and
190 * stride; the presence bitmap distinguishes absent fields from zero or
191 * empty values.
192 *
193 * Usage:
194 * @code
195 * CompositeLayout layout;
196 * layout.add_field<std::string>("name");
197 * layout.add_field<double>("score");
198 * CompositeArray items(std::move(layout));
199 * auto index = items.append();
200 * items.set_text(index, "name", "Ada");
201 * items.set(index, "score", 0.95);
202 * auto item = items.at(index);
203 * @endcode
204 *
205 * @note Views returned by at(), access(), and slice() borrow this array's
206 * stores and layout. Keep the array alive and at the same address while
207 * using them. Include CompositeAccess.hpp for slice and projection APIs.
208 */
209class MAYAFLUX_API CompositeArray {
210public:
211 /**
212 * @brief Finalize a layout and construct an empty array.
213 * @param layout Field declarations to take ownership of.
214 * @throws std::invalid_argument If the layout has no fields.
215 * @throws std::length_error If its packed stride exceeds addressable size.
216 */
217 explicit CompositeArray(CompositeLayout layout);
218
219 /** @brief Finalized layout shared by all elements. */
220 [[nodiscard]] const CompositeLayout& layout() const noexcept { return m_layout; }
221 /** @brief Number of packed elements. */
222 [[nodiscard]] size_t size() const noexcept;
223 /** @brief Whether this array contains no elements. */
224 [[nodiscard]] bool empty() const noexcept { return size() == 0; }
225 /**
226 * @brief Borrow an element by index.
227 * @param index Zero-based element index.
228 * @return Element view, or std::nullopt if out of range.
229 */
230 [[nodiscard]] std::optional<Composite> at(size_t index) const noexcept;
231 /**
232 * @brief Validate and borrow the packed NDData stores and layout.
233 * @return Access view, or std::nullopt if storage is incompatible.
234 */
235 [[nodiscard]] std::optional<CompositeAccess> access() const;
236 /**
237 * @brief Borrow consecutive elements without copying.
238 * @param start First element index.
239 * @param count Number of elements, including zero.
240 * @return Slice, or std::nullopt if the range exceeds the array.
241 */
242 [[nodiscard]] std::optional<CompositeSlice> slice(size_t start, size_t count) const;
243 /**
244 * @brief Borrow an inclusive, one-dimensional Region of elements.
245 * @param region Element bounds and optional attributes to retain.
246 * @return Slice, or std::nullopt for invalid bounds.
247 */
248 [[nodiscard]] std::optional<CompositeSlice> slice(const Region& region) const;
249
250 /**
251 * @brief Materialize one exact-type numeric field as homogeneous NDData.
252 * @tparam T Declared field type and supported DataVariant element type.
253 * @param field_name Field to project.
254 * @return Values, presence mask, and dimension metadata; std::nullopt if
255 * the field is absent from the layout or its type differs from T.
256 * @note This copies selected values. Packed storage remains available
257 * without copying through access() and element_data().
258 */
259 template <typename T>
260 requires(ArithmeticData<T> && DataVariantElement<T>)
261 [[nodiscard]] std::optional<CompositeProjection> to_nddata(std::string_view field_name) const;
262
263 /**
264 * @brief Append one zero-initialized element with no fields present.
265 * @return Index of the new element.
266 * @throws std::length_error If the packed storage cannot grow.
267 */
268 size_t append();
269
270 /**
271 * @brief Write an arithmetic field of an existing element.
272 * @tparam T Declared arithmetic type, or int64_t/double for a dynamic field.
273 * @param index Element index.
274 * @param field_name Field to write.
275 * @param value Value to store.
276 * @return True on success; false for an invalid index, name, or type.
277 */
278 template <typename T>
279 requires ArithmeticData<T>
280 bool set(size_t index, std::string_view field_name, T value)
281 {
282 if constexpr (std::same_as<T, int64_t> || std::same_as<T, double>) {
283 const auto field_index = m_layout.find_field(field_name);
284 if (field_index && m_layout.fields()[*field_index].type == typeid(detail::CompositeDynamic)) {
285 if constexpr (std::same_as<T, int64_t>)
286 return set_integer(index, field_name, value);
287 else
288 return set_real(index, field_name, value);
289 }
290 }
291 const auto field_index = validate_write(index, field_name, typeid(T));
292 if (!field_index)
293 return false;
294
295 const auto& field = m_layout.fields()[*field_index];
296 auto& rows = std::get<std::vector<uint8_t>>(m_rows);
297 std::memcpy(rows.data() + index * m_layout.stride_bytes() + field.offset_bytes,
298 &value, sizeof(T));
299 set_present(index, *field_index, true);
300 return true;
301 }
302
303 /**
304 * @brief Write UTF-8 bytes to a std::string field.
305 * @param index Element index.
306 * @param field_name Text field to write.
307 * @param value UTF-8 bytes to append to the text store.
308 * @return True on success; false for an invalid index, name, type, or
309 * text storage size. Existing text views may be invalidated.
310 */
311 bool set_text(size_t index, std::string_view field_name, std::string_view value);
312 /** @brief Write a present NULL to a dynamic field. */
313 bool set_null(size_t index, std::string_view field_name);
314 /** @brief Write binary bytes to a fixed or dynamic BLOB field. */
315 bool set_blob(size_t index, std::string_view field_name, std::span<const uint8_t> value);
316 /**
317 * @brief Mark a field absent without removing its stored bytes.
318 * @param index Element index.
319 * @param field_name Field to clear.
320 * @return True on success; false for an invalid index or name.
321 */
322 bool clear(size_t index, std::string_view field_name);
323
324 /** @brief Borrow the full packed-element DataVariant without conversion. */
325 [[nodiscard]] const DataVariant& element_data() const noexcept { return m_rows; }
326 /** @brief Borrow the UTF-8 byte DataVariant used by text fields. */
327 [[nodiscard]] const DataVariant& text_data() const noexcept { return m_text; }
328 /** @brief Borrow the binary byte store used by BLOB fields. */
329 [[nodiscard]] const DataVariant& blob_data() const noexcept { return m_blob; }
330
331private:
332 friend class Composite;
333 friend class CompositeInsertion;
334
336 DataVariant m_rows { std::vector<uint8_t> {} };
337 DataVariant m_text { std::vector<uint8_t> {} };
338 DataVariant m_blob { std::vector<uint8_t> {} };
339
340 [[nodiscard]] bool is_present(size_t index, size_t field_index) const noexcept;
341 void set_present(size_t index, size_t field_index, bool present) noexcept;
342 [[nodiscard]] std::optional<size_t> validate_write(
343 size_t index, std::string_view field_name, std::type_index type) const;
344 [[nodiscard]] std::optional<size_t> validate_dynamic_write(
345 size_t index, std::string_view field_name) const;
346 bool set_integer(size_t index, std::string_view field_name, int64_t value);
347 bool set_real(size_t index, std::string_view field_name, double value);
348};
349
350template <typename T>
351 requires ArithmeticData<T>
352std::optional<T> Composite::get(std::string_view field_name) const noexcept
353{
354 const auto field_index = m_layout->find_field(field_name);
355 if (!field_index || !has(field_name))
356 return std::nullopt;
357
358 const auto& field = m_layout->fields()[*field_index];
359 if (field.type == typeid(detail::CompositeDynamic)) {
360 if constexpr (std::same_as<T, int64_t> || std::same_as<T, double>) {
361 const auto dynamic = value(field_name);
362 if (dynamic) {
363 if (const auto* typed = std::get_if<T>(&*dynamic))
364 return *typed;
365 }
366 }
367 return std::nullopt;
368 }
369 if (field.type != std::type_index(typeid(T)))
370 return std::nullopt;
371
372 const auto& rows = std::get<std::vector<uint8_t>>(*m_rows);
373 T value {};
374
375 std::memcpy(&value,
376 rows.data() + m_index * m_layout->stride_bytes() + field.offset_bytes,
377 sizeof(T));
378
379 return value;
380}
381
382}
bool size
uint32_t rows
glm::vec3 value
vk::PhysicalDeviceType type
Definition VKDevice.cpp:126
std::string name
Definition VKDevice.cpp:123
uint32_t index
Definition VKDevice.cpp:122
size_t count
Validated, non-owning access to packed Composite NDData storage.
const CompositeLayout & layout() const noexcept
Finalized layout shared by all elements.
const DataVariant & element_data() const noexcept
Borrow the full packed-element DataVariant without conversion.
bool set(size_t index, std::string_view field_name, T value)
Write an arithmetic field of an existing element.
const DataVariant & blob_data() const noexcept
Borrow the binary byte store used by BLOB fields.
const DataVariant & text_data() const noexcept
Borrow the UTF-8 byte DataVariant used by text fields.
Owns a sequence of values that share a CompositeLayout.
Mutable insertion adapter for a CompositeArray.
size_t stride_bytes() const noexcept
Bytes per packed element after finalization.
Definition Composite.hpp:86
bool add_field(std::string name)
Declare a named arithmetic, text, or binary field.
Definition Composite.hpp:65
const std::vector< CompositeField > & fields() const noexcept
Declared fields in element order, with finalized byte offsets.
Definition Composite.hpp:82
bool add_dynamic_field(std::string name)
Declare a field with a per-element NULL, INTEGER, REAL, TEXT, or BLOB kind.
Definition Composite.hpp:76
size_t presence_bytes() const noexcept
Bytes reserved for the per-element presence bitmap.
Definition Composite.hpp:88
std::vector< CompositeField > m_fields
Definition Composite.hpp:93
Shared field description and byte layout for a CompositeArray.
Definition Composite.hpp:52
Composite(const CompositeLayout &layout, const DataVariant &rows, const DataVariant &text, const DataVariant &blob, size_t index) noexcept
const DataVariant * m_blob
const DataVariant * m_rows
const CompositeLayout * m_layout
std::optional< T > get(std::string_view field_name) const noexcept
Read a present arithmetic field by exact value type.
const DataVariant * m_text
Read view of one value in a CompositeArray.
Concept to constrain types to valid DataVariant elements.
Definition NDData.hpp:127
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
Definition NDData.hpp:70
std::variant< std::monostate, int64_t, double, std::string_view, std::span< const uint8_t > > CompositeValue
Borrowed value of a present dynamic field; monostate represents NULL.
Definition Composite.hpp:29
Description of one named value in a Composite.
Definition Composite.hpp:16
Represents a point or span in N-dimensional space.
Definition Region.hpp:73