From 2ef9fdf9e8c48def81c75926d8a95aaeaa1fdc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Wed, 25 Sep 2024 21:46:14 -0400 Subject: [PATCH] [max] Implement proper support for dicts as outputs --- cmake/avendish.max.cmake | 1 + cmake/avendish.sources.cmake | 4 +- examples/Complete/CompleteMessageExample.hpp | 20 +- .../Complete/CompleteMessageExample.maxhelp | 511 ++++++++++++++++++ include/avnd/binding/max/dict.hpp | 87 +++ .../avnd/binding/max/message_processor.hpp | 12 +- include/avnd/binding/max/outputs.hpp | 148 ++++- include/avnd/binding/max/to_atoms.hpp | 50 +- include/avnd/introspection/generic.hpp | 4 +- include/avnd/wrappers/generic.hpp | 90 +++ 10 files changed, 868 insertions(+), 59 deletions(-) create mode 100644 examples/Complete/CompleteMessageExample.maxhelp create mode 100644 include/avnd/binding/max/dict.hpp create mode 100644 include/avnd/wrappers/generic.hpp diff --git a/cmake/avendish.max.cmake b/cmake/avendish.max.cmake index ef561b45..10f89026 100644 --- a/cmake/avendish.max.cmake +++ b/cmake/avendish.max.cmake @@ -111,6 +111,7 @@ function(avnd_make_max) "${AVND_SOURCE_DIR}/include/avnd/binding/max/atom_iterator.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/audio_processor.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/configure.hpp" + "${AVND_SOURCE_DIR}/include/avnd/binding/max/dict.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/dsp.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/from_atoms.hpp" "${AVND_SOURCE_DIR}/include/avnd/binding/max/helpers.hpp" diff --git a/cmake/avendish.sources.cmake b/cmake/avendish.sources.cmake index 889987dc..b434d435 100644 --- a/cmake/avendish.sources.cmake +++ b/cmake/avendish.sources.cmake @@ -12,7 +12,9 @@ if(MSVC) "-Zc:inline" "-Zc:preprocessor" "-Zc:templateScope" - -wd4244) + -wd4244 # float -> int lossy conversion warning + -wd4068 # disables warning C4068: unknown pragma 'GCC' + ) target_compile_definitions(Avendish PUBLIC -DNOMINMAX=1 -DWIN32_LEAN_AND_MEAN=1) elseif(APPLE) target_compile_options(Avendish diff --git a/examples/Complete/CompleteMessageExample.hpp b/examples/Complete/CompleteMessageExample.hpp index f1ce04d3..ceacfcbf 100644 --- a/examples/Complete/CompleteMessageExample.hpp +++ b/examples/Complete/CompleteMessageExample.hpp @@ -136,20 +136,20 @@ struct CompleteMessageExample halp::val_port<"out_0", int> out_int; // Case 2: outputting a basic tuple - // pd: will output [float 123 456 789> - // max: will output [float 123 456 789> + // pd: will output [list 123 456 789> + // max: will output [list 123 456 789> halp::val_port<"out_1", std::array> out_vec3; // Case 2: outputting an object without names // Note that the object can be defined elsewhere. // Only rules are no specific constructors / destructors, only aggregate types. - // pd: a list - // max: a list + // pd: will output [list 123 foo> + // max: will output [list 123 foo> halp::val_port<"out_2", some_object> out_obj1; // Case 3: outputting an object with names - // pd: a list - // max: a dict + // pd: will output [list 123 foo> + // max: will output [dict x:123 v:foo> halp::val_port<"out_3", some_object_named> out_obj2; // Case 4: outputting messages @@ -188,11 +188,11 @@ struct CompleteMessageExample std::cerr << "Test1: " << inputs.test1.value << "\n"; std::cerr << "Test2: " << inputs.test2.value << "\n"; std::cerr << "Test3: " << inputs.test3.value << "\n"; - outputs.out_int = 123; + outputs.out_int++; outputs.out_vec3 = std::array{4.f, 5.f, 6.f}; - outputs.out_obj1 = some_object{13, "hello"}; - outputs.out_obj2 = some_object_named{31, "bye"}; - outputs.out_msg1(inputs.slider.value, 2, "from bang"); + outputs.out_obj1 = some_object{outputs.out_int, "hello"}; + outputs.out_obj2 = some_object_named{outputs.out_int, "bye"}; + outputs.out_msg1(inputs.slider.value, outputs.out_int, "from bang"); outputs.out_msg2("text1", "text2"); outputs.out_msg_list(std::vector{"text1", "text2", "text3"}); std::vector v{"a", "b", "c", "d"}; diff --git a/examples/Complete/CompleteMessageExample.maxhelp b/examples/Complete/CompleteMessageExample.maxhelp new file mode 100644 index 00000000..ec6c24b1 --- /dev/null +++ b/examples/Complete/CompleteMessageExample.maxhelp @@ -0,0 +1,511 @@ +{ + "patcher" : { + "fileversion" : 1, + "appversion" : { + "major" : 8, + "minor" : 5, + "revision" : 6, + "architecture" : "x64", + "modernui" : 1 + } +, + "classnamespace" : "box", + "rect" : [ 1658.0, 417.0, 1852.0, 921.0 ], + "bglocked" : 0, + "openinpresentation" : 0, + "default_fontsize" : 12.0, + "default_fontface" : 0, + "default_fontname" : "Arial", + "gridonopen" : 1, + "gridsize" : [ 15.0, 15.0 ], + "gridsnaponopen" : 1, + "objectsnaponopen" : 1, + "statusbarvisible" : 2, + "toolbarvisible" : 1, + "lefttoolbarpinned" : 0, + "toptoolbarpinned" : 0, + "righttoolbarpinned" : 0, + "bottomtoolbarpinned" : 0, + "toolbars_unpinned_last_save" : 0, + "tallnewobj" : 0, + "boxanimatetime" : 200, + "enablehscroll" : 1, + "enablevscroll" : 1, + "devicewidth" : 0.0, + "description" : "", + "digest" : "", + "tags" : "", + "style" : "", + "subpatcher_template" : "", + "assistshowspatchername" : 0, + "boxes" : [ { + "box" : { + "id" : "obj-20", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 178.0, 594.0, 148.0, 22.0 ], + "text" : "x : 46 v : bye" + } + + } +, { + "box" : { + "id" : "obj-16", + "maxclass" : "newobj", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 178.0, 566.0, 75.0, 22.0 ], + "text" : "dict.serialize" + } + + } +, { + "box" : { + "id" : "obj-36", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 384.0, 315.0, 90.0, 22.0 ], + "text" : "list 1 3 5 7 9 12" + } + + } +, { + "box" : { + "id" : "obj-33", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 330.0, 287.0, 75.0, 22.0 ], + "text" : "symbol hello" + } + + } +, { + "box" : { + "id" : "obj-29", + "maxclass" : "number", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 302.5, 255.0, 50.0, 22.0 ] + } + + } +, { + "box" : { + "id" : "obj-28", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 238.5, 139.0, 104.0, 22.0 ], + "text" : "Test5 1 3 5 7 9 12" + } + + } +, { + "box" : { + "id" : "obj-24", + "maxclass" : "number", + "numinlets" : 1, + "numoutlets" : 2, + "outlettype" : [ "", "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 255.0, 178.0, 50.0, 22.0 ] + } + + } +, { + "box" : { + "id" : "obj-21", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 281.0, 215.0, 93.0, 22.0 ], + "text" : "m1 1 2.3 foobar" + } + + } +, { + "box" : { + "id" : "obj-19", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 229.0, 100.0, 54.0, 22.0 ], + "text" : "Test3 15" + } + + } +, { + "box" : { + "id" : "obj-17", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 211.0, 67.0, 61.0, 22.0 ], + "text" : "Test2 123" + } + + } +, { + "box" : { + "id" : "obj-15", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 192.0, 29.0, 47.0, 22.0 ], + "text" : "Test1 1" + } + + } +, { + "box" : { + "id" : "obj-13", + "maxclass" : "button", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "bang" ], + "parameter_enable" : 0, + "patching_rect" : [ 42.0, 276.0, 24.0, 24.0 ] + } + + } +, { + "box" : { + "id" : "obj-11", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 839.0, 148.0, 22.0 ], + "text" : "random_sym x y z w" + } + + } +, { + "box" : { + "id" : "obj-10", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 796.0, 148.0, 22.0 ], + "text" : "a b c d" + } + + } +, { + "box" : { + "id" : "obj-9", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 750.0, 148.0, 22.0 ], + "text" : "text1 text2 text3" + } + + } +, { + "box" : { + "id" : "obj-8", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 700.0, 148.0, 22.0 ], + "text" : "text1 text2" + } + + } +, { + "box" : { + "id" : "obj-7", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 656.0, 148.0, 22.0 ], + "text" : "0.5 2 \"from bang\"" + } + + } +, { + "box" : { + "id" : "obj-6", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 178.0, 538.0, 148.0, 22.0 ], + "text" : "dictionary u258000268" + } + + } +, { + "box" : { + "id" : "obj-5", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 482.0, 148.0, 22.0 ], + "text" : "46 hello" + } + + } +, { + "box" : { + "id" : "obj-4", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 441.0, 148.0, 22.0 ], + "text" : "4. 5. 6." + } + + } +, { + "box" : { + "id" : "obj-3", + "maxclass" : "message", + "numinlets" : 2, + "numoutlets" : 1, + "outlettype" : [ "" ], + "patching_rect" : [ 174.0, 398.0, 148.0, 22.0 ], + "text" : "46" + } + + } +, { + "box" : { + "id" : "obj-1", + "maxclass" : "newobj", + "numinlets" : 4, + "numoutlets" : 9, + "outlettype" : [ "long", "anything", "anything", "anything", "anything", "anything", "anything", "anything", "anything" ], + "patching_rect" : [ 202.0, 355.0, 197.0, 22.0 ], + "text" : "avnd_complete_message_example" + } + + } +, { + "box" : { + "attr" : "Test1", + "id" : "obj-25", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "parameter_enable" : 0, + "patching_rect" : [ 38.0, 193.0, 150.0, 22.0 ] + } + + } +, { + "box" : { + "attr" : "Test2", + "id" : "obj-26", + "maxclass" : "attrui", + "numinlets" : 1, + "numoutlets" : 1, + "outlettype" : [ "" ], + "parameter_enable" : 0, + "patching_rect" : [ 42.0, 231.0, 150.0, 22.0 ] + } + + } + ], + "lines" : [ { + "patchline" : { + "destination" : [ "obj-10", 1 ], + "source" : [ "obj-1", 7 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-11", 1 ], + "source" : [ "obj-1", 8 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-3", 1 ], + "source" : [ "obj-1", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-4", 1 ], + "source" : [ "obj-1", 1 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-5", 1 ], + "source" : [ "obj-1", 2 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-6", 1 ], + "source" : [ "obj-1", 3 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-7", 1 ], + "source" : [ "obj-1", 4 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-8", 1 ], + "source" : [ "obj-1", 5 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-9", 1 ], + "source" : [ "obj-1", 6 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "order" : 0, + "source" : [ "obj-13", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-6", 0 ], + "order" : 1, + "source" : [ "obj-13", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-15", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-20", 1 ], + "source" : [ "obj-16", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-17", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-19", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-21", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-24", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-25", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-26", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 0 ], + "source" : [ "obj-28", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 1 ], + "source" : [ "obj-29", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 2 ], + "source" : [ "obj-33", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-1", 3 ], + "source" : [ "obj-36", 0 ] + } + + } +, { + "patchline" : { + "destination" : [ "obj-16", 0 ], + "source" : [ "obj-6", 0 ] + } + + } + ], + "dependency_cache" : [ { + "name" : "avnd_complete_message_example.mxe64", + "type" : "mx64" + } + ], + "autosave" : 0 + } + +} diff --git a/include/avnd/binding/max/dict.hpp b/include/avnd/binding/max/dict.hpp new file mode 100644 index 00000000..baf15455 --- /dev/null +++ b/include/avnd/binding/max/dict.hpp @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include +#include + +#include +#include + +namespace max +{ +template +concept parameter_with_field_names + = avnd::parameter && avnd::has_field_names; + +generate_predicate_introspection(parameter_with_field_names); +generate_member_introspection(parameter_with_field_names, inputs); +generate_member_introspection(parameter_with_field_names, outputs); + +struct dict_state +{ + dict_state() + : d{dictionary_new()} + { + } + dict_state(const dict_state&) = delete; + dict_state& operator=(const dict_state&) = delete; + dict_state(dict_state&&) = delete; + dict_state& operator=(dict_state&&) = delete; + + ~dict_state() { object_free(d); } + + t_dictionary* d{}; + t_symbol* s{}; +}; + +generate_port_state_holders(parameter_with_field_names, dict_state); + +template +struct dict_storage +{ + [[no_unique_address]] + dict_state_input_storage inputs; + [[no_unique_address]] + dict_state_output_storage outputs; + + void init(avnd::effect_container& t) + { + if constexpr(parameter_with_field_names_outputs_introspection::size > 0) + parameter_with_field_names_outputs_introspection::for_all_n( + avnd::get_outputs(t), + [this](F& field, avnd::predicate_index) { + auto& state = get(outputs.handles); + dictobj_register(state.d, &state.s); + }); + } + + void release(avnd::effect_container& t) + { + if constexpr(parameter_with_field_names_outputs_introspection::size > 0) + parameter_with_field_names_outputs_introspection::for_all_n( + avnd::get_outputs(t), + [this](F& field, avnd::predicate_index) { + auto& state = get(outputs.handles); + dictobj_release(state.d); + }); + } + + template + auto& get_input(avnd::field_index) + { + constexpr std::size_t NPred + = parameter_with_field_names_outputs_introspection::template field_index_to_index( + avnd::field_index{}); + return get(inputs.handles); + } + + template + auto& get_output(avnd::field_index) + { + constexpr std::size_t NPred + = parameter_with_field_names_outputs_introspection::template field_index_to_index( + avnd::field_index{}); + return get(outputs.handles); + } +}; +} diff --git a/include/avnd/binding/max/message_processor.hpp b/include/avnd/binding/max/message_processor.hpp index 65ec4544..9282fc70 100644 --- a/include/avnd/binding/max/message_processor.hpp +++ b/include/avnd/binding/max/message_processor.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -51,10 +52,15 @@ struct message_processor [[no_unique_address]] outputs output_setup; + [[no_unique_address]] dict_storage dicts; + // we don't use ctor / dtor, because // this breaks aggregate-ness... void init(int argc, t_atom* argv) { + /// Create internal metadata /// + dicts.init(implementation); + /// Create ports /// // Create inlets input_setup.init(implementation, x_obj); @@ -100,7 +106,9 @@ struct message_processor } } - void destroy() { } + void destroy() { + dicts.release(implementation); + } void process_inlet_control(int inlet, t_atom_long val) { @@ -186,7 +194,7 @@ struct message_processor if_possible(implementation.effect()); // Then bang - output_setup.commit(implementation); + output_setup.commit(*this); } template diff --git a/include/avnd/binding/max/outputs.hpp b/include/avnd/binding/max/outputs.hpp index 3fb72cad..609e1c01 100644 --- a/include/avnd/binding/max/outputs.hpp +++ b/include/avnd/binding/max/outputs.hpp @@ -3,6 +3,7 @@ /* SPDX-License-Identifier: GPL-3.0-or-later */ #include +#include #include #include namespace max @@ -21,7 +22,7 @@ struct do_value_to_max_typed } void operator()(std::integral auto v) const noexcept { - outlet_long(p, v); + outlet_int(p, v); } void operator()(std::string_view v) const noexcept { @@ -36,15 +37,6 @@ struct do_value_to_max_typed requires std::is_aggregate_v void operator()(const T& v) const noexcept { - if constexpr(avnd::has_field_names) - { - aggregate_to_dict dict; - dict(v); - dictionary_dump(dict.d, true, true); - object_free(dict.d); - } - else - { to_list l; l(v); @@ -52,7 +44,7 @@ struct do_value_to_max_typed l.atoms.resize(std::numeric_limits::max()); outlet_list(p, nullptr, (short)l.atoms.size(), l.atoms.data()); - } + } void operator()(const avnd::vector_ish auto& v) const noexcept @@ -99,11 +91,53 @@ struct do_value_to_max_typed }; // FIXME port the thread-local mechanism to pd in to_list -template + +/// Versions for the paramter (one-value) case +// One-arg overload to handle the dict case +template +inline void value_to_max_dispatch(T& self, avnd::field_index idx, t_outlet* outlet, Arg&& v) noexcept +{ + // 1. Get the dict from object storage + const dict_state& storage = self.dicts.get_output(idx); + + // 2. Update it + aggregate_to_dict dict{storage.d}; + dict(v); + + // 3. Output it + t_atom a; + atom_setsym(&a, storage.s); + outlet_anything(outlet, _sym_dictionary, 1, &a); +} + +template +inline void value_to_max_dispatch(T& self, avnd::field_index, t_outlet* outlet, Arg&& v) noexcept +{ + if constexpr(avnd::has_symbol || avnd::has_c_name) + { + // FIXME + static const auto sym = get_static_symbol(); + // return do_value_to_max_anything{}(outlet, sym, std::forward(v)); + } + //else + { + return do_value_to_max_typed{outlet}(std::forward(v)); + } +} + +template +inline void value_to_max_dispatch(T& self, avnd::field_index, t_outlet* outlet, avnd::string_ish auto&& dsym) noexcept +{ + outlet_anything(outlet, gensym(dsym.data()), 0, nullptr); +} + +/// Versions only for the avnd::callback cases +template inline void value_to_max_dispatch(t_outlet* outlet, Args&&... v) noexcept { if constexpr(avnd::has_symbol || avnd::has_c_name) { + // FIXME static const auto sym = get_static_symbol(); // return do_value_to_max_anything{}(outlet, sym, std::forward(v)...); } @@ -115,13 +149,19 @@ inline void value_to_max_dispatch(t_outlet* outlet, Args&&... v) noexcept template inline void value_to_max_dispatch( - t_outlet* outlet, avnd::string_ish auto&& dsym, avnd::span_ish auto&& v) noexcept + t_outlet* outlet, avnd::string_ish auto&& dsym, avnd::span_ish auto&& v) noexcept { to_list conv; conv(v); outlet_anything(outlet, gensym(dsym.data()), conv.atoms.size(), conv.atoms.data()); } +template +inline void value_to_max_dispatch(t_outlet* outlet, avnd::string_ish auto&& dsym) noexcept +{ + outlet_anything(outlet, gensym(dsym.data()), 0, nullptr); +} + template inline void value_to_max_dispatch(t_outlet* outlet, Args&&... v) noexcept { @@ -137,6 +177,61 @@ inline void value_to_max_dispatch(t_outlet* outlet, Args&&... v) noexcept }(v...); } +template +struct value_writer +{ + T& self; + + template + requires(!avnd::sample_accurate_parameter) + void operator()(Field& ctrl, t_outlet* port, avnd::num) const noexcept + { + value_to_max_dispatch(self, avnd::field_index{}, port, ctrl.value); + } + + template + void operator()(Field& ctrl, t_outlet* port, avnd::num) const noexcept + { + // FIXME +#if 0 + auto& buffers = self.control_buffers.linear_inputs; + // Idx is the index of the port in the complete input array. + // We need to map it to the linear input index. + using processor_type = typename T::processor_type; + using lin_out = avnd::linear_timed_parameter_output_introspection; + using indices = typename lin_out::indices_n; + static constexpr int storage_index = avnd::index_of_element(indices{}); + + auto& buffer = get(buffers); + + for(int i = 0, N = self.buffer_size; i < N; i++) + { + if(buffer[i]) + { + value_to_max_dispatch(self, avnd::field_index{}, port, *buffer[i]); + buffer[i] = {}; + } + } +#endif + } + + template + void operator()(Field& ctrl, t_outlet* port, avnd::num) const noexcept + { + for(auto& [timestamp, val] : ctrl.values) + { + value_to_max_dispatch(self, avnd::field_index{}, port, val); + } + ctrl.values.clear(); + } + + // does not make sense as output, only as input + template + void operator()(Field& ctrl, t_outlet* port, avnd::num) const noexcept = delete; + + void operator()(auto&&...) const noexcept { } +}; + template struct outputs { @@ -159,7 +254,7 @@ struct outputs } template - static void setup(C& out, t_outlet& outlet) + static void setup(T& self, C& out, t_outlet& outlet) { using call_type = decltype(C::call); if constexpr(avnd::function_view_ish) @@ -177,18 +272,21 @@ struct outputs { } - void commit(avnd::effect_container& implementation) + template + void commit(Self& self) { - int k = 0; - avnd::output_introspection::for_all( - avnd::get_outputs(implementation), [this, &k](C& ctl) { - // FIXME handle all the normal output types - if constexpr(requires(float v) { v = ctl.value; }) - { - outlet_float(outlets[k], ctl.value); - } - ++k; - }); + using info = avnd::output_introspection; + if constexpr(info::size > 0) + { + // FIXME stops being correct if we loose the avnd port <-> outlet correspondance + // with e.g. multiple callbacks in a single port + auto& outs = avnd::get_outputs(self.implementation); + [&](std::integer_sequence) { + (value_writer{self}( + avnd::pfr::get(outs), outlets[Index], avnd::num{}), + ...); + }(typename info::indices_n{}); + } } void init(avnd::effect_container& implementation, t_object& x_obj) diff --git a/include/avnd/binding/max/to_atoms.hpp b/include/avnd/binding/max/to_atoms.hpp index bd2927f4..4ec01e7c 100644 --- a/include/avnd/binding/max/to_atoms.hpp +++ b/include/avnd/binding/max/to_atoms.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include @@ -12,12 +12,11 @@ namespace max { struct to_list { - static inline thread_local boost::container::vector atoms; + boost::container::small_vector atoms; to_list() { atoms.clear(); - atoms.reserve(256); } void operator()(std::floating_point auto arg) noexcept @@ -125,18 +124,12 @@ struct aggregate_to_dict { t_dictionary* d{}; - explicit aggregate_to_dict() noexcept - : d{dictionary_new()} - { - - } - - void operator()(t_symbol* k, std::floating_point auto v) noexcept + void operator()(t_symbol* k, std::floating_point auto&& v) noexcept { dictionary_appendfloat(d, k, v); } - void operator()(t_symbol* k, std::integral auto v) noexcept + void operator()(t_symbol* k, std::integral auto&& v) noexcept { dictionary_appendlong(d, k, v); } @@ -146,22 +139,41 @@ struct aggregate_to_dict dictionary_appendstring(d, k, v.data()); } - template - void operator()(t_symbol* k, std::array v) noexcept + template + void operator()(t_symbol* k, T&& v) noexcept + { + to_list l; + l(v); + dictionary_appendatoms(d, k, l.atoms.size(), l.atoms.data()); + } + + template + requires avnd::has_field_names> + void operator()(t_symbol* k, T&& v) noexcept { + t_object* cur{}; + bool is_new{}; + if(dictionary_getdictionary(d, k, &cur) != MAX_ERR_NONE || !cur) { + cur = (t_object*)dictionary_new(); + is_new = true; + } + + aggregate_to_dict sub{(t_dictionary*)cur}; + sub(v); + + dictionary_appenddictionary(d, k, cur); } template - requires avnd::has_field_names + requires avnd::has_field_names> void operator()(F&& f) { - constexpr auto field_names = F::field_names(); int k = 0; - avnd::for_each_field_ref( - f, [&](const auto& f) { - constexpr auto name = field_names[k++]; + avnd::for_each_field_ref_n( + f, [&](const auto& f, avnd::field_index) { + static constexpr auto name = std::remove_cvref_t::field_names()[N]; static const auto sym = gensym(name.data()); - add(sym, f); + (*this)(sym, f); }); } }; diff --git a/include/avnd/introspection/generic.hpp b/include/avnd/introspection/generic.hpp index a45fbbc6..d3c25b2c 100644 --- a/include/avnd/introspection/generic.hpp +++ b/include/avnd/introspection/generic.hpp @@ -7,11 +7,11 @@ using is_##Concept##_t = boost::mp11::mp_bool>; \ \ template \ - using Concept##_introspection = predicate_introspection; + using Concept##_introspection = avnd::predicate_introspection; #define generate_member_introspection(Concept, Member) \ template \ struct Concept##_##Member##_introspection \ - : Concept##_introspection::type> \ + : Concept##_introspection::type> \ { \ }; diff --git a/include/avnd/wrappers/generic.hpp b/include/avnd/wrappers/generic.hpp new file mode 100644 index 00000000..5ddee3fb --- /dev/null +++ b/include/avnd/wrappers/generic.hpp @@ -0,0 +1,90 @@ +#pragma once + +/** + * \macro generate_port_state_holders + * + * This beautiful macro will generate data types that allow to store per-port data, + * for ports matching a specific predicate, in an optimal way memory-wise. + * + * e.g. given for instance an object with: + * + * ``` + * struct inputs { + * struct { int color; int value; } a; + * struct { std::string value; } b; + * struct { int color; float** samples; } c; + * }; + * struct outputs { + * struct { float value; } a; + * }; + * ``` + * + * and a concept: + * + * ``` + * template + * concept has_color = requires(T t) { t.color; }; + * ``` + * + * Then given the state class: + * + * ``` + * struct state_for_color { + * std::string hue; + * }; + * ``` + * + * The macro call `generate_port_state_holders(has_color, state_for_color)` + * will create `state_for_color_input_storage` and `state_for_color_output_storage`. + * + * In our example, they will look like this: not a single byte is wasted. + * + * ``` + * struct state_for_color_input_storage { + * // one for A, one for C + * tuple handles; + * }; + * struct state_for_color_output_storage { + * }; + * ``` + * + */ +#define generate_port_state_holders(Concept, State) \ + template \ + struct State##_type; \ + \ + template \ + struct State##_type : State \ + { \ + }; \ + \ + template \ + struct State##_input_storage \ + { \ + }; \ + \ + template \ + requires(Concept##_inputs_introspection::size > 0) \ + struct State##_input_storage \ + { \ + using hdl_tuple \ + = avnd::filter_and_apply; \ + \ + [[no_unique_address]] hdl_tuple handles; \ + }; \ + \ + template \ + struct State##_output_storage \ + { \ + }; \ + \ + template \ + requires(Concept##_outputs_introspection::size > 0) \ + struct State##_output_storage \ + { \ + using hdl_tuple \ + = avnd::filter_and_apply; \ + \ + [[no_unique_address]] hdl_tuple handles; \ + } +\