diff --git a/IPA.Loader/Config/ConfigRuntime.cs b/IPA.Loader/Config/ConfigRuntime.cs
index 0729d5ff..d9c4a203 100644
--- a/IPA.Loader/Config/ConfigRuntime.cs
+++ b/IPA.Loader/Config/ConfigRuntime.cs
@@ -64,19 +64,19 @@ namespace IPA.Config
=> ShutdownRuntime();
internal static void ShutdownRuntime()
{
- try
- {
- watcherTrackConfigs.Clear();
- var watchList = watchers.ToArray();
- watchers.Clear();
-
- foreach (var pair in watchList)
- pair.Value.EnableRaisingEvents = false;
-
- loadScheduler.Join(); // we can wait for the loads to finish
- saveThread.Abort(); // eww, but i don't like any of the other potential solutions
-
- SaveAll();
+ try
+ {
+ watcherTrackConfigs.Clear();
+ var watchList = watchers.ToArray();
+ watchers.Clear();
+
+ foreach (var pair in watchList)
+ pair.Value.EnableRaisingEvents = false;
+
+ loadScheduler.Join(); // we can wait for the loads to finish
+ saveThread.Abort(); // eww, but i don't like any of the other potential solutions
+
+ SaveAll();
}
catch
{
diff --git a/IPA.Loader/Config/Stores/CustomObjectConverter.cs b/IPA.Loader/Config/Stores/CustomObjectConverter.cs
index e361a3e2..be881cc7 100644
--- a/IPA.Loader/Config/Stores/CustomObjectConverter.cs
+++ b/IPA.Loader/Config/Stores/CustomObjectConverter.cs
@@ -85,56 +85,56 @@ namespace IPA.Config.Stores.Converters
=> Serialize(obj, parent);
}
- ///
- /// A for custom value types, serialized identically to the reference types serialized with
- /// .
- ///
+ ///
+ /// A for custom value types, serialized identically to the reference types serialized with
+ /// .
+ ///
/// the type of the value to convert
- public class CustomValueTypeConverter : ValueConverter where T : struct
- {
- private static readonly GeneratedStoreImpl.SerializeObject serialize
- = GeneratedStoreImpl.GetSerializerDelegate();
- private static readonly GeneratedStoreImpl.DeserializeObject deserialize
- = GeneratedStoreImpl.GetDeserializerDelegate();
-
+ public class CustomValueTypeConverter : ValueConverter where T : struct
+ {
+ private static readonly GeneratedStoreImpl.SerializeObject serialize
+ = GeneratedStoreImpl.GetSerializerDelegate();
+ private static readonly GeneratedStoreImpl.DeserializeObject deserialize
+ = GeneratedStoreImpl.GetDeserializerDelegate();
+
///
/// Deserializes into a with the given .
///
/// the to deserialize
/// the parent object that will own the deserialized value
/// the deserialized value
- ///
- public static T Deserialize(Value value, object parent)
- => deserialize(value, parent);
-
+ ///
+ public static T Deserialize(Value value, object parent)
+ => deserialize(value, parent);
+
///
/// Serializes into a corresponding structure.
///
/// the object to serialize
/// the tree that represents
- ///
- public static Value Serialize(T obj)
- => serialize(obj);
-
+ ///
+ public static Value Serialize(T obj)
+ => serialize(obj);
+
///
/// Deserializes into a with the given .
///
/// the to deserialize
/// the parent object that will own the deserialized value
/// the deserialized value
- ///
- public override T FromValue(Value value, object parent)
- => Deserialize(value, parent);
-
+ ///
+ public override T FromValue(Value value, object parent)
+ => Deserialize(value, parent);
+
///
/// Serializes into a structure, given .
///
/// the object to serialize
/// the parent object that owns
/// the tree that represents
- ///
- public override Value ToValue(T obj, object parent)
- => Serialize(obj);
+ ///
+ public override Value ToValue(T obj, object parent)
+ => Serialize(obj);
}
}
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
index f820202c..e75aa819 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/GeneratedStoreImpl.cs
@@ -16,7 +16,8 @@ using Boolean = IPA.Config.Data.Boolean;
using System.Collections;
using IPA.Utilities;
using System.ComponentModel;
-using System.Collections.Concurrent;
+using System.Collections.Concurrent;
+using IPA.Utilities.Async;
#if NET3
using Net3_Proxy;
using Array = Net3_Proxy.Array;
@@ -38,38 +39,13 @@ namespace IPA.Config.Stores
internal static T Create(IGeneratedStore parent) where T : class => (T)Create(typeof(T), parent);
private static IConfigStore Create(Type type, IGeneratedStore parent)
- => GetCreator(type)(parent);
-
- private static readonly ConcurrentDictionary generatedCreators
- = new ConcurrentDictionary();
+ => GetCreator(type)(parent);
+
+ private static readonly SingleCreationValueCache generatedCreators
+ = new SingleCreationValueCache();
private static (GeneratedStoreCreator ctor, Type type) GetCreatorAndGeneratedType(Type t)
- {
- retry:
- if (generatedCreators.TryGetValue(t, out var gen))
- {
- if (gen.wh != null)
- {
- gen.wh.Wait();
- goto retry; // this isn't really a good candidate for a loop
- // the loop condition will never be hit, and this should only
- // jump back to the beginning in exceptional situations
- }
- return (gen.ctor, gen.type);
- }
- else
- {
- var wh = new ManualResetEventSlim(false);
- var cmp = (wh, (GeneratedStoreCreator)null, (Type)null);
- if (!generatedCreators.TryAdd(t, cmp))
- goto retry; // someone else beat us to the punch, retry getting their value and wait for them
- var (ctor, type) = MakeCreator(t);
- while (!generatedCreators.TryUpdate(t, (null, ctor, type), cmp))
- throw new InvalidOperationException("Somehow, multiple MakeCreators started running for the same target type!");
- wh.Set();
- return (ctor, type);
- }
- }
+ => generatedCreators.GetOrAdd(t, MakeCreator);
internal static GeneratedStoreCreator GetCreator(Type t)
=> GetCreatorAndGeneratedType(t).ctor;
@@ -111,6 +87,7 @@ namespace IPA.Config.Stores
}
}
+ // TODO: does this need to be a SingleCreationValueCache or similar?
private static readonly Dictionary> TypeRequiredConverters = new Dictionary>();
private static void CreateAndInitializeConvertersFor(Type type, IEnumerable structure)
{
diff --git a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
index 963dd712..aa443657 100644
--- a/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
+++ b/IPA.Loader/Config/Stores/GeneratedStoreImpl/ObjectStructure.cs
@@ -2,6 +2,7 @@
using IPA.Config.Stores.Converters;
using IPA.Logging;
using IPA.Utilities;
+using IPA.Utilities.Async;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -127,14 +128,11 @@ namespace IPA.Config.Stores
return true;
}
- private static readonly Dictionary objectStructureCache = new Dictionary();
+ private static readonly SingleCreationValueCache objectStructureCache
+ = new SingleCreationValueCache();
private static IEnumerable ReadObjectMembers(Type type)
- {
- if (!objectStructureCache.TryGetValue(type, out var structure))
- objectStructureCache.Add(type, structure = ReadObjectMembersInternal(type).ToArray());
- return structure;
- }
+ => objectStructureCache.GetOrAdd(type, t => ReadObjectMembersInternal(type).ToArray());
private static IEnumerable ReadObjectMembersInternal(Type type)
{
diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj
index 6354d825..c5b0d59e 100644
--- a/IPA.Loader/IPA.Loader.csproj
+++ b/IPA.Loader/IPA.Loader.csproj
@@ -149,6 +149,7 @@
+
diff --git a/IPA.Loader/Loader/Features/Feature.cs b/IPA.Loader/Loader/Features/Feature.cs
index ba5c4ecb..20d07728 100644
--- a/IPA.Loader/Loader/Features/Feature.cs
+++ b/IPA.Loader/Loader/Features/Feature.cs
@@ -88,6 +88,8 @@ namespace IPA.Loader.Features
/// if this will be stored on the plugin metadata, otherwise
protected internal virtual bool StoreOnPlugin => true;
+ // TODO: rework features to take arguments as JSON objects
+
static Feature()
{
Reset();
diff --git a/IPA.Loader/Loader/PluginComponent.cs b/IPA.Loader/Loader/PluginComponent.cs
index e6bbd98e..add00600 100644
--- a/IPA.Loader/Loader/PluginComponent.cs
+++ b/IPA.Loader/Loader/PluginComponent.cs
@@ -1,8 +1,8 @@
using IPA.Config;
using IPA.Loader.Composite;
-using IPA.Utilities;
-using IPA.Utilities.Async;
-using System.Diagnostics.CodeAnalysis;
+using IPA.Utilities;
+using IPA.Utilities.Async;
+using System.Diagnostics.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;
// ReSharper disable UnusedMember.Local
@@ -28,7 +28,7 @@ namespace IPA.Loader
if (!initialized)
{
- UnityGame.SetMainThread();
+ UnityGame.SetMainThread();
UnityGame.EnsureRuntimeGameVersion();
PluginManager.Load();
@@ -47,16 +47,16 @@ namespace IPA.Loader
SceneManager.activeSceneChanged += OnActiveSceneChanged;
SceneManager.sceneLoaded += OnSceneLoaded;
- SceneManager.sceneUnloaded += OnSceneUnloaded;
-
- var unitySched = UnityMainThreadTaskScheduler.Default as UnityMainThreadTaskScheduler;
- if (!unitySched.IsRunning)
+ SceneManager.sceneUnloaded += OnSceneUnloaded;
+
+ var unitySched = UnityMainThreadTaskScheduler.Default as UnityMainThreadTaskScheduler;
+ if (!unitySched.IsRunning)
StartCoroutine(unitySched.Coroutine());
- initialized = true;
-
-#if DEBUG
- Config.Stores.GeneratedStoreImpl.DebugSaveAssembly("GeneratedAssembly.dll");
+ initialized = true;
+
+#if DEBUG
+ Config.Stores.GeneratedStoreImpl.DebugSaveAssembly("GeneratedAssembly.dll");
#endif
}
}
diff --git a/IPA.Loader/Utilities/Accessor.cs b/IPA.Loader/Utilities/Accessor.cs
index ad1cd89c..1433f02c 100644
--- a/IPA.Loader/Utilities/Accessor.cs
+++ b/IPA.Loader/Utilities/Accessor.cs
@@ -1,8 +1,11 @@
-using System;
+using IPA.Utilities.Async;
+using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
+using System.Threading;
#if NET3
using Net3_Proxy;
using Array = Net3_Proxy.Array;
@@ -25,9 +28,6 @@ namespace IPA.Utilities
/// a reference to the field's value
public delegate ref U Accessor(ref T obj);
- // field name -> accessor
- private static readonly Dictionary accessors = new Dictionary();
-
private static Accessor MakeAccessor(string fieldName)
{
var field = typeof(T).GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
@@ -51,6 +51,9 @@ namespace IPA.Utilities
return (Accessor)dyn.CreateDelegate(typeof(Accessor));
}
+ // field name -> accessor
+ private static readonly SingleCreationValueCache accessors = new SingleCreationValueCache();
+
///
/// Gets an for the field named on .
///
@@ -58,11 +61,7 @@ namespace IPA.Utilities
/// an accessor for the field
/// if the field does not exist on
public static Accessor GetAccessor(string name)
- {
- if (!accessors.TryGetValue(name, out var accessor))
- accessors.Add(name, accessor = MakeAccessor(name));
- return accessor;
- }
+ => accessors.GetOrAdd(name, MakeAccessor);
///
/// Accesses a field for an object by name.
@@ -149,8 +148,6 @@ namespace IPA.Utilities
/// the new property value
public delegate void Setter(ref T obj, U val);
- private static readonly Dictionary props = new Dictionary();
-
private static (Getter, Setter) MakeAccessors(string propName)
{
var prop = typeof(T).GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
@@ -201,12 +198,11 @@ namespace IPA.Utilities
return (getter, setter);
}
+ private static readonly SingleCreationValueCache props
+ = new SingleCreationValueCache();
+
private static (Getter get, Setter set) GetAccessors(string propName)
- {
- if (!props.TryGetValue(propName, out var access))
- props.Add(propName, access = MakeAccessors(propName));
- return access;
- }
+ => props.GetOrAdd(propName, MakeAccessors);
///
/// Gets a for the property identified by .
@@ -289,8 +285,6 @@ namespace IPA.Utilities
/// the delegate type to create, and to use as a signature to search for
public static class MethodAccessor where TDelegate : Delegate
{
- private static readonly Dictionary methods = new Dictionary();
-
static MethodAccessor()
{
// ensure that first argument of delegate type is valid
@@ -325,6 +319,8 @@ namespace IPA.Utilities
return (TDelegate)Delegate.CreateDelegate(AccessorDelegateInfo.Type, method, true);
}
+ private static readonly SingleCreationValueCache methods = new SingleCreationValueCache();
+
///
/// Gets a delegate to the named method with the signature specified by .
///
@@ -333,11 +329,7 @@ namespace IPA.Utilities
/// if does not represent the name of a method with the given signature
/// if the method found returns a type incompatable with the return type of
public static TDelegate GetDelegate(string name)
- {
- if (!methods.TryGetValue(name, out var del))
- methods.Add(name, del = MakeDelegate(name));
- return del;
- }
+ => methods.GetOrAdd(name, MakeDelegate);
}
}
diff --git a/IPA.Loader/Utilities/Async/SingleCreationValueCache.cs b/IPA.Loader/Utilities/Async/SingleCreationValueCache.cs
new file mode 100644
index 00000000..71d42e8a
--- /dev/null
+++ b/IPA.Loader/Utilities/Async/SingleCreationValueCache.cs
@@ -0,0 +1,154 @@
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace IPA.Utilities.Async
+{
+ ///
+ /// A dictionary-like type intended for thread-safe value caches whose values are created only once ever.
+ ///
+ /// the key type of the cache
+ /// the value type of the cache
+ ///
+ /// This object basically wraps a with some special handling
+ /// to ensure that values are only created once ever, without having multiple parallel constructions.
+ ///
+ public class SingleCreationValueCache
+ {
+ private readonly ConcurrentDictionary dict;
+
+ private static KeyValuePair ExpandKeyValuePair(KeyValuePair kvp)
+ => new KeyValuePair(kvp.Key, (null, kvp.Value));
+ private static KeyValuePair CompressKeyValuePair(KeyValuePair kvp)
+ => new KeyValuePair(kvp.Key, kvp.Value.value);
+
+ #region Constructors
+ ///
+ /// Initializes a new instance of the
+ /// class that is empty, has the default concurrency level, has the default initial
+ /// capacity, and uses the default comparer for the key type.
+ ///
+ public SingleCreationValueCache()
+ => dict = new ConcurrentDictionary();
+ ///
+ /// Initializes a new instance of the
+ /// class that contains elements copied from the specified ,
+ /// has the default concurrency level, has the default initial capacity, and uses
+ /// the default comparer for the key type.
+ ///
+ /// the whose element are to be used for the new cache
+ /// when any arguments are null
+ /// contains duplicate keys
+ public SingleCreationValueCache(IEnumerable> collection)
+ => dict = new ConcurrentDictionary(collection.Select(ExpandKeyValuePair));
+ ///
+ /// Initializes a new instance of the
+ /// class that is empty, has the default concurrency level and capacity, and uses
+ /// the specified .
+ ///
+ /// the equality comparer to use when comparing keys
+ /// is null
+ public SingleCreationValueCache(IEqualityComparer comparer)
+ => dict = new ConcurrentDictionary(comparer);
+ ///
+ /// Initializes a new instance of the
+ /// class that contains elements copied from the specified
+ /// has the default concurrency level, has the default initial capacity, and uses
+ /// the specified .
+ ///
+ /// the whose elements are to be used for the new cache
+ /// the equality comparer to use when comparing keys
+ /// or is null
+ public SingleCreationValueCache(IEnumerable> collection, IEqualityComparer comparer)
+ => dict = new ConcurrentDictionary(collection.Select(ExpandKeyValuePair), comparer);
+ #endregion
+
+ ///
+ /// Gets a value that indicates whether this cache is empty.
+ ///
+ public bool IsEmpty => dict.IsEmpty;
+ ///
+ /// Gets the number of elements that this cache contains.
+ ///
+ public int Count => dict.Count;
+
+ ///
+ /// Clears the cache.
+ ///
+ public void Clear() => dict.Clear();
+ ///
+ /// Gets a value indicating whether or not this cache contains .
+ ///
+ /// the key to search for
+ /// if the cache contains the key, otherwise
+ public bool ContainsKey(TKey key) => dict.ContainsKey(key);
+ ///
+ /// Copies the key-value pairs stored by the cache to a new array, filtering all elements that are currently being
+ /// created.
+ ///
+ /// an array containing a snapshot of the key-value pairs contained in this cache
+ public KeyValuePair[] ToArray()
+ => dict.ToArray().Where(k => k.Value.wh == null).Select(CompressKeyValuePair).ToArray();
+
+ ///
+ /// Attempts to get the value associated with the specified key from the cache.
+ ///
+ /// the key to search for
+ /// the value retrieved, if any
+ /// if the value was found, otherwise
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ if (dict.TryGetValue(key, out var pair) && pair.wh != null)
+ {
+ value = pair.val;
+ return true;
+ }
+ value = default;
+ return false;
+ }
+
+ ///
+ /// Gets the value associated with the specified key from the cache. If it does not exist, and
+ /// no creators are currently running for this key, then the creator is called to create the value
+ /// and the value is added to the cache. If there is a creator currently running for the key, then
+ /// this waits for the creator to finish and retrieves the value.
+ ///
+ /// the key to search for
+ /// the delegate to use to create the value if it does not exist
+ /// the value that was found, or the result of
+ public TValue GetOrAdd(TKey key, Func creator)
+ {
+ retry:
+ if (dict.TryGetValue(key, out var value))
+ {
+ if (value.wh != null)
+ {
+ value.wh.Wait();
+ goto retry; // this isn't really a good candidate for a loop
+ // the loop condition will never be hit, and this should only
+ // jump back to the beginning in exceptional situations
+ }
+ return value.val;
+ }
+ else
+ {
+ var wh = new ManualResetEventSlim(false);
+ var cmp = (wh, default(TValue));
+ if (!dict.TryAdd(key, cmp))
+ goto retry; // someone else beat us to the punch, retry getting their value and wait for them
+ var val = creator(key);
+ while (!dict.TryUpdate(key, (null, val), cmp))
+ throw new InvalidOperationException();
+ wh.Set();
+ return val;
+ }
+ }
+ }
+}