diff options
author | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-05-23 10:08:29 +0800 |
commit | 8722a9920c1f6119bf6e769cba270e63097f8e25 (patch) | |
tree | 2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs | |
parent | 3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff) |
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs new file mode 100644 index 0000000..0cdd360 --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Utilities/RWLock.cs @@ -0,0 +1,215 @@ +// #define DEBUG_RWLOCK +using Unity.Jobs; + +namespace Pathfinding.Jobs { + /// <summary> + /// A simple read/write lock for use with the Unity Job System. + /// + /// The RW-lock makes the following assumptions: + /// - Only the main thread will call the methods on this lock. + /// - If jobs are to use locked data, you should call <see cref="Read"/> or <see cref="Write"/> on the lock and pass the returned JobHandle as a dependency the job, and then call <see cref="WriteLockAsync.UnlockAfter"/> on the lock object, with the newly scheduled job's handle. + /// - When taking a Read lock, you should only read data, but if you take a Write lock you may modify data. + /// - On the main thread, multiple synchronous write locks may be nested. + /// + /// You do not need to care about dependencies when calling the <see cref="ReadSync"/> and <see cref="WriteSync"/> methods. That's handled automatically for you. + /// + /// See: https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + /// + /// <code> + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// </code> + /// </summary> + public class RWLock { + JobHandle lastWrite; + JobHandle lastRead; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + int heldSyncLocks; + bool pendingAsync; +#if DEBUG_RWLOCK + string pendingStackTrace; +#endif + + void CheckPendingAsync () { +#if DEBUG_RWLOCK + if (pendingAsync) throw new System.InvalidOperationException("An async lock was previously aquired, but UnlockAfter was never called on it. The lock was aquired at\n" + pendingStackTrace + "\n\n"); +#else + if (pendingAsync) throw new System.InvalidOperationException("An async lock was previously aquired, but UnlockAfter was never called on it."); +#endif + } +#endif + + void AddPendingSync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckPendingAsync(); +#if DEBUG_RWLOCK + pendingStackTrace = System.Environment.StackTrace; +#endif + heldSyncLocks++; +#endif + } + + void RemovePendingSync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (heldSyncLocks <= 0) throw new System.InvalidOperationException("Tried to unlock a lock which was not locked. Did you call Unlock twice?"); + heldSyncLocks--; +#endif + } + + void AddPendingAsync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CheckPendingAsync(); +#if DEBUG_RWLOCK + if (heldSyncWriteLocks > 0) throw new System.InvalidOperationException("A synchronous lock is already being held. You cannot lock it asynchronously at the same time. The sync lock was aquired at\n" + pendingStackTrace + "\n\n"); + pendingStackTrace = System.Environment.StackTrace; +#else + if (heldSyncLocks > 0) throw new System.InvalidOperationException("A synchronous lock is already being held. You cannot lock it asynchronously at the same time."); +#endif + pendingAsync = true; +#endif + } + + void RemovePendingAsync () { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + pendingAsync = false; +#endif + } + + /// <summary> + /// Aquire a read lock on the main thread. + /// This method will block until all pending write locks have been released. + /// </summary> + public LockSync ReadSync () { + AddPendingSync(); + lastWrite.Complete(); + lastWrite = default; // Setting this to default will avoid a call into unity's c++ parts next time we call Complete (improves perf slightly) + return new LockSync(this); + } + + /// <summary> + /// Aquire a read lock on the main thread. + /// This method will not block until all asynchronous write locks have been released, instead you should make sure to add the returned JobHandle as a dependency to any jobs that use the locked data. + /// + /// If a synchronous write lock is currently held, this method will throw an exception. + /// + /// <code> + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// </code> + /// </summary> + public ReadLockAsync Read () { + AddPendingAsync(); + return new ReadLockAsync(this, lastWrite); + } + + /// <summary> + /// Aquire a write lock on the main thread. + /// This method will block until all pending read and write locks have been released. + /// </summary> + public LockSync WriteSync () { + AddPendingSync(); + lastWrite.Complete(); + lastWrite = default; // Setting this to default will avoid a call into unity's c++ parts next time we call Complete (improves perf slightly) + lastRead.Complete(); + return new LockSync(this); + } + + /// <summary> + /// Aquire a write lock on the main thread. + /// This method will not block until all asynchronous read and write locks have been released, instead you should make sure to add the returned JobHandle as a dependency to any jobs that use the locked data. + /// + /// If a synchronous write lock is currently held, this method will throw an exception. + /// + /// <code> + /// var readLock = AstarPath.active.LockGraphDataForReading(); + /// var handle = new MyJob { + /// // ... + /// }.Schedule(readLock.dependency); + /// readLock.UnlockAfter(handle); + /// </code> + /// </summary> + public WriteLockAsync Write () { + AddPendingAsync(); + return new WriteLockAsync(this, JobHandle.CombineDependencies(lastRead, lastWrite)); + } + + public readonly struct CombinedReadLockAsync { + readonly RWLock lock1; + readonly RWLock lock2; + public readonly JobHandle dependency; + + public CombinedReadLockAsync(ReadLockAsync lock1, ReadLockAsync lock2) { + this.lock1 = lock1.inner; + this.lock2 = lock2.inner; + dependency = JobHandle.CombineDependencies(lock1.dependency, lock2.dependency); + } + + /// <summary>Release the lock after the given job has completed</summary> + public void UnlockAfter (JobHandle handle) { + if (lock1 != null) { + lock1.RemovePendingAsync(); + lock1.lastRead = JobHandle.CombineDependencies(lock1.lastRead, handle); + } + if (lock2 != null) { + lock2.RemovePendingAsync(); + lock2.lastRead = JobHandle.CombineDependencies(lock2.lastRead, handle); + } + } + } + + public readonly struct ReadLockAsync { + internal readonly RWLock inner; + public readonly JobHandle dependency; + + public ReadLockAsync(RWLock inner, JobHandle dependency) { + this.inner = inner; + this.dependency = dependency; + } + + /// <summary>Release the lock after the given job has completed</summary> + public void UnlockAfter (JobHandle handle) { + if (inner != null) { + inner.RemovePendingAsync(); + inner.lastRead = JobHandle.CombineDependencies(inner.lastRead, handle); + } + } + } + + public readonly struct WriteLockAsync { + readonly RWLock inner; + public readonly JobHandle dependency; + + public WriteLockAsync(RWLock inner, JobHandle dependency) { + this.inner = inner; + this.dependency = dependency; + } + + /// <summary>Release the lock after the given job has completed</summary> + public void UnlockAfter (JobHandle handle) { + if (inner != null) { + inner.RemovePendingAsync(); + inner.lastWrite = handle; + } + } + } + + public readonly struct LockSync { + readonly RWLock inner; + + public LockSync(RWLock inner) { + this.inner = inner; + } + + /// <summary>Release the lock</summary> + public void Unlock () { + if (inner != null) inner.RemovePendingSync(); + } + } + } +} |