diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs deleted file mode 100644 index 9ea3ae6e5c..0000000000 --- a/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Concurrent; - -namespace Ryujinx.Core.OsHle.Services.Nv -{ - class NvChNvMap - { - private static ConcurrentDictionary NvMaps; - - public void Create(ServiceCtx Context) - { - long InputPosition = Context.Request.GetBufferType0x21Position(); - long OutputPosition = Context.Request.GetBufferType0x22Position(); - - int Size = Context.Memory.ReadInt32(InputPosition); - - int Handle = AddNvMap(Context, new NvMap(Size)); - - Context.Memory.WriteInt32(OutputPosition, Handle); - } - - private int AddNvMap(ServiceCtx Context, NvMap Map) - { - return NvMaps[Context.Process].Add(Map); - } - - public NvMap GetNvMap(ServiceCtx Context, int Handle) - { - return NvMaps[Context.Process].GetData(Handle); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs new file mode 100644 index 0000000000..c5cee361ca --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvHelper.cs @@ -0,0 +1,10 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + static class NvHelper + { + public static void Crash() + { + + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs new file mode 100644 index 0000000000..245481235f --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlIoctl.cs @@ -0,0 +1,320 @@ +using ChocolArm64.Memory; +using Ryujinx.Core.Logging; +using System.Collections.Concurrent; +using System.Threading; + +namespace Ryujinx.Core.OsHle.Services.Nv +{ + class NvHostCtrlIoctl + { + private const int EventsCount = 64; + + private static ConcurrentDictionary EventArrays; + + private static ConcurrentDictionary NvSyncPts; + + static NvHostCtrlIoctl() + { + EventArrays = new ConcurrentDictionary(); + + NvSyncPts = new ConcurrentDictionary(); + } + + private int SyncPtRead(ServiceCtx Context) + { + return SyncPtReadMinOrMax(Context, Max: false); + } + + private int SyncPtIncr(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + + int Id = Context.Memory.ReadInt32(InputPosition); + + GetOrAddNvSyncPt(Context, Id).Increment(); + + return NvResult.Success; + } + + private int SyncPtWait(ServiceCtx Context) + { + return SyncPtWait(Context, Extended: false); + } + + private int SyncPtWaitEx(ServiceCtx Context) + { + return SyncPtWait(Context, Extended: true); + } + + private int SyncPtReadMax(ServiceCtx Context) + { + return SyncPtReadMinOrMax(Context, Max: true); + } + + private int SyncPtReadMinOrMax(ServiceCtx Context, bool Max) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvHostCtrlSyncPtRead Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + if (Max) + { + Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMax; + } + else + { + Args.Value = GetOrAddNvSyncPt(Context, Args.Id).CounterMin; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private int SyncPtWait(ServiceCtx Context, bool Extended) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvHostCtrlSyncPtWait Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + NvSyncPt SyncPt = GetOrAddNvSyncPt(Context, Args.Id); + + void WriteSyncPtMin() + { + if (Extended) + { + Context.Memory.WriteInt32(OutputPosition + 0xc, SyncPt.CounterMin); + } + } + + if (SyncPt.MinCompare(Args.Thresh)) + { + WriteSyncPtMin(); + + return NvResult.Success; + } + + if (Args.Timeout == 0) + { + return NvResult.TryAgain; + } + + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); + + bool Signaled = true; + + using (ManualResetEvent WaitEvent = new ManualResetEvent(false)) + { + SyncPt.AddWaiter(Args.Thresh, WaitEvent); + + if (Args.Timeout != -1) + { + //Note: Negative (> INT_MAX) timeouts aren't valid on .NET, + //in this case we just use the maximum timeout possible. + int Timeout = Args.Timeout; + + if (Timeout < 0) + { + Timeout = int.MaxValue; + } + + Signaled = WaitEvent.WaitOne(Timeout); + } + else + { + WaitEvent.WaitOne(); + } + } + + WriteSyncPtMin(); + + if (Signaled) + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); + } + else + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Timed out, resuming..."); + } + + return Signaled + ? NvResult.Success + : NvResult.TimedOut; + } + + private int EventWait(ServiceCtx Context, bool IsEvent) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + int Result, Value; + + NvHostCtrlSyncPtWait Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + if (Args.Timeout == 0) + { + return NvResult.TryAgain; + } + + if (IsEvent || Args.Timeout == -1) + { + int EventIndex = GetFreeEvent(Context, Args.Id, out NvHostEvent Event); + + if (EventIndex == EventsCount) + { + return NvResult.OutOfMemory; + } + + Event.Id = Args.Id; + Event.Thresh = Args.Thresh; + + Event.Free = false; + + if (IsEvent) + { + Value = ((Args.Id & 0xfff) << 16) | 0x10000000; + } + else + { + Value = Args.Id << 4; + } + + Value |= EventIndex; + + Result = NvResult.TryAgain; + } + else + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Waiting syncpt with timeout of " + Args.Timeout + "ms..."); + + bool Signaled = true; + + NvSyncPt SyncPt = GetOrAddNvSyncPt(Context, Args.Id); + + using (ManualResetEvent WaitEvent = new ManualResetEvent(false)) + { + SyncPt.AddWaiter(Args.Thresh, WaitEvent); + + if (Args.Timeout != -1) + { + //Note: Negative (> INT_MAX) timeouts aren't valid on .NET, + //in this case we just use the maximum timeout possible. + int Timeout = Args.Timeout; + + if (Timeout < 0) + { + Timeout = int.MaxValue; + } + + Signaled = WaitEvent.WaitOne(Timeout); + } + else + { + WaitEvent.WaitOne(); + } + } + + if (Signaled) + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Resuming..."); + } + else + { + Context.Ns.Log.PrintDebug(LogClass.ServiceNv, "Timed out, resuming..."); + } + + Value = SyncPt.CounterMin; + + Result = Signaled + ? NvResult.Success + : NvResult.TimedOut; + } + + Context.Memory.WriteInt32(OutputPosition + 0xc, Value); + + return Result; + } + + private NvSyncPt GetOrAddNvSyncPt(ServiceCtx Context, int Id) + { + NvSyncPt SyncPt = GetNvSyncPt(Context, Id); + + if (SyncPt == null) + { + IdDictionary SyncPts = NvSyncPts.GetOrAdd(Context.Process, (Key) => new IdDictionary()); + + SyncPt = new NvSyncPt(); + + SyncPts.Add(Id, SyncPt); + } + + return SyncPt; + } + + public NvSyncPt GetNvSyncPt(ServiceCtx Context, int Id) + { + if (NvSyncPts.TryGetValue(Context.Process, out IdDictionary SyncPts)) + { + return SyncPts.GetData(Id); + } + + return null; + } + + private int GetFreeEvent(ServiceCtx Context, int Id, out NvHostEvent Event) + { + NvHostEvent[] Events = GetEvents(Context); + + int NullIndex = EventsCount; + int FreeIndex = EventsCount; + + for (int Index = 0; Index < EventsCount; Index++) + { + Event = Events[Index]; + + if (Event != null) + { + if (Event.Free && MinCompare(Context, + Event.Id, + Event.Thresh)) + { + if (Event.Id == Id) + { + return Index; + } + + FreeIndex = Index; + } + } + else if (NullIndex == EventsCount) + { + NullIndex = Index; + } + } + + if (NullIndex < EventsCount) + { + Events[NullIndex] = Event = new NvHostEvent(); + + return NullIndex; + } + + Event = FreeIndex < EventsCount ? Events[FreeIndex] : null; + + return FreeIndex; + } + + private bool MinCompare(ServiceCtx Context, int Id, int Threshold) + { + //TODO + return false; + } + + private NvHostEvent[] GetEvents(ServiceCtx Context) + { + return EventArrays.GetOrAdd(Context.Process, (Key) => new NvHostEvent[EventsCount]); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs new file mode 100644 index 0000000000..438712290a --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtRead.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvHostCtrlSyncPtRead + { + public int Id; + public int Value; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs new file mode 100644 index 0000000000..83e62526ec --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostCtrlSyncPtWait.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvHostCtrlSyncPtWait + { + public int Id; + public int Thresh; + public int Timeout; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs new file mode 100644 index 0000000000..a5dd99f350 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvHostCtrl/NvHostEvent.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Core.OsHle.Services.Nv +{ + class NvHostEvent + { + public int Id; + public int Thresh; + + public bool Free; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs index 570cef6827..1bc9bb4352 100644 --- a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs @@ -1,3 +1,5 @@ +using System.Threading; + namespace Ryujinx.Core.OsHle.Services.Nv { class NvMap @@ -10,11 +12,28 @@ namespace Ryujinx.Core.OsHle.Services.Nv public long CpuAddress; public long GpuAddress; - public NvMap() { } + private long m_RefCount; - public NvMap(int Size) + public long RefCount => m_RefCount; + + public NvMap() + { + m_RefCount = 1; + } + + public NvMap(int Size) : this() { this.Size = Size; } + + public long IncrementRefCount() + { + return Interlocked.Increment(ref m_RefCount); + } + + public long DecrementRefCount() + { + return Interlocked.Decrement(ref m_RefCount); + } } } \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapAlloc.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapAlloc.cs new file mode 100644 index 0000000000..b87ed2464d --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapAlloc.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapAlloc + { + public int Handle; + public int HeapMask; + public int Flags; + public int Align; + public long Kind; + public long Address; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapCreate.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapCreate.cs new file mode 100644 index 0000000000..d69ce0887d --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapCreate.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapCreate + { + public int Size; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapFree.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapFree.cs new file mode 100644 index 0000000000..dcb87a35b4 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapFree.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapFree + { + public int Handle; + public int Padding; + public long RefCount; + public int Size; + public int Flags; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapFromId.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapFromId.cs new file mode 100644 index 0000000000..77f48ee1ff --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapFromId.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapFromId + { + public int Id; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapGetId.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapGetId.cs new file mode 100644 index 0000000000..dfabc09521 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapGetId.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapGetId + { + public int Id; + public int Handle; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapIoctl.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapIoctl.cs new file mode 100644 index 0000000000..7a022b65bf --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapIoctl.cs @@ -0,0 +1,238 @@ +using ChocolArm64.Memory; +using Ryujinx.Graphics.Gpu; +using System.Collections.Concurrent; + +namespace Ryujinx.Core.OsHle.Services.Nv +{ + class NvMapIoctl + { + private NsGpuMemoryMgr Vmm; + + private static ConcurrentDictionary NvMaps; + + private object NvMapLock; + + private const int FlagNotFreedYet = 1; + + private enum NvMapParam + { + Size = 1, + Align = 2, + Base = 3, + Heap = 4, + Kind = 5, + Compr = 6 + } + + public NvMapIoctl() + { + NvMapLock = new object(); + } + + public int Create(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvMapCreate Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + if (Args.Size == 0) + { + return NvResult.InvalidInput; + } + + Args.Handle = AddNvMap(Context, new NvMap(Args.Size)); + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + public int IocFromId(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvMapFromId Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + lock (NvMapLock) + { + NvMap Map = GetNvMap(Context, Args.Id); + + if (Map == null) + { + return NvResult.InvalidInput; + } + + Map.IncrementRefCount(); + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + public int Alloc(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvMapAlloc Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + NvMap Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + return NvResult.InvalidInput; + } + + if ((Args.Align & (Args.Align - 1)) != 0) + { + return NvResult.InvalidInput; + } + + if ((uint)Args.Align < 0x1000) + { + Args.Align = 0x1000; + } + + Map.Align = Args.Align; + + Map.Kind = (byte)Args.Kind; + + Args.Address = Vmm.Reserve(Args.Address, (uint)Map.Size, (uint)Map.Align); + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + public int Free(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvMapFree Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + lock (NvMapLock) + { + NvMap Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + return NvResult.InvalidInput; + } + + long RefCount = Map.DecrementRefCount(); + + if (RefCount <= 0) + { + DeleteNvMap(Context, Args.Handle); + + Args.Flags = 0; + } + else + { + Args.Flags = FlagNotFreedYet; + } + + Args.RefCount = RefCount; + Args.Size = Map.Size; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + public int Param(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + Nv.NvMapParam Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + NvMap Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + return NvResult.InvalidInput; + } + + switch ((NvMapParam)Args.Param) + { + case NvMapParam.Size: Args.Result = Map.Size; break; + case NvMapParam.Align: Args.Result = Map.Align; break; + case NvMapParam.Heap: Args.Result = 0x40000000; break; + case NvMapParam.Kind: Args.Result = Map.Kind; break; + case NvMapParam.Compr: Args.Result = 0; break; + + //Note: Base is not supported and returns an error. + //Any other value also returns an error. + default: return NvResult.InvalidInput; + } + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + public int GetId(ServiceCtx Context) + { + long InputPosition = Context.Request.GetBufferType0x21Position(); + long OutputPosition = Context.Request.GetBufferType0x22Position(); + + NvMapGetId Args = AMemoryHelper.Read(Context.Memory, InputPosition); + + NvMap Map = GetNvMap(Context, Args.Handle); + + if (Map == null) + { + return NvResult.InvalidInput; + } + + Args.Id = Args.Handle; + + AMemoryHelper.Write(Context.Memory, OutputPosition, Args); + + return NvResult.Success; + } + + private int AddNvMap(ServiceCtx Context, NvMap Map) + { + IdDictionary Maps = NvMaps.GetOrAdd(Context.Process, (Key) => new IdDictionary()); + + return Maps.Add(Map); + } + + private bool DeleteNvMap(ServiceCtx Context, int Handle) + { + if (NvMaps.TryGetValue(Context.Process, out IdDictionary Maps)) + { + return Maps.Delete(Handle) != null; + } + + return false; + } + + public NvMap GetNvMapWithFb(ServiceCtx Context, int Handle) + { + if (NvMaps.TryGetValue(Context.Process, out IdDictionary Maps)) + { + return Maps.GetData(Handle); + } + + return null; + } + + public NvMap GetNvMap(ServiceCtx Context, int Handle) + { + if (Handle != 0 && NvMaps.TryGetValue(Context.Process, out IdDictionary Maps)) + { + return Maps.GetData(Handle); + } + + return null; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMapParam.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMapParam.cs new file mode 100644 index 0000000000..c41142055d --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvMapParam.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + struct NvMapParam + { + public int Handle; + public int Param; + public int Result; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs b/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs new file mode 100644 index 0000000000..f145c475cf --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvResult.cs @@ -0,0 +1,12 @@ +namespace Ryujinx.Core.OsHle.Services.Nv +{ + static class NvResult + { + public const int Success = 0; + public const int TryAgain = -11; + public const int OutOfMemory = -12; + public const int InvalidInput = -22; + public const int NotSupported = -25; + public const int TimedOut = -110; + } +} \ No newline at end of file diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvSyncPt.cs b/Ryujinx.Core/OsHle/Services/Nv/NvSyncPt.cs new file mode 100644 index 0000000000..05ac3dcce5 --- /dev/null +++ b/Ryujinx.Core/OsHle/Services/Nv/NvSyncPt.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; + +namespace Ryujinx.Core.OsHle.Services.Nv +{ + class NvSyncPt + { + private int m_CounterMin; + private int m_CounterMax; + + public int CounterMin => m_CounterMin; + public int CounterMax => m_CounterMax; + + public bool IsEvent { get; set; } + + private ConcurrentDictionary Waiters; + + public NvSyncPt() + { + Waiters = new ConcurrentDictionary(); + } + + public int Increment() + { + Interlocked.Increment(ref m_CounterMax); + + return IncrementMin(); + } + + public int IncrementMin() + { + int Value = Interlocked.Increment(ref m_CounterMin); + + WakeUpWaiters(Value); + + return Value; + } + + public int IncrementMax() + { + return Interlocked.Increment(ref m_CounterMax); + } + + public void AddWaiter(int Threshold, EventWaitHandle WaitEvent) + { + if (!Waiters.TryAdd(WaitEvent, Threshold)) + { + throw new InvalidOperationException(); + } + } + + public bool RemoveWaiter(EventWaitHandle WaitEvent) + { + return Waiters.TryRemove(WaitEvent, out _); + } + + private void WakeUpWaiters(int NewValue) + { + foreach (KeyValuePair KV in Waiters) + { + if (MinCompare(NewValue, m_CounterMax, KV.Value)) + { + KV.Key.Set(); + + Waiters.TryRemove(KV.Key, out _); + } + } + } + + public bool MinCompare(int Threshold) + { + return MinCompare(m_CounterMin, m_CounterMax, Threshold); + } + + private bool MinCompare(int Min, int Max, int Threshold) + { + int MinDiff = Min - Threshold; + int MaxDiff = Max - Threshold; + + if (IsEvent) + { + return MinDiff >= 0; + } + else + { + return (uint)MaxDiff >= (uint)MinDiff; + } + } + } +} \ No newline at end of file