using System; using System.Collections.Concurrent; using System.Collections.Generic; namespace FSO.Common.Utils { public static class TimedReferenceController { private static int CurRingNum = 0; private static List> ReferenceRing; private static Dictionary ObjectToRing; private static int CheckFreq = 0; private static int TicksToNextCheck; private static CacheType Type; private static object InternalLock = new object { }; public static CacheType CurrentType { get { return Type; } } static TimedReferenceController() { SetMode(CacheType.ACTIVE); } public static void Clear() { lock (InternalLock) { ObjectToRing = new Dictionary(); foreach (var item in ReferenceRing) { foreach (var obj in item) { (obj as ITimedCachable)?.Rereferenced(false); } item.Clear(); } GC.Collect(2); } } public static void SetMode(CacheType type) { lock (InternalLock) { ObjectToRing = new Dictionary(); switch (type) { case CacheType.AGGRESSIVE: ReferenceRing = new List>(); CheckFreq = 1 * 60; for (int i = 0; i < 5; i++) ReferenceRing.Add(new HashSet()); break; case CacheType.ACTIVE: ReferenceRing = new List>(); CheckFreq = 5 * 60; for (int i = 0; i < 3; i++) ReferenceRing.Add(new HashSet()); break; case CacheType.PERMANENT: case CacheType.PASSIVE: ReferenceRing = new List>(); CheckFreq = int.MaxValue; ReferenceRing.Add(new HashSet()); break; } Type = type; } } public static void Tick() { if (Type == CacheType.PERMANENT) return; if (TicksToNextCheck-- <= 0) { lock (InternalLock) { var toDereference = ReferenceRing[CurRingNum]; foreach (var obj in toDereference) ObjectToRing.Remove(obj); toDereference.Clear(); CurRingNum = (CurRingNum + 1) % ReferenceRing.Count; } TicksToNextCheck = CheckFreq; //GC.Collect(); if (CurRingNum == 0) GC.Collect(); } } public static void KeepAlive(object o, KeepAliveType type) { if (type == KeepAliveType.ACCESS && (o is ITimedCachable)) ((ITimedCachable)o).Rereferenced(true); //should be called whenever the object is referenced lock (InternalLock) { var offset = ReferenceRing.Count - 1; var becomes = (CurRingNum + offset) % ReferenceRing.Count; int oldring; if (ObjectToRing.TryGetValue(o, out oldring)) { if (becomes != oldring) { ReferenceRing[oldring].Remove(o); ObjectToRing.Remove(oldring); } } else { ReferenceRing[becomes].Add(o); ObjectToRing.Add(o, becomes); } } } } public enum KeepAliveType { ACCESS, DEREFERENCED } public enum CacheType { AGGRESSIVE, ACTIVE, PASSIVE, PERMANENT } public class TimedReferenceCache { private ConcurrentDictionary Cache = new ConcurrentDictionary(); private bool PermaMode = false; private List PermaRef = new List(); private VALUE GetOrAddInternal(KEY key, Func valueFactory) { bool didCreate = false; VALUE created = default(VALUE); var value = Cache.GetOrAdd(key, (k) => { //ConcurrentDictionary does not ensure we don't accidentally create things twice. This lock will help us, but I don't think it ensures a perfect world. lock (this) { WeakReference prev; if (Cache.TryGetValue(key, out prev) && prev.IsAlive) return prev; //already created this value. created = valueFactory(k); didCreate = true; return new WeakReference(created, true); } }); var refVal = value.Target; if (didCreate && refVal == (object)created) { //i made this. if we're perma cache ensure a permanent reference. if (TimedReferenceController.CurrentType == CacheType.PERMANENT) { PermaRef.Add(created); PermaMode = true; } return created; } else if (refVal != null || value.IsAlive) return (VALUE)refVal; else { //refrerence died. must recache. WeakReference resultRemove; var removed = Cache.TryRemove(key, out resultRemove); return GetOrAddInternal(key, valueFactory); } } public VALUE GetOrAdd(KEY key, Func valueFactory) { var result = GetOrAddInternal(key, valueFactory); if (result != null && !PermaMode) { TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS); } return result; } public bool TryRemove(KEY key, out VALUE value) { WeakReference resultRef; if (Cache.TryRemove(key, out resultRef)) { value = (VALUE)resultRef.Target; return true; } else { value = default(VALUE); return false; } } } }