Treasure chest of Unity developer tools. Professional inspector tooling, high-performance utilities, spatial queries, and 20+ editor tools.
This guide covers several foundational data structures used across the library and when to use them.
When to use vs. DotNET queues
CyclicBuffer<T> over Queue<T> when you want bounded memory with O(1) push/pop at both ends and predictable behavior under backpressure (drop/overwrite oldest, or pop proactively).Queue<T> when you need unbounded growth without wrap semantics.API snapshot
using WallstopStudios.UnityHelpers.Core.DataStructure;
var rb = new CyclicBuffer<int>(capacity: 4);
rb.Add(10); rb.Add(20); rb.Add(30);
// Pop from front/back
if (rb.TryPopFront(out var first)) { /* first == 10 */ }
if (rb.TryPopBack(out var last)) { /* last == 30 */ }
// Remove by value / predicate
rb.Add(40); rb.Add(50);
rb.Remove(20); // O(n) compacting via temp buffer
rb.RemoveAll(x => x % 2 == 0); // remove evens
// Resize in place (may drop tail elements)
rb.Resize(8);
Tips and pitfalls
Add will wrap and overwrite the oldest only once capacity is reached; use TryPopFront periodically to keep buffer from evicting data you still need.Remove/RemoveAll are O(n); keep hot paths to Add/TryPop* when possible.When to use vs Queue<T> / Stack<T>
Deque<T> when you need both push_front and push_back in O(1) amortized.Queue<T> for simple FIFO; Stack<T> for LIFO only.API snapshot
using WallstopStudios.UnityHelpers.Core.DataStructure;
var dq = new Deque<string>(capacity: 8);
dq.PushFront("start");
dq.PushBack("end");
if (dq.TryPopFront(out var a)) { /* a == "start" */ }
if (dq.TryPopBack(out var b)) { /* b == "end" */ }
// Peeking without removal
dq.PushBack("x");
if (dq.TryPeekFront(out var f)) { /* f == "x" */ }
Tips
TrimExcess() after spikes to return memory.When to use vs SortedSet<T>
Heap<T>/PriorityQueue<T> for frequent push/pop top in O(log n) with low overhead.SortedSet<T> for ordered iteration and fast remove arbitrary item (by key), at higher constants.API snapshot (Heap)
using WallstopStudios.UnityHelpers.Core.DataStructure;
var minHeap = new Heap<int>(); // default comparer => min-heap
minHeap.Add(5); minHeap.Add(2); minHeap.Add(9);
if (minHeap.TryPop(out var top)) { /* top == 2 */ }
if (minHeap.TryPeek(out var peek)) { /* peek == 5 */ }
API snapshot (PriorityQueue)
using WallstopStudios.UnityHelpers.Core.DataStructure;
var pq = PriorityQueue<(int priority, string job)>.CreateMin(
capacity: 32
);
pq.Enqueue((1, "emergency"));
pq.Enqueue((5, "later"));
pq.TryDequeue(out var item); // (1, "emergency")
Tips
PriorityQueue<T>.CreateMax() to flip ordering without writing a custom comparer.When to use
API snapshot (int-based)
using WallstopStudios.UnityHelpers.Core.DataStructure;
var uf = new DisjointSet(n: 6); // elements 0..5
uf.TryUnion(0, 1);
uf.TryUnion(2, 3);
uf.TryIsConnected(0, 3, out var conn); // false
uf.TryUnion(1, 3);
uf.TryIsConnected(0, 3, out conn); // true
API snapshot (generic)
using WallstopStudios.UnityHelpers.Core.DataStructure;
var people = new[] { "Ana", "Bo", "Cy" };
var uf = new DisjointSet<string>(people);
uf.TryUnion("Ana", "Bo");
uf.TryIsConnected("Ana", "Cy", out var conn); // false
Tips
When to use vs HashSet<T>
SparseSet when your IDs are small integers (0..N) and you need O(1) contains with dense, cache-friendly iteration over active items.HashSet<T> for arbitrary keys, very large/unbounded key spaces, or when memory for sparse cannot scale to the max ID.API snapshot (int IDs)
using WallstopStudios.UnityHelpers.Core.DataStructure;
var set = new SparseSet(capacity: 1000); // supports IDs in [0,1000)
set.Add(42);
bool has42 = set.Contains(42); // true
set.Remove(42);
// Dense iteration order over active IDs
foreach (int id in set) { /* ... */ }
API snapshot (generic values)
var set = new SparseSet<MyComponent>(capacity: 1024);
set.Add(100, new MyComponent()); // key 100 -> value
var comp = set[0]; // dense index 0 value
Tips
When to use vs dictionaries
Trie for lots of prefix queries and auto-complete where per-character traversal beats repeated hashing.Dictionary<string, T> when you rarely do prefix scans and primarily need exact lookup.API snapshot
using WallstopStudios.UnityHelpers.Core.DataStructure;
// Words only
var words = new Trie(new[] { "cat", "car", "dog" });
bool hasDog = words.Contains("dog"); // true
List<string> outWords = new();
int count = words.SearchByPrefix("ca", outWords); // ["cat","car"]
// Key -> Value
var items = new Dictionary<string, int> { ["apple"] = 1, ["apricot"] = 2 };
var trie = new Trie<int>(items);
if (trie.TryGetValue("apricot", out var v)) { /* 2 */ }
List<int> values = new();
trie.SearchValuesByPrefix("ap", values); // [1,2]
Tips
When to use vs bool[] / HashSet<int>
BitSet for dense boolean sets with fast bitwise ops (masks, layers, filters) and compact storage.bool[] for tiny, fixed schemas you manipulate rarely; use HashSet<int> for sparse, very large universes.API snapshot
using WallstopStudios.UnityHelpers.Core.DataStructure;
var bits = new BitSet(initialCapacity: 128);
bits[3] = true; // indexer calls TrySet/TryClear
bits.TrySet(64);
bool any = bits.Any(); // any bit set?
int count = bits.CountSetBits();
// Bitwise ops
bits.Not(); // invert
bits.LeftShift(2); // multiply-by-4 mask window
bits.RightShift(1);
Tips
Common pitfalls
Remove/RemoveAll are O(n); keep hot paths to TryPop/Add.Notes on constants