Class ScyllaCSRGraph<TNodeData, TEdgeData>
Immutable CSR graph that stores arbitrary user data of type TNodeData
per node and TEdgeData per edge, while delegating all topology
queries to an inner ScyllaCSRGraph.
Inherited Members
Namespace: Scylla.Core.Structures
Assembly: ScyllaCore.dll
Syntax
public sealed class ScyllaCSRGraph<TNodeData, TEdgeData> : IScyllaReadOnlyGraph<TNodeData, TEdgeData>, IScyllaReadOnlyGraph<TNodeData>, IScyllaReadOnlyGraph, IScyllaGraph, IScyllaCollection
Type Parameters
| Name | Description |
|---|---|
| TNodeData | The type of user data stored per node. May be any reference or value type. |
| TEdgeData | The type of user data stored per edge. May be any reference or value type. For undirected graphs, each direction of a non-self-loop edge has its own data slot because both directions are stored as separate CSR entries. |
Remarks
All graph topology is owned by a wrapped ScyllaCSRGraph. This class adds two parallel data arrays:
-
_nodeDataArray- one entry per node, indexed by the same sorted-by-ID order used by the base graph. -
_edgeDataArray- one entry per edge entry in the CSR array, in the same CSR order. A local copy of the CSR_edgeOffsetsarray enables O(1) slice lookup for per-edge data without requiring the base graph to expose its internal arrays.
The internal _nodeIDToIndex and _edgeOffsets arrays intentionally
duplicate data already held inside ScyllaCSRGraph. This is an
intentional design trade-off that keeps the base graph's internals private while
still allowing O(1) typed data access.
Like ScyllaCSRGraph, this class is fully immutable after construction. Calling Clear() always throws InvalidOperationException.
The only way to create an instance is via CreateFromGraph(IScyllaReadOnlyGraph<TNodeData, TEdgeData>), which converts any IScyllaReadOnlyGraph<TNodeData, TEdgeData> into this frozen CSR representation.
Properties
Capabilities
Gets the feature and capability flags supported by this collection instance.
Declaration
public ScyllaCollectionCapabilities Capabilities { get; }
Property Value
| Type | Description |
|---|---|
| ScyllaCollectionCapabilities | A bitfield of ScyllaCollectionCapabilities flags describing which optional operations the concrete instance supports. The value None indicates that only the base contract (Count, IsEmpty, Clear()) is available. |
Remarks
Use Capabilities for feature detection in place of type-casting. For example:
if ((collection.Capabilities & ScyllaCollectionCapabilities.SupportsPeek) != 0)
{
// safe to call TryPeek through IScyllaCollection<T>
}
Synchronized wrappers cache the capability flags of their inner collection at construction time, so the value returned is stable and does not require synchronization.
See Also
Count
Gets the number of items currently contained in the collection.
Declaration
public int Count { get; }
Property Value
| Type | Description |
|---|---|
| int | A non-negative integer representing the current element count.
Returns |
Remarks
On unsynchronized collections, Count is not thread-safe and may return a stale
value when accessed concurrently. On synchronized wrappers, reading Count is
protected by the internal lock.
EdgeCount
Gets the number of edges currently contained in the graph.
Declaration
public int EdgeCount { get; }
Property Value
| Type | Description |
|---|---|
| int | A non-negative integer representing the total edge count.
Returns |
Remarks
For undirected graphs, each conceptual edge is counted exactly once, even though the underlying storage may maintain adjacency entries in both directions. For directed graphs, each directed edge is counted independently, so a pair of reciprocal edges (A->B and B->A) contributes 2 to the count.
IsEmpty
Gets a value indicating whether the collection contains no items.
Declaration
public bool IsEmpty { get; }
Property Value
| Type | Description |
|---|---|
| bool |
|
Remarks
This is a convenience property equivalent to Count == 0. Prefer it over comparing
Count directly when the actual count is not needed, as some implementations may
compute it more efficiently.
NodeCount
Gets the number of nodes currently contained in the graph.
Declaration
public int NodeCount { get; }
Property Value
| Type | Description |
|---|---|
| int | A non-negative integer representing the total node count.
Returns |
Remarks
This property is the canonical source of the node count and is also surfaced through
the inherited Count property. For clarity, prefer
NodeCount over Count in graph-specific code so the intent is explicit.
Properties
Gets the structural property flags that describe this graph instance.
Declaration
public ScyllaGraphProperties Properties { get; }
Property Value
| Type | Description |
|---|---|
| ScyllaGraphProperties | A bitfield of ScyllaGraphProperties flags set at construction time. The value is stable for the lifetime of the graph and does not change as nodes or edges are added or removed. |
Remarks
Use this property for feature detection rather than type-casting. For example,
check Directed before interpreting the
directionality of edges returned by GetNeighborsNonAlloc(int, Span<ScyllaGraphEdge>), or check
Weighted before relying on edge weight values.
Pathfinding and traversal algorithms in ScyllaGraphAlgorithms inspect this
property to select the appropriate implementation branch.
See Also
SyncRoot
Gets the synchronization root object used to externally coordinate multi-step operations with this collection.
Declaration
public object SyncRoot { get; }
Property Value
| Type | Description |
|---|---|
| object | A non-null object suitable for use with |
Remarks
When this collection is a synchronized wrapper, SyncRoot must be non-null and
stable for the entire lifetime of the wrapper. All internal operations on the wrapper
must lock on this same object. This allows callers to perform atomic multi-step
sequences:
lock (collection.SyncRoot)
{
if (!collection.IsEmpty)
typedCollection.TryRemove(out var item);
}
For unsynchronized collections (SyncRoot == null), callers are responsible for
supplying and consistently applying their own external synchronization mechanism.
A null SyncRoot does not mean the collection is thread-safe;
consult ThreadSafety for the authoritative guarantee.
See Also
ThreadSafety
Gets the thread-safety guarantees provided by this collection instance.
Declaration
public ScyllaCollectionThreadSafety ThreadSafety { get; }
Property Value
| Type | Description |
|---|---|
| ScyllaCollectionThreadSafety | A ScyllaCollectionThreadSafety value indicating whether the collection is unsynchronized, synchronized via a lock, or safe for lock-free concurrent access. Most core implementations return Unsynchronized. Synchronized wrappers return Synchronized. |
Remarks
Always check this property (or SyncRoot) before assuming a collection is
safe for concurrent use. Do not rely solely on the absence of a non-null SyncRoot
to infer thread safety; use this property as the authoritative source.
See Also
Methods
Clear()
Not supported. ScyllaCSRGraph<TNodeData, TEdgeData> is immutable and cannot be cleared.
Declaration
public void Clear()
Exceptions
| Type | Condition |
|---|---|
| InvalidOperationException | Always thrown. Use CreateFromGraph(IScyllaReadOnlyGraph<TNodeData, TEdgeData>) to create a new graph instead. |
ContainsEdge(int, int)
Determines whether the graph contains an edge between the specified nodes.
Declaration
public bool ContainsEdge(int fromID, int toID)
Parameters
| Type | Name | Description |
|---|---|---|
| int | fromID | The ID of the source (or first) node. |
| int | toID | The ID of the target (or second) node. |
Returns
| Type | Description |
|---|---|
| bool |
|
Remarks
For undirected graphs, the argument order does not matter: calling
ContainsEdge(A, B) and ContainsEdge(B, A) always return the same
result. For directed graphs, only the edge in the specified direction is checked;
the reverse direction is independent.
If either fromID or toID does not
correspond to an existing node, the method returns false.
ContainsNode(int)
Determines whether the graph contains a node with the specified ID.
Declaration
public bool ContainsNode(int nodeID)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The integer ID of the node to look up. |
Returns
| Type | Description |
|---|---|
| bool |
|
CopyAllEdges(Span<ScyllaGraphEdge>)
Copies all edge descriptors from the graph into the provided span without allocating any managed memory.
Declaration
public int CopyAllEdges(Span<ScyllaGraphEdge> destination)
Parameters
| Type | Name | Description |
|---|---|---|
| Span<ScyllaGraphEdge> | destination | The caller-supplied span into which ScyllaGraphEdge descriptors will be written. The span should be at least EdgeCount elements long to receive all edges; if it is smaller, only as many edges as fit are copied. |
Returns
| Type | Description |
|---|---|
| int | The number of ScyllaGraphEdge descriptors written to
|
Remarks
For undirected graphs, each conceptual edge is copied exactly once. The FromID and ToID fields reflect the canonical storage direction and may not match the order in which the edge was originally added.
For directed graphs, each directed edge appears as a separate descriptor; reciprocal edges (A->B and B->A) produce two distinct entries.
The order of edges within destination is
implementation-defined and should not be relied upon. For typed edge graphs,
retrieve per-edge user data via
TryGetEdgeData(int, int, out TEdgeData)
after copying the base descriptors.
See Also
CopyAllNodes(Span<ScyllaGraphNode>)
Copies all node descriptors from the graph into the provided span without allocating any managed memory.
Declaration
public int CopyAllNodes(Span<ScyllaGraphNode> destination)
Parameters
| Type | Name | Description |
|---|---|---|
| Span<ScyllaGraphNode> | destination | The caller-supplied span into which ScyllaGraphNode descriptors will be written. The span should be at least NodeCount elements long to receive all nodes; if it is smaller, only as many nodes as fit are copied. |
Returns
| Type | Description |
|---|---|
| int | The number of ScyllaGraphNode descriptors written to
|
Remarks
The order in which nodes are written into destination is
implementation-defined. Callers must not assume any particular ordering.
For typed graphs, this method copies only the base descriptor (ID and flags). To retrieve per-node user data alongside the descriptor, call TryGetNodeData(int, out TNodeData) for each node after copying. See the remarks on that interface for guidance on performing typed bulk operations.
See Also
CreateFromGraph(IScyllaReadOnlyGraph<TNodeData, TEdgeData>)
Creates a new immutable ScyllaCSRGraph<TNodeData, TEdgeData> by converting an existing IScyllaReadOnlyGraph<TNodeData, TEdgeData> into CSR format, preserving both the graph topology and all per-node and per-edge user data.
Declaration
public static ScyllaCSRGraph<TNodeData, TEdgeData> CreateFromGraph(IScyllaReadOnlyGraph<TNodeData, TEdgeData> source)
Parameters
| Type | Name | Description |
|---|---|---|
| IScyllaReadOnlyGraph<TNodeData, TEdgeData> | source | The typed source graph to convert. Must not be |
Returns
| Type | Description |
|---|---|
| ScyllaCSRGraph<TNodeData, TEdgeData> | A new ScyllaCSRGraph<TNodeData, TEdgeData> containing the same nodes,
edges, per-node data, and per-edge data as |
Remarks
Internally, this method:
- Delegates to CreateFromGraph(IScyllaReadOnlyGraph) to build the untyped CSR base graph.
-
Queries each node's data via
TryGetNodeData(int, out TNodeData). If a
node's data is not found, the slot is left at
default(TNodeData). - Queries each node's typed outgoing edges via GetNeighborsNonAlloc(int, Span<ScyllaGraphEdge<TEdgeData>>) using a reusable, dynamically-resized buffer. Edge data values are extracted from the returned ScyllaGraphEdge<TEdgeData> structs and stored in a flat array parallel to the CSR edge order of the base graph.
- Builds a local copy of the prefix-sum offset array to mirror the base graph's internal CSR layout for O(1) per-edge data access.
Exceptions
| Type | Condition |
|---|---|
| ArgumentNullException | Thrown when |
GetNeighborCount(int)
Returns the number of outgoing neighbors (adjacent nodes) for the specified node.
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 specified node, or |
Remarks
The sentinel value -1 is used instead of a separate bool-returning
overload to keep the API lightweight for high-frequency graph traversal loops.
Callers that need to distinguish "zero neighbors" from "node not found" should
check the return value against -1 explicitly.
For undirected graphs, every stored edge is counted in both directions, so the neighbor count equals the degree of the node. For directed graphs, only outgoing edges are counted (the out-degree).
GetNeighborsNonAlloc(int, Span<ScyllaGraphEdge<TEdgeData>>)
Copies the outgoing typed edges of the specified node into a caller-provided span,
combining each untyped ScyllaGraphEdge with its corresponding
TEdgeData value from _edgeDataArray.
Declaration
public int GetNeighborsNonAlloc(int nodeID, Span<ScyllaGraphEdge<TEdgeData>> destination)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node whose outgoing typed edges should be retrieved. |
| Span<ScyllaGraphEdge<TEdgeData>> | destination | Span into which the ScyllaGraphEdge<TEdgeData> descriptors will be written. Must be allocated by the caller; no allocation is performed by this method (unless the neighbor count exceeds 128, in which case an internal temporary array is heap-allocated). |
Returns
| Type | Description |
|---|---|
| int | The number of typed edges written into |
Remarks
If the number of outgoing edges is 128 or fewer, a stack-allocated buffer is used to fetch the untyped edge descriptors from the base graph, avoiding any heap allocation. For more than 128 edges, a temporary heap array is used instead.
If destination is smaller than the actual neighbor count,
only as many typed edges as fit are written. Use
GetNeighborCount(int) to pre-size the buffer if all edges
are needed.
GetNeighborsNonAlloc(int, Span<ScyllaGraphEdge>)
Copies the outgoing edges of the specified node into the provided span without allocating any managed memory.
Declaration
public int GetNeighborsNonAlloc(int nodeID, Span<ScyllaGraphEdge> destination)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node whose neighbors to retrieve. |
| Span<ScyllaGraphEdge> | destination | The caller-supplied span into which ScyllaGraphEdge descriptors will be written. The span must be allocated by the caller and may reside on the stack or in a rented array to avoid heap allocations in hot paths. |
Returns
| Type | Description |
|---|---|
| int | The number of edge descriptors written to |
Remarks
This method follows the same sentinel convention as GetNeighborCount(int):
a return value of -1 means "node not found", while 0 means "node
exists but has no outgoing edges".
If destination is smaller than the node's actual neighbor
count, only the first destination.Length edges are written. Callers
should use GetNeighborCount(int) to pre-size the span when all edges
must be retrieved. To avoid the extra call in tight loops, allocate a span large
enough for the maximum expected degree.
The order of edges written into destination is
implementation-defined and should not be relied upon across calls.
For graphs that also carry per-edge user data, prefer the typed overload GetNeighborsNonAlloc(int, Span<ScyllaGraphEdge<TEdgeData>>) which fills ScyllaGraphEdge<TEdgeData> descriptors instead. The untyped overload defined here remains available through the base interface for callers that do not need edge data.
See Also
TryGetEdge(int, int, out ScyllaGraphEdge)
Attempts to retrieve the edge descriptor for the edge between two nodes.
Declaration
public bool TryGetEdge(int fromID, int toID, out ScyllaGraphEdge edge)
Parameters
| Type | Name | Description |
|---|---|---|
| int | fromID | The ID of the source node. For undirected graphs, argument order is irrelevant. |
| int | toID | The ID of the target node. For undirected graphs, argument order is irrelevant. |
| ScyllaGraphEdge | edge | When this method returns |
Returns
| Type | Description |
|---|---|
| bool |
|
Remarks
For undirected graphs, the fromID and toID
arguments are interchangeable. The returned FromID
and ToID will match the canonical storage direction
used internally, which may differ from the argument order supplied.
For directed graphs, only the edge in the direction
(fromID -> toID) is looked up.
The reverse direction is independent; call this method again with swapped
arguments to check the reverse edge separately.
To check existence without retrieving the descriptor, prefer ContainsEdge(int, int) which may be implemented more efficiently in some graph representations.
See Also
TryGetEdgeData(int, int, out TEdgeData)
Attempts to retrieve the user data associated with the edge from one node to another.
Declaration
public bool TryGetEdgeData(int fromID, int toID, out TEdgeData data)
Parameters
| Type | Name | Description |
|---|---|---|
| int | fromID | ID of the source node. |
| int | toID | ID of the target node. |
| TEdgeData | data | When this method returns |
Returns
| Type | Description |
|---|---|
| bool |
|
Remarks
To locate the edge data, this method first resolves fromID to
its node index via _nodeIDToIndex, then reads the CSR edge slice for that
node using _edgeOffsets. If the count of edges in the slice is 128 or
fewer, a stack-allocated buffer is used to avoid heap allocation; larger slices
fall back to a heap array. The edge entries are scanned linearly for a matching
ToID.
TryGetNodeData(int, out TNodeData)
Attempts to retrieve the user data associated with the specified node.
Declaration
public bool TryGetNodeData(int nodeID, out TNodeData data)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node whose data should be retrieved. |
| TNodeData | data | When this method returns |
Returns
| Type | Description |
|---|---|
| bool |
|
TryGetNodeFlags(int, out ScyllaGraphNodeFlags)
Attempts to retrieve the flags associated with the specified node.
Declaration
public bool TryGetNodeFlags(int nodeID, out ScyllaGraphNodeFlags flags)
Parameters
| Type | Name | Description |
|---|---|---|
| int | nodeID | The ID of the node to query. |
| ScyllaGraphNodeFlags | flags | When this method returns |
Returns
| Type | Description |
|---|---|
| bool |
|
Remarks
Node flags are bitpacked into a uint. Bit 0 is the
Walkable flag; bits 1-31 are user-defined
tags (Tag0 through
Tag30) that callers may use for domain-specific
categorization such as terrain type, cost multiplier tier, or zone membership.
Flags can be mutated on mutable graphs via TrySetNodeFlags(int, ScyllaGraphNodeFlags).