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 where TVal : Cacheable { public long MaxMemoryUsage = 536870912; //512mb public long CurrentMemoryUsage = 0; public double CacheTime = 10.0; //seconds to keep something that's not used public DateTime CurrentTime = DateTime.Now; private LinkedList loadedList = new LinkedList(); private object loadedListLock = new object(); private Dictionary> loadedListDict = new Dictionary>(); public int Count { get { return loadedList.Count; } } public Cache() { } public Cache(long maxMemoryUsage, double cacheTime) { MaxMemoryUsage = maxMemoryUsage; CacheTime = cacheTime; } public void BeginFrame() { CurrentTime = DateTime.Now; Compact(); } public TVal TryGet(TKey key) { LinkedListNode lln = null; lock (loadedListLock) { if (loadedListDict.TryGetValue(key, out lln)) { loadedList.Remove(lln); loadedList.AddLast(lln); lln.Value.LastUseTime = CurrentTime; } } return (lln != null) ? lln.Value : null; } public bool TryAdd(TKey key, TVal item) { item.Key = key; if (CanAdd()) { lock(loadedListLock) { var lln = loadedList.AddLast(item); loadedListDict.Add(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 = CacheTime; int iter = 0, maxiter = 2; while (!CanAdd() && (iter cachetime)) { lock(loadedListLock) { Interlocked.Add(ref CurrentMemoryUsage, -oldlln.Value.MemoryUsage); loadedListDict.Remove(oldlln.Value.Key); loadedList.Remove(oldlln); //gc should free up memory later.. } oldlln.Value = null; oldlln = null; //GC.Collect(); oldlln = loadedList.First; } cachetime *= 0.5; iter++; } if (CanAdd()) //see if there's enough memory now... { lock(loadedListLock) { var newlln = loadedList.AddLast(item); loadedListDict.Add(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() { return Interlocked.Read(ref CurrentMemoryUsage) < MaxMemoryUsage; } public void Clear() { lock(loadedList) { loadedList.Clear(); loadedListDict.Clear(); CurrentMemoryUsage = 0; } } public void Remove(TKey key) { if (!loadedListDict.ContainsKey(key)) { return; } lock(loadedListLock) { LinkedListNode n; if (loadedListDict.TryGetValue(key, out n)) { loadedListDict.Remove(key); loadedList.Remove(n); Interlocked.Add(ref CurrentMemoryUsage, -n.Value.MemoryUsage); } } } public void Compact() { lock(loadedList) { 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); loadedListDict.Remove(oldlln.Value.Key); loadedList.Remove(oldlln); //gc should free up memory later.. oldlln.Value = null; oldlln = nextln; } } } } public abstract class Cacheable { public TKey Key; public DateTime LastUseTime; public long MemoryUsage; } }