Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
@@ -0,0 +1,67 @@
/******************************************************************************
* 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;
using System.Threading;
/// <summary>
/// An array with wrap-around access for LockFreeWorkStealingDeque 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
/// Modified to support negative indices.
/// </summary>
public class CircularArray<T> {
private int size;
private T[] segment;
private uint mask;
public int Size { get { return size; } }
public CircularArray (int sizePoT) {
this.size = sizePoT;
segment = new T[sizePoT];
mask = (uint)(sizePoT - 1);
}
public T Get (int i) {
return this.segment[((uint)i) & mask];
}
public void Put (int i, T item) {
this.segment[((uint)i) & mask] = item;
}
public CircularArray<T> Grow (int b, int t, int newSizePoT) {
CircularArray<T> a = new CircularArray<T>(newSizePoT);
for (int i = t; i < b; i++) {
a.Put(i, this.Get(i));
}
return a;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 347b87d200483c24aaa3764e9eaf2c8f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,80 @@
/******************************************************************************
* 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;
using System.Threading;
/// <summary>
/// A generic lock-free single-producer single-consumer queue.
/// </summary>
public class LockFreeSPSCQueue<T> {
private readonly int capacity;
private readonly T[] circularBuffer;
private int headIndex;
private int tailIndex;
public LockFreeSPSCQueue (int allocatedCapacity) {
capacity = allocatedCapacity;
circularBuffer = new T[allocatedCapacity];
headIndex = tailIndex = 0;
}
/// <summary>Enqueues an item if there is space available.</summary>
/// <returns>True if the item was successfully enqueued, false otherwise.</returns>
public bool Enqueue (T item) {
int head = Thread.VolatileRead(ref headIndex);
int nextHead = (head + 1) % capacity;
if (nextHead == Thread.VolatileRead(ref tailIndex))
return false; // queue is full
circularBuffer[head] = item;
Thread.VolatileWrite(ref headIndex, nextHead);
return true;
}
/// <summary>
/// Dequeues an item unless the queue is empty.
/// </summary>
/// <param name="item">The dequeued item, or the default element if empty.</param>
/// <returns>True if the item was successfully dequeued, false otherwise.</returns>
public bool Dequeue (out T item) {
int tail = Thread.VolatileRead(ref tailIndex);
if (tail == Thread.VolatileRead(ref headIndex)) {
item = default(T); // queue is empty
return false;
}
item = circularBuffer[tail];
int nextTail = (tail + 1) % capacity;
Thread.VolatileWrite(ref tailIndex, nextTail);
return true;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6dd6c3762358e442b1b446b610e959e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,141 @@
/******************************************************************************
* 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;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac7c706babeba4847a4a99f0c35ce0fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,143 @@
/******************************************************************************
* 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.
*****************************************************************************/
#define ENABLE_WORK_STEALING
using System;
using System.Collections.Generic;
using System.Threading;
#if SPINE_ENABLE_THREAD_PROFILING
using UnityEngine.Profiling;
#endif
/// Class to distribute work items like ThreadPool.QueueUserWorkItem but keep the same tasks at the same thread
/// across frames, increasing core affinity (and in some scenarios with lower cache pressure on secondary cores,
/// perhaps even reduce cache eviction).
public class LockFreeWorkStealingWorkerPool<T> : IDisposable {
public class Task {
public T parameters;
public Action<T, int> function;
}
private readonly int _threadCount;
private readonly Thread[] _threads;
private readonly LockFreeWorkStealingDeque<Task>[] _taskQueues;
private readonly AutoResetEvent[] _taskAvailable;
private volatile bool _running = true;
public LockFreeWorkStealingWorkerPool (int threadCount, int queueCapacity = 8) {
_threadCount = threadCount;
_threads = new Thread[_threadCount];
_taskQueues = new LockFreeWorkStealingDeque<Task>[_threadCount];
_taskAvailable = new AutoResetEvent[_threadCount];
for (int i = 0; i < _threadCount; i++) {
_taskQueues[i] = new LockFreeWorkStealingDeque<Task>(queueCapacity);
_taskAvailable[i] = new AutoResetEvent(false);
int index = i; // Capture the index for the thread
_threads[i] = new Thread(() => WorkerLoop(index));
}
for (int i = 0; i < _threadCount; i++) {
_threads[i].Start();
}
}
/// <summary>Enqueues a task item if there is space available, but does
/// not start processing until <see cref="AllowTaskProcessing"/> is called.</summary>
/// <returns>True if the item was successfully enqueued, false otherwise.</returns>
public bool EnqueueTask (int threadIndex, Task task) {
if (threadIndex < 0 || threadIndex >= _threadCount)
throw new ArgumentOutOfRangeException("threadIndex");
_taskQueues[threadIndex].PushTop(task);
return true;
}
/// <summary>
/// Call this method after <see cref="EnqueueTaskWithoutProcessing"/> to start processing all enqueued tasks.
/// This limitation comes from LockFreeWorkStealingDeque requiring the same thread calling Push and Pop,
/// which would not be the case here.
/// </summary>
/// <param name="numAsyncThreads">Limits the number of active worker threads. Note that when work stealing is
/// enabled, empty threads steal tasks from other threads even if no tasks were originally enqueued at them.</param>
public void AllowTaskProcessing (int numAsyncThreads) {
for (int t = 0; t < numAsyncThreads; ++t)
_taskAvailable[t].Set();
}
private void WorkerLoop (int threadIndex) {
#if SPINE_ENABLE_THREAD_PROFILING
Profiler.BeginThreadProfiling("Spine Threads", "Spine Thread " + threadIndex);
#endif
while (_running) {
_taskAvailable[threadIndex].WaitOne();
Task task = null;
bool success;
do {
success = _taskQueues[threadIndex].Pop(out task);
if (success) {
task.function(task.parameters, threadIndex);
} else {
#if ENABLE_WORK_STEALING
int stealThreadIndex = (threadIndex + 1) % _threadCount;
while (stealThreadIndex != threadIndex) { // circle complete
while (true) {
task = null;
bool stealSuccessful = _taskQueues[stealThreadIndex].Steal(out task);
if (!stealSuccessful)
break;
task.function(task.parameters, threadIndex);
}
stealThreadIndex = (stealThreadIndex + 1) % _threadCount;
}
#endif
}
} while (success);
}
#if SPINE_ENABLE_THREAD_PROFILING
Profiler.EndThreadProfiling();
#endif
}
public void Dispose () {
_running = false;
for (int i = 0; i < _threadCount; i++) {
_taskAvailable[i].Set(); // Wake up threads to exit
}
foreach (var thread in _threads) {
thread.Join();
}
for (int i = 0; i < _threadCount; i++) {
_taskAvailable[i].Close();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14b683fce49a5a641b55e8af49df35a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,113 @@
/******************************************************************************
* 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;
using System.Collections.Generic;
using System.Threading;
#if SPINE_ENABLE_THREAD_PROFILING
using UnityEngine.Profiling;
#endif
/// Class to distribute work items like ThreadPool.QueueUserWorkItem but keep the same tasks at the same thread
/// across frames, increasing core affinity (and in some scenarios with lower cache pressure on secondary cores,
/// perhaps even reduce cache eviction).
public class LockFreeWorkerPool<T> : IDisposable {
public class Task {
public T parameters;
public Action<T, int> function;
}
private readonly int _threadCount;
private readonly Thread[] _threads;
private readonly LockFreeSPSCQueue<Task>[] _taskQueues;
private readonly AutoResetEvent[] _taskAvailable;
private volatile bool _running = true;
public LockFreeWorkerPool (int threadCount, int queueCapacity = 2) {
_threadCount = threadCount;
_threads = new Thread[_threadCount];
_taskQueues = new LockFreeSPSCQueue<Task>[_threadCount];
_taskAvailable = new AutoResetEvent[_threadCount];
for (int i = 0; i < _threadCount; i++) {
_taskQueues[i] = new LockFreeSPSCQueue<Task>(queueCapacity);
_taskAvailable[i] = new AutoResetEvent(false);
int index = i; // Capture the index for the thread
_threads[i] = new Thread(() => WorkerLoop(index));
_threads[i].Start();
}
}
/// <summary>Enqueues a task item if there is space available.</summary>
/// <returns>True if the item was successfully enqueued, false otherwise.</returns>
public bool EnqueueTask (int threadIndex, Task task) {
if (threadIndex < 0 || threadIndex >= _threadCount)
throw new ArgumentOutOfRangeException("threadIndex");
bool success = _taskQueues[threadIndex].Enqueue(task);
if (!success) {
return false;
}
_taskAvailable[threadIndex].Set();
return true;
}
private void WorkerLoop (int threadIndex) {
#if SPINE_ENABLE_THREAD_PROFILING
Profiler.BeginThreadProfiling("Spine Threads", "Spine Thread " + threadIndex);
#endif
while (_running) {
Task task = null;
bool success = _taskQueues[threadIndex].Dequeue(out task);
if (success) {
task.function(task.parameters, threadIndex);
} else {
_taskAvailable[threadIndex].WaitOne();
}
}
#if SPINE_ENABLE_THREAD_PROFILING
Profiler.EndThreadProfiling();
#endif
}
public void Dispose () {
_running = false;
for (int i = 0; i < _threadCount; i++) {
_taskAvailable[i].Set(); // Wake up threads to exit
}
foreach (var thread in _threads) {
thread.Join();
}
for (int i = 0; i < _threadCount; i++) {
_taskAvailable[i].Close();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 013e48636028fc245a5581519017ff3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a1427d268719124ab5d9841d5f4ddd7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: