From 1325902f14a8f17e268a4242528a49b6cf591fa9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Said Date: Sat, 4 Jul 2026 17:07:43 +0100 Subject: [PATCH] Add scratchpad --- scratchpad/dag.c | 327 +++++++++++++++++++++++++++++++++++++++ scratchpad/dag_man.c | 353 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 680 insertions(+) create mode 100644 scratchpad/dag.c create mode 100644 scratchpad/dag_man.c diff --git a/scratchpad/dag.c b/scratchpad/dag.c new file mode 100644 index 0000000..65fee40 --- /dev/null +++ b/scratchpad/dag.c @@ -0,0 +1,327 @@ +#include +#include +#include +#include "../src/wapp/wapp.h" + +/* --------------------------------------------------------------------------- + * 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; + +static void prPoolInit(PrPool *pool, WpAllocator *arena, u64 slot_size) { + pool->allocator = arena; + pool->free_head = NULL; + pool->slot_size = slot_size; +} + +static 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); +} + +static void prPoolFree(PrPool *pool, void *slot) { + if (!slot) { return; } + PrPoolFreeNode *node = (PrPoolFreeNode *)slot; + node->next = pool->free_head; + pool->free_head = node; +} + +/* --------------------------------------------------------------------------- + * Graph — unified nodes with values + forward/backward adjacency + * + * - 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) + +typedef struct PrEdgeNode PrEdgeNode; +struct PrEdgeNode { + PrEdgeNode *pool_next; /* used by pool free list when freed */ + PrEdgeNode *next_forward; /* chain in source's forward list */ + PrEdgeNode *next_backward; /* chain in target's backward list */ + u64 source_idx; + u64 target_idx; +}; + +typedef struct { + u64 index; + u64 generation; +} PrHandle; + +typedef struct { + int value; + u64 generation; /* bumped on every free */ + u64 next_free; /* free-list index (PR_INVALID_INDEX = active) */ + PrEdgeNode *forward_head; /* outgoing edges */ + PrEdgeNode *backward_head; /* incoming edges */ +} PrGraphNode; + +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; +} PrGraph; + +/* --- initialisation ---------------------------------------------------- */ + +static void prGraphInit(PrGraph *g, WpAllocator *arena, u64 max_nodes) { + g->nodes = wpArrayAllocCapacity(PrGraphNode, arena, max_nodes, WP_ARRAY_INIT_FILLED); + g->max_nodes = max_nodes; + g->max_ever = 0; + g->free_head = PR_INVALID_INDEX; + g->count = 0; + prPoolInit(&g->edge_pool, arena, sizeof(PrEdgeNode)); + + /* Build the free list — last slot's next_free stays PR_INVALID_INDEX */ + for (u64 i = 0; i < max_nodes; i++) { + g->nodes[i].next_free = (i < max_nodes - 1) ? i + 1 : PR_INVALID_INDEX; + } + g->free_head = 0; +} + +/* --- handle validation ------------------------------------------------ */ + +static b8 prHandleValid(PrGraph *g, PrHandle h) { + if (h.index >= g->max_nodes) { return false; } + PrGraphNode *node = &g->nodes[h.index]; + return node->generation == h.generation && node->next_free == PR_INVALID_INDEX; +} + +/* --- node management -------------------------------------------------- */ + +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; + PrGraphNode *n = &g->nodes[idx]; + g->free_head = n->next_free; + + 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) { + if (!prHandleValid(g, h)) { return NULL; } + return &g->nodes[h.index].value; +} + +/* --- internal: unlink edge helpers ------------------------------------ */ + +static PrEdgeNode *_unlinkForward(PrGraph *g, u64 from_idx, u64 target_idx) { + PrGraphNode *src = &g->nodes[from_idx]; + PrEdgeNode *prev = NULL; + PrEdgeNode *curr = src->forward_head; + while (curr) { + if (curr->target_idx == target_idx) { + if (prev) { prev->next_forward = curr->next_forward; } + else { src->forward_head = curr->next_forward; } + return curr; + } + prev = curr; + curr = curr->next_forward; + } + return NULL; +} + +static void _unlinkBackward(PrGraph *g, u64 to_idx, u64 source_idx) { + PrGraphNode *dst = &g->nodes[to_idx]; + PrEdgeNode *prev = NULL; + PrEdgeNode *curr = dst->backward_head; + // 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) { + if (curr->source_idx == source_idx) { + if (prev) { prev->next_backward = curr->next_backward; } + else { dst->backward_head = curr->next_backward; } + return; + } + prev = curr; + curr = curr->next_backward; + } +} + +/* --- edge management -------------------------------------------------- */ + +static b8 prEdgeAdd(PrGraph *g, PrHandle from, PrHandle to) { + if (!prHandleValid(g, from) || !prHandleValid(g, to)) { return false; } + + PrEdgeNode *edge = (PrEdgeNode *)prPoolAlloc(&g->edge_pool); + if (!edge) { return false; } + + edge->source_idx = from.index; + edge->target_idx = to.index; + + PrGraphNode *src = &g->nodes[from.index]; + edge->next_forward = src->forward_head; + src->forward_head = edge; + + PrGraphNode *dst = &g->nodes[to.index]; + edge->next_backward = dst->backward_head; + dst->backward_head = edge; + + return true; +} + +/* --- node removal (tears down incident edges) ------------------------- */ + +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 */ + PrEdgeNode *edge = n->forward_head; + while (edge) { + PrEdgeNode *next = edge->next_forward; + _unlinkBackward(g, edge->target_idx, idx); + prPoolFree(&g->edge_pool, edge); + edge = next; + } + + /* Free incoming edges: unlink from each source's forward list */ + edge = n->backward_head; + while (edge) { + PrEdgeNode *next = edge->next_backward; + /* edge is also in the source's forward list — unlink by target_idx */ + _unlinkForward(g, edge->source_idx, idx); + prPoolFree(&g->edge_pool, edge); + edge = next; + } + + /* Return node slot to free list with bumped generation */ + n->generation++; + n->next_free = g->free_head; + g->free_head = idx; + g->count--; +} + +/* --- traversal helpers ------------------------------------------------ */ + +static void prDump(PrGraph *g) { + printf("Active nodes: %llu\n\n", (unsigned long long)g->count); + + for (u64 i = 0; i < g->max_ever; i++) { + PrGraphNode *n = &g->nodes[i]; + if (n->next_free != PR_INVALID_INDEX) { continue; } + + printf(" [%llu] g=%llu value=%d\n", + (unsigned long long)i, + (unsigned long long)n->generation, + n->value); + + printf(" forward ──▶"); + PrEdgeNode *e = n->forward_head; + if (!e) { printf(" (none)"); } + while (e) { + PrGraphNode *tv = &g->nodes[e->target_idx]; + printf(" %llu[%d]", (unsigned long long)e->target_idx, tv->value); + e = e->next_forward; + if (e) { printf(","); } + } + 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("\n"); + } +} + +/* --------------------------------------------------------------------------- + * Demo + * -------------------------------------------------------------------------*/ + +int main(void) { + srand((unsigned)time(NULL)); + + WpAllocator arena = wpMemArenaAllocatorInitZero(KiB(64)); + if (wpMemAllocatorInvalid(&arena)) { + fprintf(stderr, "arena init failed\n"); + return 1; + } + + PrGraph g; + prGraphInit(&g, &arena, 16); + + printf("=== Add 6 nodes ===\n"); + PrHandle nodes[10]; + for (u64 i = 0; i < 6; i++) { + nodes[i] = prNodeAdd(&g, rand() % 100); + printf(" node[%llu] = handle{%llu g%llu} val=%d\n", + (unsigned long long)i, + (unsigned long long)nodes[i].index, + (unsigned long long)nodes[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); + return 0; +} diff --git a/scratchpad/dag_man.c b/scratchpad/dag_man.c new file mode 100644 index 0000000..3846e07 --- /dev/null +++ b/scratchpad/dag_man.c @@ -0,0 +1,353 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#include "../src/wapp/wapp.h" +#include +#include +#include + +/* --------------------------------------------------------------------------- + * 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; +} + +/* --------------------------------------------------------------------------- + * Graph + * -------------------------------------------------------------------------*/ + +#define INVALID_NODE_INDEX (u32)-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 { + u32 index; + u32 generation; +} PrNodeId; + +typedef struct { + union { + WpStr8 path; + f32 blur; + f32 gain; + } params; + PrNodeType type; + u32 generation; + u32 next_free; +} PrNode; +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; + PrEdgeVertex *prev; + PrNodeId source; + PrNodeId target; +}; +typedef PrEdgeVertex *PrEdgeVertexArray; + +typedef struct { + PrPool vertex_pool; + PrNodeArray nodes; + PrEdgeVertexArray vertices; + u64 capacity; + u64 max_count_ever; + u64 count; + u32 free_head; +} PrGraph; + +wp_intern b8 prGraphIsStaleId(const PrGraph *graph, PrNodeId id) { + u32 generation = graph->nodes[id.index].generation; + return id.generation != generation; +} + +wp_intern b8 prGraphIsActiveNode(const PrGraph *graph, PrNodeId id) { + u32 next_free = graph->nodes[id.index].next_free; + return !prGraphIsStaleId(graph, id) && next_free == INVALID_NODE_INDEX; +} + +wp_intern PrNodeId prGraphGetNode(const PrGraph *graph, u32 index) { + return (PrNodeId){ .index = index, .generation = graph->nodes[index].generation }; +} + +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; + + prPoolInit(&graph->vertex_pool, allocator, sizeof(PrEdgeVertex)); + memset(graph->nodes, 0, capacity * sizeof(PrNode)); + memset(graph->vertices, 0, capacity * sizeof(PrEdgeVertex)); + + 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; + } +} + +wp_intern void prGraphAddEdge(PrGraph *graph, PrNodeId from, PrNodeId to) { + if (!prGraphIsActiveNode(graph, from) || !prGraphIsActiveNode(graph, to)) { return; } + + PrEdgeVertex *edge = (PrEdgeVertex *)prPoolAlloc(&graph->vertex_pool); + + PrEdgeVertex *src = &graph->vertices[from.index]; + PrEdgeVertex *dst = &graph->vertices[to.index]; + + edge->source = from; + edge->target = to; + edge->next = src->next; + edge->prev = dst->prev; + src->next = edge; + dst->prev = edge; +} + +wp_intern PrNodeId prGraphAddNode(PrGraph *graph, PrNodeType type) { + u32 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 _unlinkForward(PrGraph *graph, PrNodeId from, PrEdgeVertex *edge) { + if (!prGraphIsActiveNode(graph, from)) { return; } + + PrEdgeVertex *src = &graph->vertices[from.index]; + PrEdgeVertex *curr = src->next; + PrEdgeVertex *prev = NULL; + while (curr) { + if (curr == edge) { + if (prev) { + prev->next = curr->next; + } else { + src->next = curr->next; + } + return; + } + + prev = curr; + curr = curr->next; + } +} + +wp_intern void _unlinkBackward(PrGraph *graph, PrNodeId to, PrEdgeVertex *edge) { + if (!prGraphIsActiveNode(graph, to)) { return; } + + PrEdgeVertex *dst = &graph->vertices[to.index]; + PrEdgeVertex *curr = dst->prev; + PrEdgeVertex *prev = NULL; + while (curr) { + if (curr == edge) { + if (prev) { + prev->prev = curr->prev; + } else { + dst->prev = curr->prev; + } + return; + } + + prev = curr; + curr = curr->prev; + } +} + +wp_intern void prGraphRemoveNode(PrGraph *graph, PrNodeId id) { + if (!prGraphIsActiveNode(graph, id)) { return; } + + PrNode *node = &graph->nodes[id.index]; + u32 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) && !(vertex->prev)) { + goto REMOVE_NODE_UNSET_POINTERS; + } + + PrEdgeVertex *curr = vertex->next; + while (curr) { + PrEdgeVertex *next = curr->next; + _unlinkBackward(graph, curr->target, curr); + prPoolFree(&graph->vertex_pool, curr); + curr = next; + } + + curr = vertex->prev; + while (curr) { + PrEdgeVertex *prev = curr->prev; + _unlinkForward(graph, curr->source, curr); + prPoolFree(&graph->vertex_pool, curr); + curr = prev; + } + +REMOVE_NODE_UNSET_POINTERS: + vertex->next = vertex->prev = NULL; + + graph->count--; +} + +wp_intern void prGraphDump(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; } + + PrEdgeVertex *vertex = &graph->vertices[i]; + + printf("%u:", id.index + 1); + + if (!(vertex->prev)) { + printf(" (none)"); + } else { + PrEdgeVertex *curr = vertex->prev; + while (curr) { + printf(" %u", curr->source.index + 1); + curr = curr->prev; + } + } + + printf("\n"); + } + + printf("==============OUTPUTS==============\n"); + for (u64 i = 0; i < graph->max_count_ever; ++i) { + PrNodeId id = prGraphGetNode(graph, i); + if (!prGraphIsActiveNode(graph, id)) { continue; } + + PrEdgeVertex *vertex = &graph->vertices[i]; + + printf("%u:", id.index + 1); + + if (!(vertex->next)) { + printf(" (none)"); + } else { + PrEdgeVertex *curr = vertex->next; + while (curr) { + printf(" %u", curr->target.index + 1); + curr = curr->next; + } + } + + printf("\n"); + } +} + +i32 main(void) { + WpAllocator arena = wpMemArenaAllocatorInitZero(MiB(16)); + + PrGraph graph = {0}; + prGraphInit(&graph, &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); + + prGraphAddEdge(&graph, n1, n2); + prGraphAddEdge(&graph, n1, n3); + prGraphAddEdge(&graph, n1, n5); + prGraphAddEdge(&graph, n2, n4); + prGraphAddEdge(&graph, n3, n4); + prGraphAddEdge(&graph, n3, n5); + + prGraphDump(&graph); + + prGraphRemoveNode(&graph, n3); + + printf("\n"); + prGraphDump(&graph); + + PrNodeId n6 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + + prGraphAddEdge(&graph, n4, n6); + + printf("\n"); + prGraphDump(&graph); + + prGraphRemoveNode(&graph, n5); + + printf("\n"); + prGraphDump(&graph); + + PrNodeId n7 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + + prGraphAddEdge(&graph, n1, n7); + prGraphAddEdge(&graph, n2, n7); + + printf("\n"); + prGraphDump(&graph); + + PrNodeId n8 = prGraphAddNode(&graph, PR_NODE_TYPE_READ); + + prGraphAddEdge(&graph, n6, n8); + prGraphAddEdge(&graph, n7, n8); + + printf("\n"); + prGraphDump(&graph); + + wpMemArenaAllocatorDestroy(&arena); + + return 0; +}