Added WaitAll and WaitAny to JobScheduler;
Added generation support to UnsafeSparseSet;
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>True</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
public enum JobState
|
public enum JobState
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The job is in an invalid state, indicating an error or uninitialized state. Or already finished the execution and cleaned up by the system.
|
/// The job is in an invalid state, indicating an error or uninitialized state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Invalid = -1,
|
Invalid = -1,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ namespace Misaki.HighPerformance.Jobs;
|
|||||||
/// worker threads.</remarks>
|
/// worker threads.</remarks>
|
||||||
public unsafe sealed class JobScheduler : IDisposable
|
public unsafe sealed class JobScheduler : IDisposable
|
||||||
{
|
{
|
||||||
|
private const int _SLEEP_THRESHOLD = 100;
|
||||||
|
|
||||||
private FreeList _jobDataAllocator;
|
private FreeList _jobDataAllocator;
|
||||||
private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool;
|
private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool;
|
||||||
private readonly ConcurrentQueue<JobHandle> _jobQueue;
|
private readonly ConcurrentQueue<JobHandle> _jobQueue;
|
||||||
@@ -268,7 +270,7 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedules a single job for execution on a specified thread, with an optional dependency on another job.
|
/// Schedules a single job for execution on a specified thread without dependency.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
|
||||||
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
@@ -279,6 +281,30 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
where T : unmanaged, IJob
|
where T : unmanaged, IJob
|
||||||
=> Schedule(ref job, threadIndex, JobHandle.Invalid);
|
=> Schedule(ref job, threadIndex, JobHandle.Invalid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules a single job for execution on any thread, with an optional dependency on another job.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
|
||||||
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
|
/// <param name="threadIndex">The index of the thread that will execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
|
||||||
|
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
|
||||||
|
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
|
||||||
|
public JobHandle Schedule<T>(ref T job, JobHandle dependency)
|
||||||
|
where T : unmanaged, IJob
|
||||||
|
=> Schedule(ref job, -1, dependency);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules a single job for execution on any thread without dependency.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJob"/> and be unmanaged.</typeparam>
|
||||||
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
|
/// <param name="threadIndex">The index of the thread that will execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
|
||||||
|
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
|
||||||
|
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
|
||||||
|
public JobHandle Schedule<T>(ref T job)
|
||||||
|
where T : unmanaged, IJob
|
||||||
|
=> Schedule(ref job, -1, JobHandle.Invalid);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -328,7 +354,7 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads.
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on a specified thread without dependency.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
||||||
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
@@ -341,6 +367,34 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
where T : unmanaged, IJobParallelFor
|
where T : unmanaged, IJobParallelFor
|
||||||
=> ScheduleParallel(ref job, totalIteration, batchSize, threadIndex, JobHandle.Invalid);
|
=> ScheduleParallel(ref job, totalIteration, batchSize, threadIndex, JobHandle.Invalid);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread, with an optional dependency on another job..
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
||||||
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
|
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
|
||||||
|
/// <param name="batchSize">The number of iterations to include in each batch.</param>
|
||||||
|
/// <param name="threadIndex">The index of the thread that will execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
|
||||||
|
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
|
||||||
|
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
|
||||||
|
public JobHandle ScheduleParallel<T>(ref T job, int totalIteration, int batchSize, JobHandle dependency)
|
||||||
|
where T : unmanaged, IJobParallelFor
|
||||||
|
=> ScheduleParallel(ref job, totalIteration, batchSize, -1, dependency);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules a parallel job for execution, dividing the workload into batches and distributing it across threads on any thread without dependency.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the job to execute. Must implement <see cref="IJobParallelFor"/> and be unmanaged.</typeparam>
|
||||||
|
/// <param name="job">The job instance to be executed. The job data will be copied internally.</param>
|
||||||
|
/// <param name="totalIteration">The total number of iterations to be processed by the job.</param>
|
||||||
|
/// <param name="batchSize">The number of iterations to include in each batch.</param>
|
||||||
|
/// <param name="threadIndex">The index of the thread that will execute the job. This is used to assign thread-specific data. Use -1 to allow any thread to execute the job.</param>
|
||||||
|
/// <returns>A <see cref="JobHandle"/> that can be used to track the completion of the scheduled job.
|
||||||
|
/// Returns <see cref="JobHandle.Invalid"/> if the job data allocation fails.</returns>
|
||||||
|
public JobHandle ScheduleParallel<T>(ref T job, int totalIteration, int batchSize)
|
||||||
|
where T : unmanaged, IJobParallelFor
|
||||||
|
=> ScheduleParallel(ref job, totalIteration, batchSize, -1, JobHandle.Invalid);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Combines multiple job dependencies into a single <see cref="JobHandle"/>.
|
/// Combines multiple job dependencies into a single <see cref="JobHandle"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -379,7 +433,7 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
ref var jobInfo = ref _jobInfoPool.GetElementReferenceAt(handle._id, handle._generation, out var exist);
|
ref var jobInfo = ref _jobInfoPool.GetElementReferenceAt(handle._id, handle._generation, out var exist);
|
||||||
if (!exist)
|
if (!exist)
|
||||||
{
|
{
|
||||||
return JobState.Invalid;
|
return JobState.Completed; // We assume completed if not found. Invalid state is reserved for error.
|
||||||
}
|
}
|
||||||
|
|
||||||
return (JobState)Volatile.Read(ref Unsafe.As<JobState, int>(ref jobInfo.state));
|
return (JobState)Volatile.Read(ref Unsafe.As<JobState, int>(ref jobInfo.state));
|
||||||
@@ -404,7 +458,69 @@ public unsafe sealed class JobScheduler : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spin.SpinOnce(-1);
|
spin.SpinOnce(_SLEEP_THRESHOLD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Blocks the calling thread until all specified job handles have completed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method waits for all jobs referenced by the provided handles to complete before
|
||||||
|
/// returning. The calling thread will be blocked until every job has finished. If any handle is invalid or does not
|
||||||
|
/// correspond to an active job, it is considered completed. This method is not thread-safe and should not be called
|
||||||
|
/// concurrently from multiple threads.</remarks>
|
||||||
|
/// <param name="handles">A collection of job handles to wait for. Each handle represents an asynchronous job whose completion is awaited.
|
||||||
|
/// The collection must not be empty.</param>
|
||||||
|
public void WaitAll(params ReadOnlySpan<JobHandle> handles)
|
||||||
|
{
|
||||||
|
var sleepThreshold = _SLEEP_THRESHOLD * handles.Length;
|
||||||
|
var spin = new SpinWait();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var completedCount = 0;
|
||||||
|
foreach (var handle in handles)
|
||||||
|
{
|
||||||
|
if (!_jobInfoPool.Contain(handle._id, handle._generation))
|
||||||
|
{
|
||||||
|
completedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completedCount == handles.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin.SpinOnce(sleepThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Waits until any of the specified job handles has completed and returns the first completed handle.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method blocks the calling thread until at least one of the specified jobs has finished.
|
||||||
|
/// The returned handle corresponds to the job that completed first among those provided. The order of handles in
|
||||||
|
/// the span may affect which handle is returned if multiple jobs complete simultaneously.</remarks>
|
||||||
|
/// <param name="handles">A read-only span containing the job handles to monitor for completion. Each handle represents a job whose
|
||||||
|
/// completion status will be checked.</param>
|
||||||
|
/// <returns>The first job handle from the provided collection that has completed.</returns>
|
||||||
|
public JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles)
|
||||||
|
{
|
||||||
|
var sleepThreshold = _SLEEP_THRESHOLD * handles.Length;
|
||||||
|
var spin = new SpinWait();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
foreach (var handle in handles)
|
||||||
|
{
|
||||||
|
if (!_jobInfoPool.Contain(handle._id, handle._generation))
|
||||||
|
{
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spin.SpinOnce(sleepThreshold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>True</IsPackable>
|
||||||
|
<Version>1.1.0</Version>
|
||||||
|
<AssemblyVersion>1.1.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.1.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
namespace Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
|
|
||||||
public readonly struct CollectionHandle
|
|
||||||
{
|
|
||||||
public readonly int id;
|
|
||||||
public readonly int generation;
|
|
||||||
|
|
||||||
public static CollectionHandle Invalid => new(-1, -1);
|
|
||||||
|
|
||||||
public bool IsValid => this != Invalid;
|
|
||||||
|
|
||||||
internal CollectionHandle(int id, int generation)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
this.generation = generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(CollectionHandle other)
|
|
||||||
{
|
|
||||||
return id == other.id && generation == other.generation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
return obj is CollectionHandle handle && Equals(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return HashCode.Combine(id, generation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return IsValid ? $"CollectionHandle({id}, {generation})" : "CollectionHandle(Invalid)";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator ==(CollectionHandle left, CollectionHandle right)
|
|
||||||
{
|
|
||||||
return left.Equals(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool operator !=(CollectionHandle left, CollectionHandle right)
|
|
||||||
{
|
|
||||||
return !(left == right);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -76,10 +76,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
#if DISABLE_COLLECTION_CHECKS
|
||||||
if (index < 0 || index >= _count)
|
if (index < 0 || index >= _count)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
|
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
|
||||||
}
|
}
|
||||||
@@ -90,10 +92,12 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
#if DISABLE_COLLECTION_CHECKS
|
||||||
if (index >= _count)
|
if (index >= _count)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
throw new ArgumentOutOfRangeException(nameof(index), "Index is out of range.");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
|
return ref UnsafeUtilities.ReadArrayElementRef<T>(_buffer, index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ namespace Misaki.HighPerformance.LowLevel.Collections;
|
|||||||
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
|
private struct SlotEntry
|
||||||
|
{
|
||||||
|
public T value;
|
||||||
|
public int generation;
|
||||||
|
}
|
||||||
|
|
||||||
public struct Enumerator : IEnumerator<T>
|
public struct Enumerator : IEnumerator<T>
|
||||||
{
|
{
|
||||||
private UnsafeSparseSet<T>* _collection;
|
private UnsafeSparseSet<T>* _collection;
|
||||||
@@ -34,10 +40,14 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
public bool MoveNext()
|
public bool MoveNext()
|
||||||
{
|
{
|
||||||
_index++;
|
_index++;
|
||||||
if (_index < _collection->_count)
|
if (_index < _collection->_sparse.Count)
|
||||||
{
|
{
|
||||||
_value = UnsafeUtilities.ReadArrayElement<T>(_collection->_dense.GetUnsafePtr(), _index);
|
var index = _collection->_sparse[_index];
|
||||||
return true;
|
if (index >= 0 && index < _collection->_count)
|
||||||
|
{
|
||||||
|
_value = _collection->_dense[index].value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_value = default;
|
_value = default;
|
||||||
@@ -66,7 +76,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsafeArray<T> _dense;
|
private UnsafeArray<SlotEntry> _dense;
|
||||||
private UnsafeArray<int> _sparse;
|
private UnsafeArray<int> _sparse;
|
||||||
private UnsafeArray<int> _reverse; // Maps dense index to sparse index
|
private UnsafeArray<int> _reverse; // Maps dense index to sparse index
|
||||||
private UnsafeArray<int> _freeList; // Stack of available sparse indices
|
private UnsafeArray<int> _freeList; // Stack of available sparse indices
|
||||||
@@ -103,7 +113,7 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity must be greater than zero.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_dense = new UnsafeArray<T>(capacity, ref handle, allocationOption);
|
_dense = new UnsafeArray<SlotEntry>(capacity, ref handle, allocationOption);
|
||||||
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_sparse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_reverse = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
_freeList = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
_freeList = new UnsafeArray<int>(capacity, ref handle, allocationOption);
|
||||||
@@ -130,8 +140,9 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
/// Adds a value to the sparse set and returns a unique sparse index for the value.
|
/// Adds a value to the sparse set and returns a unique sparse index for the value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value to add to the sparse set.</param>
|
/// <param name="value">The value to add to the sparse set.</param>
|
||||||
|
/// <param name="generation">Outputs the generation number associated with the added value.</param>
|
||||||
/// <returns>A unique sparse index that can be used to reference this value.</returns>
|
/// <returns>A unique sparse index that can be used to reference this value.</returns>
|
||||||
public int Add(T value)
|
public int Add(T value, out int generation)
|
||||||
{
|
{
|
||||||
int sparseIndex;
|
int sparseIndex;
|
||||||
|
|
||||||
@@ -162,68 +173,27 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add the value to the dense array and update mappings
|
// Add the value to the dense array and update mappings
|
||||||
_dense[_count] = value;
|
ref var entry = ref _dense[_count];
|
||||||
|
entry.value = value;
|
||||||
|
|
||||||
_sparse[sparseIndex] = _count;
|
_sparse[sparseIndex] = _count;
|
||||||
_reverse[_count] = sparseIndex;
|
_reverse[_count] = sparseIndex;
|
||||||
_count++;
|
_count++;
|
||||||
|
|
||||||
|
generation = entry.generation;
|
||||||
|
|
||||||
return sparseIndex;
|
return sparseIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a value to the sparse set at the specified sparse index.
|
|
||||||
/// This method is provided for compatibility when you need to specify the exact sparse index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sparseIndex">The index in the sparse array where the value should be mapped.</param>
|
|
||||||
/// <param name="value">The value to add to the sparse set.</param>
|
|
||||||
/// <returns>True if the value was added, false if the sparse index is already occupied.</returns>
|
|
||||||
public bool AddAt(int sparseIndex, T value)
|
|
||||||
{
|
|
||||||
if (sparseIndex < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index must be non-negative.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sparseIndex >= _sparse.Count)
|
|
||||||
{
|
|
||||||
ResizeSparse(sparseIndex + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Contains(sparseIndex))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_count >= _dense.Count)
|
|
||||||
{
|
|
||||||
var newCapacity = _dense.Count + Math.Max(1, _dense.Count / 2);
|
|
||||||
_dense.Resize(newCapacity);
|
|
||||||
_reverse.Resize(newCapacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the value to the dense array and update mappings
|
|
||||||
_dense[_count] = value;
|
|
||||||
_sparse[sparseIndex] = _count;
|
|
||||||
_reverse[_count] = sparseIndex; // Store reverse mapping
|
|
||||||
_count++;
|
|
||||||
|
|
||||||
// Update _nextId if we're using a higher ID
|
|
||||||
if (sparseIndex >= _nextId)
|
|
||||||
{
|
|
||||||
_nextId = sparseIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the value at the specified sparse index.
|
/// Removes the value at the specified sparse index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
|
/// <param name="sparseIndex">The sparse index of the value to remove.</param>
|
||||||
|
/// <param name="generation">The generation number associated with the sparse index to validate.</param>
|
||||||
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
|
/// <returns>True if the value was removed, false if the sparse index was not found.</returns>
|
||||||
public bool Remove(int sparseIndex)
|
public bool Remove(int sparseIndex, int generation)
|
||||||
{
|
{
|
||||||
if (!Contains(sparseIndex))
|
if (!Contains(sparseIndex, generation))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -246,12 +216,14 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
|
|
||||||
// Mark the sparse index as unused and add to free list
|
// Mark the sparse index as unused and add to free list
|
||||||
_sparse[sparseIndex] = -1;
|
_sparse[sparseIndex] = -1;
|
||||||
|
_dense[lastIndex].generation++; // Increment generation to invalidate old references
|
||||||
|
|
||||||
// Add the freed sparse index to the free list for reuse
|
// Add the freed sparse index to the free list for reuse
|
||||||
if (_freeCount >= _freeList.Count)
|
if (_freeCount >= _freeList.Count)
|
||||||
{
|
{
|
||||||
_freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2));
|
_freeList.Resize(_freeList.Count + Math.Max(1, _freeList.Count / 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
_freeList[_freeCount] = sparseIndex;
|
_freeList[_freeCount] = sparseIndex;
|
||||||
_freeCount++;
|
_freeCount++;
|
||||||
|
|
||||||
@@ -264,9 +236,10 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
/// Checks if the sparse set contains a value at the specified sparse index.
|
/// Checks if the sparse set contains a value at the specified sparse index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sparseIndex">The sparse index to check.</param>
|
/// <param name="sparseIndex">The sparse index to check.</param>
|
||||||
|
/// <param name="generation">The generation number to validate against the stored generation.</param>
|
||||||
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
|
/// <returns>True if the sparse index is valid and contains a value, false otherwise.</returns>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public readonly bool Contains(int sparseIndex)
|
public readonly bool Contains(int sparseIndex, int generation)
|
||||||
{
|
{
|
||||||
if (sparseIndex < 0 || sparseIndex >= _sparse.Count)
|
if (sparseIndex < 0 || sparseIndex >= _sparse.Count)
|
||||||
{
|
{
|
||||||
@@ -274,21 +247,24 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
var denseIndex = _sparse[sparseIndex];
|
||||||
return denseIndex >= 0 && denseIndex < _count;
|
return denseIndex >= 0 && denseIndex < _count && _dense[denseIndex].generation == generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value at the specified sparse index.
|
/// Gets the value at the specified sparse index and generation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
|
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
|
||||||
|
/// <param name="generation">The generation number to validate against the stored generation.</param>
|
||||||
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
|
/// <param name="value">When this method returns, contains the value at the specified sparse index, if found.</param>
|
||||||
/// <returns>True if the sparse index contains a value, false otherwise.</returns>
|
/// <returns>True if the sparse index contains a value, false otherwise.</returns>
|
||||||
public readonly bool TryGetValue(int sparseIndex, out T value)
|
public readonly bool TryGetValue(int sparseIndex, int generation, out T value)
|
||||||
{
|
{
|
||||||
if (Contains(sparseIndex))
|
if (Contains(sparseIndex, generation))
|
||||||
{
|
{
|
||||||
var denseIndex = _sparse[sparseIndex];
|
var denseIndex = _sparse[sparseIndex];
|
||||||
value = _dense[denseIndex];
|
ref var entry = ref _dense[denseIndex];
|
||||||
|
|
||||||
|
value = entry.value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,40 +273,71 @@ public unsafe struct UnsafeSparseSet<T> : IUnsafeCollection<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value at the specified sparse index.
|
/// Gets the value at the specified sparse index and generation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
|
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
|
||||||
|
/// <param name="generation">The generation number to validate against the stored generation.</param>
|
||||||
/// <returns>The value at the specified sparse index.</returns>
|
/// <returns>The value at the specified sparse index.</returns>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
|
||||||
public readonly T GetValue(int sparseIndex)
|
public readonly T GetValue(int sparseIndex, int generation)
|
||||||
{
|
{
|
||||||
if (!Contains(sparseIndex))
|
if (!Contains(sparseIndex, generation))
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index not found in the set.");
|
throw new ArgumentOutOfRangeException(nameof(sparseIndex), "Sparse index and feneration not found in the set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
var denseIndex = _sparse[sparseIndex];
|
||||||
return _dense[denseIndex];
|
ref var entry = ref _dense[denseIndex];
|
||||||
|
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets reference of the value at the specified sparse index and generation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sparseIndex">The sparse index to retrieve the value from.</param>
|
||||||
|
/// <param name="generation">The generation number to validate against the stored generation.</param>
|
||||||
|
/// <param name="exist">Outputs whether the sparse index exists in the set.</param>
|
||||||
|
/// <returns>Reference of the value at the specified sparse index.</returns>
|
||||||
|
/// <exception cref="ArgumentOutOfRangeException">Thrown when the sparse index is not found.</exception>
|
||||||
|
public readonly ref T GetValueReference(int sparseIndex, int generation, out bool exist)
|
||||||
|
{
|
||||||
|
if (!Contains(sparseIndex, generation))
|
||||||
|
{
|
||||||
|
exist = false;
|
||||||
|
return ref Unsafe.NullRef<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var denseIndex = _sparse[sparseIndex];
|
||||||
|
ref var entry = ref _dense[denseIndex];
|
||||||
|
|
||||||
|
exist = true;
|
||||||
|
|
||||||
|
return ref entry.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the value at the specified sparse index.
|
/// Updates the value at the specified sparse index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sparseIndex">The sparse index of the value to update.</param>
|
/// <param name="sparseIndex">The sparse index of the value to update.</param>
|
||||||
|
/// <param name="generation">The generation number to validate against the stored generation.</param>
|
||||||
/// <param name="value">The new value.</param>
|
/// <param name="value">The new value.</param>
|
||||||
/// <returns>True if the value was updated, false if the sparse index was not found.</returns>
|
/// <returns>True if the value was updated, false if the sparse index was not found.</returns>
|
||||||
public bool SetValue(int sparseIndex, T value)
|
public bool SetValue(int sparseIndex, int generation, T value)
|
||||||
{
|
{
|
||||||
if (!Contains(sparseIndex))
|
if (!Contains(sparseIndex, generation))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var denseIndex = _sparse[sparseIndex];
|
var denseIndex = _sparse[sparseIndex];
|
||||||
_dense[denseIndex] = value;
|
ref var entry = ref _dense[denseIndex];
|
||||||
|
entry.value = value;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void ResizeSparse(int newSize)
|
private void ResizeSparse(int newSize)
|
||||||
{
|
{
|
||||||
var oldSize = _sparse.Count;
|
var oldSize = _sparse.Count;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>True</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Title>$(AssemblyName)</Title>
|
<Title>$(AssemblyName)</Title>
|
||||||
<Authors>Misaki</Authors>
|
<Authors>Misaki</Authors>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>True</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -14,33 +14,40 @@ public class TestUnsafeSparseSet
|
|||||||
_sparseSet = new UnsafeSparseSet<int>(16, Allocator.Persistent);
|
_sparseSet = new UnsafeSparseSet<int>(16, Allocator.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_sparseSet.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Add()
|
public void Add()
|
||||||
{
|
{
|
||||||
var id = _sparseSet.Add(10);
|
var id = _sparseSet.Add(10, out var gen);
|
||||||
Assert.IsTrue(_sparseSet.Contains(id));
|
Assert.IsTrue(_sparseSet.Contains(id, gen));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Remove()
|
public void Remove()
|
||||||
{
|
{
|
||||||
var id = _sparseSet.Add(20);
|
var id = _sparseSet.Add(20, out var gen);
|
||||||
Assert.IsTrue(_sparseSet.Contains(id));
|
Assert.IsTrue(_sparseSet.Contains(id, gen));
|
||||||
|
|
||||||
_sparseSet.Remove(id);
|
_sparseSet.Remove(id, gen);
|
||||||
Assert.IsFalse(_sparseSet.Contains(id));
|
Assert.IsFalse(_sparseSet.Contains(id, gen));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void IndexReuse()
|
public void IndexReuse()
|
||||||
{
|
{
|
||||||
var id = _sparseSet.Add(20);
|
var id = _sparseSet.Add(20, out var gen);
|
||||||
Assert.IsTrue(_sparseSet.Contains(id));
|
Assert.IsTrue(_sparseSet.Contains(id, gen));
|
||||||
|
|
||||||
_sparseSet.Remove(id);
|
_sparseSet.Remove(id, gen);
|
||||||
Assert.IsFalse(_sparseSet.Contains(id));
|
Assert.IsFalse(_sparseSet.Contains(id, gen));
|
||||||
|
|
||||||
var newId = _sparseSet.Add(30);
|
var newId = _sparseSet.Add(30, out var newGen);
|
||||||
Assert.AreEqual(id, newId);
|
Assert.AreEqual(id, newId);
|
||||||
|
Assert.AreNotEqual(gen, newGen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ public unsafe class TestJobSystem
|
|||||||
result = result
|
result = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle1 = _jobScheduler.Schedule(ref job1, -1);
|
var handle1 = _jobScheduler.Schedule(ref job1);
|
||||||
_jobScheduler.WaitComplete(handle1);
|
_jobScheduler.WaitComplete(handle1);
|
||||||
|
|
||||||
var job2 = new AddJob
|
var job2 = new AddJob
|
||||||
@@ -84,7 +84,7 @@ public unsafe class TestJobSystem
|
|||||||
result = result
|
result = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle2 = _jobScheduler.Schedule(ref job2, -1, handle1);
|
var handle2 = _jobScheduler.Schedule(ref job2, handle1);
|
||||||
_jobScheduler.WaitComplete(handle2);
|
_jobScheduler.WaitComplete(handle2);
|
||||||
|
|
||||||
Assert.AreEqual(8.0f, *result);
|
Assert.AreEqual(8.0f, *result);
|
||||||
@@ -101,7 +101,7 @@ public unsafe class TestJobSystem
|
|||||||
result = result
|
result = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle1 = _jobScheduler.Schedule(ref job1, -1);
|
var handle1 = _jobScheduler.Schedule(ref job1);
|
||||||
|
|
||||||
var job2 = new AddJob
|
var job2 = new AddJob
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,7 @@ public unsafe class TestJobSystem
|
|||||||
result = result
|
result = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle2 = _jobScheduler.Schedule(ref job2, -1, handle1);
|
var handle2 = _jobScheduler.Schedule(ref job2, handle1);
|
||||||
|
|
||||||
var job3 = new AddJob
|
var job3 = new AddJob
|
||||||
{
|
{
|
||||||
@@ -118,7 +118,7 @@ public unsafe class TestJobSystem
|
|||||||
};
|
};
|
||||||
|
|
||||||
var combinedHandle = _jobScheduler.CombineDependencies(handle1, handle2);
|
var combinedHandle = _jobScheduler.CombineDependencies(handle1, handle2);
|
||||||
var handle3 = _jobScheduler.Schedule(ref job3, -1, combinedHandle);
|
var handle3 = _jobScheduler.Schedule(ref job3, combinedHandle);
|
||||||
|
|
||||||
_jobScheduler.WaitComplete(handle3);
|
_jobScheduler.WaitComplete(handle3);
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ public unsafe class TestJobSystem
|
|||||||
inout = result
|
inout = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle = _jobScheduler.ScheduleParallel(ref job, size, 64, -1, JobHandle.Invalid);
|
var handle = _jobScheduler.ScheduleParallel(ref job, size, 64);
|
||||||
_jobScheduler.WaitComplete(handle);
|
_jobScheduler.WaitComplete(handle);
|
||||||
|
|
||||||
Assert.AreEqual(1.0f, result[500]);
|
Assert.AreEqual(1.0f, result[500]);
|
||||||
@@ -189,13 +189,66 @@ public unsafe class TestJobSystem
|
|||||||
output = result
|
output = result
|
||||||
};
|
};
|
||||||
|
|
||||||
var handle1 = _jobScheduler.ScheduleParallel(ref addJob, arraySize, 64, -1, JobHandle.Invalid);
|
var handle1 = _jobScheduler.ScheduleParallel(ref addJob, arraySize, 64);
|
||||||
var handle2 = _jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64, -1, handle1);
|
var handle2 = _jobScheduler.ScheduleParallel(ref multiplyJob, arraySize, 64);
|
||||||
var handle3 = _jobScheduler.Schedule(ref sumJob, -1, handle2);
|
var handle3 = _jobScheduler.Schedule(ref sumJob, handle2);
|
||||||
|
|
||||||
_jobScheduler.WaitComplete(handle3);
|
_jobScheduler.WaitComplete(handle3);
|
||||||
|
|
||||||
var expected = ComputeExpectedSum(arraySize);
|
var expected = ComputeExpectedSum(arraySize);
|
||||||
Assert.AreEqual(expected, *result, 0.01f);
|
Assert.AreEqual(expected, *result, 0.01f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void WaitAll()
|
||||||
|
{
|
||||||
|
var result1 = stackalloc float[1];
|
||||||
|
var result2 = stackalloc float[1];
|
||||||
|
|
||||||
|
var job1 = new AddJob
|
||||||
|
{
|
||||||
|
value = 1.0f,
|
||||||
|
result = result1
|
||||||
|
};
|
||||||
|
|
||||||
|
var job2 = new AddJob
|
||||||
|
{
|
||||||
|
value = 1.0f,
|
||||||
|
result = result2
|
||||||
|
};
|
||||||
|
|
||||||
|
var handle1 = _jobScheduler.Schedule(ref job1);
|
||||||
|
var handle2 = _jobScheduler.Schedule(ref job2);
|
||||||
|
|
||||||
|
_jobScheduler.WaitAll(handle1, handle2);
|
||||||
|
|
||||||
|
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(handle1));
|
||||||
|
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(handle2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void WaitAny()
|
||||||
|
{
|
||||||
|
var result1 = stackalloc float[1];
|
||||||
|
var result2 = stackalloc float[1];
|
||||||
|
|
||||||
|
var job1 = new AddJob
|
||||||
|
{
|
||||||
|
value = 1.0f,
|
||||||
|
result = result1
|
||||||
|
};
|
||||||
|
|
||||||
|
var job2 = new AddJob
|
||||||
|
{
|
||||||
|
value = 1.0f,
|
||||||
|
result = result2
|
||||||
|
};
|
||||||
|
|
||||||
|
var handle1 = _jobScheduler.Schedule(ref job1);
|
||||||
|
var handle2 = _jobScheduler.Schedule(ref job2);
|
||||||
|
|
||||||
|
var completedHandle = _jobScheduler.WaitAny(handle1, handle2);
|
||||||
|
|
||||||
|
Assert.AreEqual(JobState.Completed, _jobScheduler.GetJobStatus(completedHandle));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,13 @@ namespace Misaki.HighPerformance.Collections;
|
|||||||
|
|
||||||
public class ConcurrentSlotMap<T> : IEnumerable<T>
|
public class ConcurrentSlotMap<T> : IEnumerable<T>
|
||||||
{
|
{
|
||||||
|
private struct SlotEntry
|
||||||
|
{
|
||||||
|
public T? value;
|
||||||
|
public int generation;
|
||||||
|
public int isValid;
|
||||||
|
}
|
||||||
|
|
||||||
public struct Enumerator : IEnumerator<T>
|
public struct Enumerator : IEnumerator<T>
|
||||||
{
|
{
|
||||||
private readonly ConcurrentSlotMap<T> _slotMap;
|
private readonly ConcurrentSlotMap<T> _slotMap;
|
||||||
@@ -42,21 +49,6 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock-free slot using separate fields for atomic operations
|
|
||||||
private struct SlotEntry
|
|
||||||
{
|
|
||||||
public T? value;
|
|
||||||
public int generation;
|
|
||||||
public int isValid;
|
|
||||||
|
|
||||||
public SlotEntry()
|
|
||||||
{
|
|
||||||
value = default;
|
|
||||||
generation = 0;
|
|
||||||
isValid = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile SlotEntry[] _data;
|
private volatile SlotEntry[] _data;
|
||||||
private readonly ConcurrentQueue<int> _freeSlots;
|
private readonly ConcurrentQueue<int> _freeSlots;
|
||||||
|
|
||||||
@@ -215,6 +207,29 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
|
|||||||
return false; // Another thread already removed it
|
return false; // Another thread already removed it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Contain(int slotIndex, int generation)
|
||||||
|
{
|
||||||
|
if (slotIndex < 0 || slotIndex >= Volatile.Read(ref _capacity))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var slot = ref _data[slotIndex];
|
||||||
|
|
||||||
|
var currentGeneration = Volatile.Read(ref slot.generation);
|
||||||
|
var isValid = Volatile.Read(ref slot.isValid) == 1;
|
||||||
|
|
||||||
|
if (isValid && currentGeneration == generation)
|
||||||
|
{
|
||||||
|
if (Volatile.Read(ref slot.isValid) == 1 && Volatile.Read(ref slot.generation) == generation)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetElement(int slotIndex, int generation, [MaybeNullWhen(false)] out T value)
|
public bool TryGetElement(int slotIndex, int generation, [MaybeNullWhen(false)] out T value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ public class SlotMap<T> : IEnumerable<T>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Contain(int slotIndex, int generation)
|
||||||
|
{
|
||||||
|
if (slotIndex < 0 || slotIndex >= Volatile.Read(ref _capacity))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var slot = ref _data[slotIndex];
|
||||||
|
|
||||||
|
if (slot.isValid && slot.generation == generation)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public ref T GetElementAt(int slotIndex, int generation)
|
public ref T GetElementAt(int slotIndex, int generation)
|
||||||
{
|
{
|
||||||
if (slotIndex < 0 || slotIndex >= _capacity)
|
if (slotIndex < 0 || slotIndex >= _capacity)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>True</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
Reference in New Issue
Block a user