#pragma once #include #include #include #include // Metafunctions that scan a pack of Edge<> types, group edges by source port, // auto-insert FanoutNode where N>1, and produce an expanded edge list // plus a tuple type for the owned fanout nodes. // // Key types produced: // expanded_edges_t — type list of SimpleEdge after fanout insertion // fanout_storage_t — std::tuple, ...> to own namespace kpn::tmp { // ── Type list ───────────────────────────────────────────────────────────────── template struct TypeList {}; template struct append; template struct append, T> { using type = TypeList; }; template using append_t = typename append::type; template struct concat; template struct concat, TypeList> { using type = TypeList; }; template using concat_t = typename concat::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 struct SrcPort {}; template using src_port_of = SrcPort; // ── Count occurrences of a SrcPort in an edge list ─────────────────────────── template struct count_port; template struct count_port> : std::integral_constant {}; template struct count_port> : std::integral_constant> ? 1 : 0) + count_port>::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 struct DstDesc {}; template struct collect_dsts; template struct collect_dsts> { using type = TypeList<>; }; template struct collect_dsts> { using rest = typename collect_dsts>::type; using type = std::conditional_t< std::is_same_v>, append_t>, 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 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 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 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 EdgeList, // TypeList> — accumulated output typename RemainingEdges> // TypeList still to process struct expand_impl; // Base case — no more edges template struct expand_impl> { using fanouts = FanoutList; using edges = EdgeList; }; // Helper: is Port in ProcessedPorts? template struct already_processed : std::false_type {}; template struct already_processed> : std::conditional_t, std::true_type, already_processed>> {}; // Helper: given DstDesc list + FanoutPlaceholder id, produce SimpleEdge list // FanoutNode::output → DstNode::input template struct fanout_to_dst_edges; template struct fanout_to_dst_edges, I> { using type = TypeList<>; }; template struct fanout_to_dst_edges, DstTail...>, I> { using head_edge = SimpleEdge, I, DstNodeT, DstI>; using rest = typename fanout_to_dst_edges, I+1>::type; using type = append_t; }; // Recursive case — process head edge template struct expand_impl> { using AllEdges = TypeList; using Port = src_port_of; using SrcNode = typename Head::src_node_t; using T = std::tuple_element_t; static constexpr std::size_t N = count_port::value + count_port::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 struct PortCount {}; // Build port count list from full edge list template struct build_port_counts; template struct build_port_counts> { using type = TypeList<>; }; template struct build_port_counts> { static constexpr std::size_t cnt = count_port::value; using rest = typename build_port_counts>::type; using type = append_t>; }; // Collect unique source ports from edge list template> struct unique_src_ports; template struct unique_src_ports, SeenSoFar> { using type = SeenSoFar; }; template struct unique_src_ports, SeenSoFar> { using Port = src_port_of; using next_seen = std::conditional_t< already_processed::value, SeenSoFar, append_t>; using type = typename unique_src_ports, next_seen>::type; }; // Look up count for a port template struct lookup_count : std::integral_constant {}; template struct lookup_count, Rest...>> : std::integral_constant {}; template struct lookup_count> : lookup_count> {}; // Fold state for the wiring pass template 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 struct process_edge { using Port = src_port_of; using SrcNode = typename Edge::src_node_t; using T = std::tuple_element_t; static constexpr std::size_t N = lookup_count::value; // N==1: pass through unchanged using passthrough_edges = append_t>; // N>1, first encounter: emit src→fanout + all fanout→dst edges using DstDescs = typename collect_dsts::type; static constexpr std::size_t fid = State::next_id; using fanout_type = FanoutPlaceholder; using src_to_fan = SimpleEdge, 0>; using fan_to_dsts = typename fanout_to_dst_edges::type; using fanout_edges = concat_t, fan_to_dsts>; using new_fanouts = append_t; static constexpr bool seen = already_processed::value; using type = std::conditional_t< (N == 1), FoldState, std::conditional_t< !seen, FoldState, State::next_id + 1, new_fanouts, fanout_edges>, // already processed — skip (fanout edges already emitted) State>>; }; // Fold over all edges template struct fold_edges; template struct fold_edges, CountList, AllEdges> { using type = State; }; template struct fold_edges, CountList, AllEdges> { using next = typename process_edge::type; using type = typename fold_edges, CountList, AllEdges>::type; }; // ── Public interface ─────────────────────────────────────────────────────────── template struct expand_all { using AllEdges = TypeList; using UniquePorts = typename unique_src_ports::type; using CountList = typename build_port_counts::type; using InitState = FoldState, 0, TypeList<>, TypeList<>>; using FinalState = typename fold_edges::type; using fanout_placeholders = typename FinalState::fanouts; // TypeList> using expanded_edges = typename FinalState::edges; // TypeList> }; // Convert TypeList...> to std::tuple...> template struct to_fanout_tuple; template<> struct to_fanout_tuple> { using type = std::tuple<>; }; template struct to_fanout_tuple, Rest...>> { using rest = typename to_fanout_tuple>::type; // prepend FanoutNode template struct prepend; template struct prepend> { using type = std::tuple, Ts...>; }; using type = typename prepend::type; }; template using fanout_storage_t = typename to_fanout_tuple< typename expand_all::fanout_placeholders>::type; template using expanded_edges_t = typename expand_all::expanded_edges; } // namespace kpn::tmp