Files
2026-07-10 10:24:29 +08:00

141 lines
4.6 KiB
C#

/******************************************************************************
* Spine Runtimes License Agreement
* Last updated July 28, 2023. Replaces all prior versions.
*
* Copyright (c) 2013-2026, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software or
* otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
using System.Threading;
/// <summary>
/// A generic lock-free deque supporting work-stealing based on the paper
/// "Dynamic Circular Work-Stealing Deque", authors David Chase and Yossi Lev
/// https://www.dre.vanderbilt.edu/~schmidt/PDF/work-stealing-dequeue.pdf.
/// Requires that Push and Pop are called from the same thread.
/// Simplified by not supporting growing the array size during a Push,
/// in our usage scenario we populate tasks ahead of time before the first Pop.
/// </summary>
public class LockFreeWorkStealingDeque<T> {
public static readonly T Empty = default(T);
public static readonly T Abort = default(T);
private /*volatile*/ CircularArray<T> activeArray;
private volatile int bottom = 0;
private volatile int top = 0;
public int Capacity { get { return activeArray.Size; } }
public LockFreeWorkStealingDeque (int capacity) {
capacity = UnityEngine.Mathf.NextPowerOfTwo(capacity);
activeArray = new CircularArray<T>(capacity);
bottom = 0;
top = 0;
}
/// <summary>Push an element (at the bottom), has to be called by owner of the deque, not a thief.</summary>
public void Push (T item) {
int b = bottom;
int t = top;
CircularArray<T> a = this.activeArray;
int size = b - t;
if (size >= a.Size - 1) {
a = a.Grow(b, t, a.Size * 2);
this.activeArray = a;
}
a.Put(b, item);
bottom = b + 1;
}
/// <summary>Non-standard addition for ahead-of-time pushing to maintain queue FIFO order.
/// Push an element at the top, must only be called before any other thread calls Push, Pop or Steal.</summary>
public void PushTop (T item) {
int b = bottom;
int t = top;
CircularArray<T> a = this.activeArray;
int size = b - t;
if (size >= a.Size - 1) {
a = a.Grow(b, t, a.Size * 2);
this.activeArray = a;
}
int newT = t - 1;
a.Put(newT, item);
top = newT;
}
/// <summary>
/// Makes a different worker than the owner steal an element (from the top).
/// Returns false if empty.
/// </summary>
public bool Steal (out T item) {
int t = top;
int b = bottom;
CircularArray<T> a = this.activeArray;
int size = b - t;
if (size <= 0) {
item = Empty;
return false;
}
T o = a.Get(t);
// increment top
if (Interlocked.CompareExchange(ref top, t + 1, t) != t) {
item = Abort;
return false;
}
item = o;
return true;
}
/// <summary>Pop an element (from the bottom), has to be called by owner of the deque, not a thief.</summary>
/// <returns>false if empty.</returns>
public bool Pop (out T item) {
int b = bottom;
CircularArray<T> a = this.activeArray;
--b;
this.bottom = b;
int t = top;
int size = b - t;
if (size < 0) {
bottom = t;
item = Empty;
return false;
}
T o = a.Get(b);
if (size > 0) {
item = o;
return true;
}
bool wasSuccessful = true;
if (Interlocked.CompareExchange(ref top, t + 1, t) != t) {
item = Empty;
wasSuccessful = false;
}
else {
item = o;
}
bottom = t + 1;
return wasSuccessful;
}
}