Struct ScyllaGraphDOTS
DOTS/Burst-compatible fixed-capacity graph stored in Compressed Sparse Row (CSR) format. Designed for read-heavy traversal in Unity Jobs and Burst-compiled code where managed heap allocations and garbage collection are unacceptable.
Lifecycle - two-phase build pattern:
-
Build phase: Call AddNode(int, ScyllaGraphNodeFlags) and AddEdge(int, int, float)
to populate the graph. During this phase, nodes and edges are accumulated in
NativeListbuffers for efficient appending. -
Finalize phase: Call BuildCSR() once all nodes and
edges have been added. This compacts the build lists into the immutable CSR
arrays and disposes the temporary build buffers. After this point,
AddNode(int, ScyllaGraphNodeFlags) and AddEdge(int, int, float) return
false.
CSR layout: Outgoing edges for node at dense index i are
stored in _edges[_edgeOffsets[i] .. _edgeOffsets[i+1]). This gives O(1)
neighbor access with a single range read - ideal for cache-coherent traversal in
job workers.
Undirected graphs: Each logical edge is stored twice internally (once in each direction) so that neighbor queries on either endpoint return the correct results. EdgeCount always reflects the logical count (each undirected edge counted once).
Disposal: This struct owns native memory and must be disposed by calling Dispose() when no longer needed. Failing to do so leaks unmanaged memory.
WARNING - shallow copy hazard: As a struct containing
NativeArray and NativeList fields, copying this value by assignment
creates a shallow copy where both copies reference the same native memory buffers.
Disposing one copy invalidates the other. Always pass by ref and avoid
struct assignment.
Implements
Inherited Members
Namespace: Scylla.Core.Structures
Assembly: ScyllaCore.dll
Syntax
[BurstCompile]
public struct ScyllaGraphDOTS : IDisposable
Remarks
// Typical usage: build then traverse.
var graph = ScyllaGraphDOTS.CreateBuilder()
.WithNodeCapacity(100)
.WithEdgeCapacity(400)
.Directed()
.WithAllocator(Allocator.Persistent)
.Build();
graph.AddNode(0, ScyllaGraphNodeFlags.Walkable);
graph.AddNode(1, ScyllaGraphNodeFlags.Walkable);
graph.AddEdge(0, 1, 1.0f);
graph.BuildCSR();
// CSR is now ready for traversal in Burst jobs.
var neighbors = new NativeArray<ScyllaGraphEdge>(graph.GetNeighborCount(0), Allocator.Temp);
graph.GetNeighborsNonAlloc(0, neighbors);
neighbors.Dispose();
graph.Dispose();
Constructors
ScyllaGraphDOTS(int, int, bool, Allocator)
Creates a new DOTS graph and allocates all native build-phase containers using the specified capacities and allocator. The graph starts in the build phase (not finalized) and is directed or undirected as specified.
Negative capacity values are clamped to zero. The underlying
NativeList and NativeHashMap containers will grow dynamically
beyond the initial capacity as needed, so the capacity values serve as
initial hints to reduce re-allocation during the build phase rather than
as hard limits.
Prefer the fluent CreateBuilder() API over calling this constructor directly, particularly when the graph configuration may change in the future.
Declaration
public ScyllaGraphDOTS(int nodeCapacity, int edgeCapacity, bool isDirected, Allocator allocator)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeCapacity | Initial capacity hint for the node lists and the ID-to-index hash map. Values below zero are clamped to zero. |
| int | edgeCapacity | Initial capacity hint for the build-phase edge list. For undirected graphs, the list may store up to twice this many entries internally (one entry per direction). Values below zero are clamped to zero. |
| bool | isDirected |
|
| Allocator | allocator | The Unity memory allocator to use for all native containers. Must not be Unity.Collections.Allocator.None or Unity.Collections.Allocator.Invalid. Use Unity.Collections.Allocator.Persistent for long-lived graphs and Unity.Collections.Allocator.TempJob or Unity.Collections.Allocator.Temp for short-lived ones. |
Exceptions
| Type | Condition |
|---|---|
| ArgumentException | Thrown when |
Properties
EdgeCount
The number of logical edges in the graph. For undirected graphs, each edge is counted once (not twice), even though two directed entries are stored internally in the CSR representation. Reflects all edges added via AddEdge(int, int, float).
Declaration
public int EdgeCount { get; }
Property Value
| Type | Description |
|---|---|
| int | Non-negative integer; zero for an empty or disposed graph. |
IsCreated
Returns true if the underlying native buffers have been allocated and the
graph is safe to use. Delegates to the IsCreated property of the internal
NativeHashMap, which is created in the constructor and disposed last.
Always check this property before using a ScyllaGraphDOTS value that may have been default-initialized or disposed.
Declaration
public bool IsCreated { get; }
Property Value
| Type | Description |
|---|---|
| bool |
|
IsDirected
Whether edges in this graph have a direction (from-node to to-node). Set at construction time via the constructor or the ScyllaGraphDOTS.Builder.
When true, AddEdge(int, int, float) stores a single directed entry.
When false, AddEdge(int, int, float) stores both the forward and
reverse entries so that neighbor queries on either endpoint succeed.
Declaration
public bool IsDirected { get; }
Property Value
| Type | Description |
|---|---|
| bool |
|
IsFinalized
Returns true if BuildCSR() has been called and completed
successfully, locking the graph into its compact CSR representation for
read-only traversal. Once finalized, AddNode(int, ScyllaGraphNodeFlags) and
AddEdge(int, int, float) become no-ops.
Declaration
public bool IsFinalized { get; }
Property Value
| Type | Description |
|---|---|
| bool |
|
NodeCount
The number of logical nodes in the graph. Reflects all nodes added via AddNode(int, ScyllaGraphNodeFlags) regardless of whether BuildCSR() has been called.
Declaration
public int NodeCount { get; }
Property Value
| Type | Description |
|---|---|
| int | Non-negative integer; zero for an empty or disposed graph. |
Methods
AddEdge(int, int, float)
Adds a weighted directed or undirected edge between two existing nodes during the build phase.
For undirected graphs (IsDirected is false), both the
forward edge (fromID to toID) and the
reverse edge (toID to fromID) are stored
in the internal build list so that neighbor queries on either endpoint work
correctly after BuildCSR(). Self-loop edges (fromID
equals toID) are stored only once even for undirected graphs.
EdgeCount is incremented by one per call to this method regardless of graph directedness, reflecting the logical (user-facing) edge count.
Declaration
public bool AddEdge(int fromID, int toID, float weight = 1)
Parameters
| Type | Name | Description |
|---|---|---|
| int | fromID | The source node ID. Must already exist in the graph (added via AddNode(int, ScyllaGraphNodeFlags)). |
| int | toID | The target node ID. Must already exist in the graph (added via AddNode(int, ScyllaGraphNodeFlags)). |
| float | weight | The cost or distance associated with traversing this edge. Defaults to
|
Returns
| Type | Description |
|---|---|
| bool |
|
AddNode(int, ScyllaGraphNodeFlags)
Adds a node with the specified ID and flags to the graph during the build phase. The node ID must be unique within this graph; duplicate IDs are rejected.
Internally, the node is appended to the build-phase lists and a mapping from
the node ID to its dense index (0..NodeCount-1) is recorded in the
hash map. This dense index is used by the CSR arrays after
BuildCSR() is called.
Declaration
public bool AddNode(int nodeID, ScyllaGraphNodeFlags flags = ScyllaGraphNodeFlags.Walkable)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The unique integer identifier for this node. Must not already exist in the graph. Node IDs do not need to be contiguous or sequential. |
| ScyllaGraphNodeFlags | flags | Per-node flags describing traversal properties such as walkability. Defaults to Walkable. |
Returns
| Type | Description |
|---|---|
| bool |
|
BuildCSR()
Finalizes the graph by converting the build-phase lists into compact Compressed
Sparse Row (CSR) arrays. After this call the graph is immutable: AddNode(int, ScyllaGraphNodeFlags)
and AddEdge(int, int, float) will return false, and the read-phase methods
(GetNeighborCount(int), GetNeighborsNonAlloc(int, NativeArray<ScyllaGraphEdge>),
TryGetNodeFlags(int, out ScyllaGraphNodeFlags)) become available.
CSR construction steps (O(N + E)):
-
Copy build-list data into final compact
NativeArraybuffers for node IDs and node flags. - Count the number of outgoing edges per source node via a counting pass over the build edge list.
-
Build the
_edgeOffsetsarray as an inclusive prefix sum of the per-node edge counts, giving each node a contiguous slot in the edge array. -
Scatter edges into the final
_edgesarray using per-node write cursors to place each edge at the correct offset.
On success, the three build-phase NativeList buffers are disposed.
If an exception is thrown during construction, any partially allocated CSR
arrays are cleaned up and the exception is re-thrown; the graph remains in
an unusable finalized state (caller should dispose).
Calling this method on an already-finalized graph is a no-op.
Declaration
public void BuildCSR()
ContainsNode(int)
Returns true if the graph contains a node with the specified ID.
This lookup is O(1) and is valid both before and after BuildCSR(),
because the ID-to-index hash map is populated during the build phase and retained
after finalization.
Declaration
public bool ContainsNode(int nodeID)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The node ID to look up. |
Returns
| Type | Description |
|---|---|
| bool |
|
CreateBuilder()
Returns a new ScyllaGraphDOTS.Builder instance for constructing a ScyllaGraphDOTS with a fluent configuration API.
Prefer this factory method over the constructor directly when the graph configuration may need to change, when the allocator selection is deferred, or when you want self-documenting construction code.
Declaration
public static ScyllaGraphDOTS.Builder CreateBuilder()
Returns
| Type | Description |
|---|---|
| ScyllaGraphDOTS.Builder | A new ScyllaGraphDOTS.Builder with default settings: directed graph, 64 node capacity, 256 edge capacity, and no allocator set (must be configured via WithAllocator(Allocator) before calling Build()). |
Remarks
var graph = ScyllaGraphDOTS.CreateBuilder()
.WithNodeCapacity(256)
.WithEdgeCapacity(1024)
.Undirected()
.WithAllocator(Allocator.Persistent)
.Build();
Dispose()
Releases all native memory held by this graph instance. Disposes both the build-phase lists (if still allocated, i.e. BuildCSR() was never called or threw an exception) and the finalized CSR arrays (if present).
After disposal, IsCreated returns false,
NodeCount and EdgeCount return zero, and
IsFinalized returns false. Any further calls to graph
methods on a disposed instance will produce undefined behavior.
Each native container is checked with IsCreated before disposal to
make this method safe to call on partially constructed instances (e.g. if the
constructor threw after some containers were allocated).
Declaration
public void Dispose()
GetNeighborCount(int)
Returns the number of outgoing edges (out-degree) for the specified node.
Computed in O(1) from the CSR offset array as
_edgeOffsets[index + 1] - _edgeOffsets[index].
For undirected graphs, the out-degree of a node equals the number of adjacent nodes (since both directions are stored explicitly).
Declaration
public int GetNeighborCount(int nodeID)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node to query. |
Returns
| Type | Description |
|---|---|
| int | The number of outgoing edges from the node, or |
GetNeighborsNonAlloc(int, NativeArray<ScyllaGraphEdge>)
Copies outgoing edges for the specified node into a caller-provided Unity.Collections.NativeArray<T> buffer without any managed allocation.
This method uses Unity.Collections.NativeArray<T> instead of
Span<T> because Burst-compiled jobs cannot marshal
managed Span types across the job boundary. NativeArray is the
standard buffer type for DOTS/Burst interoperability.
If destination is smaller than the node's actual neighbor
count, only the first destination.Length edges are copied (silent
truncation). Call GetNeighborCount(int) first and size the buffer
accordingly to avoid truncation.
Declaration
public int GetNeighborsNonAlloc(int nodeID, NativeArray<ScyllaGraphEdge> destination)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node whose outgoing edges are requested. |
| NativeArray<ScyllaGraphEdge> | destination | Pre-allocated Unity.Collections.NativeArray<T> into which edges are written. Edges are copied starting at index 0. The array must be valid and created before calling this method. |
Returns
| Type | Description |
|---|---|
| int | The number of edges actually copied into |
TryGetNodeFlags(int, out ScyllaGraphNodeFlags)
Attempts to retrieve the ScyllaGraphNodeFlags for a node by its ID.
This method is valid both before and after BuildCSR(): in the build
phase it reads from the NativeList; after finalization it reads from the
compact NativeArray.
Declaration
public bool TryGetNodeFlags(int nodeID, out ScyllaGraphNodeFlags flags)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node whose flags are requested. |
| ScyllaGraphNodeFlags | flags | When this method returns |
Returns
| Type | Description |
|---|---|
| bool |
|