#pragma once #include "channel.hpp" #include "node.hpp" #include "traits.hpp" #include #include #include #include #include #include namespace kpn { // ── unique_types TMP helper ─────────────────────────────────────────────────── namespace detail { template struct unique_types_impl; template struct unique_types_impl> { using type = std::tuple; }; template struct unique_types_impl, Head, Tail...> { using type = std::conditional_t< (std::is_same_v || ...), typename unique_types_impl, Tail...>::type, typename unique_types_impl, Tail...>::type >; }; template using unique_types_t = typename unique_types_impl, Ts...>::type; template struct tuple_to_variant; template struct tuple_to_variant> { using type = std::variant; }; } // namespace detail // ── IVariantChannel ─────────────────────────────────────────────────────────── // Type-erased channel surface used by PyNetwork. // The underlying FIFO stores raw T — variant conversion happens only at push/pop. template class IVariantChannel { public: virtual ~IVariantChannel() = default; virtual void push(Variant v) = 0; virtual Variant pop() = 0; virtual std::type_index type_index() const = 0; virtual std::string type_name() const = 0; virtual void enable() = 0; virtual void disable() = 0; }; // ── VariantChannel ──────────────────────────────────────────────── // Shares a Channel with the node that owns the input slot. // push(): std::get from Variant → raw T into the queue. // pop(): raw T from queue → wrapped into Variant. template class VariantChannel : public IVariantChannel { public: explicit VariantChannel(std::shared_ptr> ch) : channel_(std::move(ch)) {} void push(Variant v) override { channel_->push(std::get(std::move(v))); } Variant pop() override { return Variant{ channel_->pop() }; } std::type_index type_index() const override { return std::type_index(typeid(T)); } std::string type_name() const override { return typeid(T).name(); } void enable() override { channel_->enable(); } void disable() override { channel_->disable(); } Channel* raw_ptr() { return channel_.get(); } private: std::shared_ptr> channel_; }; // ── IVariantNode ────────────────────────────────────────────────────────────── template class IVariantNode : public INode, public std::enable_shared_from_this> { public: void set_name(std::string name) override { name_ = std::move(name); } const std::string& name() const { return name_; } virtual std::size_t input_count() const = 0; virtual std::size_t output_count() const = 0; virtual std::type_index input_type(std::size_t i) const = 0; virtual std::type_index output_type(std::size_t i) const = 0; // Returns the IVariantChannel wrapping this node's input slot i. // PyNetwork calls this on the *destination* node to get the channel to wire upstream into. virtual std::shared_ptr> input_channel(std::size_t i) = 0; // Wires this node's output slot i into ch (ch belongs to the downstream node's input). virtual void set_output_channel(std::size_t i, std::shared_ptr> ch) = 0; private: std::string name_; }; // ── VariantNodeWrapper ────────────────────────── // Wraps a Node for use inside a PyNetwork. // // At construction: for each input port I, builds a shared_ptr> and // installs it in both the wrapped node (via set_input_channel) and a // VariantChannel adapter. PyNetwork passes the adapter to the upstream // node's set_output_channel so the upstream raw pointer points at the same Channel. // // At connect (output side): downcasts the provided IVariantChannel to // VariantChannel, then calls node_.set_output_channel with the raw ptr. template, typename OutputTag = out<>> class VariantNodeWrapper; template class VariantNodeWrapper, out> : public IVariantNode { using NodeT = Node, out>; public: using args_tuple = typename NodeT::args_tuple; using return_tuple = typename NodeT::return_tuple; static constexpr std::size_t n_in = NodeT::input_count; static constexpr std::size_t n_out = NodeT::output_count; explicit VariantNodeWrapper(std::size_t fifo_capacity = 5) : node_(fifo_capacity) , in_channels_(n_in) , out_channels_(n_out) , out_type_indices_(n_out, std::type_index(typeid(void))) { init_inputs(std::make_index_sequence{}, fifo_capacity); init_out_types(std::make_index_sequence{}); } // ── INode ───────────────────────────────────────────────────────────────── void start() override { node_.start(); } void stop() override { node_.stop(); } bool running() const override { return node_.running(); } const NodeStats& stats() const override { return node_.stats(); } void set_name(std::string name) override { node_.set_name(std::move(name)); } NodeSnapshot node_snapshot(const std::string& name, double elapsed_s) const override { return node_.node_snapshot(name, elapsed_s); } // ── IVariantNode ────────────────────────────────────────────────────────── std::size_t input_count() const override { return n_in; } std::size_t output_count() const override { return n_out; } std::type_index input_type(std::size_t i) const override { return in_channels_[i]->type_index(); } std::type_index output_type(std::size_t i) const override { return out_type_indices_[i]; } std::shared_ptr> input_channel(std::size_t i) override { return in_channels_[i]; } void set_output_channel(std::size_t i, std::shared_ptr> ch) override { set_output_impl(i, std::move(ch), std::make_index_sequence{}); } private: template void init_inputs(std::index_sequence, std::size_t cap) { ((init_one_input(cap)), ...); } template void init_one_input(std::size_t cap) { using T = std::tuple_element_t; auto shared_ch = std::make_shared>(cap); // Install the shared Channel in the node's input slot node_.template set_input_channel(shared_ch); // Wrap it in a VariantChannel for PyNetwork to use in_channels_[I] = std::make_shared>(std::move(shared_ch)); } template void init_out_types(std::index_sequence) { ((out_type_indices_[Is] = std::type_index(typeid(std::tuple_element_t))), ...); } template void set_output_impl(std::size_t port, std::shared_ptr> ch, std::index_sequence) { bool matched = false; ((Is == port && (set_output_at(std::move(ch)), matched = true)), ...); if (!matched) throw std::out_of_range("set_output_channel: port index out of range"); } template void set_output_at(std::shared_ptr> ch) { using T = std::tuple_element_t; auto* typed = dynamic_cast*>(ch.get()); if (!typed) throw std::runtime_error( "set_output_channel: type mismatch at output port " + std::to_string(I)); // The downstream VariantChannel holds a shared Channel; point our output at it. // We need a raw ptr — extract it via a getter. node_.template set_output_channel(typed->raw_ptr()); out_channels_[I] = std::move(ch); } NodeT node_; std::vector>> in_channels_; std::vector>> out_channels_; std::vector out_type_indices_; }; // ── PythonConverter ─────────────────────────────────────────────────────────── template struct PythonConverter { static_assert(sizeof(T) == 0, "PythonConverter must be specialised for each type used in a PyNetwork"); }; } // namespace kpn