A high-performance, memory-efficient Compressed Sparse Row (CSR) graph data structure plugin for Unreal Engine 5.
CSR is a compact representation for sparse graphs that stores all neighbor indices in a single contiguous array, with a separate offset array indicating where each node's neighbors begin. This layout provides:
- Optimal cache locality for neighbor iteration
- Minimal memory overhead compared to adjacency lists or matrices
- O(1) access to any node's neighbor list
- Cache-friendly traversal for graph algorithms
TCSRGraph<IndexType>- Basic CSR graph for adjacency relationshipsTCSRGraphWithEdgeData<IndexType, EdgeDataType>- CSR graph with per-edge payload data (weights, labels, etc.)- Optional neighbor sorting for deterministic iteration
- Optional duplicate edge removal with "first edge wins" semantics
- Header-only implementation - no linking required
- Zero external dependencies beyond Unreal Engine core
- Copy the
CompressedSparseRowfolder to your project'sPlugins/directory - Regenerate project files
- Add
"CompressedSparseRow"to your module'sBuild.cs:
PublicDependencyModuleNames.AddRange(new string[] { "CompressedSparseRow" });- Copy the
CompressedSparseRowfolder toEngine/Plugins/Runtime/ - Regenerate project files
#include "CompressedSparseRow.h"
// Create a graph with 4 nodes
TCSRGraph<int32> Graph;
TArray<TCSRGraph<int32>::FEdge> Edges;
// Add directed edges
Edges.Add({0, 1}); // 0 -> 1
Edges.Add({0, 2}); // 0 -> 2
Edges.Add({1, 2}); // 1 -> 2
Edges.Add({2, 3}); // 2 -> 3
Edges.Add({3, 0}); // 3 -> 0 (cycle)
// Build the CSR structure
// Parameters: NumNodes, Edges, bSortNeighbors, bRemoveDuplicates
Graph.Build(4, Edges, true, true);
// Iterate neighbors of node 0
for (int32 Neighbor : Graph.GetNeighbors(0))
{
UE_LOG(LogTemp, Log, TEXT("Node 0 -> Node %d"), Neighbor);
}#include "CompressedSparseRow.h"
// Graph with float edge weights
TCSRGraphWithEdgeData<int32, float> WeightedGraph;
TArray<TCSRGraphWithEdgeData<int32, float>::FEdge> Edges;
// Add weighted edges
Edges.Add({0, 1, 1.5f}); // 0 -> 1, weight 1.5
Edges.Add({0, 2, 2.0f}); // 0 -> 2, weight 2.0
Edges.Add({1, 2, 0.5f}); // 1 -> 2, weight 0.5
WeightedGraph.Build(3, Edges);
// Access neighbors and weights together
TConstArrayView<int32> Neighbors = WeightedGraph.GetNeighbors(0);
TConstArrayView<float> Weights = WeightedGraph.GetEdgeData(0);
for (int32 i = 0; i < Neighbors.Num(); ++i)
{
UE_LOG(LogTemp, Log, TEXT("Edge 0 -> %d, Weight: %f"),
Neighbors[i], Weights[i]);
}// Edge with multiple properties
struct FMyEdgeData
{
float Weight;
int32 Category;
bool bEnabled;
};
TCSRGraphWithEdgeData<int32, FMyEdgeData> Graph;
TArray<TCSRGraphWithEdgeData<int32, FMyEdgeData>::FEdge> Edges;
Edges.Add({0, 1, {1.0f, 2, true}});
Edges.Add({0, 2, {0.5f, 1, false}});
Graph.Build(3, Edges);| Method | Description |
|---|---|
Build(NumNodes, Edges, bSort, bDedup) |
Constructs the CSR from an edge list |
GetNeighbors(Node) |
Returns TConstArrayView of neighbor indices |
GetDegree(Node) |
Returns the out-degree (neighbor count) for a node |
GetNumNodes() |
Returns the number of nodes |
GetNumEdges() |
Returns the total number of edges |
IsValidNode(Node) |
Checks if a node index is valid |
GetOffsets() |
Returns raw offset array for direct access |
GetNeighborArray() |
Returns raw neighbor array for direct access |
Reset() |
Clears the graph and releases memory |
Empty(Slack) |
Clears the graph but retains allocated memory |
All methods from TCSRGraph, plus:
| Method | Description |
|---|---|
GetEdgeData(Node) |
Returns TConstArrayView of edge payloads (parallel to neighbors) |
GetEdgeDataArray() |
Returns raw edge data array for direct access |
Example: 4-node graph with edges 0->1, 0->3, 1->0, 1->2, 1->4, 3->0, 3->1, 3->2
Offsets: [0, 2, 5, 5, 8] (length = NumNodes + 1)
Neighbors: [1, 3, 0, 2, 4, 0, 1, 2] (length = NumEdges)
Node 0: Neighbors[0..2) = {1, 3}
Node 1: Neighbors[2..5) = {0, 2, 4}
Node 2: Neighbors[5..5) = {} (no outgoing edges)
Node 3: Neighbors[5..8) = {0, 1, 2}
| Operation | Complexity |
|---|---|
| Build (unsorted) | O(E) |
| Build (sorted) | O(E log(MaxDegree)) |
| Get neighbors | O(1) |
| Get degree | O(1) |
| Iterate neighbors | O(degree) |
| Memory usage | O(V + E) |
Where V = number of nodes, E = number of edges.
- AI Navigation: Store connectivity between navmesh polygons
- Pathfinding: Dijkstra, A*, BFS, DFS implementations
- Dependency Graphs: Module loading, task scheduling
- Social Networks: Follower/following relationships
- Spatial Queries: K-nearest neighbors, range queries
- Flow Networks: Max flow, min cut algorithms
- Scene Graphs: Parent-child relationships with edge metadata
- Static structure: Not designed for frequent edge insertion/removal after
Build() - Directed edges: For undirected graphs, add edges in both directions
- int32 edge limit: Total edges must fit in int32 (~2 billion)
- Signed IndexType: IndexType must be a signed integer type
- Unreal Engine 5.0+
- C++17 or later
Copyright Hitbox Games, LLC. All Rights Reserved.
Licensed under the MIT License. See LICENSE file for details.