Files
agent_compositor_test/scratchpad/dag.c
T
2026-08-08 20:25:25 +01:00

567 lines
17 KiB
C

// vim:fileencoding=utf-8:foldmethod=marker
#include "../src/vendor/wapp/wapp.h"
#include <inttypes.h>
#include <stdio.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 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;
u64 source_idx;
u64 target_idx;
};
/* ---------------------------------------------------------------------------
* Graph vertex — compact adjacency head (two pointers + active flag)
* -------------------------------------------------------------------------*/
typedef struct PrGraphVertex PrGraphVertex;
struct PrGraphVertex {
PrGraphEdge *next_forward;
PrGraphEdge *next_backward;
b8 active;
};
typedef PrGraphVertex *PrGraphVertexArray;
/* ---------------------------------------------------------------------------
* PrGraph — owns only topology (edges + adjacency heads)
* -------------------------------------------------------------------------*/
typedef struct {
PrPool edge_pool;
PrGraphVertexArray vertices;
u64 capacity;
u64 max_vertex_ever;
u64 vertex_count;
} 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 prNodeManagerDumpGraph(const PrNodeManager *mgr);
wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity);
wp_intern void prGraphAddVertex(PrGraph *graph, u64 idx);
wp_intern void prGraphRemoveVertex(PrGraph *graph, u64 idx);
wp_intern b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx);
wp_intern b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx);
wp_intern u64 prGraphVertexCount(const PrGraph *graph);
wp_intern WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator);
wp_intern void _unlinkForward(PrGraph *graph, u64 from_idx, PrGraphEdge *edge);
wp_intern void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge);
/* ---------------------------------------------------------------------------
* 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; }
prGraphAddVertex(&mgr->graph, idx);
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 and mark vertex inactive */
prGraphRemoveVertex(&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; }
if (prGraphEdgeExists(&mgr->graph, from.index, to.index)) { return; }
prGraphAddEdge(&mgr->graph, from.index, to.index);
}
/* ---------------------------------------------------------------------------
* PrGraph implementation
* -------------------------------------------------------------------------*/
wp_intern void prGraphInit(PrGraph *graph, WpAllocator *allocator, u64 capacity) {
graph->vertices = wpArrayAllocCapacity(PrGraphVertex, allocator, capacity, WP_ARRAY_INIT_FILLED);
graph->capacity = capacity;
graph->max_vertex_ever = 0;
graph->vertex_count = 0;
if (!graph->vertices) {
graph->capacity = 0;
return;
}
prPoolInit(&graph->edge_pool, allocator, sizeof(PrGraphEdge));
memset(graph->vertices, 0, capacity * sizeof(PrGraphVertex));
}
/* --- internal: unlink edge helpers (raw indices, caller guarantees validity) -- */
wp_intern void _unlinkForward(PrGraph *graph, u64 from_idx, PrGraphEdge *edge) {
PrGraphVertex *vtx = &graph->vertices[from_idx];
PrGraphEdge *curr = vtx->next_forward;
PrGraphEdge *prev = NULL;
while (curr) {
if (curr == edge) {
if (prev) {
prev->next_forward = curr->next_forward;
} else {
vtx->next_forward = curr->next_forward;
}
return;
}
prev = curr;
curr = curr->next_forward;
}
}
wp_intern void _unlinkBackward(PrGraph *graph, u64 to_idx, PrGraphEdge *edge) {
PrGraphVertex *vtx = &graph->vertices[to_idx];
PrGraphEdge *curr = vtx->next_backward;
PrGraphEdge *prev = NULL;
while (curr) {
if (curr == edge) {
if (prev) {
prev->next_backward = curr->next_backward;
} else {
vtx->next_backward = curr->next_backward;
}
return;
}
prev = curr;
curr = curr->next_backward;
}
}
/* --- vertex lifecycle ------------------------------------------------- */
wp_intern void prGraphAddVertex(PrGraph *graph, u64 idx) {
graph->vertices[idx].active = true;
graph->vertex_count++;
if (idx >= graph->max_vertex_ever) {
graph->max_vertex_ever = idx + 1;
}
}
wp_intern void prGraphRemoveVertex(PrGraph *graph, u64 idx) {
PrGraphVertex *vtx = &graph->vertices[idx];
if (!vtx->active) { return; }
/* Free outgoing edges: unlink from each target's backward list */
PrGraphEdge *curr = vtx->next_forward;
while (curr) {
PrGraphEdge *next = curr->next_forward;
_unlinkBackward(graph, curr->target_idx, curr);
prPoolFree(&graph->edge_pool, curr);
curr = next;
}
/* Free incoming edges: unlink from each source's forward list */
curr = vtx->next_backward;
while (curr) {
PrGraphEdge *next = curr->next_backward;
_unlinkForward(graph, curr->source_idx, curr);
prPoolFree(&graph->edge_pool, curr);
curr = next;
}
vtx->next_forward = NULL;
vtx->next_backward = NULL;
vtx->active = false;
graph->vertex_count--;
}
/* --- edge management -------------------------------------------------- */
wp_intern b8 prGraphEdgeExists(const PrGraph *graph, u64 from_idx, u64 to_idx) {
PrGraphVertex *vtx = &graph->vertices[from_idx];
PrGraphEdge *curr = vtx->next_forward;
while (curr) {
if (curr->target_idx == to_idx) { return true; }
curr = curr->next_forward;
}
return false;
}
wp_intern b8 prGraphAddEdge(PrGraph *graph, u64 from_idx, u64 to_idx) {
PrGraphEdge *edge = (PrGraphEdge *)prPoolAlloc(&graph->edge_pool);
if (!edge) { return false; }
edge->source_idx = from_idx;
edge->target_idx = to_idx;
/* Link into adjacency chains */
PrGraphVertex *src = &graph->vertices[from_idx];
PrGraphVertex *dst = &graph->vertices[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));
WpU64Array sorted = prGraphTopologicalSort(graph, &scratch);
u64 sorted_n = sorted ? wpArrayCount(sorted) : 0;
if (sorted_n < graph->vertex_count) {
_unlinkForward(graph, from_idx, edge);
_unlinkBackward(graph, to_idx, edge);
prPoolFree(&graph->edge_pool, edge);
return false;
}
return true;
}
wp_intern u64 prGraphVertexCount(const PrGraph *graph) {
return graph->vertex_count;
}
/* ---------------------------------------------------------------------------
* 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 WpU64Array prGraphTopologicalSort(const PrGraph *graph, const WpAllocator *allocator) {
if (graph->vertex_count == 0) { return NULL; }
if (!graph->vertices || graph->capacity == 0) { return NULL; }
WpU64Array result = wpArrayAllocCapacity(u64, allocator, graph->vertex_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 < graph->max_vertex_ever; i++) {
if (!graph->vertices[i].active) { continue; }
PrGraphEdge *curr = graph->vertices[i].next_forward;
while (curr) {
in_degree[curr->target_idx]++;
curr = curr->next_forward;
}
}
WpQueue queue = wpQueueAlloc(u64, &local_arena, graph->vertex_count);
for (u64 i = 0; i < graph->max_vertex_ever; i++) {
if (!graph->vertices[i].active) { continue; }
if (in_degree[i] == 0) {
wpQueuePush(u64, &queue, &i);
}
}
while (queue.count > 0) {
u64 *node_idx = wpQueuePop(u64, &queue);
if (!node_idx) { break; }
wpArrayAppendCapped(u64, result, node_idx);
PrGraphEdge *curr = graph->vertices[*node_idx].next_forward;
while (curr) {
u64 target_idx = curr->target_idx;
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 prNodeManagerDumpGraph(const PrNodeManager *mgr) {
const PrGraph *graph = &mgr->graph;
printf("==============INPUTS==============\n");
for (u64 i = 0; i < mgr->max_count_ever; ++i) {
PrNodeId id = prNodeManagerGetNode(mgr, i);
if (!prNodeManagerIsActiveNode(mgr, id)) { continue; }
printf("%" PRIu64 ":", id.index + 1);
PrGraphEdge *curr = graph->vertices[i].next_backward;
if (!curr) {
printf(" (none)");
} else {
do {
printf(" %" PRIu64, curr->source_idx + 1);
curr = curr->next_backward;
} while (curr);
}
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; }
printf("%" PRIu64 ":", id.index + 1);
PrGraphEdge *curr = graph->vertices[i].next_forward;
if (!curr) {
printf(" (none)");
} else {
do {
printf(" %" PRIu64, curr->target_idx + 1);
curr = curr->next_forward;
} while (curr);
}
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);
prNodeManagerDumpGraph(&mgr);
prNodeManagerRemoveNode(&mgr, n3);
printf("\n");
prNodeManagerDumpGraph(&mgr);
PrNodeId n6 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
prNodeManagerAddEdge(&mgr, n4, n6);
printf("\n");
prNodeManagerDumpGraph(&mgr);
prNodeManagerRemoveNode(&mgr, n5);
printf("\n");
prNodeManagerDumpGraph(&mgr);
PrNodeId n7 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
prNodeManagerAddEdge(&mgr, n1, n7);
prNodeManagerAddEdge(&mgr, n2, n7);
printf("\n");
prNodeManagerDumpGraph(&mgr);
PrNodeId n8 = prNodeManagerAddNode(&mgr, PR_NODE_TYPE_READ);
prNodeManagerAddEdge(&mgr, n6, n8);
prNodeManagerAddEdge(&mgr, n7, n8);
printf("\n");
prNodeManagerDumpGraph(&mgr);
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);
prNodeManagerDumpGraph(&mgr);
printf("\n=== Topological sort ===\n");
WpU64Array sorted = prGraphTopologicalSort(&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++) {
PrNodeId id = prNodeManagerGetNode(&mgr, sorted[i]);
printf(" [%llu] idx=%llu gen=%llu\n",
(unsigned long long)i,
(unsigned long long)sorted[i],
(unsigned long long)id.generation);
}
}
wpMemArenaAllocatorDestroy(&arena);
return 0;
}