mirror of
https://github.com/simtactics/mysimulation.git
synced 2025-03-15 14:51:21 +00:00
- NioTSO client isn't needed because we're using RayLib - Added FreeSO's API server to handle most backend operations
193 lines
6.5 KiB
C#
Executable file
193 lines
6.5 KiB
C#
Executable file
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<HashSet<object>> ReferenceRing;
|
|
private static Dictionary<object, int> 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<object, int>();
|
|
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<object, int>();
|
|
switch (type)
|
|
{
|
|
case CacheType.AGGRESSIVE:
|
|
ReferenceRing = new List<HashSet<object>>();
|
|
CheckFreq = 1 * 60;
|
|
for (int i = 0; i < 5; i++) ReferenceRing.Add(new HashSet<object>());
|
|
break;
|
|
case CacheType.ACTIVE:
|
|
ReferenceRing = new List<HashSet<object>>();
|
|
CheckFreq = 5 * 60;
|
|
for (int i = 0; i < 3; i++) ReferenceRing.Add(new HashSet<object>());
|
|
break;
|
|
case CacheType.PERMANENT:
|
|
case CacheType.PASSIVE:
|
|
ReferenceRing = new List<HashSet<object>>();
|
|
CheckFreq = int.MaxValue;
|
|
ReferenceRing.Add(new HashSet<object>());
|
|
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<KEY, VALUE>
|
|
{
|
|
private ConcurrentDictionary<KEY, WeakReference> Cache = new ConcurrentDictionary<KEY, WeakReference>();
|
|
private bool PermaMode = false;
|
|
private List<VALUE> PermaRef = new List<VALUE>();
|
|
private VALUE GetOrAddInternal(KEY key, Func<KEY, VALUE> 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<KEY, VALUE> 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;
|
|
}
|
|
}
|
|
}
|
|
}
|