238 lines
9.5 KiB
C++
238 lines
9.5 KiB
C++
#pragma once
|
|
#include "channel.hpp"
|
|
#include "diagnostics.hpp"
|
|
#include "fanout.hpp"
|
|
#include "node.hpp"
|
|
#include "port.hpp"
|
|
#include "tmp/fanout_groups.hpp"
|
|
#include "tmp/topo_sort.hpp"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace kpn {
|
|
|
|
// ── Edge descriptor ───────────────────────────────────────────────────────────
|
|
//
|
|
// Carries compile-time type info and runtime references to the two endpoints.
|
|
// Constructed by the edge() factory; consumed by make_network().
|
|
|
|
template<typename SrcNode, std::size_t SrcIdx,
|
|
typename DstNode, std::size_t DstIdx>
|
|
struct Edge {
|
|
using src_node_t = SrcNode;
|
|
using dst_node_t = DstNode;
|
|
static constexpr std::size_t src_idx = SrcIdx;
|
|
static constexpr std::size_t dst_idx = DstIdx;
|
|
|
|
SrcNode& src;
|
|
DstNode& dst;
|
|
};
|
|
|
|
template<typename SrcNode, std::size_t SrcIdx,
|
|
typename DstNode, std::size_t DstIdx>
|
|
auto edge(OutputPort<SrcNode, SrcIdx>, InputPort<DstNode, DstIdx> in)
|
|
-> Edge<SrcNode, SrcIdx, DstNode, DstIdx>; // deduction only; defined below
|
|
|
|
template<typename SrcNode, std::size_t SrcIdx,
|
|
typename DstNode, std::size_t DstIdx>
|
|
Edge<SrcNode, SrcIdx, DstNode, DstIdx>
|
|
edge(OutputPort<SrcNode, SrcIdx> out, InputPort<DstNode, DstIdx> in) {
|
|
return {out.node, in.node};
|
|
}
|
|
|
|
// ── StaticNetwork ─────────────────────────────────────────────────────────────
|
|
|
|
// node_label<NodeT>: returns Label NTTP as string_view if present, else empty.
|
|
template<typename NodeT, typename = void>
|
|
struct node_label_helper {
|
|
static constexpr std::string_view value = "";
|
|
};
|
|
template<typename NodeT>
|
|
struct node_label_helper<NodeT, std::void_t<decltype(NodeT::label())>> {
|
|
static constexpr std::string_view value = NodeT::label();
|
|
};
|
|
|
|
template<typename NodeT>
|
|
inline constexpr std::string_view node_label_v = node_label_helper<NodeT>::value;
|
|
|
|
// node_display_name<NodeT, UniqueTag>: Label if non-empty, else "node[UniqueTag]"
|
|
// Returned as std::string at runtime (called once at StaticNetwork construction).
|
|
template<typename NodeT>
|
|
std::string node_display_name() {
|
|
constexpr std::string_view lbl = node_label_v<NodeT>;
|
|
if constexpr (!lbl.empty()) {
|
|
return std::string(lbl);
|
|
} else if constexpr (requires { NodeT::unique_tag; }) {
|
|
return "node[" + std::to_string(NodeT::unique_tag) + "]";
|
|
} else {
|
|
return "node[?]";
|
|
}
|
|
}
|
|
|
|
template<typename FanoutStorage, typename TopoNodeList>
|
|
class StaticNetwork : public INode {
|
|
public:
|
|
// FanoutStorage = std::tuple<FanoutNode<T0,N0>, ...> (owned, heap-allocated)
|
|
// TopoNodeList = tmp::TypeList<NodeA, NodeB, ...> sources-first
|
|
|
|
StaticNetwork(std::unique_ptr<FanoutStorage> fanouts,
|
|
std::vector<INode*> user_nodes_topo,
|
|
std::vector<INode*> fanout_ptrs,
|
|
std::vector<std::string> user_node_names)
|
|
: fanouts_(std::move(fanouts))
|
|
, user_nodes_topo_(std::move(user_nodes_topo))
|
|
, fanout_nodes_ptr_(std::move(fanout_ptrs))
|
|
, user_node_names_(std::move(user_node_names))
|
|
{}
|
|
|
|
~StaticNetwork() override { stop(); }
|
|
|
|
void start() override {
|
|
stop_flag_ = false;
|
|
for (auto* n : user_nodes_topo_) n->start();
|
|
for (auto* n : fanout_nodes_ptr_) n->start();
|
|
}
|
|
|
|
void stop() override {
|
|
stop_flag_ = true;
|
|
for (auto it = fanout_nodes_ptr_.rbegin(); it != fanout_nodes_ptr_.rend(); ++it)
|
|
(*it)->stop();
|
|
for (auto it = user_nodes_topo_.rbegin(); it != user_nodes_topo_.rend(); ++it)
|
|
(*it)->stop();
|
|
}
|
|
|
|
bool running() const override { return !stop_flag_; }
|
|
|
|
void set_name(std::string name) override { name_ = std::move(name); }
|
|
const NodeStats& stats() const override { static NodeStats dummy; return dummy; }
|
|
NodeSnapshot node_snapshot(const std::string& n, double) const override {
|
|
return {n, 0, 0, 0, 0, 0, 0, 0};
|
|
}
|
|
|
|
// Print diagnostics using compile-time node labels
|
|
void print_diagnostics(std::ostream& os = std::cerr) const {
|
|
os << "\n┌─ KPN++ StaticNetwork diagnostics ─────────────────────────────\n";
|
|
for (std::size_t i = 0; i < user_nodes_topo_.size(); ++i) {
|
|
auto snap = user_nodes_topo_[i]->node_snapshot(user_node_names_[i], 0.0);
|
|
os << "│ " << snap.name
|
|
<< " frames=" << snap.frames_processed
|
|
<< " ema=" << snap.ema_exec_ms << "ms\n";
|
|
}
|
|
os << "└────────────────────────────────────────────────────────────────\n";
|
|
}
|
|
|
|
FanoutStorage& fanouts_storage() { return *fanouts_; }
|
|
|
|
private:
|
|
std::string name_;
|
|
bool stop_flag_{false};
|
|
std::unique_ptr<FanoutStorage> fanouts_;
|
|
std::vector<INode*> user_nodes_topo_;
|
|
std::vector<INode*> fanout_nodes_ptr_;
|
|
std::vector<std::string> user_node_names_; // parallel to user_nodes_topo_
|
|
};
|
|
|
|
// ── make_network ──────────────────────────────────────────────────────────────
|
|
|
|
template<typename... Edges>
|
|
auto make_network(Edges&&... edges) {
|
|
// 1. Expand edges — detect fan-outs, splice FanoutNodes
|
|
using FanoutSto = tmp::fanout_storage_t<std::decay_t<Edges>...>;
|
|
using ExpandedEdges = tmp::expanded_edges_t<std::decay_t<Edges>...>;
|
|
|
|
// 2. Duplicate-tag check — fires before cycle check for a cleaner error message
|
|
using UserNodes = typename tmp::all_node_types<tmp::TypeList<std::decay_t<Edges>...>>::type;
|
|
static_assert(!tmp::has_duplicate_tags_v<std::decay_t<Edges>...>,
|
|
"make_network: two nodes have the same (Func, UniqueTag) — they are "
|
|
"indistinguishable as graph vertices. Add a UniqueTag: "
|
|
"make_node<func, \"label\", 1>(capacity)");
|
|
|
|
// 3. Cycle check
|
|
using Topo = tmp::topo_sort<ExpandedEdges>;
|
|
constexpr bool has_cycle = Topo::has_cycle;
|
|
static_assert(!has_cycle,
|
|
"make_network: graph contains a directed cycle");
|
|
|
|
// 4. Construct owned fanout storage on the heap (FanoutNode has jthread — not moveable)
|
|
auto fanout_storage = std::make_unique<FanoutSto>();
|
|
|
|
// 5. Collect unique user node pointers + their display names, in edge-declaration order
|
|
std::vector<INode*> user_node_ptrs;
|
|
std::vector<std::string> user_node_names;
|
|
auto collect = [&](auto& e) {
|
|
using SrcT = std::decay_t<decltype(e.src)>;
|
|
using DstT = std::decay_t<decltype(e.dst)>;
|
|
auto* s = static_cast<INode*>(&e.src);
|
|
auto* d = static_cast<INode*>(&e.dst);
|
|
if (std::find(user_node_ptrs.begin(), user_node_ptrs.end(), s) == user_node_ptrs.end()) {
|
|
user_node_ptrs.push_back(s);
|
|
user_node_names.push_back(node_display_name<SrcT>());
|
|
}
|
|
if (std::find(user_node_ptrs.begin(), user_node_ptrs.end(), d) == user_node_ptrs.end()) {
|
|
user_node_ptrs.push_back(d);
|
|
user_node_names.push_back(node_display_name<DstT>());
|
|
}
|
|
};
|
|
(collect(edges), ...);
|
|
|
|
// 5. Wire all expanded SimpleEdges.
|
|
// find_node<NodeT>: searches fanout storage then user edge pack, returns NodeT*.
|
|
// Uses if constexpr in a fold so mismatched types never reach assignment.
|
|
auto find_node = [&]<typename NodeT>() -> NodeT* {
|
|
NodeT* ptr = nullptr;
|
|
std::apply([&](auto&... fn) {
|
|
([&](auto& node) {
|
|
if constexpr (std::is_same_v<std::decay_t<decltype(node)>, NodeT>)
|
|
if (!ptr) ptr = &node;
|
|
}(fn), ...);
|
|
}, *fanout_storage);
|
|
if (!ptr) {
|
|
([&](auto& e) {
|
|
if (!ptr) {
|
|
if constexpr (std::is_same_v<std::decay_t<decltype(e.src)>, NodeT>)
|
|
ptr = &e.src;
|
|
else if constexpr (std::is_same_v<std::decay_t<decltype(e.dst)>, NodeT>)
|
|
ptr = &e.dst;
|
|
}
|
|
}(edges), ...);
|
|
}
|
|
return ptr;
|
|
};
|
|
|
|
auto wire_one = [&]<typename SE>(SE) {
|
|
using SrcNode = typename SE::src_node_t;
|
|
using DstNode = typename SE::dst_node_t;
|
|
constexpr std::size_t SrcIdx = SE::src_idx;
|
|
constexpr std::size_t DstIdx = SE::dst_idx;
|
|
|
|
auto* src = find_node.template operator()<SrcNode>();
|
|
auto* dst = find_node.template operator()<DstNode>();
|
|
if (src && dst)
|
|
src->template set_output_channel<SrcIdx>(
|
|
&dst->template input_channel<DstIdx>());
|
|
};
|
|
|
|
[&]<typename... SEs>(tmp::TypeList<SEs...>) {
|
|
(wire_one(SEs{}), ...);
|
|
}(ExpandedEdges{});
|
|
|
|
// 6. Collect fanout node pointers
|
|
std::vector<INode*> fanout_ptrs;
|
|
std::apply([&](auto&... fn) {
|
|
(fanout_ptrs.push_back(static_cast<INode*>(&fn)), ...);
|
|
}, *fanout_storage);
|
|
|
|
// 7. Construct and return the StaticNetwork
|
|
using Net = StaticNetwork<FanoutSto, typename Topo::topo>;
|
|
return Net(std::move(fanout_storage),
|
|
std::move(user_node_ptrs),
|
|
std::move(fanout_ptrs),
|
|
std::move(user_node_names));
|
|
}
|
|
|
|
} // namespace kpn
|