List
, Queue
, Stack
, HashSet
, Dictionary
are copied from dotnet/runtime/System/Collections/Generic, along with other utilities to accomodate their functionality.ArrayDictionary
is copied from sebas77/Svelto.Common/SveltoDictionary.You can install this package from the Open UPM registry.
More details here.
openupm add org.nuget.system.runtime.compilerservices.unsafe
openupm add com.zbase.collections.pooled
"name": "Unity NuGet",
"url": "https://unitynuget-registry.azurewebsites.net",
"scopes": [
"org.nuget"
]
+
dropdownhttps://github.com/Zitga-Tech/ZBase.Collections.Pooled.git?path=Packages/ZBase.Collections.Pooled
List<T>
Queue<T>
Stack<T>
HashSet<T>
Dictionary<TKey, TValue>
ArrayHashSet<T>
ArrayDictionary<TKey, TValue>
Their functionality is the same as provided by the standard collections in System.Collections.Generic
namespace.
There are also some additional APIs for high performant scenarios.
Designed as quick drop-in replacements for their standard counterparts.
ValueArray<T>
ValueList<T>
ValueQueue<T>
ValueStack<T>
ValueHashSet<T>
ValueDictionary<TKey, TValue>
ValueArrayHashSet<T>
ValueArrayDictionary<TKey, TValue>
Functionally the same as their class-based counterparts.
Designed as struct
s to reduce GC allocation of the collection itself.
:warning: Must be created using the static Create
methods.
TempArray<T>
TempList<T>
TempQueue<T>
TempStack<T>
TempHashSet<T>
TempDictionary<TKey, TValue>
TempArrayHashSet<T>
TempArrayDictionary<TKey, TValue>
Functionally the same as their struct-based counterparts.
Designed as ref struct
s to prevent unintentional GC allocations.
:warning: Must be created using the static Create
methods.
At the end of their lifetime, the collections should be Dispose
d in order to return their internal arrays to the pool.
using Collections.Pooled.Generic;
void Test_Manual_Disposing()
{
var lst = new List<int>();
using var arr = ValueArray<int>.Create(10);
...
lst.Dispose();
arr.Dispose();
}
void Test_Automatic_Disposing()
{
using var lst = new List<int>();
using var arr = TempArray<int>.Create(10);
...
} // at the end of this method, `lst` and `arr` will be disposed automatically
If the lifetime of the collections is extended, they should be disposed through the use of IDisposable
interface.
using System;
using Collections.Pooled.Generic;
class ExtendedLifetime : IDisposable
{
// The lifetime of these collections is extended
private Queue<int> _queue = new Queue<int>();
private ValueDictionary<int, string> _dict = new ValueDictionary<int, string>()
public void Dispose()
{
_queue.Dispose();
_dict.Dispose();
}
}
void Test_Extended_Lifetime()
{
using var xyz = new ExtendedLifetime();
...
} // at the end of this method, `xyz` will be disposed automatically, along with its internal collections
To enable the scenario of developing high performant extension modules, this library allows direct access to the internal fields of each collection.
Collections.Pooled.Generic
*Internals
*InternalsRef
*InternalsRefUnsafe
Collections.Pooled.Generic.StructBased
Value*Internals
Value*InternalsRef
Value*InternalsRefUnsafe
Collections.Pooled.Generic.Temporary
Temp*Internals
Temp*InternalsRef
Temp*InternalsRefUnsafe
Safe internals accessing APIs are exposed through the static *CollectionsInternals
classes.
TakeOwnership
methodsCollectionInternals .TakeOwnership(collection)
ValueCollectionInternals .TakeOwnership(valueCollection)
TempCollectionInternals .TakeOwnership(tempCollection)
The procedure follows these steps:
Creates an *Internals
structure to hold references to internal arrays of a source collection.
Removes the source references by assigning them to null
.
Dispose
the source collection.
Returns that structure to the outside.
:arrow_forward: Consequentially, this structure has overtaken the ownership of those internal arrays.
:x: DO NOT use the source collections after this procedure to avoid unknown behaviours, because they have already been disposed.
using Collections.Pooled.Generic;
using Collections.Pooled.Generic.Internals;
void Test_TakeOwnership()
{
using var list = new List<int>();
using var internals = CollectionInternals.TakeOwnership(list);
// `list` has already been disposed
// DO NOT use `list` after this line
...
} // at the end of this method, `internals` will be disposed automatically
Sometimes it is desired for *Internals
structures to not be disposed because the ownership is going to be transferred to other places. In that case, do not use using
or call Dispose
on the returned structure.
using Collections.Pooled.Generic;
using Collections.Pooled.Generic.Internals;
void Test_Ownership_Transferring()
{
using var list = new List<int>();
var internals = CollectionInternals.TakeOwnership(list);
var customList = SomeCustomList<int>.From(internals);
// `customList` is holding ownership of the internals of `list`
// no need to declare `using var internals = ...`
// no need to call `internals.Dispose()`
...
} // at the end of this method, `internals` WON'T be disposed automatically
GetRef
methodsCollectionInternals .GetRef(collection)
ValueCollectionInternals .GetRef(valueCollection)
TempCollectionInternals .GetRef(tempCollection)
This method returns an *InternalsRef
structure that holds indirect references to internal arrays of a source collection.
The indirect references are represented as ReadOnlySpan
s to only expose high performant read operations.
*InternalsRef
s are designed as ref struct
s to prevent unintentional lingering effect of the references (could cause memory leaks).
:warning: The source collections WON'T be disposed, they still hold the ownership to internal arrays of their own.
using Collections.Pooled.Generic;
using Collections.Pooled.Generic.Internals;
void Test_GetRef_Safe()
{
using var list = new List<int>();
var internalsRef = CollectionInternals.GetRef(list);
// `list` is NOT disposed
// `list` is USABLE even after this line
...
}
AsReadOnlySpan
methodsCollectionInternals .AsReadOnlySpan(collection)
ValueCollectionInternals .AsReadOnlySpan(valueCollection)
TempCollectionInternals .AsReadOnlySpan(tempCollection)
This method returns an indirect reference to the internal main array of a source collection.
The indirect references are represented as ReadOnlySpan
to only expose read operations.
For Dictionary
and HashSet
, it returns the _entries
array.
For other collections, it returns _items
or _array
.
Unsafe internals accessing APIs are exposed through the static *CollectionInternalsUnsafe
classes.
GetRef
methodsCollectionInternalsUnsafe .GetRef(collection)
ValueCollectionInternalsUnsafe .GetRef(valuecollection)
TempCollectionInternalsUnsafe .GetRef(tempCollection)
*InternalsRefUnsafe
structure that holds direct references to internal arrays of a source collection.:warning: *InternalsRefUnsafe
s are designed as struct
s to be usable in any context. Coupled with direct references to internal arrays, they should be used carefully.
:warning: The source collections WON'T be disposed, they still hold the ownership to internal arrays of their own.
using Collections.Pooled.Generic;
using Collections.Pooled.Generic.Internals.Unsafe;
void Test_GetRef_Unsafe()
{
using var list = new List<int>();
var internalsRefUnsafe = CollectionInternalsUnsafe.GetRef(list);
// `list` is NOT disposed
// `list` is USABLE even after this line
...
}
AsSpan
methodsCollectionInternals .AsSpan(collection)
ValueCollectionInternals .AsSpan(valueCollection)
TempCollectionInternals .AsSpan(tempCollection)
This method returns an indirect reference to the internal main array of a source collection.
The indirect references are represented as Span
to expose read and write operations.
For Dictionary
and HashSet
, it returns the _entries
array.
For other collections, it returns _items
or _array
.
GetUnsafe
methodscollection .GetUnsafe(out array, out count)
valueCollection .GetUnsafe(out array, out count)
tempCollection .GetUnsafe(out array, out count)
This method returns a direct reference to the internal main array of a source collection, along with the size of that array (named count
).
For Dictionary
and HashSet
, it returns the _entries
array.
For other collections, it returns _items
or _array
.
For Queue
, it also returns head
and tail
values.
queue .GetUnsafe(out array, out count, out head, out tail)
valueQueue .GetUnsafe(out array, out count, out head, out tail)
tempQueue .GetUnsafe(out array, out count, out head, out tail)
*ArrayDictionary
types based on SveltoDictionary
var dict = new ArrayDictionary<int, int>();
dict.GetUnsafe(out var keys, out var values, out var count);
for (var i = 0; i < count; i++)
{
Debug.Log($"{keys[i].Key} = {values[i]}");
}
*Internals
types related to *ArrayDictionary<TKey, TValue>
*Dictionary
and *HashSet
TrimExcess
must return old arrays to the poolsArrayPool
null
s and null checks for the internal _buckets
array1
to the HashHelpers.primes
array to support the scenarios of only 1 elementSystem.Runtime.CompilerServices.dll
version 6.0.0
*ArrayDictionary
because they are safe*CollectionInternalUnsafe
for the same reasonReadOnlyValueArray
and ReadOnlyTempArray
IsNullOrEmpty
extension method to check if arrays are null or emptyValueArray
and TempArray
constructorReadOnlyArray
AsReadOnlySpan
and AsSpan
extension methodsValueArray
and TempArray
in unsafe contextSystemDebug
System.Diagnostics.Debug
with SystemDebug
to allow disabling Debug.Assert
ArrayDictionary.Resize
should work if newCapacity
is greater than the current capacityArrayDictionary.Resize
should return the newly rented arrays if its length is smaller than the current capacityCopyTo
, AsSpan
, and AsReadOnlySpan
*ArrayHashSet
and related APIsIntersect
, Exclude
, Union
APIs from ArrayDictionary
System.Memory<T>
CopyTo
methods for ValueArray
and TempArray
ReadOnlySpan
instead of Span
)ZBase.Collections.Pooled
package.json