mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-16 20:17:30 +08:00
193 lines
5.9 KiB
C#
193 lines
5.9 KiB
C#
using CodeWalker.GameFiles;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CodeWalker
|
|
{
|
|
public class Cache<TKey, TVal> where TVal : Cacheable<TKey> where TKey : notnull
|
|
{
|
|
public long MaxMemoryUsage = 536870912; //512mb
|
|
public long CurrentMemoryUsage = 0;
|
|
public double CacheTime = 10.0; //seconds to keep something that's not used
|
|
public double LoadingCacheTime = 1.0;
|
|
public DateTime CurrentTime = DateTime.Now;
|
|
|
|
private LinkedList<TVal> loadedList = new LinkedList<TVal>();
|
|
private object loadedListLock = new object();
|
|
private ConcurrentDictionary<TKey, LinkedListNode<TVal>> loadedListDict = new ConcurrentDictionary<TKey, LinkedListNode<TVal>>();
|
|
|
|
public int Count => loadedList.Count;
|
|
|
|
public Cache()
|
|
{
|
|
}
|
|
public Cache(long maxMemoryUsage, double cacheTime)
|
|
{
|
|
MaxMemoryUsage = maxMemoryUsage;
|
|
CacheTime = cacheTime;
|
|
}
|
|
|
|
public void BeginFrame()
|
|
{
|
|
var now = DateTime.Now;
|
|
if (now - CurrentTime < TimeSpan.FromSeconds(0.05))
|
|
{
|
|
return;
|
|
}
|
|
CurrentTime = DateTime.Now;
|
|
Compact();
|
|
}
|
|
|
|
public TVal? TryGet(in TKey key)
|
|
{
|
|
lock (loadedListLock)
|
|
{
|
|
if (loadedListDict.TryGetValue(key, out var lln))
|
|
{
|
|
loadedList.Remove(lln);
|
|
loadedList.AddLast(lln);
|
|
|
|
lln.Value.LastUseTime = CurrentTime;
|
|
}
|
|
return lln?.Value;
|
|
}
|
|
}
|
|
public bool TryAdd(TKey key, TVal item)
|
|
{
|
|
item.Key = key;
|
|
if (CanAdd())
|
|
{
|
|
LinkedListNode<TVal> lln;
|
|
lock(loadedListLock)
|
|
{
|
|
lln = loadedList.AddLast(item);
|
|
}
|
|
|
|
lln.Value.LastUseTime = CurrentTime;
|
|
loadedListDict.TryAdd(key, lln);
|
|
Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//cache full, check the front of the list for oldest..
|
|
var oldlln = loadedList.First;
|
|
var cachetime = LoadingCacheTime;
|
|
int iter = 0, maxiter = 2;
|
|
while (!CanAdd() && iter<maxiter)
|
|
{
|
|
while (!CanAdd() && oldlln is not null && (CurrentTime - oldlln.Value.LastUseTime).TotalSeconds > cachetime)
|
|
{
|
|
Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage);
|
|
lock (loadedListLock)
|
|
{
|
|
loadedList.Remove(oldlln); //gc should free up memory later..
|
|
}
|
|
|
|
loadedListDict.TryRemove(oldlln.Value.Key, out _);
|
|
|
|
if (oldlln.Value is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
|
|
//GC.Collect();
|
|
oldlln = loadedList.First;
|
|
}
|
|
cachetime *= 0.5;
|
|
iter++;
|
|
}
|
|
if (CanAdd()) //see if there's enough memory now...
|
|
{
|
|
LinkedListNode<TVal> newlln;
|
|
lock(loadedListLock)
|
|
{
|
|
newlln = loadedList.AddLast(item);
|
|
}
|
|
loadedListDict.TryAdd(key, newlln);
|
|
Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
//really shouldn't get here, but it's possible under stress.
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool CanAdd() => CurrentMemoryUsage < MaxMemoryUsage;
|
|
|
|
|
|
public void Clear()
|
|
{
|
|
lock(loadedList)
|
|
{
|
|
loadedList.Clear();
|
|
loadedListDict.Clear();
|
|
}
|
|
Interlocked.Exchange(ref CurrentMemoryUsage, 0);
|
|
}
|
|
|
|
public void Remove(in TKey key)
|
|
{
|
|
if (!loadedListDict.ContainsKey(key))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (loadedListDict.TryRemove(key, out var n))
|
|
{
|
|
lock (loadedListLock)
|
|
{
|
|
loadedList.Remove(n);
|
|
}
|
|
if (n is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage);
|
|
}
|
|
}
|
|
|
|
|
|
public void Compact()
|
|
{
|
|
lock(loadedListLock)
|
|
{
|
|
var oldlln = loadedList.First;
|
|
while (oldlln is not null)
|
|
{
|
|
if ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds < CacheTime)
|
|
break;
|
|
var nextln = oldlln.Next;
|
|
Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage);
|
|
loadedListDict.TryRemove(oldlln.Value.Key, out var n);
|
|
if (n is IDisposable disposable)
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
loadedList.Remove(oldlln); //gc should free up memory later..
|
|
|
|
oldlln = nextln;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public abstract class Cacheable<TKey> where TKey : notnull
|
|
{
|
|
public TKey Key;
|
|
public DateTime LastUseTime;
|
|
public long MemoryUsage;
|
|
}
|
|
|
|
}
|