224 lines
9.4 KiB
C++
224 lines
9.4 KiB
C++
#pragma once
|
|
#include "fanout_groups.hpp"
|
|
#include <cstddef>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
|
|
// Compile-time topological sort + cycle detection over a TypeList<SimpleEdge<...>>.
|
|
//
|
|
// Nodes are identified by type (not by index), since two different node types
|
|
// are always distinct vertices. The sort produces a TypeList of node types in
|
|
// topological order (sources first).
|
|
//
|
|
// cycle detection: if DFS revisits a node currently on the stack → static_assert.
|
|
|
|
namespace kpn::tmp {
|
|
|
|
// ── Collect all unique node types from an expanded edge list ──────────────────
|
|
|
|
template<typename EdgeList, typename Seen = TypeList<>>
|
|
struct all_node_types;
|
|
|
|
template<typename Seen>
|
|
struct all_node_types<TypeList<>, Seen> { using type = Seen; };
|
|
|
|
template<typename Head, typename... Tail, typename Seen>
|
|
struct all_node_types<TypeList<Head, Tail...>, Seen> {
|
|
using S1 = std::conditional_t<
|
|
already_processed<typename Head::src_node_t, Seen>::value,
|
|
Seen, append_t<Seen, typename Head::src_node_t>>;
|
|
using S2 = std::conditional_t<
|
|
already_processed<typename Head::dst_node_t, S1>::value,
|
|
S1, append_t<S1, typename Head::dst_node_t>>;
|
|
using type = typename all_node_types<TypeList<Tail...>, S2>::type;
|
|
};
|
|
|
|
// ── Collect successors (dst node types) of a given src node type ──────────────
|
|
|
|
template<typename NodeType, typename EdgeList>
|
|
struct successors;
|
|
|
|
template<typename NodeType>
|
|
struct successors<NodeType, TypeList<>> { using type = TypeList<>; };
|
|
|
|
template<typename NodeType, typename Head, typename... Tail>
|
|
struct successors<NodeType, TypeList<Head, Tail...>> {
|
|
using rest = typename successors<NodeType, TypeList<Tail...>>::type;
|
|
using type = std::conditional_t<
|
|
std::is_same_v<typename Head::src_node_t, NodeType>,
|
|
std::conditional_t<
|
|
already_processed<typename Head::dst_node_t, rest>::value,
|
|
rest,
|
|
append_t<rest, typename Head::dst_node_t>>,
|
|
rest>;
|
|
};
|
|
|
|
// ── DFS state ─────────────────────────────────────────────────────────────────
|
|
|
|
// Color: 0=white(unseen), 1=grey(on stack), 2=black(done)
|
|
// We track grey nodes as a TypeList to detect back-edges.
|
|
|
|
template<typename Node, typename GreyList>
|
|
struct is_grey : already_processed<Node, GreyList> {};
|
|
|
|
// DFS result: has_cycle flag + topo order (black nodes appended post-visit)
|
|
template<bool Cycle, typename TopoList>
|
|
struct DfsResult { static constexpr bool has_cycle = Cycle; using topo = TopoList; };
|
|
|
|
template<typename Node, typename EdgeList,
|
|
typename GreyList, typename BlackList, typename TopoList>
|
|
struct dfs_node;
|
|
|
|
// Iterate successors
|
|
template<typename SuccList, typename EdgeList,
|
|
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
|
|
struct dfs_successors;
|
|
|
|
template<typename EdgeList, typename GreyList, typename BlackList,
|
|
typename TopoList, bool CycleSoFar>
|
|
struct dfs_successors<TypeList<>, EdgeList, GreyList, BlackList, TopoList, CycleSoFar> {
|
|
static constexpr bool has_cycle = CycleSoFar;
|
|
using black = BlackList;
|
|
using topo = TopoList;
|
|
};
|
|
|
|
template<typename Head, typename... Tail, typename EdgeList,
|
|
typename GreyList, typename BlackList, typename TopoList, bool CycleSoFar>
|
|
struct dfs_successors<TypeList<Head, Tail...>, EdgeList,
|
|
GreyList, BlackList, TopoList, CycleSoFar> {
|
|
// Visit Head
|
|
using visit = dfs_node<Head, EdgeList, GreyList, BlackList, TopoList>;
|
|
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
|
|
// Continue with remaining successors using updated black/topo
|
|
using rest = dfs_successors<TypeList<Tail...>, EdgeList,
|
|
GreyList, typename visit::black,
|
|
typename visit::topo, cycle1>;
|
|
static constexpr bool has_cycle = rest::has_cycle;
|
|
using black = typename rest::black;
|
|
using topo = typename rest::topo;
|
|
};
|
|
|
|
template<typename Node, typename EdgeList,
|
|
typename GreyList, typename BlackList, typename TopoList>
|
|
struct dfs_node {
|
|
// Already black — skip
|
|
static constexpr bool already_done = already_processed<Node, BlackList>::value;
|
|
// On stack — cycle
|
|
static constexpr bool on_stack = is_grey<Node, GreyList>::value;
|
|
|
|
// Visit successors (only if not already done / on stack)
|
|
using succs = typename successors<Node, EdgeList>::type;
|
|
using new_grey = append_t<GreyList, Node>;
|
|
|
|
using visit_succs = dfs_successors<succs, EdgeList, new_grey, BlackList, TopoList,
|
|
on_stack>;
|
|
|
|
static constexpr bool has_cycle = already_done ? false :
|
|
on_stack ? true :
|
|
visit_succs::has_cycle;
|
|
|
|
// Append node to topo after all successors (post-order = reverse topo)
|
|
using topo_after = std::conditional_t<
|
|
already_done || on_stack,
|
|
TopoList,
|
|
append_t<typename visit_succs::topo, Node>>;
|
|
|
|
using black = std::conditional_t<
|
|
already_done || on_stack,
|
|
BlackList,
|
|
append_t<typename visit_succs::black, Node>>;
|
|
|
|
using topo = topo_after;
|
|
};
|
|
|
|
// ── Top-level DFS over all nodes ──────────────────────────────────────────────
|
|
|
|
template<typename NodeList, typename EdgeList,
|
|
typename BlackList, typename TopoList, bool CycleSoFar>
|
|
struct dfs_all;
|
|
|
|
template<typename EdgeList, typename BlackList, typename TopoList, bool CycleSoFar>
|
|
struct dfs_all<TypeList<>, EdgeList, BlackList, TopoList, CycleSoFar> {
|
|
static constexpr bool has_cycle = CycleSoFar;
|
|
using topo = TopoList;
|
|
};
|
|
|
|
template<typename Head, typename... Tail, typename EdgeList,
|
|
typename BlackList, typename TopoList, bool CycleSoFar>
|
|
struct dfs_all<TypeList<Head, Tail...>, EdgeList, BlackList, TopoList, CycleSoFar> {
|
|
using visit = dfs_node<Head, EdgeList, TypeList<>, BlackList, TopoList>;
|
|
static constexpr bool cycle1 = CycleSoFar || visit::has_cycle;
|
|
using rest = dfs_all<TypeList<Tail...>, EdgeList,
|
|
typename visit::black, typename visit::topo, cycle1>;
|
|
static constexpr bool has_cycle = rest::has_cycle;
|
|
using topo = typename rest::topo;
|
|
};
|
|
|
|
// ── Public interface ───────────────────────────────────────────────────────────
|
|
|
|
// topo_sort<EdgeList>:
|
|
// ::topo — TypeList of node types, sources first (start order)
|
|
// ::has_cycle — true if a cycle was detected
|
|
template<typename EdgeList>
|
|
struct topo_sort {
|
|
using Nodes = typename all_node_types<EdgeList>::type;
|
|
using result = dfs_all<Nodes, EdgeList, TypeList<>, TypeList<>, false>;
|
|
// DFS post-order gives reverse topo; reverse the list for sources-first order.
|
|
// Reversing a TypeList:
|
|
template<typename List, typename Acc = TypeList<>>
|
|
struct reverse_list;
|
|
template<typename Acc>
|
|
struct reverse_list<TypeList<>, Acc> { using type = Acc; };
|
|
template<typename H, typename... T, typename Acc>
|
|
struct reverse_list<TypeList<H, T...>, Acc>
|
|
: reverse_list<TypeList<T...>, append_t<Acc, H>> {}; // wrong direction intentionally
|
|
|
|
// Post-order appends children before parent, so the list is already
|
|
// reverse-topo (sinks first). Reverse it to get sources first.
|
|
template<typename List, typename Acc = TypeList<>>
|
|
struct rev;
|
|
template<typename Acc>
|
|
struct rev<TypeList<>, Acc> { using type = Acc; };
|
|
template<typename H, typename... T, typename Acc>
|
|
struct rev<TypeList<H, T...>, Acc> : rev<TypeList<T...>, TypeList<H, Acc>> {
|
|
// prepend H to Acc: TypeList<H, Acc...>
|
|
};
|
|
// Simpler: just reverse by prepending
|
|
template<typename L, typename A = TypeList<>>
|
|
struct rev2 { using type = A; };
|
|
template<typename H, typename... T, typename... As>
|
|
struct rev2<TypeList<H, T...>, TypeList<As...>>
|
|
: rev2<TypeList<T...>, TypeList<H, As...>> {};
|
|
|
|
static constexpr bool has_cycle = result::has_cycle;
|
|
using topo = typename rev2<typename result::topo>::type;
|
|
};
|
|
|
|
// ── Duplicate-tag detection ───────────────────────────────────────────────────
|
|
//
|
|
// Two user nodes share a (Func, UniqueTag) pair iff they have the same type.
|
|
// has_duplicate_tags_v<Edges...> is true if any two user node types are identical.
|
|
|
|
template<typename T, typename List>
|
|
struct type_in_list : std::false_type {};
|
|
template<typename T, typename H, typename... Tail>
|
|
struct type_in_list<T, TypeList<H, Tail...>>
|
|
: std::conditional_t<std::is_same_v<T, H>,
|
|
std::true_type,
|
|
type_in_list<T, TypeList<Tail...>>> {};
|
|
|
|
template<typename NodeList>
|
|
struct has_duplicate_node_types : std::false_type {};
|
|
template<typename H, typename... Tail>
|
|
struct has_duplicate_node_types<TypeList<H, Tail...>>
|
|
: std::conditional_t<type_in_list<H, TypeList<Tail...>>::value,
|
|
std::true_type,
|
|
has_duplicate_node_types<TypeList<Tail...>>> {};
|
|
|
|
template<typename... Edges>
|
|
inline constexpr bool has_duplicate_tags_v =
|
|
has_duplicate_node_types<
|
|
typename all_node_types<TypeList<Edges...>>::type>::value;
|
|
|
|
} // namespace kpn::tmp
|