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:
@@ -217,6 +217,20 @@ public:
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Raw node handle by name — lets a binding dynamic_cast to a concrete wrapper
|
||||
// type and call its functor's runtime setters (persistent-pipeline reuse).
|
||||
VNode* node_ptr(const std::string& name) { return &node_at(name); }
|
||||
|
||||
// Per-node timing snapshot for profiling where a replay spends its time.
|
||||
std::map<std::string, double> node_stats(const std::string& name) {
|
||||
auto& n = node_at(name);
|
||||
NodeSnapshot s = n.node_snapshot(name, 0.0);
|
||||
return {{"frames", double(s.frames_processed)},
|
||||
{"exec_ms", s.ema_exec_ms}, {"max_ms", s.max_exec_ms},
|
||||
{"blocked_ms", s.total_blocked_ms}, {"fps", s.throughput_fps},
|
||||
{"cpu_ms", s.total_cpu_ms}, {"cpu_util_pct", s.cpu_util_pct}};
|
||||
}
|
||||
|
||||
private:
|
||||
VNode& node_at(const std::string& name) {
|
||||
auto it = nodes_.find(name);
|
||||
@@ -422,13 +436,17 @@ private:
|
||||
|
||||
for (std::size_t i = 0; i < out_channels_.size(); ++i) {
|
||||
if (out_channels_[i])
|
||||
out_channels_[i]->push(std::move(outputs[i]));
|
||||
// Lossless: wait for space rather than drop. A dropped frame
|
||||
// silently corrupts a replay's score; backpressure just slows
|
||||
// the producer. (Was push() + "drop on overflow".)
|
||||
out_channels_[i]->push_blocking(std::move(outputs[i]));
|
||||
}
|
||||
|
||||
} catch (const ChannelClosedError&) {
|
||||
break;
|
||||
} catch (const ChannelOverflowError&) {
|
||||
// drop and continue
|
||||
// no longer reachable with push_blocking, kept for safety
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -530,7 +548,8 @@ void register_py_network(nb::module_& m, const char* class_name = "Network") {
|
||||
.def("read", &Net::read,
|
||||
nb::arg("node"), nb::arg("out_idx") = std::size_t(0))
|
||||
.def("write", &Net::write,
|
||||
nb::arg("node"), nb::arg("in_idx"), nb::arg("value"));
|
||||
nb::arg("node"), nb::arg("in_idx"), nb::arg("value"))
|
||||
.def("node_stats", &Net::node_stats, nb::arg("node"));
|
||||
}
|
||||
|
||||
} // namespace kpn::python
|
||||
|
||||
Reference in New Issue
Block a user