mysimulation/server/tso.common/Utils/TimedReferenceCache.cs
Tony Bark 22191ce648 Removed NioTSO client and server
- NioTSO client isn't needed because we're using RayLib
- Added FreeSO's API server to handle most backend operations
2024-05-01 02:55:43 -04:00

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;
}
}
}
}