Ryujinx/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs

191 lines
No EOL
4.5 KiB
C#

using Ryujinx.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLCachedResource<T>
{
public delegate void DeleteValue(T Value);
private const int MinTimeDelta = 5 * 60000;
private const int MaxRemovalsPerRun = 10;
private struct CacheBucket
{
public T Value { get; private set; }
public LinkedListNode<long> Node { get; private set; }
public long DataSize { get; private set; }
public long Timestamp { get; private set; }
public CacheBucket(T Value, long DataSize, LinkedListNode<long> Node)
{
this.Value = Value;
this.DataSize = DataSize;
this.Node = Node;
Timestamp = PerformanceCounter.ElapsedMilliseconds;
}
}
private Dictionary<long, CacheBucket> Cache;
private LinkedList<long> SortedCache;
private DeleteValue DeleteValueCallback;
private Queue<T> DeletePending;
private bool Locked;
private long MaxSize;
private long TotalSize;
public OGLCachedResource(DeleteValue DeleteValueCallback, long MaxSize)
{
this.MaxSize = MaxSize;
if (DeleteValueCallback == null)
{
throw new ArgumentNullException(nameof(DeleteValueCallback));
}
this.DeleteValueCallback = DeleteValueCallback;
Cache = new Dictionary<long, CacheBucket>();
SortedCache = new LinkedList<long>();
DeletePending = new Queue<T>();
}
public void Lock()
{
Locked = true;
}
public void Unlock()
{
Locked = false;
while (DeletePending.TryDequeue(out T Value))
{
DeleteValueCallback(Value);
}
ClearCacheIfNeeded();
}
public void AddOrUpdate(long Key, T Value, long Size)
{
if (!Locked)
{
ClearCacheIfNeeded();
}
LinkedListNode<long> Node = SortedCache.AddLast(Key);
CacheBucket NewBucket = new CacheBucket(Value, Size, Node);
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
if (Locked)
{
DeletePending.Enqueue(Bucket.Value);
}
else
{
DeleteValueCallback(Bucket.Value);
}
SortedCache.Remove(Bucket.Node);
TotalSize -= Bucket.DataSize;
Cache[Key] = NewBucket;
}
else
{
Cache.Add(Key, NewBucket);
}
TotalSize += Size;
}
public bool TryGetValue(long Key, out T Value)
{
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
Value = Bucket.Value;
SortedCache.Remove(Bucket.Node);
LinkedListNode<long> Node = SortedCache.AddLast(Key);
Cache[Key] = new CacheBucket(Value, Bucket.DataSize, Node);
return true;
}
Value = default(T);
return false;
}
public bool TryGetSize(long Key, out long Size)
{
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
Size = Bucket.DataSize;
return true;
}
Size = 0;
return false;
}
private void ClearCacheIfNeeded()
{
long Timestamp = PerformanceCounter.ElapsedMilliseconds;
int Count = 0;
while (Count++ < MaxRemovalsPerRun)
{
LinkedListNode<long> Node = SortedCache.First;
if (Node == null)
{
break;
}
CacheBucket Bucket = Cache[Node.Value];
long TimeDelta = Timestamp - Bucket.Timestamp;
if (TimeDelta <= MinTimeDelta && !UnderMemoryPressure())
{
break;
}
SortedCache.Remove(Node);
Cache.Remove(Node.Value);
DeleteValueCallback(Bucket.Value);
TotalSize -= Bucket.DataSize;
}
}
private bool UnderMemoryPressure()
{
return TotalSize >= MaxSize;
}
}
}