CodeWalker/CodeWalker.Core/Utils/Cache.cs

186 lines
5.6 KiB
C#
Raw Normal View History

using CodeWalker.GameFiles;
using System;
using System.Collections.Concurrent;
2017-09-21 18:33:05 +08:00
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>
{
public long MaxMemoryUsage = 536870912; //512mb
public long CurrentMemoryUsage = 0;
2019-12-10 21:31:56 +08:00
public double CacheTime = 10.0; //seconds to keep something that's not used
2023-11-12 01:59:17 +08:00
public double LoadingCacheTime = 1.0;
2019-12-10 21:31:56 +08:00
public DateTime CurrentTime = DateTime.Now;
2017-09-21 18:33:05 +08:00
private LinkedList<TVal> loadedList = new LinkedList<TVal>();
private object loadedListLock = new object();
2023-11-12 01:59:17 +08:00
private ConcurrentDictionary<TKey, LinkedListNode<TVal>> loadedListDict = new ConcurrentDictionary<TKey, LinkedListNode<TVal>>();
2017-09-21 18:33:05 +08:00
public int Count
{
get
{
return loadedList.Count;
}
}
2017-09-21 18:33:05 +08:00
public Cache()
{
}
public Cache(long maxMemoryUsage, double cacheTime)
{
MaxMemoryUsage = maxMemoryUsage;
CacheTime = cacheTime;
}
2019-12-10 21:31:56 +08:00
public void BeginFrame()
{
CurrentTime = DateTime.Now;
Compact();
}
2017-09-21 18:33:05 +08:00
public TVal TryGet(TKey key)
{
lock (loadedListLock)
2017-09-21 18:33:05 +08:00
{
2023-11-12 01:59:17 +08:00
if (loadedListDict.TryGetValue(key, out var lln))
{
loadedList.Remove(lln);
loadedList.AddLast(lln);
lln.Value.LastUseTime = CurrentTime;
}
2023-11-12 01:59:17 +08:00
return (lln != null) ? lln.Value : null;
2017-09-21 18:33:05 +08:00
}
}
public bool TryAdd(TKey key, TVal item)
{
item.Key = key;
if (CanAdd())
{
2023-11-12 01:59:17 +08:00
LinkedListNode<TVal> lln;
lock(loadedListLock)
{
2023-11-12 01:59:17 +08:00
lln = loadedList.AddLast(item);
}
2023-11-12 01:59:17 +08:00
lln.Value.LastUseTime = CurrentTime;
loadedListDict.TryAdd(key, lln);
Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage);
2017-09-21 18:33:05 +08:00
return true;
}
else
{
//cache full, check the front of the list for oldest..
var oldlln = loadedList.First;
2023-11-12 01:59:17 +08:00
var cachetime = LoadingCacheTime;
2017-09-21 18:33:05 +08:00
int iter = 0, maxiter = 2;
while (!CanAdd() && (iter<maxiter))
{
2019-12-10 21:31:56 +08:00
while ((!CanAdd()) && (oldlln != null) && ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds > cachetime))
2017-09-21 18:33:05 +08:00
{
2023-11-12 01:59:17 +08:00
Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage);
lock (loadedListLock)
{
loadedList.Remove(oldlln); //gc should free up memory later..
}
2023-11-12 01:59:17 +08:00
loadedListDict.TryRemove(oldlln.Value.Key, out _);
2017-09-21 18:33:05 +08:00
oldlln.Value = null;
oldlln = null;
//GC.Collect();
oldlln = loadedList.First;
}
cachetime *= 0.5;
iter++;
}
if (CanAdd()) //see if there's enough memory now...
{
2023-11-12 01:59:17 +08:00
LinkedListNode<TVal> newlln;
lock(loadedListLock)
{
2023-11-12 01:59:17 +08:00
newlln = loadedList.AddLast(item);
}
2023-11-12 01:59:17 +08:00
loadedListDict.TryAdd(key, newlln);
Interlocked.Add(ref CurrentMemoryUsage, item.MemoryUsage);
2017-09-21 18:33:05 +08:00
return true;
}
else
{
//really shouldn't get here, but it's possible under stress.
}
}
return false;
}
public bool CanAdd()
{
return Interlocked.Read(ref CurrentMemoryUsage) < MaxMemoryUsage;
}
public void Clear()
{
lock(loadedList)
{
loadedList.Clear();
loadedListDict.Clear();
}
2023-11-12 01:59:17 +08:00
Interlocked.Exchange(ref CurrentMemoryUsage, 0);
2017-09-21 18:33:05 +08:00
}
public void Remove(TKey key)
{
if (!loadedListDict.ContainsKey(key))
2017-09-21 18:33:05 +08:00
{
return;
}
2023-11-12 01:59:17 +08:00
if (loadedListDict.TryRemove(key, out var n))
{
2023-11-12 01:59:17 +08:00
lock (loadedListLock)
{
loadedList.Remove(n);
}
2023-11-12 01:59:17 +08:00
Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage);
2019-12-10 21:31:56 +08:00
}
}
public void Compact()
{
2023-11-12 01:59:17 +08:00
lock(loadedListLock)
2019-12-10 21:31:56 +08:00
{
var oldlln = loadedList.First;
while (oldlln != null)
{
if ((CurrentTime - oldlln.Value.LastUseTime).TotalSeconds < CacheTime) break;
var nextln = oldlln.Next;
Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage);
2023-11-12 01:59:17 +08:00
loadedListDict.TryRemove(oldlln.Value.Key, out _);
loadedList.Remove(oldlln); //gc should free up memory later..
oldlln.Value = null;
oldlln = nextln;
}
2017-09-21 18:33:05 +08:00
}
}
2019-12-10 21:31:56 +08:00
2017-09-21 18:33:05 +08:00
}
public abstract class Cacheable<TKey>
{
public TKey Key;
2019-12-10 21:31:56 +08:00
public DateTime LastUseTime;
2017-09-21 18:33:05 +08:00
public long MemoryUsage;
}
}