Skip to content

feat: Add SparseSet data structure - #8

Merged
apotema merged 13 commits into
mainfrom
7-add-sparse-set
Jan 8, 2026
Merged

apotema merged 13 commits into
mainfrom
7-add-sparse-set

Conversation

@apotema

@apotema apotema commented Jan 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Add SparseSet data structure - O(1) key-value mapping with cache-friendly iteration.

Features

  • O(1) worst-case insert, remove, lookup (no amortization)
  • Cache-friendly dense array iteration
  • Generic value type support
  • Proper errdefer for allocation failure cleanup

Performance

Operation SparseSet HashMap
contains 0.76 ns 4.61 ns

6x faster than std.AutoHashMap for lookups.

Trade-off

Fixed memory allocation based on max key range (~40KB for 10k keys) vs variable for HashMap.

Use Cases

  • Entity -> component mappings in ECS
  • Physics body lookups
  • Any sparse key -> value scenario where O(1) worst-case is needed

Closes #7

🤖 Generated with Claude Code

O(1) key-value mapping with cache-friendly iteration.
Useful for entity -> component mappings and other sparse key scenarios.

Performance: 0.76 ns/lookup vs 4.61 ns for HashMap (6x faster)
Trade-off: Fixed ~40KB memory for 10k max keys

Closes #7

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jan 8, 2026 •

Copy link
Copy Markdown

PR Summary

Introduces major graph algorithms and data structures, updates APIs/exports, and upgrades build tooling.

  • Adds AStar with pluggable heuristics and entity-ID mapping; includes heuristics module
  • Adds FloydWarshall and optimized FloydWarshallOptimized (SIMD/parallel) with path reconstruction and ID mapping
  • New collections: SparseSet (O(1) key→value) and ZIndexBuckets (ordered storage by z-index)
  • QuadTree refactor: compile-time QuadTreeConfig and node capacity genericization; updates tests to QuadTree(T, .{})
  • New compile-time utilities: zon_coercion (struct/union coercion) and hook_dispatcher (comptime hooks)
  • Updates src/root.zig exports to include new modules and types
  • Expands test suite for all new modules and algorithms; updates docs (claude.md)
  • CI/tooling: bump setup-zig to v2 and minimum Zig to 0.15.2; update zspec dependency hash

Written by Cursor Bugbot for commit 825b801. This will update automatically on new commits. Configure here.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @apotema, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new SparseSet data structure designed for highly efficient key-value mapping. It prioritizes O(1) worst-case performance for core operations and cache-friendly iteration, making it particularly suitable for scenarios like Entity-Component-System (ECS) architectures. While offering substantial performance benefits over traditional hash maps, it requires a fixed memory allocation based on the maximum expected key range.

Highlights

  • Performance: Introduces a SparseSet data structure offering O(1) worst-case performance for insert, remove, and lookup operations, without amortization.
  • Efficiency: Provides cache-friendly iteration over dense arrays, making it highly efficient for data access patterns.
  • Benchmarking: Demonstrates significant performance gains, being 6x faster than std.AutoHashMap for contains operations.
  • Genericity: Supports generic value types, enhancing its reusability across various data types.
  • Resource Management: Implements proper errdefer for robust allocation failure cleanup, ensuring resource safety.
  • Trade-offs: Utilizes a fixed memory allocation based on the maximum expected key range (e.g., ~40KB for 10k keys), contrasting with the variable memory usage of HashMap.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a SparseSet data structure, which is a great addition for performance-sensitive scenarios. However, a medium-severity vulnerability has been identified related to a potential integer overflow when the set grows to a very large size, which could lead to data corruption. Additionally, there's a critical bug in the memory resizing logic that could lead to a corrupted state, and further suggestions have been provided for improving robustness and maintainability. Addressing these points will make this a very useful utility.

Comment thread src/sparse_set.zig Outdated
Comment on lines +61 to +64
const new_cap = self.capacity * 2;
self.dense_keys = try self.allocator.realloc(self.dense_keys, new_cap);
self.dense_values = try self.allocator.realloc(self.dense_values, new_cap);
self.capacity = new_cap;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This resizing logic has a critical flaw. The two realloc calls are not atomic. If the first realloc (for dense_keys) succeeds but the second one (for dense_values) fails, the SparseSet is left in a corrupted state with inconsistent slice capacities. This can lead to memory safety violations and crashes. Additionally, if self.capacity is 0, new_cap will also be 0, which is not a valid growth capacity and will cause issues with realloc.

