Skip to content
GraphCommitChanges

GraphCommitChanges

Description

Apply all pending edge removals and node deletion marks.

Deleted slots remain reusable and future nodes may reuse their slot indices with fresh generations. Any deleted node id or GraphNode handle becomes invalid immediately after commit returns.

This deferred mutation model is meant for passes that need stable traversal first and destructive graph rewrites second.

Parameters

Name Direction Description
g in,out Graph to commit.

Success

Returns the count of pending removals applied (explicit edge removals plus node deletion marks; cascading edge cleanup from node deletion is not counted separately). live_count shrinks by the number of deleted nodes; edge_count shrinks accordingly; pending-delete count drops to 0. Freed slot indices are pushed onto the reuse list with incremented generations; previously-stored payloads have been torn down via copy_deinit (if configured). All previously-issued node ids and GraphNode handles for the deleted nodes are now stale.

Failure

Does not return - aborts via LOG_FATAL for an invalid graph (caller bug).

Usage example (Cross-references)

Usage examples (Cross-references)
    
    static bool test_graph_mark_delete_commit_and_reuse(void) {
        WriteFmt("Testing GraphMarkNodeForDeletion and GraphCommitChanges\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
    
        bool result  = GraphContainsNode(&graph, b) && (graph.pending_delete_count == 1);
        u64  removed = GraphCommitChanges(&graph);
    
        result = result && (removed == 1);
        result      = result && !GraphNodeMarkedForDeletion(node);
        result      = result && !GraphUnmarkNodeForDeletion(node);
        result      = result && (GraphCommitChanges(&graph) == 0);
        result      = result && GraphContainsNode(&graph, a);
        result      = result && (GraphNodeCount(&graph) == 1);
        result      = result && !GraphMarkEdgeForRemoval(&graph, c, b);
    
        u64 committed = GraphCommitChanges(&graph);
    
        result = result && (committed == 2);
        result      = result && !GraphEdgeMarkedForRemoval(&graph, a, b);
        result      = result && !GraphUnmarkEdgeForRemoval(&graph, a, b);
        result      = result && (GraphCommitChanges(&graph) == 0);
        result      = result && GraphHasEdge(&graph, a, b);
        result      = result && (GraphEdgeCount(&graph) == 1);
        result      = result && !GraphEdgeMarkedForRemoval(&graph, a, b);
        result      = result && GraphEdgeMarkedForRemoval(&graph, a, c);
        result      = result && (GraphCommitChanges(&graph) == 1);
        result      = result && GraphHasEdge(&graph, a, b);
        result      = result && !GraphHasEdge(&graph, a, c);
        result      = result && (GraphPredecessorAt(&graph, a, 0) == a);
        result      = result && GraphMarkEdgeForRemoval(&graph, a, a);
        result      = result && (GraphCommitChanges(&graph) == 1);
        result      = result && (GraphEdgeCount(&graph) == 0);
        result      = result && (GraphOutDegree(&graph, a) == 0);
        result      = result && GraphEdgeMarkedForRemoval(&graph, a, b);
        result      = result && GraphNodeMarkedForDeletion(GraphGetNode(&graph, b));
        result      = result && (GraphCommitChanges(&graph) == 2);
        result      = result && !GraphContainsNode(&graph, b);
        result      = result && (GraphNodeCount(&graph) == 3);
    
        bool result = GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        result      = result && (GraphCommitChanges(&graph) == 1);
        result      = result && !GraphContainsNode(&graph, b);
    
        (void)GraphMarkNodeForDeletion(node);
        (void)GraphCommitChanges(&graph);
        (void)GraphNodeVisit(node);
        GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
    
        u64 committed = GraphCommitChanges(&graph);
    
        bool result = (committed == 1);
        // Delete b so its slot is free at the time GraphClear walks it.
        bool result = GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        result      = result && (GraphCommitChanges(&graph) == 1);
        result      = result && !GraphContainsNode(&graph, b);
        (void)a;
        // One deletion leaves a single entry in free_indices going into clear.
        bool result = GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        result      = result && (GraphCommitChanges(&graph) == 1);
    
        GraphClear(&graph);
        // First commit frees b's slot (free-list entry, generation bumped).
        bool result  = GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        u64  removed = GraphCommitChanges(&graph);
        result       = result && (removed == 1);
        result       = result && !GraphContainsNode(&graph, b);
        // iterate every slot; the free slot must be skipped, not validated.
        result  = result && GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        removed = GraphCommitChanges(&graph);
    
        result = result && (removed == 1);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
        (void)GraphAddNodeR(&graph, 99); // reuse a's slot, a is now stale
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
        (void)GraphAddNodeR(&graph, 99); // reuse b's slot, b is now stale
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
        (void)GraphAddNodeR(&graph, 99); // reuse a's slot, a is now stale
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
        (void)GraphAddNodeR(&graph, 99); // reuse b's slot, b is now stale
        // returns the explicit-removal count (1). Mutant: over-scan finds stale b,
        // in-side find fails -> abort.
        u64 committed = GraphCommitChanges(&graph);
        result        = result && (committed == 1);
        GENERIC_GRAPH(&graph)->__magic &= ~MAGIC_VALIDATED_BIT;
    
        u64 removed = GraphCommitChanges(&graph);
    
        // Restore the hidden slot (real code never touched it).
        GENERIC_GRAPH(&graph)->__magic &= ~MAGIC_VALIDATED_BIT;
    
        u64 removed = GraphCommitChanges(&graph);
    
        // Restore the hidden slot (real code never reached it; c is still marked).
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        u64         old_epoch = GraphMutationEpoch(&graph);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        g_fail_copy           = true;
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        g_fail_copy = true;
        // Delete + commit b so its slot lands on the free list (reuse target).
        bool result = GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        result      = result && (GraphCommitChanges(&graph) == 1);
    
        size before = DebugAllocatorLiveCount(&dbg);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        GenericGraph *g = GENERIC_GRAPH(&graph);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        GenericGraph *g = GENERIC_GRAPH(&graph);
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, c));
        (void)GraphCommitChanges(&graph);
    
        GenericGraph *g = GENERIC_GRAPH(&graph);
        // live slots, then commit so the deletion materializes.
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
        (void)GraphCommitChanges(&graph);
    
        u64 visited = 0;
        GraphNode stale = GraphGetNode(&graph, a);
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
        (void)GraphAddNodeR(&graph, 99); // reuse a's slot at a higher generation
    // aborts on the next iteration step -> DEADEND.
    static bool test_graph_commit_invalidates_live_iterator_deadend(void) {
        WriteFmt("Testing GraphCommitChanges invalidates a live node iterator (should abort)\n");
    
        DefaultAllocator alloc = DefaultAllocatorInit();
            // mutation epoch, so the iterator's next step must abort.
            (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, b));
            (void)GraphCommitChanges(&graph);
        }
        // GraphNodeIdGeneration(a) + 1.
        result = result && GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        result = result && (GraphCommitChanges(&graph) == 1);
        result = result && !GraphContainsNode(&graph, a);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
    
        // a now refers to a freed slot with a stale generation.
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
        // Reuse a's slot at a higher generation; `a` is now stale.
        (void)GraphAddNodeR(&graph, 99);
    
        (void)GraphMarkNodeForDeletion(GraphGetNode(&graph, a));
        (void)GraphCommitChanges(&graph);
    
        // Slot a's index is now free with generation bumped to gen(a)+1. Build an
Last updated on