From c616916c46d3713084c91f536163048544ca7258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sun, 20 Oct 2024 13:04:00 -0400 Subject: [PATCH] [core] Refactor controls --- include/avnd/binding/ossia/callbacks.hpp | 11 ++ include/avnd/binding/ossia/controls.hpp | 28 +++- .../avnd/binding/ossia/message_process.hpp | 136 +++++++++++------- include/avnd/binding/ossia/node.hpp | 4 +- .../binding/ossia/port_run_postprocess.hpp | 27 ++-- include/avnd/binding/ossia/to_value.hpp | 11 +- include/avnd/concepts/all.hpp | 1 + include/avnd/concepts/control.hpp | 86 +++++++++++ include/avnd/concepts/parameter.hpp | 78 ---------- include/avnd/wrappers/effect_container.hpp | 18 ++- include/avnd/wrappers/metadatas.hpp | 86 +++++++++-- 11 files changed, 310 insertions(+), 176 deletions(-) create mode 100644 include/avnd/concepts/control.hpp diff --git a/include/avnd/binding/ossia/callbacks.hpp b/include/avnd/binding/ossia/callbacks.hpp index 3336aac4..977dfa28 100644 --- a/include/avnd/binding/ossia/callbacks.hpp +++ b/include/avnd/binding/ossia/callbacks.hpp @@ -34,6 +34,17 @@ struct do_callback port.data.write_value(std::move(vec), self.start_frame_for_this_tick); } + // FIXME handle control feedback ! + // if constexpr(avnd::control) + // { + // // Get the index of the control in [0; N[ + // using type = typename Exec_T::processor_type; + // using controls = avnd::control_output_introspection; + // constexpr int control_index = controls::field_index_to_index(idx); + // + // // Mark the control as changed + // self.control.outputs_set.set(control_index); + // } if constexpr(!std::is_void_v) return R{}; } diff --git a/include/avnd/binding/ossia/controls.hpp b/include/avnd/binding/ossia/controls.hpp index 825bef83..0cd05e8e 100644 --- a/include/avnd/binding/ossia/controls.hpp +++ b/include/avnd/binding/ossia/controls.hpp @@ -1,15 +1,34 @@ #pragma once #include #include -#include +// #include #include namespace oscr { template -using controls_type = std::decay_t; +struct controls_type; +template + requires avnd::control +struct controls_type +{ + using type = std::decay_t; +}; + +template +using controls_type_t = typename controls_type::type; + +template + requires avnd::control +struct controls_type +{ + // FIXME maybe the tuple of arguments? + using type = ossia::value; +}; + +#if 0 template using atomic_shared_ptr = boost::atomic_shared_ptr; @@ -35,6 +54,7 @@ struct controls_mirror std::bitset inputs_bits; std::bitset outputs_bits; }; +#endif template struct controls_input_queue @@ -52,7 +72,7 @@ struct controls_input_queue { static constexpr int i_size = avnd::control_input_introspection::size; using i_tuple - = avnd::filter_and_apply; + = avnd::filter_and_apply; ossia::mpmc_queue ins_queue; std::bitset inputs_set; @@ -64,7 +84,7 @@ struct controls_output_queue { static constexpr int o_size = avnd::control_output_introspection::size; using o_tuple - = avnd::filter_and_apply; + = avnd::filter_and_apply; ossia::mpmc_queue outs_queue; diff --git a/include/avnd/binding/ossia/message_process.hpp b/include/avnd/binding/ossia/message_process.hpp index f0c5043a..e356fe87 100644 --- a/include/avnd/binding/ossia/message_process.hpp +++ b/include/avnd/binding/ossia/message_process.hpp @@ -1,4 +1,11 @@ #pragma once +#include +#include +#include +#include + +#include +#include namespace oscr { @@ -57,18 +64,31 @@ struct message_processor const auto& vec = *val.target>(); if(vec.size() == count) { - int i = 0; // FIXME doable at compile-time - std::apply([&self, &vec, &i](Ts&&... args) { - (self.from_ossia_value(field, vec[i++], args, avnd::field_index{}), + std::apply( + [&self, &vec, it = vec.begin()](Ts&&... args) mutable { + (self.from_ossia_value(field, *(it++), args, avnd::field_index{}), ...); }, tuple); return tuple; } break; } - case ossia::val_type::MAP: - // FIXME + case ossia::val_type::MAP: { + static constexpr M field; + Args tuple; + const auto& vec = *val.target(); + if(vec.size() == count) + { + std::apply( + [&self, &vec, it = vec.begin()](Ts&&... args) mutable { + (self.from_ossia_value( + field, (it++)->second, args, avnd::field_index{}), + ...); + }, tuple); + return tuple; + } break; + } default: break; } @@ -96,51 +116,90 @@ struct message_processor else { // M contains a function pointer to a free function f - f(first_args..., std::forward(args)...); + f(std::forward(first_args)..., std::forward(args)...); } }, res); } + template void invoke_message_first_arg_is_object( - auto& self, const ossia::value& val, avnd::field_reflection) + auto& self, const ossia::value& val, int64_t ts, avnd::field_reflection) { auto& impl = self.impl; using refl = avnd::message_reflection; using namespace boost::mp11; using first_arg_type = std::remove_cvref_t>; - if constexpr(std::is_same_v) + + if constexpr(avnd::tag_timestamp) { - using main_args = mp_pop_front; - using no_ref = mp_transform; - using args = mp_rename; + if constexpr(std::is_same_v) + { + static_assert(boost::mp11::mp_size::value >= 2); + using main_args = mp_pop_front>; + using no_ref = mp_transform; + using args = mp_rename; - if(auto res = value_to_argument_tuple(self, val)) + if(auto res = value_to_argument_tuple(self, val)) + { + for(auto& m : impl.effects()) + { + invoke_message_impl(self, *res, m, m, ts); + } + } + } + else { - for(auto& m : impl.effects()) + static_assert(boost::mp11::mp_size::value >= 1); + using main_args = mp_pop_front; + using no_ref = mp_transform; + using args = mp_rename; + if(auto res = value_to_argument_tuple(self, val)) { - invoke_message_impl(self, *res, m, m); + for(auto& m : impl.effects()) + { + invoke_message_impl(self, *res, m, ts); + } } } } else { - using main_args = typename refl::arguments; - using no_ref = mp_transform; - using args = mp_rename; - if(auto res = value_to_argument_tuple(self, val)) + if constexpr(std::is_same_v) { - for(auto& m : impl.effects()) + static_assert(boost::mp11::mp_size::value >= 1); + using main_args = mp_pop_front; + using no_ref = mp_transform; + using args = mp_rename; + + if(auto res = value_to_argument_tuple(self, val)) { - invoke_message_impl(self, *res, m); + for(auto& m : impl.effects()) + { + invoke_message_impl(self, *res, m, m); + } + } + } + else + { + using main_args = typename refl::arguments; + using no_ref = mp_transform; + using args = mp_rename; + if(auto res = value_to_argument_tuple(self, val)) + { + for(auto& m : impl.effects()) + { + invoke_message_impl(self, *res, m); + } } } } } template - void - invoke_message(auto& self, const ossia::value& val, avnd::field_reflection idx) + void invoke_message( + auto& self, const ossia::value& val, int64_t ts, + avnd::field_reflection idx) { auto& impl = self.impl; if constexpr(!std::is_void_v>) @@ -154,37 +213,10 @@ struct message_processor invoke_message_impl(self, std::tuple<>{}, m); } } - else // if constexpr(refl::count == 1) - { - invoke_message_first_arg_is_object(self, val, idx); - } - /* else { - using namespace boost::mp11; - using first_arg_type = std::remove_cvref_t>; - using second_arg_type = std::remove_cvref_t>; - if constexpr( - std::is_same_v && avnd::has_tick - && std::is_same_v) - { - using main_args = mp_pop_front; - using no_ref = mp_transform; - using args = mp_rename; - - if(auto res = value_to_argument_tuple(self, val)) - { - for(auto& m : impl.effects()) - { - invoke_message_impl(self, *res, m, m); - } - } - } - else - { - invoke_message_first_arg_is_object(self, val, idx); - } - }*/ + invoke_message_first_arg_is_object(self, val, ts, idx); + } } } @@ -196,7 +228,7 @@ struct message_processor return; for(const auto& val : inl.data.get_data()) { - invoke_message(self, val.value, avnd::field_reflection{}); + invoke_message(self, val.value, val.timestamp, avnd::field_reflection{}); } } }; diff --git a/include/avnd/binding/ossia/node.hpp b/include/avnd/binding/ossia/node.hpp index 90faeb1e..52c9e3c3 100644 --- a/include/avnd/binding/ossia/node.hpp +++ b/include/avnd/binding/ossia/node.hpp @@ -155,9 +155,9 @@ class safe_node_base_base : public ossia::nonowning_graph_node [[no_unique_address]] oscr::message_processor messages; using control_input_values_type - = avnd::filter_and_apply; + = avnd::filter_and_apply; using control_output_values_type - = avnd::filter_and_apply; + = avnd::filter_and_apply; }; template diff --git a/include/avnd/binding/ossia/port_run_postprocess.hpp b/include/avnd/binding/ossia/port_run_postprocess.hpp index fe98880d..d03cb682 100644 --- a/include/avnd/binding/ossia/port_run_postprocess.hpp +++ b/include/avnd/binding/ossia/port_run_postprocess.hpp @@ -78,17 +78,7 @@ struct process_after_run { } template - requires(!avnd::control && !ossia_port) - void write_value( - Field& ctrl, ossia::value_outlet& port, auto& val, int64_t ts, - avnd::field_index) const noexcept - { - if(auto v = to_ossia_value(ctrl, val); v.valid()) - port->write_value(std::move(v), ts); - } - - template - requires(avnd::control && !ossia_port) + requires(!ossia_port) void write_value( Field& ctrl, ossia::value_outlet& port, auto& val, int64_t ts, avnd::field_index idx) const noexcept @@ -97,13 +87,16 @@ struct process_after_run { port->write_value(std::move(v), ts); - // Get the index of the control in [0; N[ - using type = typename Exec_T::processor_type; - using controls = avnd::control_output_introspection; - constexpr int control_index = controls::field_index_to_index(idx); + if constexpr(avnd::control) + { + // Get the index of the control in [0; N[ + using type = typename Exec_T::processor_type; + using controls = avnd::control_output_introspection; + constexpr int control_index = controls::field_index_to_index(idx); - // Mark the control as changed - self.control.outputs_set.set(control_index); + // Mark the control as changed + self.control.outputs_set.set(control_index); + } } } diff --git a/include/avnd/binding/ossia/to_value.hpp b/include/avnd/binding/ossia/to_value.hpp index 966598c9..f1011626 100644 --- a/include/avnd/binding/ossia/to_value.hpp +++ b/include/avnd/binding/ossia/to_value.hpp @@ -399,7 +399,9 @@ ossia::value to_ossia_value(const avnd::variant_ish auto& v) { return to_ossia_value_rec(v); } -ossia::value to_ossia_value(const avnd::optional_ish auto& v) +template + requires(!std::is_same_v, T>) +ossia::value to_ossia_value(const T& v) { return to_ossia_value_rec(v); } @@ -451,6 +453,13 @@ inline ossia::value to_ossia_value(const T& v) return v; } +template + requires std::is_same_v, T> +inline ossia::value to_ossia_value(const T& v) +{ + return v ? *v : ossia::value{}; +} + inline ossia::value to_ossia_value(auto& field, const auto& src) { return to_ossia_value(src); diff --git a/include/avnd/concepts/all.hpp b/include/avnd/concepts/all.hpp index 1949ca1a..1718d41d 100644 --- a/include/avnd/concepts/all.hpp +++ b/include/avnd/concepts/all.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/include/avnd/concepts/control.hpp b/include/avnd/concepts/control.hpp new file mode 100644 index 00000000..d51505f2 --- /dev/null +++ b/include/avnd/concepts/control.hpp @@ -0,0 +1,86 @@ +#pragma once + +/* SPDX-License-Identifier: GPL-3.0-or-later OR BSL-1.0 OR CC0-1.0 OR CC-PDCC OR 0BSD */ + +#include +#include + +namespace avnd +{ + +/** + * A "control" is a parameter + some metadata: + * + * struct + * { + * // see widgets.hpp + * enum widget { slider }; + * + * + * static consteval auto range() { + * struct { + * float min = 0.; + * float max = 1.; + * float init = 0.25; + * } r; + * return r; + * } + * + * float value; + * }; + */ +template +concept control = /*(parameter || callback) &&*/ parameter + && (has_range || has_widget); + +// FIXME all this needs improving: they do not support callbacks. +// Also messages are not handled. +// Ideally, messages, callbacks and controls would all be handled exactly in the same way ? +template +concept int_control = int_parameter && control; +template +concept enum_control = enum_parameter && control; +template +concept float_control = float_parameter && control; +template +concept bool_control = bool_parameter && control; +template +concept string_control = string_parameter && control; +template +concept time_control = float_control && requires { T::time_chooser; }; + +template +concept int_value_port = int_parameter && !control; +template +concept enum_value_port = enum_parameter && !control; +template +concept float_value_port = float_parameter && !control; +template +concept bool_value_port = bool_parameter && !control; +template +concept string_value_port = string_parameter && !control; + +/** + * A value port is a parameter which is not a control. + */ +template +concept value_port = parameter && !control; + +template +concept span_control = span_parameter && control; +template +concept span_value_port = span_parameter && value_port; + +/** + * Like control but sample-accurate + */ +template +concept sample_accurate_control = sample_accurate_parameter && control; + +/** + * Like value_port but sample-accurate + */ +template +concept sample_accurate_value_port = sample_accurate_parameter && !control; + +} diff --git a/include/avnd/concepts/parameter.hpp b/include/avnd/concepts/parameter.hpp index 211cd6fd..50e0c374 100644 --- a/include/avnd/concepts/parameter.hpp +++ b/include/avnd/concepts/parameter.hpp @@ -127,67 +127,6 @@ concept program_parameter } -> std::convertible_to; }; -/** - * A "control" is a parameter + some metadata: - * - * struct - * { - * // see widgets.hpp - * enum widget { slider }; - * - * - * static consteval auto range() { - * struct { - * float min = 0.; - * float max = 1.; - * float init = 0.25; - * } r; - * return r; - * } - * - * float value; - * }; - */ - -template -concept control = parameter && (has_range || has_widget); - -template -concept int_control = int_parameter && control; -template -concept enum_control = enum_parameter && control; -template -concept float_control = float_parameter && control; -template -concept bool_control = bool_parameter && control; -template -concept string_control = string_parameter && control; -template -concept time_control = float_control && requires { T::time_chooser; }; - -template -concept int_value_port = int_parameter && ! -control; -template -concept enum_value_port = enum_parameter && ! -control; -template -concept float_value_port = float_parameter && ! -control; -template -concept bool_value_port = bool_parameter && ! -control; -template -concept string_value_port = string_parameter && ! -control; - -/** - * A value port is a parameter which is not a control. - */ -template -concept value_port = parameter && ! -control; - /** * Here T is a e.g. std::span */ @@ -202,10 +141,6 @@ concept span_value } && std::is_constructible_v && std::is_trivially_copy_constructible_v; template concept span_parameter = parameter && span_value>; -template -concept span_control = span_parameter && control; -template -concept span_value_port = span_parameter && value_port; /** * Timed values are used for sample-accurate ports. @@ -257,19 +192,6 @@ concept sample_accurate_parameter || requires(T t) { t.value = t.values.begin()->value; } // span ); -/** - * Like control but sample-accurate - */ -template -concept sample_accurate_control = sample_accurate_parameter && control; - -/** - * Like value_port but sample-accurate - */ -template -concept sample_accurate_value_port = sample_accurate_parameter && ! -control; - template concept linear_sample_accurate_parameter = sample_accurate_parameter diff --git a/include/avnd/wrappers/effect_container.hpp b/include/avnd/wrappers/effect_container.hpp index ee98bfe2..5fdcacdc 100644 --- a/include/avnd/wrappers/effect_container.hpp +++ b/include/avnd/wrappers/effect_container.hpp @@ -122,12 +122,12 @@ struct effect_container T effect; - void init_channels(int input, int output) + inline constexpr void init_channels(int input, int output) { // TODO maybe a runtime check } - auto& inputs() noexcept + inline constexpr auto& inputs() noexcept { if constexpr(has_inputs) { @@ -139,7 +139,8 @@ struct effect_container else return dummy_instance; } - auto& inputs() const noexcept + + inline constexpr auto& inputs() const noexcept { if constexpr(has_inputs) { @@ -151,7 +152,8 @@ struct effect_container else return dummy_instance; } - auto& outputs() noexcept + + inline constexpr auto& outputs() noexcept { if constexpr(has_outputs) { @@ -163,7 +165,8 @@ struct effect_container else return dummy_instance; } - auto& outputs() const noexcept + + inline constexpr auto& outputs() const noexcept { if constexpr(has_outputs) { @@ -183,12 +186,13 @@ struct effect_container decltype(std::declval().outputs())& outputs; }; - std::array full_state() noexcept + inline std::array full_state() noexcept { return {ref{effect, this->inputs(), this->outputs()}}; } - auto effects() { + inline auto effects() noexcept + { return member_iterator_one_effect{*this}; } }; diff --git a/include/avnd/wrappers/metadatas.hpp b/include/avnd/wrappers/metadatas.hpp index 0810e601..3c93afc4 100644 --- a/include/avnd/wrappers/metadatas.hpp +++ b/include/avnd/wrappers/metadatas.hpp @@ -22,6 +22,67 @@ namespace avnd #define AVND_IF_CONSTEVAL (std::is_constant_evaluated()) #endif +#define define_get_property_auto(PropName, Default) \ + template \ + constexpr auto get_##PropName() \ + { \ + if constexpr(requires { T::PropName(); }) \ + return T::PropName(); \ + else if constexpr(requires { T::PropName; }) \ + return T::PropName; \ + else \ + return Default; \ + } \ + \ + template \ + constexpr auto get_##PropName(const T& t) \ + { \ + if AVND_IF_CONSTEVAL \ + { \ + return get_##PropName(); \ + } \ + else \ + { \ + if constexpr(requires { T::PropName(); }) \ + return T::PropName(); \ + else if constexpr(requires { T::PropName; }) \ + return T::PropName; \ + else if constexpr(requires { t.PropName(); }) \ + return t.PropName(); \ + else if constexpr(requires { t.PropName; }) \ + return t.PropName; \ + else \ + return get_##PropName(); \ + } \ + } \ + \ + template \ + concept has_##PropName \ + = requires(T t) { t.PropName(); } || requires(T t) { t.PropName; }; \ + \ + struct prop_##PropName \ + { \ + template \ + static constexpr bool has() noexcept \ + { \ + return has_##PropName; \ + } \ + \ + static constexpr std::string_view name() noexcept \ + { \ + return #PropName; \ + } \ + \ + template \ + static constexpr auto get() \ + { \ + if constexpr(has_##PropName) \ + return get_##PropName(); \ + else \ + return Default; \ + } \ + }; + #define define_get_property(PropName, Type, Default) \ template \ constexpr Type get_##PropName() \ @@ -134,8 +195,10 @@ define_get_property(description, std::string_view, "(description)") define_get_property(short_description, std::string_view, "(short_description)") define_get_property(module, std::string_view, "(module)") -template -constexpr std::string_view default_osc_path() +define_get_property_auto(pixmaps, std::span{}) + + template + constexpr std::string_view default_osc_path() { return get_name(); } @@ -304,30 +367,22 @@ template /* constexpr */ int get_int_version() { if constexpr(requires { - { - T::version() - } -> std::integral; + { T::version() } -> std::integral; }) { return T::version(); } else if constexpr(requires { - { - std::declval().version - } -> std::integral; + { std::declval().version } -> std::integral; }) { return T::version; } else if constexpr(requires { - { - T::version() - } -> avnd::string_ish; + { T::version() } -> avnd::string_ish; } || requires { - { - std::declval().version - } -> avnd::string_ish; - }) + { std::declval().version } -> avnd::string_ish; + }) { auto str = avnd::get_version(); if(str.empty()) @@ -346,6 +401,7 @@ template return 1; } } + template constexpr std::array get_keywords() {