Remove old dag.c
This commit is contained in:
+420
-204
@@ -1,7 +1,12 @@
|
|||||||
|
// vim:fileencoding=utf-8:foldmethod=marker
|
||||||
|
|
||||||
|
#include "../src/wapp/wapp.h"
|
||||||
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
#include <string.h>
|
||||||
#include "../src/wapp/wapp.h"
|
|
||||||
|
wp_intern WpLogger _log = { .name = wpStr8LitRo("dag_man") };
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
* Pool allocator (arena-backed, intrusive free list)
|
* Pool allocator (arena-backed, intrusive free list)
|
||||||
@@ -18,13 +23,13 @@ typedef struct {
|
|||||||
u64 slot_size;
|
u64 slot_size;
|
||||||
} PrPool;
|
} PrPool;
|
||||||
|
|
||||||
static void prPoolInit(PrPool *pool, WpAllocator *arena, u64 slot_size) {
|
wp_intern void prPoolInit(PrPool *pool, WpAllocator *allocator, u64 slot_size) {
|
||||||
pool->allocator = arena;
|
pool->allocator = allocator;
|
||||||
pool->free_head = NULL;
|
pool->free_head = NULL;
|
||||||
pool->slot_size = slot_size;
|
pool->slot_size = slot_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *prPoolAlloc(PrPool *pool) {
|
wp_intern void *prPoolAlloc(PrPool *pool) {
|
||||||
if (pool->free_head) {
|
if (pool->free_head) {
|
||||||
PrPoolFreeNode *node = pool->free_head;
|
PrPoolFreeNode *node = pool->free_head;
|
||||||
pool->free_head = node->next;
|
pool->free_head = node->next;
|
||||||
@@ -33,7 +38,7 @@ static void *prPoolAlloc(PrPool *pool) {
|
|||||||
return wpMemAllocatorAlloc(pool->allocator, pool->slot_size);
|
return wpMemAllocatorAlloc(pool->allocator, pool->slot_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prPoolFree(PrPool *pool, void *slot) {
|
wp_intern void prPoolFree(PrPool *pool, void *slot) {
|
||||||
if (!slot) { return; }
|
if (!slot) { return; }
|
||||||
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
|
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
|
||||||
node->next = pool->free_head;
|
node->next = pool->free_head;
|
||||||
@@ -41,128 +46,230 @@ static void prPoolFree(PrPool *pool, void *slot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
* Graph — unified nodes with values + forward/backward adjacency
|
* Shared types
|
||||||
*
|
|
||||||
* - Nodes live in a flat array; free slots are linked via next_free.
|
|
||||||
* - Each node has a generation counter bumped on free; handles are
|
|
||||||
* { index, generation } so stale references are detectable.
|
|
||||||
* - Edges are pool-allocated PrEdgeNode structs with both forward
|
|
||||||
* and backward linkage so deletion traces both directions.
|
|
||||||
* -------------------------------------------------------------------------*/
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
#define PR_INVALID_INDEX ((u64)-1)
|
#define INVALID_NODE_INDEX (u64)-1
|
||||||
|
#define INVALID_NODE_ID ((PrNodeId){ .index = INVALID_NODE_INDEX, .generation = INVALID_NODE_INDEX })
|
||||||
|
|
||||||
typedef struct PrEdgeNode PrEdgeNode;
|
typedef enum {
|
||||||
struct PrEdgeNode {
|
PR_NODE_TYPE_NONE,
|
||||||
PrEdgeNode *pool_next; /* used by pool free list when freed */
|
PR_NODE_TYPE_READ,
|
||||||
PrEdgeNode *next_forward; /* chain in source's forward list */
|
PR_NODE_TYPE_BLUR,
|
||||||
PrEdgeNode *next_backward; /* chain in target's backward list */
|
PR_NODE_TYPE_GRADE,
|
||||||
u64 source_idx;
|
|
||||||
u64 target_idx;
|
COUNT_NODE_TYPES
|
||||||
};
|
} PrNodeType;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u64 index;
|
u64 index;
|
||||||
u64 generation;
|
u64 generation;
|
||||||
} PrHandle;
|
} PrNodeId;
|
||||||
|
typedef PrNodeId *PrNodeIdArray;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int value;
|
union {
|
||||||
u64 generation; /* bumped on every free */
|
WpStr8 path;
|
||||||
u64 next_free; /* free-list index (PR_INVALID_INDEX = active) */
|
f32 blur;
|
||||||
PrEdgeNode *forward_head; /* outgoing edges */
|
f32 gain;
|
||||||
PrEdgeNode *backward_head; /* incoming edges */
|
} params;
|
||||||
} PrGraphNode;
|
PrNodeType type;
|
||||||
|
u64 generation;
|
||||||
|
u64 next_free;
|
||||||
|
} PrNode;
|
||||||
|
typedef PrNode *PrNodeArray;
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Graph edge type
|
||||||
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
typedef struct PrGraphEdge PrGraphEdge;
|
||||||
|
struct PrGraphEdge {
|
||||||
|
PrGraphEdge *next_forward;
|
||||||
|
PrGraphEdge *next_backward;
|
||||||
|
PrNodeId source;
|
||||||
|
PrNodeId target;
|
||||||
|
};
|
||||||
|
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 {
|
typedef struct {
|
||||||
PrGraphNode *nodes; /* WpArray of PrGraphNode */
|
|
||||||
u64 max_nodes;
|
|
||||||
u64 max_ever; /* highest index ever allocated */
|
|
||||||
u64 free_head; /* PR_INVALID_INDEX = empty */
|
|
||||||
u64 count; /* active node count */
|
|
||||||
PrPool edge_pool;
|
PrPool edge_pool;
|
||||||
|
PrGraphNodeArray graph_nodes;
|
||||||
|
u64 capacity;
|
||||||
} PrGraph;
|
} PrGraph;
|
||||||
|
|
||||||
/* --- initialisation ---------------------------------------------------- */
|
/* ---------------------------------------------------------------------------
|
||||||
|
* PrNodeManager — owns compositor node data + handle lifecycle + topology
|
||||||
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
static void prGraphInit(PrGraph *g, WpAllocator *arena, u64 max_nodes) {
|
typedef struct {
|
||||||
g->nodes = wpArrayAllocCapacity(PrGraphNode, arena, max_nodes, WP_ARRAY_INIT_FILLED);
|
PrNodeArray nodes;
|
||||||
g->max_nodes = max_nodes;
|
PrGraph graph;
|
||||||
g->max_ever = 0;
|
u64 capacity;
|
||||||
g->free_head = PR_INVALID_INDEX;
|
u64 max_count_ever;
|
||||||
g->count = 0;
|
u64 count;
|
||||||
prPoolInit(&g->edge_pool, arena, sizeof(PrEdgeNode));
|
u64 free_head;
|
||||||
|
} PrNodeManager;
|
||||||
|
|
||||||
/* Build the free list — last slot's next_free stays PR_INVALID_INDEX */
|
/* ---------------------------------------------------------------------------
|
||||||
for (u64 i = 0; i < max_nodes; i++) {
|
* Function declarations (cross-referencing both types)
|
||||||
g->nodes[i].next_free = (i < max_nodes - 1) ? i + 1 : PR_INVALID_INDEX;
|
* -------------------------------------------------------------------------*/
|
||||||
}
|
|
||||||
g->free_head = 0;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- handle validation ------------------------------------------------ */
|
memset(mgr->nodes, 0, capacity * sizeof(PrNode));
|
||||||
|
|
||||||
static b8 prHandleValid(PrGraph *g, PrHandle h) {
|
for (u64 i = 0; i < capacity; ++i) {
|
||||||
if (h.index >= g->max_nodes) { return false; }
|
mgr->nodes[i].next_free = i < capacity - 1 ? i + 1 : INVALID_NODE_INDEX;
|
||||||
PrGraphNode *node = &g->nodes[h.index];
|
|
||||||
return node->generation == h.generation && node->next_free == PR_INVALID_INDEX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- node management -------------------------------------------------- */
|
prGraphInit(&mgr->graph, allocator, capacity);
|
||||||
|
|
||||||
static PrHandle prNodeAdd(PrGraph *g, int value) {
|
|
||||||
if (g->free_head == PR_INVALID_INDEX) {
|
|
||||||
return (PrHandle){ PR_INVALID_INDEX, 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 idx = g->free_head;
|
wp_intern b8 prNodeManagerIsStaleNode(const PrNodeManager *mgr, PrNodeId id) {
|
||||||
PrGraphNode *n = &g->nodes[idx];
|
u64 generation = mgr->nodes[id.index].generation;
|
||||||
g->free_head = n->next_free;
|
return id.generation != generation;
|
||||||
|
|
||||||
n->value = value;
|
|
||||||
n->next_free = PR_INVALID_INDEX;
|
|
||||||
n->forward_head = NULL;
|
|
||||||
n->backward_head = NULL;
|
|
||||||
g->count++;
|
|
||||||
|
|
||||||
if (idx >= g->max_ever) { g->max_ever = idx + 1; }
|
|
||||||
|
|
||||||
return (PrHandle){ idx, n->generation };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int *prNodeGetValue(PrGraph *g, PrHandle h) {
|
wp_intern b8 prNodeManagerIsActiveNode(const PrNodeManager *mgr, PrNodeId id) {
|
||||||
if (!prHandleValid(g, h)) { return NULL; }
|
u64 next_free = mgr->nodes[id.index].next_free;
|
||||||
return &g->nodes[h.index].value;
|
return !prNodeManagerIsStaleNode(mgr, id) && next_free == INVALID_NODE_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- internal: unlink edge helpers ------------------------------------ */
|
wp_intern PrNodeId prNodeManagerGetNode(const PrNodeManager *mgr, u64 index) {
|
||||||
|
return (PrNodeId){ .index = index, .generation = mgr->nodes[index].generation };
|
||||||
|
}
|
||||||
|
|
||||||
static PrEdgeNode *_unlinkForward(PrGraph *g, u64 from_idx, u64 target_idx) {
|
wp_intern PrNodeId prNodeManagerAddNode(PrNodeManager *mgr, PrNodeType type) {
|
||||||
PrGraphNode *src = &g->nodes[from_idx];
|
u64 idx = mgr->free_head;
|
||||||
PrEdgeNode *prev = NULL;
|
if (idx == INVALID_NODE_INDEX) { return INVALID_NODE_ID; }
|
||||||
PrEdgeNode *curr = src->forward_head;
|
|
||||||
|
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->graph_nodes = wpArrayAllocCapacity(PrGraphNode, allocator, capacity, WP_ARRAY_INIT_FILLED);
|
||||||
|
graph->capacity = capacity;
|
||||||
|
|
||||||
|
if (!graph->graph_nodes) {
|
||||||
|
graph->capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prPoolInit(&graph->edge_pool, allocator, sizeof(PrGraphEdge));
|
||||||
|
memset(graph->graph_nodes, 0, capacity * sizeof(PrGraphNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 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) {
|
while (curr) {
|
||||||
if (curr->target_idx == target_idx) {
|
if (curr == edge) {
|
||||||
if (prev) { prev->next_forward = curr->next_forward; }
|
if (prev) {
|
||||||
else { src->forward_head = curr->next_forward; }
|
prev->next_forward = curr->next_forward;
|
||||||
return curr;
|
} else {
|
||||||
|
gnode->next_forward = curr->next_forward;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
prev = curr;
|
prev = curr;
|
||||||
curr = curr->next_forward;
|
curr = curr->next_forward;
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _unlinkBackward(PrGraph *g, u64 to_idx, u64 source_idx) {
|
wp_intern void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge) {
|
||||||
PrGraphNode *dst = &g->nodes[to_idx];
|
PrGraphNode *gnode = &graph->graph_nodes[to_idx];
|
||||||
PrEdgeNode *prev = NULL;
|
PrGraphEdge *curr = gnode->next_backward;
|
||||||
PrEdgeNode *curr = dst->backward_head;
|
PrGraphEdge *prev = NULL;
|
||||||
// This is intended to handle cases where the target node might not be the one
|
|
||||||
// immediately feeding the node represented by to_idx
|
|
||||||
while (curr) {
|
while (curr) {
|
||||||
if (curr->source_idx == source_idx) {
|
if (curr == edge) {
|
||||||
if (prev) { prev->next_backward = curr->next_backward; }
|
if (prev) {
|
||||||
else { dst->backward_head = curr->next_backward; }
|
prev->next_backward = curr->next_backward;
|
||||||
|
} else {
|
||||||
|
gnode->next_backward = curr->next_backward;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prev = curr;
|
prev = curr;
|
||||||
@@ -172,156 +279,265 @@ static void _unlinkBackward(PrGraph *g, u64 to_idx, u64 source_idx) {
|
|||||||
|
|
||||||
/* --- edge management -------------------------------------------------- */
|
/* --- edge management -------------------------------------------------- */
|
||||||
|
|
||||||
static b8 prEdgeAdd(PrGraph *g, PrHandle from, PrHandle to) {
|
wp_intern void prGraphAddEdge(PrNodeManager *mgr, PrGraph *graph, u64 from_idx, u64 to_idx) {
|
||||||
if (!prHandleValid(g, from) || !prHandleValid(g, to)) { return false; }
|
PrGraphEdge *edge = (PrGraphEdge *)prPoolAlloc(&graph->edge_pool);
|
||||||
|
if (!edge) { return; }
|
||||||
|
|
||||||
PrEdgeNode *edge = (PrEdgeNode *)prPoolAlloc(&g->edge_pool);
|
PrNodeId from_id = prNodeManagerGetNode(mgr, from_idx);
|
||||||
if (!edge) { return false; }
|
PrNodeId to_id = prNodeManagerGetNode(mgr, to_idx);
|
||||||
|
|
||||||
edge->source_idx = from.index;
|
edge->source = from_id;
|
||||||
edge->target_idx = to.index;
|
edge->target = to_id;
|
||||||
|
|
||||||
PrGraphNode *src = &g->nodes[from.index];
|
/* Link into adjacency chains */
|
||||||
edge->next_forward = src->forward_head;
|
PrGraphNode *src = &graph->graph_nodes[from_idx];
|
||||||
src->forward_head = edge;
|
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;
|
||||||
|
|
||||||
PrGraphNode *dst = &g->nodes[to.index];
|
/* Check whether the new edge created a cycle */
|
||||||
edge->next_backward = dst->backward_head;
|
WpAllocator scratch = wpMemArenaAllocatorInitZero(KiB(16));
|
||||||
dst->backward_head = edge;
|
PrNodeIdArray sorted = prGraphTopologicalSort(mgr, graph, &scratch);
|
||||||
|
u64 sorted_cnt = sorted ? wpArrayCount(sorted) : 0;
|
||||||
return true;
|
if (sorted_cnt < mgr->count) {
|
||||||
|
_unlinkForward(graph, from_idx, edge);
|
||||||
|
_unlinkBackward(graph, to_idx, edge);
|
||||||
|
prPoolFree(&graph->edge_pool, edge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- node removal (tears down incident edges) ------------------------- */
|
wp_intern void prGraphRemoveEdges(PrGraph *graph, u64 idx) {
|
||||||
|
PrGraphNode *gnode = &graph->graph_nodes[idx];
|
||||||
static void prNodeRemove(PrGraph *g, PrHandle h) {
|
|
||||||
if (!prHandleValid(g, h)) { return; }
|
|
||||||
|
|
||||||
u64 idx = h.index;
|
|
||||||
PrGraphNode *n = &g->nodes[idx];
|
|
||||||
|
|
||||||
/* Free outgoing edges: unlink from each target's backward list */
|
/* Free outgoing edges: unlink from each target's backward list */
|
||||||
PrEdgeNode *edge = n->forward_head;
|
PrGraphEdge *curr = gnode->next_forward;
|
||||||
while (edge) {
|
while (curr) {
|
||||||
PrEdgeNode *next = edge->next_forward;
|
PrGraphEdge *next = curr->next_forward;
|
||||||
_unlinkBackward(g, edge->target_idx, idx);
|
_unlinkBackward(graph, curr->target.index, curr);
|
||||||
prPoolFree(&g->edge_pool, edge);
|
prPoolFree(&graph->edge_pool, curr);
|
||||||
edge = next;
|
curr = next;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free incoming edges: unlink from each source's forward list */
|
/* Free incoming edges: unlink from each source's forward list */
|
||||||
edge = n->backward_head;
|
curr = gnode->next_backward;
|
||||||
while (edge) {
|
while (curr) {
|
||||||
PrEdgeNode *next = edge->next_backward;
|
PrGraphEdge *next = curr->next_backward;
|
||||||
/* edge is also in the source's forward list — unlink by target_idx */
|
_unlinkForward(graph, curr->source.index, curr);
|
||||||
_unlinkForward(g, edge->source_idx, idx);
|
prPoolFree(&graph->edge_pool, curr);
|
||||||
prPoolFree(&g->edge_pool, edge);
|
curr = next;
|
||||||
edge = next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return node slot to free list with bumped generation */
|
gnode->next_forward = gnode->next_backward = NULL;
|
||||||
n->generation++;
|
|
||||||
n->next_free = g->free_head;
|
|
||||||
g->free_head = idx;
|
|
||||||
g->count--;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- traversal helpers ------------------------------------------------ */
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Kahn's algorithm — topological sort / cycle detection
|
||||||
|
*
|
||||||
|
* Returns a WpArray of PrNodeId (sorted topologically). If the result count
|
||||||
|
* is less than mgr->count, the graph contains a cycle.
|
||||||
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
static void prDump(PrGraph *g) {
|
wp_intern PrNodeIdArray prGraphTopologicalSort(const PrNodeManager *mgr, const PrGraph *graph,
|
||||||
printf("Active nodes: %llu\n\n", (unsigned long long)g->count);
|
const WpAllocator *allocator) {
|
||||||
|
if (mgr->count == 0) { return NULL; }
|
||||||
|
if (!mgr->nodes || graph->capacity == 0) { return NULL; }
|
||||||
|
|
||||||
for (u64 i = 0; i < g->max_ever; i++) {
|
PrNodeIdArray result = wpArrayAllocCapacity(PrNodeId, allocator, mgr->count, WP_ARRAY_INIT_NONE);
|
||||||
PrGraphNode *n = &g->nodes[i];
|
if (!result) { return NULL; }
|
||||||
if (n->next_free != PR_INVALID_INDEX) { continue; }
|
|
||||||
|
|
||||||
printf(" [%llu] g=%llu value=%d\n",
|
WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16));
|
||||||
(unsigned long long)i,
|
|
||||||
(unsigned long long)n->generation,
|
|
||||||
n->value);
|
|
||||||
|
|
||||||
printf(" forward ──▶");
|
WpU64Array in_degree = wpArrayAllocCapacity(u64, &local_arena, graph->capacity, WP_ARRAY_INIT_FILLED);
|
||||||
PrEdgeNode *e = n->forward_head;
|
if (!in_degree) { return result; }
|
||||||
if (!e) { printf(" (none)"); }
|
memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64));
|
||||||
while (e) {
|
|
||||||
PrGraphNode *tv = &g->nodes[e->target_idx];
|
for (u64 i = 0; i < mgr->max_count_ever; i++) {
|
||||||
printf(" %llu[%d]", (unsigned long long)e->target_idx, tv->value);
|
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
||||||
e = e->next_forward;
|
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
||||||
if (e) { printf(","); }
|
|
||||||
|
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, mgr->count);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (queue.count > 0) {
|
||||||
|
u64 *node_idx = wpQueuePop(u64, &queue);
|
||||||
|
if (!node_idx) { break; }
|
||||||
|
|
||||||
|
PrNodeId id = prNodeManagerGetNode(mgr, *node_idx);
|
||||||
|
wpArrayAppendCapped(PrNodeId, result, &id);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
in_degree[target_idx]--;
|
||||||
|
if (in_degree[target_idx] == 0) {
|
||||||
|
wpQueuePush(u64, &queue, &target_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curr = curr->next_forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Dump
|
||||||
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
wp_intern void prGraphDump(const PrNodeManager *mgr, const PrGraph *graph) {
|
||||||
|
printf("==============INPUTS==============\n");
|
||||||
|
for (u64 i = 0; i < mgr->max_count_ever; ++i) {
|
||||||
|
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
||||||
|
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
||||||
|
|
||||||
|
PrGraphNode *gnode = &graph->graph_nodes[i];
|
||||||
|
|
||||||
|
printf("%" PRIu64 ":", id.index + 1);
|
||||||
|
|
||||||
|
if (!(gnode->next_backward)) {
|
||||||
|
printf(" (none)");
|
||||||
|
} else {
|
||||||
|
PrGraphEdge *curr = gnode->next_backward;
|
||||||
|
while (curr) {
|
||||||
|
printf(" %" PRIu64, curr->source.index + 1);
|
||||||
|
curr = curr->next_backward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
printf(" backward ◀──");
|
|
||||||
e = n->backward_head;
|
|
||||||
if (!e) { printf(" (none)"); }
|
|
||||||
while (e) {
|
|
||||||
PrGraphNode *sv = &g->nodes[e->source_idx];
|
|
||||||
printf(" %llu[%d]", (unsigned long long)e->source_idx, sv->value);
|
|
||||||
e = e->next_backward;
|
|
||||||
if (e) { printf(","); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("==============OUTPUTS==============\n");
|
||||||
|
for (u64 i = 0; i < mgr->max_count_ever; ++i) {
|
||||||
|
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
||||||
|
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
||||||
|
|
||||||
|
PrGraphNode *gnode = &graph->graph_nodes[i];
|
||||||
|
|
||||||
|
printf("%" PRIu64 ":", id.index + 1);
|
||||||
|
|
||||||
|
if (!(gnode->next_forward)) {
|
||||||
|
printf(" (none)");
|
||||||
|
} else {
|
||||||
|
PrGraphEdge *curr = gnode->next_forward;
|
||||||
|
while (curr) {
|
||||||
|
printf(" %" PRIu64, curr->target.index + 1);
|
||||||
|
curr = curr->next_forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
* Demo
|
* Main
|
||||||
* -------------------------------------------------------------------------*/
|
* -------------------------------------------------------------------------*/
|
||||||
|
|
||||||
int main(void) {
|
i32 main(void) {
|
||||||
srand((unsigned)time(NULL));
|
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16));
|
||||||
|
|
||||||
WpAllocator arena = wpMemArenaAllocatorInitZero(KiB(64));
|
|
||||||
if (wpMemAllocatorInvalid(&arena)) {
|
if (wpMemAllocatorInvalid(&arena)) {
|
||||||
fprintf(stderr, "arena init failed\n");
|
wpLogFatal(&_log, wpStr8Lit("arena init failed"));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrGraph g;
|
PrNodeManager mgr = {0};
|
||||||
prGraphInit(&g, &arena, 16);
|
prNodeManagerInit(&mgr, &arena, 128);
|
||||||
|
|
||||||
printf("=== Add 6 nodes ===\n");
|
PrNodeId n1 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
PrHandle nodes[10];
|
PrNodeId n2 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
for (u64 i = 0; i < 6; i++) {
|
PrNodeId n3 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
nodes[i] = prNodeAdd(&g, rand() % 100);
|
PrNodeId n4 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
printf(" node[%llu] = handle{%llu g%llu} val=%d\n",
|
PrNodeId n5 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
|
|
||||||
|
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(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
prNodeManagerRemoveNode(&mgr, n3);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
PrNodeId n6 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
|
|
||||||
|
prNodeManagerAddEdge(&mgr, n4, n6);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
prNodeManagerRemoveNode(&mgr, n5);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
PrNodeId n7 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
|
|
||||||
|
prNodeManagerAddEdge(&mgr, n1, n7);
|
||||||
|
prNodeManagerAddEdge(&mgr, n2, n7);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
PrNodeId n8 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
||||||
|
|
||||||
|
prNodeManagerAddEdge(&mgr, n6, n8);
|
||||||
|
prNodeManagerAddEdge(&mgr, n7, n8);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
printf("\n=== Try adding cycle 8→1 (should be rejected) ===\n");
|
||||||
|
prNodeManagerAddEdge(&mgr, n8, n1);
|
||||||
|
|
||||||
|
printf("\n=== Try adding 8→1 again (still rejected) ===\n");
|
||||||
|
prNodeManagerAddEdge(&mgr, n8, n1);
|
||||||
|
|
||||||
|
printf("\n=== Try adding cycle 7→2 (should be rejected) ===\n\n");
|
||||||
|
prNodeManagerAddEdge(&mgr, n7, n2);
|
||||||
|
|
||||||
|
prGraphDump(&mgr, &mgr.graph);
|
||||||
|
|
||||||
|
printf("\n=== Topological sort ===\n");
|
||||||
|
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)mgr.count);
|
||||||
|
for (u64 i = 0; i < n; i++) {
|
||||||
|
printf(" [%llu] idx=%llu gen=%llu\n",
|
||||||
(unsigned long long)i,
|
(unsigned long long)i,
|
||||||
(unsigned long long)nodes[i].index,
|
(unsigned long long)sorted[i].index,
|
||||||
(unsigned long long)nodes[i].generation,
|
(unsigned long long)sorted[i].generation);
|
||||||
*prNodeGetValue(&g, nodes[i]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n=== Add edges: 0→1, 0→2, 1→3, 2→3, 3→4, 4→5 ===\n");
|
|
||||||
u64 edge_list[][2] = { {0,1}, {0,2}, {1,3}, {2,3}, {3,4}, {4,5} };
|
|
||||||
for (u64 i = 0; i < 6; i++) {
|
|
||||||
u64 f = edge_list[i][0], t = edge_list[i][1];
|
|
||||||
prEdgeAdd(&g, nodes[f], nodes[t]);
|
|
||||||
}
|
}
|
||||||
prDump(&g);
|
|
||||||
|
|
||||||
printf("=== Remove node 2 (index %llu) ===\n",
|
|
||||||
(unsigned long long)nodes[2].index);
|
|
||||||
prNodeRemove(&g, nodes[2]);
|
|
||||||
prDump(&g);
|
|
||||||
|
|
||||||
printf("=== Stale check ===\n");
|
|
||||||
printf(" nodes[2] handle{%llu g%llu} valid? %s\n",
|
|
||||||
(unsigned long long)nodes[2].index,
|
|
||||||
(unsigned long long)nodes[2].generation,
|
|
||||||
prHandleValid(&g, nodes[2]) ? "YES" : "NO");
|
|
||||||
|
|
||||||
printf("=== Add edge 5→0 (reuses freed edge slots) ===\n");
|
|
||||||
prEdgeAdd(&g, nodes[5], nodes[0]);
|
|
||||||
prDump(&g);
|
|
||||||
|
|
||||||
printf("=== Add a new node (reuses freed node slot) ===\n");
|
|
||||||
nodes[6] = prNodeAdd(&g, 42);
|
|
||||||
printf(" new node = handle{%llu g%llu} val=%d\n",
|
|
||||||
(unsigned long long)nodes[6].index,
|
|
||||||
(unsigned long long)nodes[6].generation,
|
|
||||||
*prNodeGetValue(&g, nodes[6]));
|
|
||||||
prDump(&g);
|
|
||||||
|
|
||||||
wpMemArenaAllocatorDestroy(&arena);
|
wpMemArenaAllocatorDestroy(&arena);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,543 +0,0 @@
|
|||||||
// vim:fileencoding=utf-8:foldmethod=marker
|
|
||||||
|
|
||||||
#include "../src/wapp/wapp.h"
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
wp_intern WpLogger _log = { .name = wpStr8LitRo("dag_man") };
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* Pool allocator (arena-backed, intrusive free list)
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
typedef struct PrPoolFreeNode PrPoolFreeNode;
|
|
||||||
struct PrPoolFreeNode {
|
|
||||||
PrPoolFreeNode *next;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
WpAllocator *allocator;
|
|
||||||
PrPoolFreeNode *free_head;
|
|
||||||
u64 slot_size;
|
|
||||||
} PrPool;
|
|
||||||
|
|
||||||
wp_intern void prPoolInit(PrPool *pool, WpAllocator *allocator, u64 slot_size) {
|
|
||||||
pool->allocator = allocator;
|
|
||||||
pool->free_head = NULL;
|
|
||||||
pool->slot_size = slot_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_intern void *prPoolAlloc(PrPool *pool) {
|
|
||||||
if (pool->free_head) {
|
|
||||||
PrPoolFreeNode *node = pool->free_head;
|
|
||||||
pool->free_head = node->next;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
return wpMemAllocatorAlloc(pool->allocator, pool->slot_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
wp_intern void prPoolFree(PrPool *pool, void *slot) {
|
|
||||||
if (!slot) { return; }
|
|
||||||
PrPoolFreeNode *node = (PrPoolFreeNode *)slot;
|
|
||||||
node->next = pool->free_head;
|
|
||||||
pool->free_head = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* Shared types
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
#define INVALID_NODE_INDEX (u64)-1
|
|
||||||
#define INVALID_NODE_ID ((PrNodeId){ .index = INVALID_NODE_INDEX, .generation = INVALID_NODE_INDEX })
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
PR_NODE_TYPE_NONE,
|
|
||||||
PR_NODE_TYPE_READ,
|
|
||||||
PR_NODE_TYPE_BLUR,
|
|
||||||
PR_NODE_TYPE_GRADE,
|
|
||||||
|
|
||||||
COUNT_NODE_TYPES
|
|
||||||
} PrNodeType;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u64 index;
|
|
||||||
u64 generation;
|
|
||||||
} PrNodeId;
|
|
||||||
typedef PrNodeId *PrNodeIdArray;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
union {
|
|
||||||
WpStr8 path;
|
|
||||||
f32 blur;
|
|
||||||
f32 gain;
|
|
||||||
} params;
|
|
||||||
PrNodeType type;
|
|
||||||
u64 generation;
|
|
||||||
u64 next_free;
|
|
||||||
} PrNode;
|
|
||||||
typedef PrNode *PrNodeArray;
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* Graph edge type
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
typedef struct PrGraphEdge PrGraphEdge;
|
|
||||||
struct PrGraphEdge {
|
|
||||||
PrGraphEdge *next_forward;
|
|
||||||
PrGraphEdge *next_backward;
|
|
||||||
PrNodeId source;
|
|
||||||
PrNodeId target;
|
|
||||||
};
|
|
||||||
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 edge_pool;
|
|
||||||
PrGraphNodeArray graph_nodes;
|
|
||||||
u64 capacity;
|
|
||||||
} PrGraph;
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* 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 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 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->graph_nodes = wpArrayAllocCapacity(PrGraphNode, allocator, capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
graph->capacity = capacity;
|
|
||||||
|
|
||||||
if (!graph->graph_nodes) {
|
|
||||||
graph->capacity = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
prPoolInit(&graph->edge_pool, allocator, sizeof(PrGraphEdge));
|
|
||||||
memset(graph->graph_nodes, 0, capacity * sizeof(PrGraphNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- 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 mgr->count, the graph contains a cycle.
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
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, mgr->count, WP_ARRAY_INIT_NONE);
|
|
||||||
if (!result) { return NULL; }
|
|
||||||
|
|
||||||
WpAllocator local_arena = wpMemArenaAllocatorInitZero(KiB(16));
|
|
||||||
|
|
||||||
WpU64Array in_degree = wpArrayAllocCapacity(u64, &local_arena, graph->capacity, WP_ARRAY_INIT_FILLED);
|
|
||||||
if (!in_degree) { return result; }
|
|
||||||
memset(in_degree, 0, wpArrayCapacity(in_degree) * sizeof(u64));
|
|
||||||
|
|
||||||
for (u64 i = 0; i < mgr->max_count_ever; i++) {
|
|
||||||
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
|
||||||
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
|
||||||
|
|
||||||
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, mgr->count);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (queue.count > 0) {
|
|
||||||
u64 *node_idx = wpQueuePop(u64, &queue);
|
|
||||||
if (!node_idx) { break; }
|
|
||||||
|
|
||||||
PrNodeId id = prNodeManagerGetNode(mgr, *node_idx);
|
|
||||||
wpArrayAppendCapped(PrNodeId, result, &id);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
in_degree[target_idx]--;
|
|
||||||
if (in_degree[target_idx] == 0) {
|
|
||||||
wpQueuePush(u64, &queue, &target_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* Dump
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
wp_intern void prGraphDump(const PrNodeManager *mgr, const PrGraph *graph) {
|
|
||||||
printf("==============INPUTS==============\n");
|
|
||||||
for (u64 i = 0; i < mgr->max_count_ever; ++i) {
|
|
||||||
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
|
||||||
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
|
||||||
|
|
||||||
PrGraphNode *gnode = &graph->graph_nodes[i];
|
|
||||||
|
|
||||||
printf("%" PRIu64 ":", id.index + 1);
|
|
||||||
|
|
||||||
if (!(gnode->next_backward)) {
|
|
||||||
printf(" (none)");
|
|
||||||
} else {
|
|
||||||
PrGraphEdge *curr = gnode->next_backward;
|
|
||||||
while (curr) {
|
|
||||||
printf(" %" PRIu64, curr->source.index + 1);
|
|
||||||
curr = curr->next_backward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("==============OUTPUTS==============\n");
|
|
||||||
for (u64 i = 0; i < mgr->max_count_ever; ++i) {
|
|
||||||
PrNodeId id = prNodeManagerGetNode(mgr, i);
|
|
||||||
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
|
|
||||||
|
|
||||||
PrGraphNode *gnode = &graph->graph_nodes[i];
|
|
||||||
|
|
||||||
printf("%" PRIu64 ":", id.index + 1);
|
|
||||||
|
|
||||||
if (!(gnode->next_forward)) {
|
|
||||||
printf(" (none)");
|
|
||||||
} else {
|
|
||||||
PrGraphEdge *curr = gnode->next_forward;
|
|
||||||
while (curr) {
|
|
||||||
printf(" %" PRIu64, curr->target.index + 1);
|
|
||||||
curr = curr->next_forward;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* Main
|
|
||||||
* -------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
i32 main(void) {
|
|
||||||
WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16));
|
|
||||||
if (wpMemAllocatorInvalid(&arena)) {
|
|
||||||
wpLogFatal(&_log, wpStr8Lit("arena init failed"));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrNodeManager mgr = {0};
|
|
||||||
prNodeManagerInit(&mgr, &arena, 128);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
prNodeManagerRemoveNode(&mgr, n3);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
PrNodeId n6 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
|
||||||
|
|
||||||
prNodeManagerAddEdge(&mgr, n4, n6);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
prNodeManagerRemoveNode(&mgr, n5);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
PrNodeId n7 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
|
||||||
|
|
||||||
prNodeManagerAddEdge(&mgr, n1, n7);
|
|
||||||
prNodeManagerAddEdge(&mgr, n2, n7);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
PrNodeId n8 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
|
|
||||||
|
|
||||||
prNodeManagerAddEdge(&mgr, n6, n8);
|
|
||||||
prNodeManagerAddEdge(&mgr, n7, n8);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
printf("\n=== Try adding cycle 8→1 (should be rejected) ===\n");
|
|
||||||
prNodeManagerAddEdge(&mgr, n8, n1);
|
|
||||||
|
|
||||||
printf("\n=== Try adding 8→1 again (still rejected) ===\n");
|
|
||||||
prNodeManagerAddEdge(&mgr, n8, n1);
|
|
||||||
|
|
||||||
printf("\n=== Try adding cycle 7→2 (should be rejected) ===\n\n");
|
|
||||||
prNodeManagerAddEdge(&mgr, n7, n2);
|
|
||||||
|
|
||||||
prGraphDump(&mgr, &mgr.graph);
|
|
||||||
|
|
||||||
printf("\n=== Topological sort ===\n");
|
|
||||||
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)mgr.count);
|
|
||||||
for (u64 i = 0; i < n; i++) {
|
|
||||||
printf(" [%llu] idx=%llu gen=%llu\n",
|
|
||||||
(unsigned long long)i,
|
|
||||||
(unsigned long long)sorted[i].index,
|
|
||||||
(unsigned long long)sorted[i].generation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wpMemArenaAllocatorDestroy(&arena);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user