Loading...
Searching...
No Matches
Table.hpp
1#pragma once
2
3#include <ossia/detail/variant.hpp>
4#include <ossia/network/value/value.hpp>
5#include <ossia/network/value/value_conversion.hpp>
6
7#include <boost/container/static_vector.hpp>
8#include <boost/mp11.hpp>
9#include <boost/multi_array.hpp>
10#include <boost/predef.h>
11
12#include <halp/controls.hpp>
13#include <halp/meta.hpp>
14
15#include <algorithm>
16#include <array>
17#include <numeric>
18
19namespace avnd_tools
20{
21
22#if BOOST_COMP_GNUC
23inline constexpr std::size_t g_table_max_dimensions = 4;
24#else
25inline constexpr std::size_t g_table_max_dimensions = 16;
26#endif
27
28using index_vec_type = boost::container::static_vector<int64_t, g_table_max_dimensions>;
29using extent_vec_type
30 = boost::container::static_vector<std::size_t, g_table_max_dimensions>;
31
32namespace detail
33{
34// Generate a variant type containing multi_array<T, 1> through multi_array<T, MaxDims>
35template <typename T, std::size_t MaxDims>
37{
38 template <std::size_t N>
39 using array_type = boost::multi_array<T, N + 1>;
40
41 using indices = boost::mp11::mp_iota_c<MaxDims>;
42
43 template <typename I>
44 using apply_array = array_type<I::value>;
45
46 using type
47 = boost::mp11::mp_rename<boost::mp11::mp_transform<apply_array, indices>,
48 ossia::slow_variant>;
49};
50
51template <typename T, std::size_t MaxDims>
52using multi_array_variant_t = typename make_multi_array_variant<T, MaxDims>::type;
53
54// Recursive element accessor: applies indices[0], indices[1], ... to get the element
55template <std::size_t Dim, std::size_t CurrentIdx = 0>
57{
58 template <typename ArrayView, typename IndexVec>
59 static auto& get(ArrayView& view, const IndexVec& indices)
60 {
61 if constexpr(CurrentIdx + 1 >= Dim)
62 {
63 return view[indices[CurrentIdx]];
64 }
65 else
66 {
67 auto sub = view[indices[CurrentIdx]];
69 }
70 }
71
72 template <typename ArrayView, typename IndexVec>
73 static const auto& get(const ArrayView& view, const IndexVec& indices)
74 {
75 if constexpr(CurrentIdx + 1 >= Dim)
76 {
77 return view[indices[CurrentIdx]];
78 }
79 else
80 {
81 const auto& sub = view[indices[CurrentIdx]];
83 }
84 }
85};
86
87// Bounds checker for N dimensions
88template <std::size_t Dim>
90{
91 template <typename Shape, typename IndexVec>
92 static bool check(const Shape& shape, const IndexVec& indices)
93 {
94 for(std::size_t i = 0; i < Dim; ++i)
95 {
96 if(indices[i] < 0 || static_cast<std::size_t>(indices[i]) >= shape[i])
97 return false;
98 }
99 return true;
100 }
101
102 template <typename IndexVec>
103 static bool check_positive(const IndexVec& indices)
104 {
105 for(std::size_t i = 0; i < Dim; ++i)
106 {
107 if(indices[i] < 0 || indices[i] >= INT_MAX)
108 return false;
109 }
110 return true;
111 }
112};
113
114// Extent generator for resize operations
115template <std::size_t Dim>
117{
118 template <typename IndexVec>
119 static auto make_extents(const IndexVec& sizes)
120 {
121 boost::array<std::size_t, Dim> extents;
122 for(std::size_t i = 0; i < Dim; ++i)
123 {
124 extents[i] = static_cast<std::size_t>(sizes[i]);
125 }
126 return extents;
127 }
128};
129
130// Shape comparison for resize determination
131template <std::size_t Dim>
133{
134 template <typename Shape, typename IndexVec>
135 static bool check(const Shape& shape, const IndexVec& indices)
136 {
137 for(std::size_t i = 0; i < Dim; ++i)
138 {
139 if(static_cast<std::size_t>(indices[i]) >= shape[i])
140 return true;
141 }
142 return false;
143 }
144
145 template <typename Shape, typename IndexVec>
146 static auto compute_new_extents(const Shape& current_shape, const IndexVec& indices)
147 {
148 boost::array<std::size_t, Dim> new_extents;
149 for(std::size_t i = 0; i < Dim; ++i)
150 {
151 new_extents[i]
152 = std::max(current_shape[i], static_cast<std::size_t>(indices[i]) + 1);
153 }
154 return new_extents;
155 }
156};
157
158template <typename T>
160{
161 using variant_type = multi_array_variant_t<T, g_table_max_dimensions>;
162
163 template <typename OutputPort>
164 static bool read(variant_type& buffer, const index_vec_type& cursor, OutputPort& output)
165 {
166 return ossia::visit(
167 [&](auto& arr) -> bool {
168 constexpr std::size_t Dim = std::remove_reference_t<decltype(arr)>::dimensionality;
169
170 if(cursor.size() != Dim)
171 return false;
172
173 if(arr.num_elements() == 0)
174 return false;
175
176 const auto* shape = arr.shape();
177 if(!bounds_checker<Dim>::check(shape, cursor))
178 return false;
179
180 output.value = element_accessor<Dim>::get(arr, cursor);
181 return true;
182 },
183 buffer);
184 }
185
186 static void set(variant_type& buffer, const index_vec_type& cursor, const T& value)
187 {
188 ossia::visit(
189 [&](auto& arr) {
190 constexpr std::size_t Dim = std::remove_reference_t<decltype(arr)>::dimensionality;
191
192 if(cursor.size() != Dim)
193 return;
194
196 return;
197
198 const auto* shape = arr.shape();
199
200 if(arr.num_elements() == 0 || needs_resize_check<Dim>::check(shape, cursor))
201 {
202 // Compute new extents
203 boost::array<std::size_t, Dim> new_extents;
204 if(arr.num_elements() == 0)
205 {
206 for(std::size_t i = 0; i < Dim; ++i)
207 new_extents[i] = static_cast<std::size_t>(cursor[i]) + 1;
208 }
209 else
210 {
211 new_extents = needs_resize_check<Dim>::compute_new_extents(shape, cursor);
212 }
213 arr.resize(new_extents);
214 }
215
216 element_accessor<Dim>::get(arr, cursor) = value;
217 },
218 buffer);
219 }
220
221 static extent_vec_type get_shape(const variant_type& buffer)
222 {
223 return ossia::visit(
224 [](const auto& arr) -> extent_vec_type {
225 constexpr std::size_t Dim = std::remove_reference_t<decltype(arr)>::dimensionality;
226 extent_vec_type result;
227 const auto* shape = arr.shape();
228 for(std::size_t i = 0; i < Dim; ++i)
229 result.push_back(shape[i]);
230 return result;
231 },
232 buffer);
233 }
234
235 static std::size_t num_elements(const variant_type& buffer)
236 {
237 return ossia::visit([](const auto& arr) { return arr.num_elements(); }, buffer);
238 }
239
240 static void clear(variant_type& buffer)
241 {
242 ossia::visit(
243 [](auto& arr) {
244 constexpr std::size_t Dim = std::remove_reference_t<decltype(arr)>::dimensionality;
245 boost::array<std::size_t, Dim> zero_extents;
246 zero_extents.fill(0);
247 arr.resize(zero_extents);
248 },
249 buffer);
250 }
251
252 static void resize(variant_type& buffer, const extent_vec_type& extents)
253 {
254 ossia::visit(
255 [&](auto& arr) {
256 constexpr std::size_t Dim = std::remove_reference_t<decltype(arr)>::dimensionality;
257
258 if(extents.size() != Dim)
259 return;
260
261 boost::array<std::size_t, Dim> new_extents;
262 for(std::size_t i = 0; i < Dim; ++i)
263 new_extents[i] = extents[i];
264 arr.resize(new_extents);
265 },
266 buffer);
267 }
268
269 static void fill(variant_type& buffer, const T& value)
270 {
271 ossia::visit(
272 [&](auto& arr) { std::fill(arr.data(), arr.data() + arr.num_elements(), value); },
273 buffer);
274 }
275
276 static std::vector<T> to_vector(const variant_type& buffer)
277 {
278 return ossia::visit(
279 [](const auto& arr) -> std::vector<T> {
280 return std::vector<T>(arr.data(), arr.data() + arr.num_elements());
281 },
282 buffer);
283 }
284
285 // Dump the array as nested vectors: vector<vector<...vector<T>...>>
286 // Returns ossia::value containing the nested structure
287 static ossia::value dump(const variant_type& buffer)
288 {
289 return ossia::visit(
290 [](const auto& arr) -> ossia::value {
291 constexpr std::size_t Dim
292 = std::remove_reference_t<decltype(arr)>::dimensionality;
293 return dump_impl<Dim>(arr);
294 },
295 buffer);
296 }
297
298private:
299 // Recursive dump implementation
300 template <std::size_t Dim, typename Array>
301 static ossia::value dump_impl(const Array& arr)
302 {
303 const auto* shape = arr.shape();
304
305 if constexpr(Dim == 1)
306 {
307 // Base case: 1D array -> vector of values
308 std::vector<ossia::value> result;
309 result.reserve(shape[0]);
310 for(std::size_t i = 0; i < shape[0]; ++i)
311 result.push_back(arr[i]);
312 return result;
313 }
314 else
315 {
316 // Recursive case: N-D array -> vector of (N-1)-D dumps
317 std::vector<ossia::value> result;
318 result.reserve(shape[0]);
319 for(std::size_t i = 0; i < shape[0]; ++i)
320 {
321 result.push_back(dump_impl<Dim - 1>(arr[i]));
322 }
323 return result;
324 }
325 }
326};
327
328template <typename T>
330{
331 using variant_type = multi_array_variant_t<T, g_table_max_dimensions>;
332
333 static void change_dimensions(variant_type& buffer, std::size_t new_dims)
334 {
335 if(new_dims < 1 || new_dims > g_table_max_dimensions)
336 return;
337
338 // Use mp_with_index to dispatch to the correct dimension at compile time
339 boost::mp11::mp_with_index<g_table_max_dimensions>(new_dims - 1, [&](auto I) {
340 constexpr std::size_t Dim = I.value + 1;
341 using new_array_type = boost::multi_array<T, Dim>;
342
343 // Create empty array with the new dimensionality
344 new_array_type new_arr;
345 buffer = std::move(new_arr);
346 });
347 }
348
349 static std::size_t current_dimensions(const variant_type& buffer)
350 {
351 return ossia::visit(
352 [](const auto& arr) -> std::size_t {
353 return std::remove_reference_t<decltype(arr)>::dimensionality;
354 },
355 buffer);
356 }
357};
358
359}
360
361struct Table
362{
363 halp_meta(name, "Table")
364 halp_meta(author, "ossia team")
365 halp_meta(category, "Control/Data processing")
366 halp_meta(description, "Store arbitrary data in an N-dimensional table (1-16D)")
367 halp_meta(c_name, "avnd_table_nd")
368 halp_meta(uuid, "98418d3a-58c3-4d1f-b716-83c0988174c3")
369 halp_meta(manual_url, "https://ossia.io/score-docs/processes/table.html")
370
371 using value_type = ossia::value;
374
375 struct
376 {
377 struct : halp::val_port<"Read", std::optional<index_vec_type>>
378 {
379 void update(Table& t)
380 {
381 if(value)
382 {
383 if(ops::read(t.buffer, *value, t.outputs.output))
384 return;
385 }
386 t.outputs.output.value = ossia::value{};
387 }
388 } read;
389
390 struct : halp::val_port<"Set cell", std::vector<ossia::value>>
391 {
392 void update(Table& t)
393 {
394 // Need at least indices + value
395 if(value.size() < 2)
396 return;
397
398 const std::size_t dims = dim_changer::current_dimensions(t.buffer);
399 if(value.size() != dims + 1)
400 return;
401
402 index_vec_type indices;
403 for(std::size_t i = 0; i < dims; i++)
404 indices.push_back(ossia::convert<int>(value[i]));
405
406 ops::set(t.buffer, indices, value.back());
407 }
408 } set;
409
410 struct : halp::val_port<"Clear cell", index_vec_type>
411 {
412 void update(Table& t) { t.clear_cell(value); }
413 } clear_cell;
414
415 struct : halp::val_port<"Resize", extent_vec_type>
416 {
417 void update(Table& t) { t.resize(value); }
418 } resize;
419
420 struct : halp::val_port<"Fill", ossia::value>
421 {
422 void update(Table& t) { ops::fill(t.buffer, value); }
423 } fill;
424
425 struct : halp::spinbox_i32<"Dimensions", halp::range{1, 16, 1}>
426 {
427 void update(Table& t)
428 {
429 if(value >= 1 && value <= 16)
430 {
431 const std::size_t current = dim_changer::current_dimensions(t.buffer);
432 if(static_cast<std::size_t>(value) != current)
433 {
434 dim_changer::change_dimensions(t.buffer, value);
435 }
436 }
437 }
438 } dims;
439 halp::maintained_button<"Clear"> clear;
440 halp::maintained_button<"Lock"> lock;
441 struct : halp::impulse_button<"Dump">
442 {
443 void update(Table& t) { t.outputs.output.value = ops::dump(t.buffer); }
444 } dump;
445 halp::toggle<"Preserve"> preserve;
446
447 } inputs;
448
449 struct
450 {
451 halp::val_port<"Output", ossia::value> output;
452 halp::val_port<"Shape", extent_vec_type> shape;
453 halp::val_port<"Size", int64_t> size;
454 } outputs;
455
456 detail::multi_array_variant_t<value_type, g_table_max_dimensions> buffer;
457
458 void clear_cell(const index_vec_type& indices)
459 {
460 ops::set(buffer, indices, ossia::value{});
461 }
462
463 void resize(const extent_vec_type& extents)
464 {
465 const std::size_t current_dims = dim_changer::current_dimensions(buffer);
466 if(extents.size() != current_dims)
467 return;
468 ops::resize(buffer, extents);
469 }
470
471 void operator()()
472 {
473 if(inputs.clear)
474 {
475 ops::clear(buffer);
476 }
477
478 // Update output ports with current state
479 auto shape = ops::get_shape(buffer);
480 outputs.shape.value.clear();
481 for(auto s : shape)
482 outputs.shape.value.push_back(static_cast<int64_t>(s));
483
484 outputs.size.value = static_cast<int64_t>(ops::num_elements(buffer));
485 }
486};
487
488}
Definition Table.hpp:362
Definition Table.hpp:90
Definition Table.hpp:117
Definition Table.hpp:160