#nullable enable using IPA.Logging; using System; using System.Collections.Generic; using System.Reflection; using System.Threading; using System.Runtime.CompilerServices; using System.ComponentModel; using IPA.Config.Data; #if NET3 using Net3_Proxy; using Array = Net3_Proxy.Array; #endif [assembly: InternalsVisibleTo(IPA.Config.Stores.GeneratedStore.AssemblyVisibilityTarget)] namespace IPA.Config.Stores { internal static partial class GeneratedStoreImpl { internal interface IGeneratedStore { Type Type { get; } IGeneratedStore Parent { get; } Impl Impl { get; } void OnReload(); void Changed(); IDisposable ChangeTransaction(); Value Serialize(); void Deserialize(Value val); } internal interface IGeneratedStore : IGeneratedStore where T : class { void CopyFrom(T source, bool useLock); } internal interface IGeneratedPropertyChanged : INotifyPropertyChanged { PropertyChangedEventHandler PropertyChangedEvent { get; } } internal class Impl : IConfigStore { private readonly IGeneratedStore generated; private long enteredTransactions; internal static ConstructorInfo Ctor = typeof(Impl).GetConstructor(new[] { typeof(IGeneratedStore) }); public Impl(IGeneratedStore store) => generated = store; private readonly AutoResetEvent resetEvent = new(false); public WaitHandle SyncObject => resetEvent; public static WaitHandle? ImplGetSyncObject(IGeneratedStore s) => FindImpl(s)?.SyncObject; internal static MethodInfo ImplGetSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetSyncObject)); public ReaderWriterLockSlim WriteSyncObject { get; } = new(); public static ReaderWriterLockSlim? ImplGetWriteSyncObject(IGeneratedStore s) => FindImpl(s)?.WriteSyncObject; internal static MethodInfo ImplGetWriteSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetWriteSyncObject)); internal static MethodInfo ImplSignalChangedMethod = typeof(Impl).GetMethod(nameof(ImplSignalChanged)); public static void ImplSignalChanged(IGeneratedStore s) => FindImpl(s)?.SignalChanged(); public void SignalChanged() { try { _ = resetEvent.Set(); } catch (ObjectDisposedException e) { Logger.config.Error($"ObjectDisposedException while signalling a change for generated store {generated?.GetType()}"); Logger.config.Error(e); } } internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged)); public static void ImplInvokeChanged(IGeneratedStore s) => FindImpl(s)?.InvokeChanged(); public void InvokeChanged() => generated.Changed(); internal static MethodInfo ImplTakeReadMethod = typeof(Impl).GetMethod(nameof(ImplTakeRead)); public static void ImplTakeRead(IGeneratedStore s) => FindImpl(s)?.TakeRead(); public void TakeRead() { if (!WriteSyncObject.IsWriteLockHeld) WriteSyncObject.EnterReadLock(); } internal static MethodInfo ImplReleaseReadMethod = typeof(Impl).GetMethod(nameof(ImplReleaseRead)); public static void ImplReleaseRead(IGeneratedStore s) => FindImpl(s)?.ReleaseRead(); public void ReleaseRead() { if (!WriteSyncObject.IsWriteLockHeld) WriteSyncObject.ExitReadLock(); } internal static MethodInfo ImplTakeWriteMethod = typeof(Impl).GetMethod(nameof(ImplTakeWrite)); public static void ImplTakeWrite(IGeneratedStore s) => FindImpl(s)?.TakeWrite(); public void TakeWrite() => WriteSyncObject.EnterWriteLock(); internal static MethodInfo ImplReleaseWriteMethod = typeof(Impl).GetMethod(nameof(ImplReleaseWrite)); public static void ImplReleaseWrite(IGeneratedStore s) => FindImpl(s)?.ReleaseWrite(); public void ReleaseWrite() => WriteSyncObject.ExitWriteLock(); internal static MethodInfo ImplChangeTransactionMethod = typeof(Impl).GetMethod(nameof(ImplChangeTransaction)); public static IDisposable? ImplChangeTransaction(IGeneratedStore s, IDisposable nest) => FindImpl(s)?.ChangeTransaction(nest); // TODO: improve trasactionals so they don't always save in every case public IDisposable ChangeTransaction(IDisposable nest, bool takeWrite = true) => GetFreeTransaction().InitWith(this, nest, takeWrite && !WriteSyncObject.IsWriteLockHeld); private static ChangeTransactionObj GetFreeTransaction() => freeTransactionObjs.Count > 0 ? freeTransactionObjs.Pop() : new ChangeTransactionObj(); // TODO: maybe sometimes clean this? private static readonly Stack freeTransactionObjs = new(); private sealed class ChangeTransactionObj : IDisposable { private struct Data { public readonly Impl impl; public readonly bool ownsWrite; public readonly IDisposable nested; public Data(Impl impl, bool takeWrite, IDisposable nest) { this.impl = impl; ownsWrite = takeWrite; nested = nest; } } private Data data; public ChangeTransactionObj InitWith(Impl impl, IDisposable nest, bool takeWrite) { data = new Data(impl, takeWrite, nest); _ = Interlocked.Increment(ref impl.enteredTransactions); if (data.ownsWrite) impl.TakeWrite(); return this; } public void Dispose() => Dispose(true); private void Dispose(bool addToStore) { if (data.impl != null && Interlocked.Decrement(ref data.impl.enteredTransactions) == 0) { data.impl.InvokeChanged(); } data.nested?.Dispose(); try { if (data.ownsWrite) data.impl?.ReleaseWrite(); } catch { } data = default; if (addToStore) freeTransactionObjs.Push(this); } ~ChangeTransactionObj() => Dispose(false); } public static Impl? FindImpl(IGeneratedStore store) { while (store?.Parent != null) store = store.Parent; // walk to the top of the tree return store?.Impl; } internal static MethodInfo ImplReadFromMethod = typeof(Impl).GetMethod(nameof(ImplReadFrom)); public static void ImplReadFrom(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.ReadFrom(provider); public void ReadFrom(ConfigProvider provider) { Logger.config.Debug($"Generated impl ReadFrom {generated.GetType()}"); var values = provider.Load(); //Logger.config.Debug($"Read {values}"); generated.Deserialize(values); using var transaction = generated.ChangeTransaction(); generated.OnReload(); } internal static MethodInfo ImplWriteToMethod = typeof(Impl).GetMethod(nameof(ImplWriteTo)); public static void ImplWriteTo(IGeneratedStore s, ConfigProvider provider) => FindImpl(s)?.WriteTo(provider); public void WriteTo(ConfigProvider provider) { Logger.config.Debug($"Generated impl WriteTo {generated.GetType()}"); var values = generated.Serialize(); //Logger.config.Debug($"Serialized {values}"); provider.Store(values); } } } }