diff --git a/IPA.Loader/Config/ConfigRuntime.cs b/IPA.Loader/Config/ConfigRuntime.cs index baf5138f..609e579a 100644 --- a/IPA.Loader/Config/ConfigRuntime.cs +++ b/IPA.Loader/Config/ConfigRuntime.cs @@ -2,15 +2,10 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; -using System.Text; using System.Threading.Tasks; using System.Threading; -using IPA.Utilities; using IPA.Utilities.Async; using System.IO; -using System.Runtime.CompilerServices; -using IPA.Logging; -using UnityEngine; using Logger = IPA.Logging.Logger; #if NET4 using Task = System.Threading.Tasks.Task; @@ -30,15 +25,17 @@ namespace IPA.Config => obj?.GetHashCode() ?? 0; } - private static readonly ConcurrentBag configs = new ConcurrentBag(); - private static readonly AutoResetEvent configsChangedWatcher = new AutoResetEvent(false); + private static readonly ConcurrentBag configs = new(); + private static readonly AutoResetEvent configsChangedWatcher = new(false); + public static readonly BlockingCollection RequiresSave = new(); private static readonly ConcurrentDictionary watchers = new ConcurrentDictionary(new DirInfoEqComparer()); private static readonly ConcurrentDictionary> watcherTrackConfigs = new ConcurrentDictionary>(); - private static SingleThreadTaskScheduler loadScheduler = null; - private static TaskFactory loadFactory = null; - private static Thread saveThread = null; + private static SingleThreadTaskScheduler loadScheduler; + private static TaskFactory loadFactory; + private static Thread saveThread; + private static Thread legacySaveThread; private static void TryStartRuntime() { @@ -55,6 +52,11 @@ namespace IPA.Config saveThread = new Thread(SaveThread); saveThread.Start(); } + if (legacySaveThread == null || !legacySaveThread.IsAlive) + { + legacySaveThread = new Thread(LegacySaveThread); + legacySaveThread.Start(); + } AppDomain.CurrentDomain.ProcessExit -= ShutdownRuntime; AppDomain.CurrentDomain.ProcessExit += ShutdownRuntime; @@ -212,15 +214,47 @@ namespace IPA.Config Logger.Config.Error($"{nameof(IConfigStore)} for {config.File} errored while reading from the {nameof(IConfigProvider)}"); Logger.Config.Error(e); } - } + } private static void SaveThread() + { + try + { + foreach (var item in RequiresSave.GetConsumingEnumerable()) + { + try + { + Save(configs.First((c) => ReferenceEquals(c.Store.WriteSyncObject, item.WriteSyncObject))); + } + catch (ThreadAbortException) + { + break; + } + catch (Exception e) + { + Logger.Config.Error($"Error waiting for in-memory updates"); + Logger.Config.Error(e); + Thread.Sleep(TimeSpan.FromSeconds(1)); + } + } + } + catch (ThreadAbortException) + { + // we got aborted :( + } + finally + { + RequiresSave.Dispose(); + } + } + + private static void LegacySaveThread() { try { while (true) { - var configArr = configs.Where(c => c.Store != null).ToArray(); + var configArr = configs.Where(c => c.Store != null).Where(c => c.Store.SyncObject != null).ToArray(); int index = -1; try { diff --git a/IPA.Loader/Config/IConfigStore.cs b/IPA.Loader/Config/IConfigStore.cs index 240ecf04..912e77f8 100644 --- a/IPA.Loader/Config/IConfigStore.cs +++ b/IPA.Loader/Config/IConfigStore.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Threading; namespace IPA.Config { @@ -17,6 +11,7 @@ namespace IPA.Config /// A synchronization object for the save thread to wait on for changes. /// It should be signaled whenever the internal state of the object is changed. /// The writer will never signal this handle. + /// This will be null for internally-implemented providers /// WaitHandle SyncObject { get; } diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs index 28620626..ee8bc0d8 100644 --- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs +++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/IGeneratedStore.cs @@ -47,9 +47,7 @@ namespace IPA.Config.Stores 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 WaitHandle? SyncObject => null; public static WaitHandle? ImplGetSyncObject(IGeneratedStore s) => FindImpl(s)?.SyncObject; internal static MethodInfo ImplGetSyncObjectMethod = typeof(Impl).GetMethod(nameof(ImplGetSyncObject)); @@ -61,15 +59,7 @@ namespace IPA.Config.Stores 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); - } + ConfigRuntime.RequiresSave.Add(this); } internal static MethodInfo ImplInvokeChangedMethod = typeof(Impl).GetMethod(nameof(ImplInvokeChanged));