diff --git a/scratchpad/dag_man.c b/scratchpad/dag_man.c index cd352f2..af6b478 100644 --- a/scratchpad/dag_man.c +++ b/scratchpad/dag_man.c @@ -46,7 +46,7 @@ wp_intern void prPoolFree(PrPool *pool, void *slot) { } /* --------------------------------------------------------------------------- - * Graph + * Shared types * -------------------------------------------------------------------------*/ #define INVALID_NODE_INDEX (u64)-1 @@ -80,80 +80,271 @@ typedef struct { typedef PrNode *PrNodeArray; /* --------------------------------------------------------------------------- - * Graph Edge/Vertex type - * If source or target are INVALID_NODE_ID, it should be treated as a vertex. - * Otherwise, it should be treated as an edge. Think of it like homogeneous - * coordinates where a vector {0, 0, 0, 0} is treated as a direction, while - * a vector {0, 0, 0, 1} is treated as a point - * ---------------------------------------------------------------------------*/ -typedef struct PrEdgeVertex PrEdgeVertex; -struct PrEdgeVertex { - PrEdgeVertex *next_forward; - PrEdgeVertex *next_backward; - PrNodeId source; - PrNodeId target; + * Graph edge type + * -------------------------------------------------------------------------*/ + +typedef struct PrGraphEdge PrGraphEdge; +struct PrGraphEdge { + PrGraphEdge *next_forward; + PrGraphEdge *next_backward; + PrNodeId source; + PrNodeId target; }; -typedef PrEdgeVertex *PrEdgeVertexArray; +typedef PrGraphEdge *PrGraphEdgeArray; + +/* --------------------------------------------------------------------------- + * Graph node — compact adjacency head (two pointers, no dead fields) + * -------------------------------------------------------------------------*/ + +typedef struct PrGraphNode PrGraphNode; +struct PrGraphNode { + PrGraphEdge *next_forward; + PrGraphEdge *next_backward; +}; +typedef PrGraphNode *PrGraphNodeArray; + +/* --------------------------------------------------------------------------- + * PrGraph — owns only topology (edges + adjacency heads) + * -------------------------------------------------------------------------*/ typedef struct { - PrPool vertex_pool; - PrNodeArray nodes; - PrEdgeVertexArray vertices; - u64 capacity; - u64 max_count_ever; - u64 count; - u64 free_head; + PrPool edge_pool; + PrGraphNodeArray graph_nodes; + u64 capacity; } PrGraph; -wp_intern b8 prGraphIsStaleId(const PrGraph *graph, PrNodeId id) { - u64 generation = graph->nodes[id.index].generation; +/* --------------------------------------------------------------------------- + * PrNodeManager — owns compositor node data + handle lifecycle + topology + * -------------------------------------------------------------------------*/ + +typedef struct { + PrNodeArray nodes; + PrGraph graph; + u64 capacity; + u64 max_count_ever; + u64 count; + u64 free_head; +} PrNodeManager; + +/* --------------------------------------------------------------------------- + * Function declarations (cross-referencing both types) + * -------------------------------------------------------------------------*/ + +wp_intern void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity); +wp_intern b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id); +wp_intern b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id); +wp_intern PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index); +wp_intern PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type); +wp_intern void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id); +wp_intern void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to); + +wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity); +wp_intern void prGraphAddEdge(PrNodeManager *mgr, PrGraph *graph, u64 from_idx, u64 to_idx); +wp_intern void prGraphRemoveEdges(PrGraph *graph, u64 idx); +wp_intern PrNodeIdArray prGraphTopologicalSort(const PrNodeManager *mgr, const PrGraph *graph, + const WpAllocator *allocator); +wp_intern void prGraphDump(const PrNodeManager *mgr, const PrGraph *graph); + +/* --------------------------------------------------------------------------- + * PrNodeManager implementation + * -------------------------------------------------------------------------*/ + +wp_intern void prNodeManagerInit(PrNodeManager *mgr, WpAllocator *allocator, u64 capacity) { + mgr->nodes = wpArrayAllocCapacity(PrNode, allocator, capacity, WP_ARRAY_INIT_FILLED); + mgr->free_head = 0; + mgr->capacity = capacity; + mgr->max_count_ever = 0; + mgr->count = 0; + + if (!mgr->nodes) { + mgr->capacity = 0; + return; + } + + memset(mgr->nodes, 0, capacity * sizeof(PrNode)); + + for (u64 i = 0; i < capacity; ++i) { + mgr->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX; + } + + prGraphInit(&mgr->graph, allocator, capacity); +} + +wp_intern b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id) { + u64 generation = mgr->nodes[id.index].generation; return id.generation != generation; } -wp_intern b8 prGraphIsActiveNode(const PrGraph *graph, PrNodeId id) { - u64 next_free = graph->nodes[id.index].next_free; - return !prGraphIsStaleId(graph, id) && next_free == INVALID_NODE_INDEX; +wp_intern b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id) { + u64 next_free = mgr->nodes[id.index].next_free; + return !prNodeManagerIsStaleNode(mgr, id) && next_free == INVALID_NODE_INDEX; } -wp_intern PrNodeId prGraphGetNode(const PrGraph *graph, u64 index) { - return (PrNodeId){ .index = index, .generation = graph->nodes[index].generation }; +wp_intern PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index) { + return (PrNodeId){ .index = index, .generation = mgr->nodes[index].generation }; } +wp_intern PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type) { + u64 idx = mgr->free_head; + if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; } + + PrNode *node = &mgr->nodes[idx]; + + mgr->free_head = node->next_free; + node->next_free = INVALID_NODE_INDEX; + node->type = type; + memset(&node->params, 0, sizeof(node->params)); + + mgr->count++; + + if (idx + 1 > mgr->max_count_ever) { mgr->max_count_ever = idx + 1; } + + return (PrNodeId){ .index = idx, .generation = node->generation }; +} + +wp_intern void prNodeManagerRemoveNode(PrNodeManager *mgr, PrNodeId id) { + if (!prNodeManagerIsActiveNode(mgr, id)) { return; } + + /* Tear down all edges incident to this node */ + prGraphRemoveEdges(&mgr->graph, id.index); + + /* Return node slot to free list with bumped generation */ + PrNode *node = &mgr->nodes[id.index]; + node->generation++; + node->next_free = mgr->free_head; + mgr->free_head = id.index; + mgr->count--; +} + +wp_intern void prNodeManagerAddEdge(PrNodeManager *mgr, PrNodeId from, PrNodeId to) { + if (!prNodeManagerIsActiveNode(mgr, from) || !prNodeManagerIsActiveNode(mgr, to)) { return; } + if (from.index == to.index) { return; } + prGraphAddEdge(mgr, &mgr->graph, from.index, to.index); +} + +/* --------------------------------------------------------------------------- + * PrGraph implementation + * -------------------------------------------------------------------------*/ + wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity) { - graph->nodes = wpArrayAllocCapacity(PrNode, allocator, capacity, WP_ARRAY_INIT_FILLED); - graph->vertices = wpArrayAllocCapacity(PrEdgeVertex, allocator, capacity, WP_ARRAY_INIT_FILLED); - graph->free_head = 0; - graph->capacity = capacity; - graph->max_count_ever = 0; - graph->count = 0; + graph->graph_nodes = wpArrayAllocCapacity(PrGraphNode, allocator, capacity, WP_ARRAY_INIT_FILLED); + graph->capacity = capacity; - if (!graph->nodes || !graph->vertices) { + if (!graph->graph_nodes) { graph->capacity = 0; return; } - prPoolInit(&graph->vertex_pool, allocator, sizeof(PrEdgeVertex)); - memset(graph->nodes, 0, capacity * sizeof(PrNode)); - memset(graph->vertices, 0, capacity * sizeof(PrEdgeVertex)); + prPoolInit(&graph->edge_pool, allocator, sizeof(PrGraphEdge)); + memset(graph->graph_nodes, 0, capacity * sizeof(PrGraphNode)); +} - for (u64 i = 0; i < capacity; ++i) { - graph->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX; - graph->vertices[i].source = graph->vertices[i].target = INVALID_NODE_ID; +/* --- internal: unlink edge helpers (raw indices, caller guarantees validity) -- */ + +wp_intern void _unlinkForward(PrGraph *graph, u64 from_idx, PrGraphEdge *edge) { + PrGraphNode *gnode = &graph->graph_nodes[from_idx]; + PrGraphEdge *curr = gnode->next_forward; + PrGraphEdge *prev = NULL; + while (curr) { + if (curr == edge) { + if (prev) { + prev->next_forward = curr->next_forward; + } else { + gnode->next_forward = curr->next_forward; + } + return; + } + prev = curr; + curr = curr->next_forward; } } +wp_intern void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge) { + PrGraphNode *gnode = &graph->graph_nodes[to_idx]; + PrGraphEdge *curr = gnode->next_backward; + PrGraphEdge *prev = NULL; + while (curr) { + if (curr == edge) { + if (prev) { + prev->next_backward = curr->next_backward; + } else { + gnode->next_backward = curr->next_backward; + } + return; + } + prev = curr; + curr = curr->next_backward; + } +} + +/* --- edge management -------------------------------------------------- */ + +wp_intern void prGraphAddEdge(PrNodeManager *mgr, PrGraph *graph, u64 from_idx, u64 to_idx) { + PrGraphEdge *edge = (PrGraphEdge *)prPoolAlloc(&graph->edge_pool); + if (!edge) { return; } + + PrNodeId from_id = prNodeManagerGetNode(mgr, from_idx); + PrNodeId to_id = prNodeManagerGetNode(mgr, to_idx); + + edge->source = from_id; + edge->target = to_id; + + /* Link into adjacency chains */ + PrGraphNode *src = &graph->graph_nodes[from_idx]; + PrGraphNode *dst = &graph->graph_nodes[to_idx]; + edge->next_forward = src->next_forward; + edge->next_backward = dst->next_backward; + src->next_forward = edge; + dst->next_backward = edge; + + /* Check whether the new edge created a cycle */ + WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16)); + PrNodeIdArray sorted = prGraphTopologicalSort(mgr, graph, &scratch); + u64 sorted_cnt = sorted ? wpArrayCount(sorted) : 0; + if (sorted_cnt < mgr->count) { + _unlinkForward(graph, from_idx, edge); + _unlinkBackward(graph, to_idx, edge); + prPoolFree(&graph->edge_pool, edge); + } +} + +wp_intern void prGraphRemoveEdges(PrGraph *graph, u64 idx) { + PrGraphNode *gnode = &graph->graph_nodes[idx]; + + /* Free outgoing edges: unlink from each target's backward list */ + PrGraphEdge *curr = gnode->next_forward; + while (curr) { + PrGraphEdge *next = curr->next_forward; + _unlinkBackward(graph, curr->target.index, curr); + prPoolFree(&graph->edge_pool, curr); + curr = next; + } + + /* Free incoming edges: unlink from each source's forward list */ + curr = gnode->next_backward; + while (curr) { + PrGraphEdge *next = curr->next_backward; + _unlinkForward(graph, curr->source.index, curr); + prPoolFree(&graph->edge_pool, curr); + curr = next; + } + + gnode->next_forward = gnode->next_backward = NULL; +} + /* --------------------------------------------------------------------------- * Kahn's algorithm — topological sort / cycle detection * * Returns a WpArray of PrNodeId (sorted topologically). If the result count - * is less than graph->count, the graph contains a cycle. + * is less than mgr->count, the graph contains a cycle. * -------------------------------------------------------------------------*/ -wp_intern PrNodeId *prGraphTopologicalSort(PrGraph *graph, const WpAllocator *allocator) { - if (graph->count == 0) { return NULL; } - if (!graph->nodes || graph->capacity == 0) { return NULL; } +wp_intern PrNodeIdArray prGraphTopologicalSort(const PrNodeManager *mgr, const PrGraph *graph, + const WpAllocator *allocator) { + if (mgr->count == 0) { return NULL; } + if (!mgr->nodes || graph->capacity == 0) { return NULL; } - PrNodeIdArray result = wpArrayAllocCapacity(PrNodeId, allocator, graph->count, WP_ARRAY_INIT_NONE); + PrNodeIdArray result = wpArrayAllocCapacity(PrNodeId, allocator, mgr->count, WP_ARRAY_INIT_NONE); if (!result) { return NULL; } WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16)); @@ -162,23 +353,23 @@ wp_intern PrNodeId *prGraphTopologicalSort(PrGraph *graph, const WpAllocator *al if (!in_degree) { return result; } memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64)); - for (u64 i = 0; i < graph->max_count_ever; i++) { - PrNodeId id = prGraphGetNode(graph, i); - if (!prGraphIsActiveNode(graph, id)) { continue; } + for (u64 i = 0; i < mgr->max_count_ever; i++) { + PrNodeId id = prNodeManagerGetNode(mgr, i); + if (!prNodeManagerIsActiveNode(mgr, id)) { continue; } - PrEdgeVertex *vertex = &graph->vertices[i]; - PrEdgeVertex *curr = vertex->next_forward; + PrGraphNode *gnode = &graph->graph_nodes[i]; + PrGraphEdge *curr = gnode->next_forward; while (curr) { in_degree[curr->target.index]++; curr = curr->next_forward; } } - WpQueue queue = wpQueueAlloc(u64, &local_arena, graph->count); + WpQueue queue = wpQueueAlloc(u64, &local_arena, mgr->count); - for (u64 i = 0; i < graph->max_count_ever; i++) { - PrNodeId id = prGraphGetNode(graph, i); - if (!prGraphIsActiveNode(graph, id)) { continue; } + for (u64 i = 0; i < mgr->max_count_ever; i++) { + PrNodeId id = prNodeManagerGetNode(mgr, i); + if (!prNodeManagerIsActiveNode(mgr, id)) { continue; } if (in_degree[i] == 0) { wpQueuePush(u64, &queue, &i); } @@ -188,11 +379,11 @@ wp_intern PrNodeId *prGraphTopologicalSort(PrGraph *graph, const WpAllocator *al u64 *node_idx = wpQueuePop(u64, &queue); if (!node_idx) { break; } - PrNodeId id = prGraphGetNode(graph, *node_idx); + PrNodeId id = prNodeManagerGetNode(mgr, *node_idx); wpArrayAppendCapped(PrNodeId, result, &id); - PrEdgeVertex *vertex = &graph->vertices[*node_idx]; - PrEdgeVertex *curr = vertex->next_forward; + PrGraphNode *gnode = &graph->graph_nodes[*node_idx]; + PrGraphEdge *curr = gnode->next_forward; while (curr) { u64 target_idx = curr->target.index; if (in_degree[target_idx] > 0) { @@ -208,151 +399,24 @@ wp_intern PrNodeId *prGraphTopologicalSort(PrGraph *graph, const WpAllocator *al return result; } -/* --- edge management -------------------------------------------------- */ +/* --------------------------------------------------------------------------- + * Dump + * -------------------------------------------------------------------------*/ -wp_intern void _unlinkForward(PrGraph *graph, PrNodeId from, PrEdgeVertex *edge) { - if (!prGraphIsActiveNode(graph, from)) { return; } - - PrEdgeVertex *src = &graph->vertices[from.index]; - PrEdgeVertex *curr = src->next_forward; - PrEdgeVertex *prev = NULL; - while (curr) { - if (curr == edge) { - if (prev) { - prev->next_forward = curr->next_forward; - } else { - src->next_forward = curr->next_forward; - } - return; - } - - prev = curr; - curr = curr->next_forward; - } -} - -wp_intern void _unlinkBackward(PrGraph *graph, PrNodeId to, PrEdgeVertex *edge) { - if (!prGraphIsActiveNode(graph, to)) { return; } - - PrEdgeVertex *dst = &graph->vertices[to.index]; - PrEdgeVertex *curr = dst->next_backward; - PrEdgeVertex *prev = NULL; - while (curr) { - if (curr == edge) { - if (prev) { - prev->next_backward = curr->next_backward; - } else { - dst->next_backward = curr->next_backward; - } - return; - } - - prev = curr; - curr = curr->next_backward; - } -} - -wp_intern void prGraphAddEdge(PrGraph *graph, PrNodeId from, PrNodeId to) { - if (!prGraphIsActiveNode(graph, from) || !prGraphIsActiveNode(graph, to)) { return; } - if (from.index == to.index) { return; } - - PrEdgeVertex *edge = (PrEdgeVertex *)prPoolAlloc(&graph->vertex_pool); - if (!edge) { return; } - - edge->source = from; - edge->target = to; - - /* Link into adjacency chains */ - PrEdgeVertex *src = &graph->vertices[from.index]; - PrEdgeVertex *dst = &graph->vertices[to.index]; - edge->next_forward = src->next_forward; - edge->next_backward = dst->next_backward; - src->next_forward = edge; - dst->next_backward = edge; - - /* Check whether the new edge created a cycle */ - WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16)); - PrNodeIdArray sorted = prGraphTopologicalSort(graph, &scratch); - u64 sorted_count = sorted ? wpArrayCount(sorted) : 0; - if (sorted_count < graph->count) { - _unlinkForward(graph, from, edge); - _unlinkBackward(graph, to, edge); - prPoolFree(&graph->vertex_pool, edge); - } - - wpMemArenaAllocatorDestroy(&scratch); -} - -wp_intern PrNodeId prGraphAddNode(PrGraph *graph, PrNodeType type) { - u64 idx = graph->free_head; - if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; } - - PrNode *node = &graph->nodes[idx]; - - graph->free_head = node->next_free; - node->next_free = INVALID_NODE_INDEX; - node->type = type; - memset(&node->params, 0, sizeof(node->params)); - - graph->count++; - - if (idx + 1 > graph->max_count_ever) { graph->max_count_ever = idx + 1; } - - return (PrNodeId){ .index = idx, .generation = node->generation }; -} - -wp_intern void prGraphRemoveNode(PrGraph *graph, PrNodeId id) { - if (!prGraphIsActiveNode(graph, id)) { return; } - - PrNode *node = &graph->nodes[id.index]; - u64 next_gen = node->generation + 1; - memset(node, 0, sizeof(PrNode)); - - node->generation = next_gen; - node->next_free = graph->free_head; - graph->free_head = id.index; - - PrEdgeVertex *vertex = &graph->vertices[id.index]; - if (!(vertex->next_forward) && !(vertex->next_backward)) { - goto REMOVE_NODE_UNSET_POINTERS; - } - - PrEdgeVertex *curr = vertex->next_forward; - while (curr) { - PrEdgeVertex *next = curr->next_forward; - _unlinkBackward(graph, curr->target, curr); - prPoolFree(&graph->vertex_pool, curr); - curr = next; - } - - curr = vertex->next_backward; - while (curr) { - PrEdgeVertex *trailing = curr->next_backward; - _unlinkForward(graph, curr->source, curr); - prPoolFree(&graph->vertex_pool, curr); - curr = trailing; - } - -REMOVE_NODE_UNSET_POINTERS: - vertex->next_forward = vertex->next_backward = NULL; - - graph->count--; -} - -wp_intern void prGraphDump(const PrGraph *graph) { +wp_intern void prGraphDump(const PrNodeManager *mgr, const PrGraph *graph) { printf("==============INPUTS==============\n"); - for (u64 i = 0; i < graph->max_count_ever; ++i) { - PrNodeId id = prGraphGetNode(graph, i); - if (!prGraphIsActiveNode(graph, id)) { continue; } + for (u64 i = 0; i < mgr->max_count_ever; ++i) { + PrNodeId id = prNodeManagerGetNode(mgr, i); + if (!prNodeManagerIsActiveNode(mgr, id)) { continue; } - PrEdgeVertex *vertex = &graph->vertices[i]; + PrGraphNode *gnode = &graph->graph_nodes[i]; printf("%" PRIu64 ":", id.index + 1); - if (!(vertex->next_backward)) { + if (!(gnode->next_backward)) { printf(" (none)"); } else { - PrEdgeVertex *curr = vertex->next_backward; + PrGraphEdge *curr = gnode->next_backward; while (curr) { printf(" %" PRIu64, curr->source.index + 1); curr = curr->next_backward; @@ -363,18 +427,18 @@ wp_intern void prGraphDump(const PrGraph *graph) { } printf("==============OUTPUTS==============\n"); - for (u64 i = 0; i < graph->max_count_ever; ++i) { - PrNodeId id = prGraphGetNode(graph, i); - if (!prGraphIsActiveNode(graph, id)) { continue; } + for (u64 i = 0; i < mgr->max_count_ever; ++i) { + PrNodeId id = prNodeManagerGetNode(mgr, i); + if (!prNodeManagerIsActiveNode(mgr, id)) { continue; } - PrEdgeVertex *vertex = &graph->vertices[i]; + PrGraphNode *gnode = &graph->graph_nodes[i]; printf("%" PRIu64 ":", id.index + 1); - if (!(vertex->next_forward)) { + if (!(gnode->next_forward)) { printf(" (none)"); } else { - PrEdgeVertex *curr = vertex->next_forward; + PrGraphEdge *curr = gnode->next_forward; while (curr) { printf(" %" PRIu64, curr->target.index + 1); curr = curr->next_forward; @@ -385,6 +449,10 @@ wp_intern void prGraphDump(const PrGraph *graph) { } } +/* --------------------------------------------------------------------------- + * Main + * -------------------------------------------------------------------------*/ + i32 main(void) { WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16)); if (wpMemAllocatorInvalid(&arena)) { @@ -392,75 +460,75 @@ i32 main(void) { return 1; } - PrGraph graph = {0}; - prGraphInit(&graph, &arena, 128); + PrNodeManager mgr = {0}; + prNodeManagerInit(&mgr, &arena, 128); - PrNodeId n1 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); - PrNodeId n2 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); - PrNodeId n3 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); - PrNodeId n4 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); - PrNodeId n5 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + PrNodeId n1 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); + PrNodeId n2 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); + PrNodeId n3 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); + PrNodeId n4 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); + PrNodeId n5 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); - prGraphAddEdge(&graph, n1, n2); - prGraphAddEdge(&graph, n1, n3); - prGraphAddEdge(&graph, n1, n5); - prGraphAddEdge(&graph, n2, n4); - prGraphAddEdge(&graph, n3, n4); - prGraphAddEdge(&graph, n3, n5); + prNodeManagerAddEdge(&mgr, n1, n2); + prNodeManagerAddEdge(&mgr, n1, n3); + prNodeManagerAddEdge(&mgr, n1, n5); + prNodeManagerAddEdge(&mgr, n2, n4); + prNodeManagerAddEdge(&mgr, n3, n4); + prNodeManagerAddEdge(&mgr, n3, n5); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); - prGraphRemoveNode(&graph, n3); + prNodeManagerRemoveNode(&mgr, n3); printf("\n"); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); - PrNodeId n6 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + PrNodeId n6 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); - prGraphAddEdge(&graph, n4, n6); + prNodeManagerAddEdge(&mgr, n4, n6); printf("\n"); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); - prGraphRemoveNode(&graph, n5); + prNodeManagerRemoveNode(&mgr, n5); printf("\n"); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); - PrNodeId n7 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + PrNodeId n7 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); - prGraphAddEdge(&graph, n1, n7); - prGraphAddEdge(&graph, n2, n7); + prNodeManagerAddEdge(&mgr, n1, n7); + prNodeManagerAddEdge(&mgr, n2, n7); printf("\n"); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); - PrNodeId n8 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + PrNodeId n8 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ); - prGraphAddEdge(&graph, n6, n8); - prGraphAddEdge(&graph, n7, n8); + prNodeManagerAddEdge(&mgr, n6, n8); + prNodeManagerAddEdge(&mgr, n7, n8); printf("\n"); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); printf("\n=== Try adding cycle 8→1 (should be rejected) ===\n"); - prGraphAddEdge(&graph, n8, n1); + prNodeManagerAddEdge(&mgr, n8, n1); printf("\n=== Try adding 8→1 again (still rejected) ===\n"); - prGraphAddEdge(&graph, n8, n1); + prNodeManagerAddEdge(&mgr, n8, n1); printf("\n=== Try adding cycle 7→2 (should be rejected) ===\n\n"); - prGraphAddEdge(&graph, n7, n2); + prNodeManagerAddEdge(&mgr, n7, n2); - prGraphDump(&graph); + prGraphDump(&mgr, &mgr.graph); printf("\n=== Topological sort ===\n"); - PrNodeId *sorted = prGraphTopologicalSort(&graph, &arena); + PrNodeId *sorted = prGraphTopologicalSort(&mgr, &mgr.graph, &arena); if (sorted) { u64 n = wpArrayCount(sorted); printf(" count: %llu / %llu active\n", (unsigned long long)n, - (unsigned long long)graph.count); + (unsigned long long)mgr.count); for (u64 i = 0; i < n; i++) { printf(" [%llu] idx=%llu gen=%llu\n", (unsigned long long)i,