KPN/include/kpn/tmp/fanout_groups.hpp

358 lines
15 KiB
C++

#pragma once
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
// Metafunctions that scan a pack of Edge<> types, group edges by source port,
// auto-insert FanoutNode<T,N> where N>1, and produce an expanded edge list
// plus a tuple type for the owned fanout nodes.
//
// Key types produced:
// expanded_edges_t<Edges...> — type list of SimpleEdge after fanout insertion
// fanout_storage_t<Edges...> — std::tuple<FanoutNode<T0,N0>, ...> to own
namespace kpn::tmp {
// ── Type list ─────────────────────────────────────────────────────────────────
template<typename... Ts>
struct TypeList {};
template<typename List, typename T>
struct append;
template<typename... Ts, typename T>
struct append<TypeList<Ts...>, T> { using type = TypeList<Ts..., T>; };
template<typename List, typename T>
using append_t = typename append<List, T>::type;
template<typename A, typename B>
struct concat;
template<typename... As, typename... Bs>
struct concat<TypeList<As...>, TypeList<Bs...>> { using type = TypeList<As..., Bs...>; };
template<typename A, typename B>
using concat_t = typename concat<A, B>::type;
// ── Source port identity (used as compile-time map key) ───────────────────────
// Two edges share a source port when their SrcNode type AND SrcIdx are identical.
template<typename SrcNode, std::size_t SrcIdx>
struct SrcPort {};
template<typename Edge>
using src_port_of = SrcPort<typename Edge::src_node_t, Edge::src_idx>;
// ── Count occurrences of a SrcPort in an edge list ───────────────────────────
template<typename Port, typename EdgeList>
struct count_port;
template<typename Port>
struct count_port<Port, TypeList<>> : std::integral_constant<std::size_t, 0> {};
template<typename Port, typename Head, typename... Tail>
struct count_port<Port, TypeList<Head, Tail...>>
: std::integral_constant<std::size_t,
(std::is_same_v<Port, src_port_of<Head>> ? 1 : 0)
+ count_port<Port, TypeList<Tail...>>::value> {};
// ── Collect all destination (DstNode&, DstIdx) for a given SrcPort ────────────
// A destination descriptor — just type tags, no references (references go in
// the runtime SimpleEdge structs produced after wiring).
template<typename DstNode, std::size_t DstIdx>
struct DstDesc {};
template<typename Port, typename EdgeList>
struct collect_dsts;
template<typename Port>
struct collect_dsts<Port, TypeList<>> { using type = TypeList<>; };
template<typename Port, typename Head, typename... Tail>
struct collect_dsts<Port, TypeList<Head, Tail...>> {
using rest = typename collect_dsts<Port, TypeList<Tail...>>::type;
using type = std::conditional_t<
std::is_same_v<Port, src_port_of<Head>>,
append_t<rest, DstDesc<typename Head::dst_node_t, Head::dst_idx>>,
rest>;
};
// ── SimpleEdge: a resolved edge after fanout expansion ────────────────────────
//
// At runtime, StaticNetwork wires edges using SimpleEdge descriptors.
// Each SimpleEdge is just a pair of (SrcNode&, SrcIdx, DstNode&, DstIdx) stored
// as a type — the actual references come from the node tuple at wire time.
template<typename SrcNode, std::size_t SrcIdx,
typename DstNode, std::size_t DstIdx>
struct SimpleEdge {
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;
};
// ── FanoutPlaceholder: a fanout node that will be owned by StaticNetwork ───────
template<typename T, std::size_t N, std::size_t FanoutId>
struct FanoutPlaceholder {
using value_type = T;
static constexpr std::size_t fan_n = N;
static constexpr std::size_t fan_id = FanoutId;
};
// ── expand_edge: for one original edge, produce the replacement SimpleEdge(s) ──
//
// If the source port has N>1 consumers: the edge from src→fanout and the
// fanout→dst edges are synthesised elsewhere (see expand_all). Here we only
// need to emit the fanout→dst edge for this particular destination.
//
// For N==1 edges we emit the edge unchanged.
//
// This is called after the fanout node type has already been determined.
} // namespace kpn::tmp
namespace kpn {
// Forward declaration — FanoutNode is defined in fanout.hpp.
template<typename T, std::size_t N, std::size_t Id> class FanoutNode;
} // namespace kpn
namespace kpn::tmp {
// ── Master expansion: iterate edges, build expanded list + storage tuple ───────
//
// Strategy:
// 1. First pass: for each unique SrcPort with N>1, record a FanoutPlaceholder.
// 2. Second pass: rewrite each edge.
// - N==1 edges become a single SimpleEdge unchanged.
// - N>1 edges: on first encounter emit src→fanout SimpleEdge + N fanout→dst
// SimpleEdges; on subsequent encounters for the same src port emit nothing
// (already handled).
//
// To implement "first encounter" tracking we carry a list of already-processed
// SrcPorts through the fold.
template<typename ProcessedPorts, std::size_t NextFanoutId,
typename FanoutList, // TypeList<FanoutPlaceholder<...>>
typename EdgeList, // TypeList<SimpleEdge<...>> — accumulated output
typename RemainingEdges> // TypeList<original edges> still to process
struct expand_impl;
// Base case — no more edges
template<typename ProcessedPorts, std::size_t NextFanoutId,
typename FanoutList, typename EdgeList>
struct expand_impl<ProcessedPorts, NextFanoutId, FanoutList, EdgeList, TypeList<>> {
using fanouts = FanoutList;
using edges = EdgeList;
};
// Helper: is Port in ProcessedPorts?
template<typename Port, typename Processed>
struct already_processed : std::false_type {};
template<typename Port, typename Head, typename... Tail>
struct already_processed<Port, TypeList<Head, Tail...>>
: std::conditional_t<std::is_same_v<Port, Head>,
std::true_type,
already_processed<Port, TypeList<Tail...>>> {};
// Helper: given DstDesc list + FanoutPlaceholder id, produce SimpleEdge list
// FanoutNode<T,N>::output<I> → DstNode::input<DstIdx>
template<std::size_t FanoutId, typename T, std::size_t N,
typename DstDescList, std::size_t I = 0>
struct fanout_to_dst_edges;
template<std::size_t FanoutId, typename T, std::size_t N, std::size_t I>
struct fanout_to_dst_edges<FanoutId, T, N, TypeList<>, I> {
using type = TypeList<>;
};
template<std::size_t FanoutId, typename T, std::size_t N,
typename DstNodeT, std::size_t DstI, typename... DstTail, std::size_t I>
struct fanout_to_dst_edges<FanoutId, T, N,
TypeList<DstDesc<DstNodeT, DstI>, DstTail...>, I> {
using head_edge = SimpleEdge<kpn::FanoutNode<T, N, FanoutId>, I, DstNodeT, DstI>;
using rest = typename fanout_to_dst_edges<FanoutId, T, N,
TypeList<DstTail...>, I+1>::type;
using type = append_t<rest, head_edge>;
};
// Recursive case — process head edge
template<typename ProcessedPorts, std::size_t NextFanoutId,
typename FanoutList, typename EdgeList,
typename Head, typename... Tail>
struct expand_impl<ProcessedPorts, NextFanoutId, FanoutList, EdgeList,
TypeList<Head, Tail...>> {
using AllEdges = TypeList<Head, Tail...>;
using Port = src_port_of<Head>;
using SrcNode = typename Head::src_node_t;
using T = std::tuple_element_t<Head::src_idx,
typename SrcNode::return_tuple>;
static constexpr std::size_t N = count_port<Port, AllEdges>::value
+ count_port<Port, EdgeList>::value
// recount against full original list approximation:
// simpler: recount in remaining + already done
;
// Recount properly against the complete original edge list is not possible here
// without passing it along. Instead we pre-compute N before entering the fold.
// See expand_all below which pre-computes per-port counts.
//
// This struct is not used directly — expand_all drives the logic with pre-computed N.
};
// ── expand_all: top-level entry point ─────────────────────────────────────────
//
// Pre-computes per-source-port counts, then runs a fold that processes edges
// one by one.
// Port count map entry
template<typename Port, std::size_t Count>
struct PortCount {};
// Build port count list from full edge list
template<typename AllEdges, typename UniquePortsSeen>
struct build_port_counts;
template<typename AllEdges>
struct build_port_counts<AllEdges, TypeList<>> {
using type = TypeList<>;
};
template<typename AllEdges, typename HeadPort, typename... TailPorts>
struct build_port_counts<AllEdges, TypeList<HeadPort, TailPorts...>> {
static constexpr std::size_t cnt = count_port<HeadPort, AllEdges>::value;
using rest = typename build_port_counts<AllEdges, TypeList<TailPorts...>>::type;
using type = append_t<rest, PortCount<HeadPort, cnt>>;
};
// Collect unique source ports from edge list
template<typename EdgeList, typename SeenSoFar = TypeList<>>
struct unique_src_ports;
template<typename SeenSoFar>
struct unique_src_ports<TypeList<>, SeenSoFar> { using type = SeenSoFar; };
template<typename Head, typename... Tail, typename SeenSoFar>
struct unique_src_ports<TypeList<Head, Tail...>, SeenSoFar> {
using Port = src_port_of<Head>;
using next_seen = std::conditional_t<
already_processed<Port, SeenSoFar>::value,
SeenSoFar,
append_t<SeenSoFar, Port>>;
using type = typename unique_src_ports<TypeList<Tail...>, next_seen>::type;
};
// Look up count for a port
template<typename Port, typename CountList>
struct lookup_count : std::integral_constant<std::size_t, 1> {};
template<typename Port, std::size_t N, typename... Rest>
struct lookup_count<Port, TypeList<PortCount<Port, N>, Rest...>>
: std::integral_constant<std::size_t, N> {};
template<typename Port, typename Head, typename... Rest>
struct lookup_count<Port, TypeList<Head, Rest...>>
: lookup_count<Port, TypeList<Rest...>> {};
// Fold state for the wiring pass
template<typename ProcessedPorts, std::size_t NextFanoutId,
typename FanoutList, typename EdgeList>
struct FoldState {
using processed = ProcessedPorts;
static constexpr std::size_t next_id = NextFanoutId;
using fanouts = FanoutList;
using edges = EdgeList;
};
// Process one edge given pre-computed port counts
template<typename State, typename Edge, typename CountList, typename AllEdges>
struct process_edge {
using Port = src_port_of<Edge>;
using SrcNode = typename Edge::src_node_t;
using T = std::tuple_element_t<Edge::src_idx, typename SrcNode::return_tuple>;
static constexpr std::size_t N = lookup_count<Port, CountList>::value;
// N==1: pass through unchanged
using passthrough_edges = append_t<typename State::edges,
SimpleEdge<SrcNode, Edge::src_idx,
typename Edge::dst_node_t, Edge::dst_idx>>;
// N>1, first encounter: emit src→fanout + all fanout→dst edges
using DstDescs = typename collect_dsts<Port, AllEdges>::type;
static constexpr std::size_t fid = State::next_id;
using fanout_type = FanoutPlaceholder<T, N, fid>;
using src_to_fan = SimpleEdge<SrcNode, Edge::src_idx, FanoutNode<T, N, fid>, 0>;
using fan_to_dsts = typename fanout_to_dst_edges<fid, T, N, DstDescs>::type;
using fanout_edges = concat_t<append_t<typename State::edges, src_to_fan>, fan_to_dsts>;
using new_fanouts = append_t<typename State::fanouts, fanout_type>;
static constexpr bool seen = already_processed<Port, typename State::processed>::value;
using type = std::conditional_t<
(N == 1),
FoldState<typename State::processed, State::next_id,
typename State::fanouts, passthrough_edges>,
std::conditional_t<
!seen,
FoldState<append_t<typename State::processed, Port>,
State::next_id + 1,
new_fanouts, fanout_edges>,
// already processed — skip (fanout edges already emitted)
State>>;
};
// Fold over all edges
template<typename State, typename EdgeList, typename CountList, typename AllEdges>
struct fold_edges;
template<typename State, typename CountList, typename AllEdges>
struct fold_edges<State, TypeList<>, CountList, AllEdges> { using type = State; };
template<typename State, typename Head, typename... Tail,
typename CountList, typename AllEdges>
struct fold_edges<State, TypeList<Head, Tail...>, CountList, AllEdges> {
using next = typename process_edge<State, Head, CountList, AllEdges>::type;
using type = typename fold_edges<next, TypeList<Tail...>, CountList, AllEdges>::type;
};
// ── Public interface ───────────────────────────────────────────────────────────
template<typename... Edges>
struct expand_all {
using AllEdges = TypeList<Edges...>;
using UniquePorts = typename unique_src_ports<AllEdges>::type;
using CountList = typename build_port_counts<AllEdges, UniquePorts>::type;
using InitState = FoldState<TypeList<>, 0, TypeList<>, TypeList<>>;
using FinalState = typename fold_edges<InitState, AllEdges, CountList, AllEdges>::type;
using fanout_placeholders = typename FinalState::fanouts; // TypeList<FanoutPlaceholder<...>>
using expanded_edges = typename FinalState::edges; // TypeList<SimpleEdge<...>>
};
// Convert TypeList<FanoutPlaceholder<T,N,Id>...> to std::tuple<FanoutNode<T,N>...>
template<typename PlaceholderList>
struct to_fanout_tuple;
template<>
struct to_fanout_tuple<TypeList<>> { using type = std::tuple<>; };
template<typename T, std::size_t N, std::size_t Id, typename... Rest>
struct to_fanout_tuple<TypeList<FanoutPlaceholder<T, N, Id>, Rest...>> {
using rest = typename to_fanout_tuple<TypeList<Rest...>>::type;
template<typename Tuple> struct prepend;
template<typename... Ts> struct prepend<std::tuple<Ts...>> {
using type = std::tuple<kpn::FanoutNode<T, N, Id>, Ts...>;
};
using type = typename prepend<rest>::type;
};
template<typename... Edges>
using fanout_storage_t = typename to_fanout_tuple<
typename expand_all<Edges...>::fanout_placeholders>::type;
template<typename... Edges>
using expanded_edges_t = typename expand_all<Edges...>::expanded_edges;
} // namespace kpn::tmp