- Inspired by jtmueller/Collections.Pooled
- The source code of
List,Queue,Stack,HashSet,Dictionaryare copied from dotnet/runtime/System/Collections/Generic, along with other utilities to accomodate their functionality. - The source code of
ArrayDictionaryis 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
- Open the Poject Settings window
- Navigate to the Package Manager section
- Add a new Scoped Registry
"name": "Unity NuGet",
"url": "https://unitynuget-registry.azurewebsites.net",
"scopes": [
"org.nuget"
]
- Open the Package Manager window
- Select the Add package from git URL option from the
+dropdown - Enter this git url:
https://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 structs to reduce GC allocation of the collection itself.
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 structs to prevent unintentional GC allocations.
Create methods.
At the end of their lifetime, the collections should be Disposed 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 automaticallyIf 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 collectionsTo 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*InternalsValue*InternalsRefValue*InternalsRefUnsafe
-
Collections.Pooled.Generic.Temporary
Temp*InternalsTemp*InternalsRefTemp*InternalsRefUnsafe
Safe internals accessing APIs are exposed through the static *CollectionsInternals classes.
CollectionInternals .TakeOwnership(collection)
ValueCollectionInternals .TakeOwnership(valueCollection)
TempCollectionInternals .TakeOwnership(tempCollection)The procedure follows these steps:
-
Creates an
*Internalsstructure to hold references to internal arrays of a source collection. -
Removes the source references by assigning them to
null. -
Disposethe source collection. -
Returns that structure to the outside.
❌ 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 automaticallySometimes 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 automaticallyCollectionInternals .GetRef(collection)
ValueCollectionInternals .GetRef(valueCollection)
TempCollectionInternals .GetRef(tempCollection)-
This method returns an
*InternalsRefstructure that holds indirect references to internal arrays of a source collection. -
The indirect references are represented as
ReadOnlySpans to only expose high performant read operations. -
*InternalsRefs are designed asref structs to prevent unintentional lingering effect of the references (could cause memory leaks).
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
...
}CollectionInternals .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
ReadOnlySpanto only expose read operations. -
For
DictionaryandHashSet, it returns the_entriesarray. -
For other collections, it returns
_itemsor_array.
Unsafe internals accessing APIs are exposed through the static *CollectionInternalsUnsafe classes.
CollectionInternalsUnsafe .GetRef(collection)
ValueCollectionInternalsUnsafe .GetRef(valuecollection)
TempCollectionInternalsUnsafe .GetRef(tempCollection)- This method returns an
*InternalsRefUnsafestructure that holds direct references to internal arrays of a source collection.
*InternalsRefUnsafes are designed as structs to be usable in any context. Coupled with direct references to internal arrays, they should be used carefully.
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
...
}CollectionInternals .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
Spanto expose read and write operations. -
For
DictionaryandHashSet, it returns the_entriesarray. -
For other collections, it returns
_itemsor_array.
collection .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
DictionaryandHashSet, it returns the_entriesarray. -
For other collections, it returns
_itemsor_array. -
For
Queue, it also returnsheadandtailvalues.
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)- Added
*ArrayDictionarytypes based onSveltoDictionary- This data structure allows iterating over its keys and values as an array.
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]}");
}- Added
*Internalstypes related to*ArrayDictionary<TKey, TValue>
- For
*Dictionaryand*HashSet- Internal pools must be set firstly in the constructors
TrimExcessmust return old arrays to the pools- Serialization should not use
ArrayPool - Remove
nulls and null checks for the internal_bucketsarray - Correct internal arrays initialization
- Add
1to theHashHelpers.primesarray to support the scenarios of only 1 element
- Correct the reference to
System.Runtime.CompilerServices.dllversion6.0.0
- Expose some internal methods of
*ArrayDictionarybecause they are safe - Remove the related methods from
*CollectionInternalUnsafefor the same reason
- Add
ReadOnlyValueArrayandReadOnlyTempArray
- Use
IsNullOrEmptyextension method to check if arrays are null or empty
- Correct
ValueArrayandTempArrayconstructor
- Add
ReadOnlyArray
- Add internal APIs for readonly arrays
- Improve Enumerators
- Make
AsReadOnlySpanandAsSpanextension methods
- Add APIs to create
ValueArrayandTempArrayin unsafe context
- Add
SystemDebug
- Replace
System.Diagnostics.DebugwithSystemDebugto allow disablingDebug.Assert
ArrayDictionary.Resizeshould work ifnewCapacityis greater than the current capacityArrayDictionary.Resizeshould return the newly rented arrays if its length is smaller than the current capacity
- Buckets array should be cleared after renting
- Add more APIs:
CopyTo,AsSpan, andAsReadOnlySpan
- Add
*ArrayHashSetand related APIs
- Remove
Intersect,Exclude,UnionAPIs fromArrayDictionary
- Add APIs to return
System.Memory<T>
- Correct
CopyTomethods forValueArrayandTempArray - Some unsafe methods return incorrect type (
ReadOnlySpaninstead ofSpan)
- Acquire ownership from @laicasaaane
- Consolidate many asmdefs into
ZBase.Collections.Pooled
- Correct
package.json - Update installation guide