@@ -0,0 +1,223 @@
|
||||
#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
|
||||
Reference in New Issue
Block a user