using System; using System.Collections.Generic; using System.Reflection; namespace MonoMod.Utils { public sealed class DynData : IDisposable where TTarget : class { private static readonly object[] _NoArgs = new object[0]; public static readonly HashSet Disposable = new HashSet(); public static event Action, TTarget> OnInitialize; private static readonly _Data_ _DataStatic = new _Data_(); private static readonly Dictionary _DataMap = new Dictionary(new WeakReferenceComparer()); private static readonly Dictionary> _SpecialGetters = new Dictionary>(); private static readonly Dictionary> _SpecialSetters = new Dictionary>(); private readonly WeakReference Weak; private readonly _Data_ _Data; private class _Data_ { public readonly Dictionary> Getters = new Dictionary>(); public readonly Dictionary> Setters = new Dictionary>(); public readonly Dictionary Data = new Dictionary(); } public Dictionary> Getters => _Data.Getters; public Dictionary> Setters => _Data.Setters; public Dictionary Data => _Data.Data; static DynData() { _DataHelper_.Collected += () => { HashSet dead = new HashSet(); foreach (WeakReference weak in _DataMap.Keys) if (!weak.IsAlive) dead.Add(weak); foreach (WeakReference weak in dead) _DataMap.Remove(weak); }; foreach (FieldInfo field in typeof(TTarget).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { string name = field.Name; _SpecialGetters[name] = (obj) => field.GetValue(obj); _SpecialSetters[name] = (obj, value) => field.SetValue(obj, value); } foreach (PropertyInfo prop in typeof(TTarget).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { string name = prop.Name; MethodInfo get = prop.GetGetMethod(true); if (get != null) { _SpecialGetters[name] = (obj) => get.Invoke(obj, _NoArgs); } MethodInfo set = prop.GetSetMethod(true); if (set != null) { _SpecialSetters[name] = (obj, value) => set.Invoke(obj, _NoArgs); } } } public bool IsAlive => Weak.IsAlive; public TTarget Target => Weak.Target as TTarget; public object this[string name] { get { if (_SpecialGetters.TryGetValue(name, out Func cb) || Getters.TryGetValue(name, out cb)) return cb(Weak.Target as TTarget); if (Data.TryGetValue(name, out object value)) return value; return null; } set { if (_SpecialSetters.TryGetValue(name, out Action cb) || Setters.TryGetValue(name, out cb)) { cb(Weak.Target as TTarget, value); return; } object prev; if (Disposable.Contains(name) && (prev = this[name]) != null && prev is IDisposable prevDisposable) prevDisposable.Dispose(); Data[name] = value; } } public DynData(TTarget obj) { if (obj != null) { WeakReference weak = new WeakReference(obj); lock (_DataMap) { if (!_DataMap.TryGetValue(weak, out _Data)) { _Data = new _Data_(); _DataMap.Add(weak, _Data); } } Weak = weak; } else { _Data = _DataStatic; } OnInitialize?.Invoke(this, obj); } public T Get(string name) => (T) this[name]; public void Set(string name, T value) => this[name] = value; public void RegisterProperty(string name, Func getter, Action setter) { Getters[name] = getter; Setters[name] = setter; } public void UnregisterProperty(string name) { Getters.Remove(name); Setters.Remove(name); } private void Dispose(bool disposing) { foreach (string name in Disposable) if (Data.TryGetValue(name, out object value) && value is IDisposable valueDisposable) valueDisposable.Dispose(); Data.Clear(); } ~DynData() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private sealed class WeakReferenceComparer : EqualityComparer { public override bool Equals(WeakReference x, WeakReference y) => ReferenceEquals(x.Target, y.Target) && x.IsAlive == y.IsAlive; public override int GetHashCode(WeakReference obj) => obj.Target?.GetHashCode() ?? 0; } } internal static class _DataHelper_ { public static event Action Collected; static _DataHelper_() { new CollectionDummy(); } private sealed class CollectionDummy { ~CollectionDummy() { GC.ReRegisterForFinalize(this); Collected?.Invoke(); } } } }