mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
avoid patching indexers instead
This commit is contained in:
7 files changed
+303
-57
No files matched your search
@@ -2,3 +2,6 @@
|
|||||||
|
|
||||||
# SA1602: Enumeration items should be documented.
|
# SA1602: Enumeration items should be documented.
|
||||||
dotnet_diagnostic.SA1602.severity = none
|
dotnet_diagnostic.SA1602.severity = none
|
||||||
|
|
||||||
|
# SA1601: Partial elements should be documented
|
||||||
|
dotnet_diagnostic.SA1601.severity = none
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Mira;
|
using MiraAPI.Events.Mira;
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ public class ExampleOptions : AbstractOptionGroup
|
|||||||
public Api WorstApi { get; set; } = Api.MiraAPI;
|
public Api WorstApi { get; set; } = Api.MiraAPI;
|
||||||
|
|
||||||
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
|
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
|
||||||
public ModdedList<ValueState> ValueStates { get; set; } =
|
public ModdedArray<ValueState> ValueStates { get; set; } = [ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
|
||||||
[ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
|
|
||||||
|
|
||||||
[ModdedPlayerOption("options.exampleOptions1.players")]
|
[ModdedPlayerOption("options.exampleOptions1.players")]
|
||||||
public NetworkedPlayerInfo PlayerSelected { get; set; }
|
public NetworkedPlayerInfo PlayerSelected { get; set; }
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,8 @@
|
|||||||
|
namespace System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
|
||||||
|
internal sealed class CollectionBuilderAttribute(Type builderType, string methodName) : Attribute
|
||||||
|
{
|
||||||
|
public Type BuilderType { get; } = builderType;
|
||||||
|
public string MethodName { get; } = methodName;
|
||||||
|
}
|
||||||
@@ -1,9 +1,294 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace MiraAPI.GameOptions;
|
namespace MiraAPI.GameOptions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A simple list that extends <see cref="List{T}"/>.
|
/// A fixed-size array created specifically for modded options that automatically synchronizes with option attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the values that the list holds.</typeparam>
|
/// <typeparam name="T">The type of the values that the array holds.</typeparam>
|
||||||
public sealed class ModdedList<T> : List<T>;
|
[CollectionBuilder(typeof(ModdedArrayBuilder), nameof(ModdedArrayBuilder.Create))]
|
||||||
|
public sealed class ModdedArray<T> : IList<T>, IReadOnlyList<T>, IList
|
||||||
|
{
|
||||||
|
private readonly T[] items;
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int Count => items.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool IList.IsFixedSize => true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object ICollection.SyncRoot => items.SyncRoot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModdedArray{T}"/> class with the provided capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">The capacity of the list.</param>
|
||||||
|
public ModdedArray(int capacity)
|
||||||
|
{
|
||||||
|
items = new T[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModdedArray{T}"/> class with initial values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">The initial collection of values to initialise with.</param>
|
||||||
|
public ModdedArray(IEnumerable<T> collection)
|
||||||
|
{
|
||||||
|
items = [.. collection];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModdedArray{T}"/> class with initial values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="span">The initial span of values to initialise with.</param>
|
||||||
|
public ModdedArray(Span<T> span)
|
||||||
|
{
|
||||||
|
items = [.. span];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ModdedArray{T}"/> class with initial values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="span">The initial span of values to initialise with.</param>
|
||||||
|
public ModdedArray(ReadOnlySpan<T> span)
|
||||||
|
{
|
||||||
|
items = [.. span];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the element at the specified index, syncing with the attribute if registered.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the value.</param>
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => ModdedOptionsManager.ListInstanceCache.TryGetValue(this, out var attribute)
|
||||||
|
? (T)attribute.GetValue(index)
|
||||||
|
: items[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (ModdedOptionsManager.ListInstanceCache.TryGetValue(this, out var attribute))
|
||||||
|
attribute.SetValue(index, value!);
|
||||||
|
else
|
||||||
|
items[index] = value;
|
||||||
|
|
||||||
|
version++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = (T)value!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Contains(T item)
|
||||||
|
{
|
||||||
|
return Array.IndexOf(items, item) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void CopyTo(T[] array, int arrayIndex)
|
||||||
|
{
|
||||||
|
items.CopyTo(array, arrayIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public int IndexOf(T item)
|
||||||
|
{
|
||||||
|
return Array.IndexOf(items, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/>
|
||||||
|
public Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return new(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool IList.Contains(object? value)
|
||||||
|
{
|
||||||
|
return ((IList)items).Contains(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList.IndexOf(object? value)
|
||||||
|
{
|
||||||
|
return ((IList)items).IndexOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void ICollection.CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
items.CopyTo(array, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IList.Add(object? value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Clear()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Insert(int index, object? value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.Remove(object? value)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
void IList.RemoveAt(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("ModdedArray is of a fixed size.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the elements of a <see cref="ModdedArray{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public struct Enumerator : IEnumerator<T>
|
||||||
|
{
|
||||||
|
private readonly ModdedArray<T> array;
|
||||||
|
private readonly int version;
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
private T? current;
|
||||||
|
|
||||||
|
internal Enumerator(ModdedArray<T> moddedArray)
|
||||||
|
{
|
||||||
|
array = moddedArray;
|
||||||
|
version = moddedArray.version;
|
||||||
|
index = 0;
|
||||||
|
current = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly T Current => current!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
readonly object? IEnumerator.Current
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (index == 0 || index == array.Count + 1)
|
||||||
|
throw new InvalidOperationException("Enumeration has not started or has already finished.");
|
||||||
|
|
||||||
|
return Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public readonly void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (version != array.version)
|
||||||
|
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
|
||||||
|
|
||||||
|
if ((uint)index < (uint)array.Count)
|
||||||
|
{
|
||||||
|
current = array[index];
|
||||||
|
index++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = array.Count + 1;
|
||||||
|
current = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
if (version != array.version)
|
||||||
|
throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
current = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a builder for <see cref="ModdedArray{T}"/> to support C# 12 collection expressions.
|
||||||
|
/// </summary>
|
||||||
|
public static class ModdedArrayBuilder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ModdedArray{T}"/> from a span of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The the type of the values.</typeparam>
|
||||||
|
/// <param name="values">The values to initialise with.</param>
|
||||||
|
/// <returns>The <see cref="ModdedArray{T}"/> instance initialised with the provided values.</returns>
|
||||||
|
public static ModdedArray<T> Create<T>(ReadOnlySpan<T> values)
|
||||||
|
{
|
||||||
|
return new(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,18 +25,15 @@ namespace MiraAPI.GameOptions;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ModdedOptionsManager
|
public static class ModdedOptionsManager
|
||||||
{
|
{
|
||||||
private static readonly ConditionalWeakTable<object, ModdedOptionListAttribute> ListInstanceCache = [];
|
|
||||||
private static readonly Dictionary<MethodBase, PropertyOptionAttribute> GetterLookups = [];
|
private static readonly Dictionary<MethodBase, PropertyOptionAttribute> GetterLookups = [];
|
||||||
private static readonly Dictionary<MethodBase, PropertyOptionAttribute> SetterLookups = [];
|
private static readonly Dictionary<MethodBase, PropertyOptionAttribute> SetterLookups = [];
|
||||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||||
private static readonly HashSet<PropertyInfo> OptionAttributes = [];
|
private static readonly HashSet<PropertyInfo> OptionAttributes = [];
|
||||||
private static readonly HashSet<Type> PatchedListTypes = [];
|
|
||||||
|
|
||||||
private static readonly HarmonyMethod CachedGetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertyGetterPatch));
|
private static readonly HarmonyMethod CachedGetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertyGetterPatch));
|
||||||
private static readonly HarmonyMethod CachedSetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertySetterPatch));
|
private static readonly HarmonyMethod CachedSetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertySetterPatch));
|
||||||
private static readonly HarmonyMethod CachedListGetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertyListGetterPatch));
|
|
||||||
private static readonly HarmonyMethod CachedListSetterPatch = new(typeof(ModdedOptionsManager), nameof(PropertyListSetterPatch));
|
|
||||||
|
|
||||||
|
internal static readonly ConditionalWeakTable<object, ModdedOptionListAttribute> ListInstanceCache = [];
|
||||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||||
internal static readonly Dictionary<OptionBehaviour, ModdedStringOption> CreatedStringOptions = [];
|
internal static readonly Dictionary<OptionBehaviour, ModdedStringOption> CreatedStringOptions = [];
|
||||||
internal static readonly Dictionary<Type, List<AbstractOptionGroup>> GameModeOptionGroups = [];
|
internal static readonly Dictionary<Type, List<AbstractOptionGroup>> GameModeOptionGroups = [];
|
||||||
@@ -278,7 +275,7 @@ public static class ModdedOptionsManager
|
|||||||
var listType = propertyList.GetType();
|
var listType = propertyList.GetType();
|
||||||
var valueType = listType.GetGenericArguments()[0];
|
var valueType = listType.GetGenericArguments()[0];
|
||||||
|
|
||||||
if (!listType.IsGenericType || listType.GetGenericTypeDefinition() != typeof(ModdedList<>))
|
if (!listType.IsGenericType || listType.GetGenericTypeDefinition() != typeof(ModdedArray<>))
|
||||||
{
|
{
|
||||||
Error($"Please use ModdedList<{valueType.Name}> for modded option lists.");
|
Error($"Please use ModdedList<{valueType.Name}> for modded option lists.");
|
||||||
return;
|
return;
|
||||||
@@ -296,19 +293,6 @@ public static class ModdedOptionsManager
|
|||||||
SetterLookups[setterOriginal] = attribute;
|
SetterLookups[setterOriginal] = attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PatchedListTypes.Add(listType))
|
|
||||||
{
|
|
||||||
var listIndex = listType.GetProperty("Item")!;
|
|
||||||
|
|
||||||
var listGetterOriginal = listIndex.GetGetMethod(true);
|
|
||||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listGetterOriginal, prefix: CachedListGetterPatch);
|
|
||||||
|
|
||||||
var listSetterOriginal = listIndex.GetSetMethod(true);
|
|
||||||
|
|
||||||
if (listSetterOriginal != null)
|
|
||||||
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(listSetterOriginal, postfix: CachedListSetterPatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
attribute.HolderOptionList = optionList;
|
attribute.HolderOptionList = optionList;
|
||||||
attribute.Value = propertyList;
|
attribute.Value = propertyList;
|
||||||
attribute.ValueType = valueType;
|
attribute.ValueType = valueType;
|
||||||
@@ -453,36 +437,4 @@ public static class ModdedOptionsManager
|
|||||||
__result = attribute.GetValue();
|
__result = attribute.GetValue();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Patches the setter of a list property to update the value of the option.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="__instance">The list's instance.</param>
|
|
||||||
/// <param name="index">The index to find in the list.</param>
|
|
||||||
/// <param name="value">The new object value.</param>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
|
||||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
|
||||||
public static void PropertyListSetterPatch(object __instance, int index, object value)
|
|
||||||
{
|
|
||||||
if (ListInstanceCache.TryGetValue(__instance, out var match))
|
|
||||||
match.SetValue(index, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Patches the getter of a list property to return the value of the option.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="__instance">The list's instance.</param>
|
|
||||||
/// <param name="index">The index to find in the list.</param>
|
|
||||||
/// <param name="__result">The result of the property getter.</param>
|
|
||||||
/// <returns>False so the original getter gets skipped.</returns>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
|
||||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Read above.")]
|
|
||||||
public static bool PropertyListGetterPatch(object __instance, int index, ref object __result)
|
|
||||||
{
|
|
||||||
if (!ListInstanceCache.TryGetValue(__instance, out var match))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
__result = match.GetValue(index);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user