feat: persistent-pipeline reuse — push_blocking, node introspection, stateful wrapper
Adds three pieces needed to build one KPN network and reuse it across many replays/configs instead of tearing down and rebuilding per run: - Channel<T>::push_blocking (+ IVariantChannel/VariantChannel forwarding): lossless backpressure push that waits for space instead of dropping when the ring is full. PyNode's run_loop now uses it so a downstream consumer lagging behind never silently drops a frame. - PyNetwork::node_ptr / node_stats: raw node handle by name (for a binding to dynamic_cast to a concrete wrapper and call functor-specific runtime setters) and a per-node timing snapshot for profiling. - ObjectVariantNodeWrapper: variant-node adapter for functors that need runtime-constructed state (a Config, a loaded gallery), mirroring VariantNodeWrapper's channel plumbing but backed by ObjectNode<Obj>. Built and used downstream in scene-actor-extraction's sae_kpn Python replay bindings for repeated threshold-sweep evaluation of the same pipeline.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
// ObjectVariantNodeWrapper — variant-node adapter for *stateful* functors.
|
||||
//
|
||||
// VariantNodeWrapper (variant_node.hpp) wraps Node<Func,...>, where Func is a
|
||||
// default-constructible NTTP callable. That doesn't fit nodes whose functor must
|
||||
// be constructed with runtime state (a Config, a loaded gallery, etc.) — those use
|
||||
// ObjectNode<Obj>, which takes `Obj& obj` at construction.
|
||||
//
|
||||
// This wrapper owns an Obj instance and exposes the same IVariantNode surface so a
|
||||
// stateful C++ node can live inside a PyNetwork. Build one via a factory that
|
||||
// constructs the functor from Python-supplied config, e.g.:
|
||||
//
|
||||
// auto n = std::make_shared<ObjectVariantNodeWrapper<
|
||||
// IdentityMatcherFunc, Variant, in<"tracked">, out<"matched">>>(
|
||||
// fifo_cap, gallery, cfg); // Obj ctor args forwarded
|
||||
// net.add("identity_matcher", n);
|
||||
//
|
||||
// The wrapper mirrors VariantNodeWrapper's channel plumbing exactly; only the
|
||||
// underlying node type (PoolObjectNode, holding Obj&) differs.
|
||||
|
||||
#include "../channel.hpp"
|
||||
#include "../node.hpp"
|
||||
#include "../variant_node.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <typeindex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace kpn {
|
||||
|
||||
template<typename Obj, typename Variant,
|
||||
typename InputTag = in<>,
|
||||
typename OutputTag = out<>>
|
||||
class ObjectVariantNodeWrapper;
|
||||
|
||||
template<typename Obj, typename Variant,
|
||||
fixed_string... InNames, fixed_string... OutNames>
|
||||
class ObjectVariantNodeWrapper<Obj, Variant, in<InNames...>, out<OutNames...>>
|
||||
: public IVariantNode<Variant>
|
||||
{
|
||||
using NodeT = ObjectNode<Obj, in<InNames...>, out<OutNames...>>;
|
||||
|
||||
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;
|
||||
|
||||
// Owns the functor; forwards remaining args to Obj's constructor.
|
||||
template<typename... ObjArgs>
|
||||
explicit ObjectVariantNodeWrapper(std::size_t fifo_capacity, ObjArgs&&... obj_args)
|
||||
: obj_(std::forward<ObjArgs>(obj_args)...)
|
||||
, node_(obj_, 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<n_in>{}, fifo_capacity);
|
||||
init_out_types(std::make_index_sequence<n_out>{});
|
||||
}
|
||||
|
||||
// Access the owned functor so callers can invoke its runtime setters (e.g. to
|
||||
// change a threshold on a persistent pipeline without rebuilding the node).
|
||||
Obj& functor() { return obj_; }
|
||||
|
||||
// ── 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<IVariantChannel<Variant>> input_channel(std::size_t i) override {
|
||||
return in_channels_[i];
|
||||
}
|
||||
|
||||
void set_output_channel(std::size_t i,
|
||||
std::shared_ptr<IVariantChannel<Variant>> ch) override {
|
||||
set_output_impl(i, std::move(ch), std::make_index_sequence<n_out>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template<std::size_t... Is>
|
||||
void init_inputs(std::index_sequence<Is...>, std::size_t cap) {
|
||||
((init_one_input<Is>(cap)), ...);
|
||||
}
|
||||
|
||||
template<std::size_t I>
|
||||
void init_one_input(std::size_t cap) {
|
||||
using T = std::tuple_element_t<I, args_tuple>;
|
||||
auto shared_ch = std::make_shared<Channel<T>>(cap);
|
||||
node_.template set_input_channel<I>(shared_ch);
|
||||
in_channels_[I] = std::make_shared<VariantChannel<T, Variant>>(std::move(shared_ch));
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
void init_out_types(std::index_sequence<Is...>) {
|
||||
((out_type_indices_[Is] =
|
||||
std::type_index(typeid(std::tuple_element_t<Is, return_tuple>))), ...);
|
||||
}
|
||||
|
||||
template<std::size_t... Is>
|
||||
void set_output_impl(std::size_t port,
|
||||
std::shared_ptr<IVariantChannel<Variant>> ch,
|
||||
std::index_sequence<Is...>) {
|
||||
bool matched = false;
|
||||
((Is == port && (set_output_at<Is>(std::move(ch)), matched = true)), ...);
|
||||
if (!matched)
|
||||
throw std::out_of_range("set_output_channel: port index out of range");
|
||||
}
|
||||
|
||||
template<std::size_t I>
|
||||
void set_output_at(std::shared_ptr<IVariantChannel<Variant>> ch) {
|
||||
using T = std::tuple_element_t<I, return_tuple>;
|
||||
auto* typed = dynamic_cast<VariantChannel<T, Variant>*>(ch.get());
|
||||
if (!typed)
|
||||
throw std::runtime_error(
|
||||
"set_output_channel: type mismatch at output port " + std::to_string(I));
|
||||
node_.template set_output_channel<I>(typed->raw_ptr());
|
||||
out_channels_[I] = std::move(ch);
|
||||
}
|
||||
|
||||
Obj obj_; // owned; node_ holds Obj& — declaration order keeps obj_ alive first
|
||||
NodeT node_;
|
||||
std::vector<std::shared_ptr<IVariantChannel<Variant>>> in_channels_;
|
||||
std::vector<std::shared_ptr<IVariantChannel<Variant>>> out_channels_;
|
||||
std::vector<std::type_index> out_type_indices_;
|
||||
};
|
||||
|
||||
} // namespace kpn
|
||||
Reference in New Issue
Block a user