A robust implementation should handle the zero-capacity case (e.g., growing to a default initial capacity) and ensure the resizing is atomic. Consider looking at how std.MultiArrayList handles growing its storage for a safe implementation pattern (it uses alloc+memcpy+free to avoid the complexities of multiple reallocs).

Comment thread src/sparse_set.zig
self.capacity = new_cap;
}

const idx: u32 = @intCast(self.count);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

In the put function, the self.count variable (of type usize) is cast to a u32 using @intCast. If the sparse set is used on a 64-bit system and stores more than 2^32 - 1 elements, this will lead to an integer overflow due to truncation, resulting in an incorrect idx and potential data corruption in dense_keys and dense_values. This could also cause memory corruption or panicking in safe build modes. To remediate, add checks in init and the resizing logic within put to ensure the capacity never exceeds u32.max. Specifically, before casting on line 67, validate that self.count is within u32 bounds and return an error.CapacityExceeded if exceeded. Example: if (self.count >= std.math.maxInt(u32)) return error.CapacityExceeded; const idx: u32 = @intCast(self.count);

Comment thread src/sparse_set.zig Outdated
const Allocator = std.mem.Allocator;

/// Generic sparse set for mapping u64 keys to values of type T
pub fn SparseSet(comptime T: type) type {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The SparseSet is generic over the value type T, which is great. However, the key type is hardcoded to u64. To make this data structure more broadly useful, consider making the key type generic as well. It could be constrained to an integer type using comptime checks.

pub fn SparseSet(comptime T: type, comptime KeyType: type) type {

Comment thread src/sparse_set.zig
const dense_keys = try allocator.alloc(u64, initial_capacity);
errdefer allocator.free(dense_keys);

const dense_values = try allocator.alloc(T, initial_capacity);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This allocation of dense_values is not paired with an errdefer. While it's currently the last fallible operation in init, this is fragile. If any failable code is added later in this function, it would introduce a memory leak on error. It's best practice to add an errdefer immediately after a successful allocation to ensure resources are cleaned up properly on any subsequent failure.

Comment thread src/sparse_set.zig Outdated
try std.testing.expectEqual(@as(u64, 855), sum); // 555 + 300
}

test "SparseSet iteration order stable after remove" {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The name of this test, SparseSet iteration order stable after remove, is misleading. The remove implementation uses a swap-and-pop algorithm, which is efficient but explicitly does not preserve the iteration order of the remaining elements. The test itself correctly verifies the behavior of swap-and-pop, but the name suggests the opposite.

A more accurate name would be something like SparseSet iteration after remove is correct. Additionally, the comment on line 220 is confusing as it doesn't reflect the actual order of elements after the removal.

Comment thread src/sparse_set.zig Outdated
Comment thread src/sparse_set.zig Outdated
apotema and others added 2 commits January 8, 2026 15:54
New modules extracted from labelle-engine:

- z_index_buckets.zig: Generic bucket-sorted storage by u8 key (256 buckets)
  for efficient z-index ordering. O(1) insert, O(bucket_size) remove.

- zon_coercion.zig: Comptime ZON coercion utilities for converting
  anonymous structs to typed structs. Handles nested structs, tuples,
  unions, and struct merging.

- hook_dispatcher.zig: Zero-overhead comptime hook dispatcher for
  event handling. Supports single dispatchers, empty dispatchers,
  and merged hook handlers.

All modules are generic with no external dependencies beyond std.

Closes #7

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apotema

apotema commented Jan 8, 2026

Copy link
Copy Markdown
Contributor Author

Additional Utilities Added

This PR now includes more utilities beyond the original SparseSet:

ZIndexBuckets

Bucket-sorted storage by u8 key (256 buckets) for efficient z-index ordering:

  • O(1) insert, O(bucket_size) remove
  • O(256 + n) ordered iteration
  • Generic over item type T

ZON Coercion

Comptime utilities for .zon file processing:

  • coerceValue - Convert values to expected types
  • buildStruct - Build typed struct from anonymous data
  • tupleToSlice - Convert tuples to slices
  • mergeStructs - Merge structs with override semantics

HookDispatcher

Zero-overhead comptime event dispatch:

  • HookDispatcher - Single handler struct dispatch
  • MergeHooks - Compose multiple handler structs
  • EmptyDispatcher - Default no-op dispatcher

All modules are generic with no external dependencies beyond std.

Comment thread src/z_index_buckets.zig
apotema and others added 3 commits January 8, 2026 16:05
If dense_keys realloc succeeds but dense_values fails, shrink dense_keys
back to original capacity to maintain consistent state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New modules from labelle-pathfinding:

- floyd_warshall.zig: Basic Floyd-Warshall all-pairs shortest path O(V³)
  with entity ID mapping support

- floyd_warshall_optimized.zig: High-performance version with:
  - Flat memory layout for cache efficiency
  - SIMD vectorization (4x u32 vectors)
  - Multi-threaded parallelization
  - 5-16x faster than basic version

- a_star.zig: A* single-source shortest path algorithm with:
  - Multiple built-in heuristics
  - Custom heuristic function support
  - Entity ID mapping
  - Adjacency list representation

- heuristics.zig: Distance heuristics for A*:
  - Euclidean (any-angle movement)
  - Manhattan (4-directional grid)
  - Chebyshev (8-dir equal diagonal)
  - Octile (8-dir realistic diagonal)
  - Zero (Dijkstra mode)

All modules have no external dependencies beyond std.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread src/floyd_warshall_optimized.zig
Comment thread src/floyd_warshall_optimized.zig
Comment thread src/floyd_warshall.zig Outdated
apotema and others added 2 commits January 8, 2026 16:56
Comptime improvements:
- SparseSet: Add generic KeyType parameter
- QuadTree: Add QuadTreeConfig for comptime capacity/gutter
- ZIndexBuckets: Add generic ZIndexType parameter
- FloydWarshall: Add generic DistanceType parameter
- AStar: Add generic WeightType parameter
- FloydWarshallOptimized: Add configurable vector_width

Data structure optimizations (AStar):
- Replace closed_set HashMap with DynamicBitSet (~32x smaller)
- Replace g_score/came_from HashMaps with flat arrays (O(1) direct indexing)
- Replace positions/ids/reverse_ids HashMaps with SparseSet

Also fixes PR review comments:
- Atomic resizing in SparseSet using alloc+memcpy+free pattern
- u32 overflow check before @intcast
- Add missing errdefer for dense_values
- Fix misleading test name

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
QuadTree now requires a config parameter: QuadTree(T, .{})

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread src/a_star.zig
Comment thread src/floyd_warshall.zig
- z_index_buckets: Fix item loss when insert fails after remove in changeZIndex
  by inserting first, then removing only on success
- floyd_warshall_optimized: Fix double-free on partial allocation in clean()
  by allocating both arrays before freeing old ones
- floyd_warshall_optimized: Fix thread spawn failure use-after-free by tracking
  spawned threads and signaling sync counters before joining on failure
- floyd_warshall: Fix memory leak in clean() by adding errdefer for local RowLists
- floyd_warshall: Fix panic after allocation error in addEdgeWithMapping by
  propagating errors instead of catching and continuing
- a_star: Fix memory leak in init() by using errdefer for SparseSet allocations
- build.zig.zon: Update zspec hash to 0.6.0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apotema

apotema commented Jan 8, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

Comment thread src/floyd_warshall.zig
Comment thread src/floyd_warshall.zig
// Each thread that owns row k signals all threads
if (k >= start_row and k < end_row) {
_ = sync_counters[k + 1].fetchAdd(thread_count_u32, .release);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parallel Floyd-Warshall has data race between iterations

High Severity

The parallel synchronization allows threads to proceed to iteration k+1 before all threads finish iteration k. Only the thread owning row k signals sync_counters[k+1], immediately setting it to thread_count. This lets all threads (including itself) start iteration k+1 while other threads may still be processing iteration k. When Thread A in iteration k+1 reads dist[j][k+1] while Thread B in iteration k is still writing to row j, a data race occurs that can produce incorrect shortest path results.

Fix in Cursor Fix in Web

Comment thread src/a_star.zig
const new_id = self.newKey();
try self.ids.put(entity, new_id);
try self.reverse_ids.put(new_id, entity);
return new_id;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mapping becomes inconsistent if second put fails

Medium Severity

In getOrCreateMapping, if self.ids.put(entity, new_id) succeeds but self.reverse_ids.put(new_id, entity) fails due to OOM, the ids map contains a forward mapping while reverse_ids lacks the corresponding reverse mapping. This leaves the data structure in an inconsistent state where findPathWithMapping can find internal IDs via ids but cannot translate them back to entity IDs via reverse_ids. The same issue exists in FloydWarshallOptimized.addEdgeWithMapping.

Additional Locations (1)

Fix in Cursor Fix in Web

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a SparseSet data structure and a suite of other valuable utilities, including A* and Floyd-Warshall pathfinding algorithms, a HookDispatcher for comptime events, and ZON coercion helpers. The code is well-structured, documented, and includes tests for the new functionality. The addition of SparseSet is particularly beneficial for performance-sensitive applications.

My review focuses on improving robustness and performance in a few areas:

  • Error Handling: An error in a_star.zig is logged but not propagated, which could lead to silent failures.
  • State Consistency: Another part of a_star.zig could silently produce an incorrect path if its internal state becomes inconsistent.
  • Performance: The non-optimized floyd_warshall.zig contains an inefficient O(N) lookup that can be optimized to O(1).
  • Clarity: A comment in the parallel implementation of floyd_warshall_optimized.zig could be rephrased for better clarity and maintainability.

Overall, this is a strong contribution. Addressing these points will make the new utilities even more robust and performant.

Comment thread src/a_star.zig Outdated
Comment on lines +164 to +169
pub fn addEdge(self: *Self, u: u32, v: u32, w: WeightType) void {
if (u >= self.adjacency.items.len or v >= self.adjacency.items.len) return;
self.adjacency.items[u].append(self.allocator, .{ .to = v, .weight = w }) catch |err| {
std.log.err("Error adding edge: {any}\n", .{err});
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The catch block here logs an allocation error but swallows it, preventing it from propagating to the caller. This can lead to an inconsistent graph state and silent failures if adding an edge fails. The function should be modified to return !void and use try to propagate the error. Consequently, the call to addEdge in addEdgeWithMapping will also need to be updated to use try.

        pub fn addEdge(self: *Self, u: u32, v: u32, w: WeightType) !void {
            if (u >= self.adjacency.items.len or v >= self.adjacency.items.len) return;
            try self.adjacency.items[u].append(self.allocator, .{ .to = v, .weight = w });
        }

Comment thread src/a_star.zig Outdated
if (cost != null) {
path.clearRetainingCapacity();
for (internal_path.items) |internal_id| {
const entity = self.reverse_ids.get(internal_id) orelse continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using orelse continue here silently skips a node in the path if its reverse ID mapping is not found. This indicates a potential data corruption or a bug in the ID mapping logic, as every internal ID in a valid path should have a corresponding reverse mapping. Instead of failing silently, this should trigger a panic (especially in debug builds) to immediately flag the inconsistent state.

                    const entity = self.reverse_ids.get(internal_id) orelse {
                        // This indicates an inconsistent state, as every internal ID
                        // in the path should have a reverse mapping.
                        std.debug.panic("Inconsistent A* state: reverse ID not found for internal ID {}", .{internal_id});
                    };

Comment thread src/floyd_warshall.zig
Comment on lines +138 to +147
pub fn nextWithMapping(self: *Self, u: u32, v: u32) u32 {
const val = self.next(self.ids.get(u).?, self.ids.get(v).?);
var result = self.ids.iterator();
while (result.next()) |entry| {
if (entry.value_ptr.* == val) {
return entry.key_ptr.*;
}
}
return std.math.maxInt(u32);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation of nextWithMapping performs a linear scan (O(N)) over the ids hash map to find the external entity ID corresponding to the internal next hop ID. This is highly inefficient for large graphs.

To achieve an O(1) lookup, a reverse mapping from internal ID to external entity ID should be maintained. The AStar and FloydWarshallOptimized implementations in this pull request already use this superior approach with a reverse_ids map. I recommend applying the same pattern here.

Comment thread src/floyd_warshall_optimized.zig Outdated
}

// If we own row k, signal that row k+1 is ready
// Each thread that owns row k signals all threads

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This comment is slightly confusing. Since start_row and end_row define a unique range for each thread, only one thread can "own" row k. The phrase "Each thread that owns row k" could be misinterpreted. A clearer comment would improve the maintainability of this complex parallel logic.

                // The thread that owns row k signals that the next stage is ready for all threads.

apotema and others added 3 commits January 8, 2026 17:21
Migrated tests from 7 source files to dedicated test files:
- sparse_set_test.zig
- z_index_buckets_test.zig
- floyd_warshall_test.zig
- floyd_warshall_optimized_test.zig
- a_star_test.zig
- heuristics_test.zig
- zon_coercion_test.zig

Tests now use zspec format with expect assertions instead of std.testing.
All 89 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- floyd_warshall.zig:
  - Fix errdefer use-after-free in clean() with ownership tracking
  - Fix silent path failures in setPathWithMapping (return error.PathNotFound)
  - Add reverse_ids map for O(1) reverse lookups (was O(N) linear scan)
  - Add errdefer rollback for mapping consistency in addEdgeWithMapping

- floyd_warshall_optimized.zig:
  - Add documentation explaining why parallel algorithm is data-race safe
  - Clarify comment about sync counter signaling mechanism

- a_star.zig:
  - Fix mapping inconsistency when reverse_ids.put fails after ids.put
  - Change addEdge to return error instead of swallowing allocation errors
  - Fix orelse continue in findPathWithMapping (return CorruptedMapping error)
  - Add PathError type for setPathWithMapping to return PathNotFound

- tests/a_star_test.zig:
  - Update tests to handle new error-returning addEdge signature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update CI workflow to use Zig 0.15.0
- Update minimum_zig_version in build.zig.zon

Required because std.array_list.Managed API was introduced in 0.15
(0.14 uses std.ArrayList directly)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apotema
apotema force-pushed the 7-add-sparse-set branch 2 times, most recently from 2107b19 to f8e10ba Compare January 8, 2026 20:40
Update CI to use mlugg/setup-zig@v2 with Zig 0.15.2
(v2 supports the 0.15.x development builds)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@apotema
apotema merged commit e901cd8 into main Jan 8, 2026
2 checks passed
@apotema
apotema deleted the 7-add-sparse-set branch January 8, 2026 20:47
try self.reverse_ids.put(key, v);
}
self.addEdge(self.ids.get(u).?, self.ids.get(v).?, w);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing errdefer causes inconsistent ID mappings on failure

Medium Severity

The addEdgeWithMapping function in the optimized Floyd-Warshall implementation lacks errdefer cleanup for the ids hash map. If reverse_ids.put(key, u) fails after ids.put(u, key) succeeds, the maps become inconsistent - ids will contain the mapping but reverse_ids won't have the reverse mapping. The non-optimized FloydWarshall implementation correctly uses errdefer _ = self.ids.remove(u) to handle this case.

Fix in Cursor Fix in Web

}
}
try path_list.append(v_node);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial path data left in list on error

Medium Severity

The setPathWithMapping and setPathWithMappingUnmanaged functions don't clean up partial path data when returning error.NoPathFound. After appending nodes to path_list in the while loop, if no path exists, the error is returned but the partially-filled list isn't restored. The non-optimized version correctly saves initial_len and calls path_list.shrinkRetainingCapacity(initial_len) before returning the error, ensuring callers don't receive corrupted state.

Additional Locations (1)

Fix in Cursor Fix in Web

Comment thread src/floyd_warshall.zig

size: u32 = 100,
graph: GraphList,
path: GraphList,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path matrix type mismatch causes overflow for small DistanceType

Medium Severity

The path matrix reuses DistanceType to store vertex indices, but these are semantically different. When size > maxInt(DistanceType) (e.g., using FloydWarshall(u8) with more than 255 vertices), the @intCast(j) in clean() will overflow and panic. The optimized version correctly uses u32 for the next-hop matrix. The path matrix should use a dedicated index type like u32 rather than conflating it with DistanceType.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add common data structures from labelle-engine

1 participant