some improvements (still broken)

This commit is contained in:
XtraCube committed 2025-03-21 18:01:42 -04:00
1 parent ef329bfcbd
commit 5eba77dba9
6 files changed
+206 -119

No files matched your search

@@ -21,6 +21,12 @@ public class ExampleOptions2 : AbstractOptionGroup
{
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
};
public ModdedMultiSelectOption<TestingData> MultiSelectOption { get; } = new(
"Emotions",
TestingData.Happy,
TestingData.All,
TestingData.None);
}
public enum TestingData : ulong
@@ -28,4 +34,6 @@ public enum TestingData : ulong
Happy,
Sad,
Neutral,
All,
None,
}
@@ -5,6 +5,7 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Modules;
using MiraAPI.Networking;
using Reactor.Localization.Utilities;
using Reactor.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -49,10 +50,25 @@ public class ModdedMultiSelectOption<T> : ModdedOption<MultiSelectValue<T>> wher
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = (values is null
? Enum.GetNames<T>()
: values)
.Select(CustomStringName.CreateAndRegister).ToArray();
data.Values = (values ?? Enum.GetNames<T>()).Select(CustomStringName.CreateAndRegister).ToArray();
}
/// <inheritdoc />
public override void BindConfig()
{
var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue.ToString());
if (entry != null)
{
try
{
Value = new MultiSelectValue<T>([.. entry.Value.Split(",").Select(x => Enum.Parse<T>(x.Trim()))]);
}
catch (Exception e)
{
Value = DefaultValue;
Logger<MiraApiPlugin>.Error($"Failed to bind config for {Title}: {e.Message}");
}
}
}
/// <inheritdoc />
@@ -32,10 +32,8 @@ public abstract class ModdedOption<T> : IModdedOption where T : struct
get => _parentMod;
set
{
if (_parentMod != null || value == null) return;
_parentMod = value;
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
BindConfig();
}
}
@@ -141,6 +139,18 @@ public abstract class ModdedOption<T> : IModdedOption where T : struct
StringOption stringOpt,
Transform container);
/// <summary>
/// Binds the option to the configuration file of the parent mod, if available.
/// </summary>
public virtual void BindConfig()
{
var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
if (entry != null)
{
Value = entry.Value;
}
}
/// <summary>
/// Implicitly converts the option to type of <typeparamref name="T"/>.
/// </summary>
+20 -23
View File
@@ -1,11 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using HarmonyLib;
using MiraAPI.Utilities;
using MonoMod.Utils;
namespace MiraAPI.Modules;
@@ -22,22 +19,22 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// </summary>
public string Values
{
readonly get => Helpers.Join(',', values);
set => values = [.. value.TrueSplit(',').Select(Enum.Parse<T>)];
readonly get => Helpers.Join(',', _values);
set => _values = [.. value.TrueSplit(',').Select(Enum.Parse<T>)];
}
private HashSet<T> values = [.. values];
private HashSet<T> _values = [.. values];
/// <summary>
/// Gets the number of values contained within.
/// </summary>
public readonly int Count => values.Count;
public readonly int Count => _values.Count;
/// <summary>
/// Adds a single enum value to the end of the collection.
/// </summary>
/// <param name="item">The enum value to add.</param>
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
public readonly bool Add(T item) => values.Add(item);
public readonly bool Add(T item) => _values.Add(item);
/// <summary>
/// Removes the first occurrence of the specified enum value.<br/>
@@ -45,7 +42,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// </summary>
/// <param name="item">The enum value to remove.</param>
/// <returns><c>true</c> if successfully removed; otherwise, <c>false</c>.</returns>
public readonly bool Remove(T item) => values.Remove(item);
public readonly bool Remove(T item) => _values.Remove(item);
/// <summary>
/// Adds multiple enum values to the end of the collection.
@@ -54,7 +51,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
public readonly void AddRange(IEnumerable<T> items)
{
foreach (var item in items)
values.Add(item);
_values.Add(item);
}
/// <summary>
@@ -76,7 +73,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// </summary>
/// <param name="item">The enum value to locate.</param>
/// <returns>true if the item is found in the collection; otherwise, false.</returns>
public readonly bool Contains(T item) => values.Contains(item);
public readonly bool Contains(T item) => _values.Contains(item);
/// <summary>
/// Removes all enum values that match the specified predicate condition.<br/>
@@ -100,43 +97,43 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// <summary>
/// Removes all enum values from the collection.
/// </summary>
public readonly void Clear() => values.Clear();
public readonly void Clear() => _values.Clear();
/// <summary>
/// Gets the first value in the collection.
/// </summary>
/// <returns>Returns the first value.</returns>
public readonly T First() => values.First();
public readonly T First() => _values.First();
/// <summary>
/// Serializes the current instance to an array of bytes.
/// </summary>
/// <returns>An array of bytes representing the current instance.</returns>
public readonly byte[] ToBytes() => Helpers.EnumsToBytes(this);
public readonly byte[] ToBytes() => EnumSerializer.EnumsToBytes(this);
/// <summary>
/// Converts the collection to its string representation.
/// </summary>
/// <returns>A comma-separated string of enum values.</returns>
public override readonly string ToString() => Values;
public readonly override string ToString() => Values;
/// <inheritdoc/>
public override readonly bool Equals(object? obj) => obj is MultiSelectValue<T> other && Equals(other);
public readonly override bool Equals(object? obj) => obj is MultiSelectValue<T> other && Equals(other);
/// <inheritdoc/>
public readonly bool Equals(MultiSelectValue<T> other) => values.SetEquals(other.values);
public readonly bool Equals(MultiSelectValue<T> other) => _values.SetEquals(other._values);
/// <inheritdoc/>
public override readonly int GetHashCode() => Values?.GetHashCode() ?? 0;
public readonly override int GetHashCode() => Values.GetHashCode();
/// <inheritdoc/>
public readonly IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)values).GetEnumerator();
public readonly IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
/// <inheritdoc/>
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc/>
public readonly void Dispose() => values.Clear();
public readonly void Dispose() => _values.Clear();
/// <summary>
/// Creates an instance of <see cref="MultiSelectValue{T}"/> from the provided byte array.
@@ -144,7 +141,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// <param name="bytes">The bytes to deserialize from.</param>
/// <returns>An instance of <see cref="MultiSelectValue{T}"/>.</returns>
#pragma warning disable CA1000 // Do not declare static members on generic types
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. Helpers.EnumsFromBytes<T>(bytes)]);
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. EnumSerializer.EnumsFromBytes<T>(bytes)]);
#pragma warning restore CA1000 // Do not declare static members on generic types
/// <summary>
@@ -157,7 +154,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
/// Converts the current instance to its array representation of values.
/// </summary>
/// <param name="value">The value to convert.</param>
public static implicit operator T[](MultiSelectValue<T> value) => [.. value.values];
public static implicit operator T[](MultiSelectValue<T> value) => [.. value._values];
/// <summary>
/// Converts the current instance to one singular value.
@@ -167,7 +164,7 @@ public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatabl
#pragma warning disable S3877 // Exceptions should not be thrown from unexpected methods
public static implicit operator T(MultiSelectValue<T> value) =>
value.Count == 1
? value.values.First()
? value._values.First()
: throw new ConversionFailException($"Tried to convert multiple or no values ({value.Values}) to a singular one");
#pragma warning restore S3877 // Exceptions should not be thrown from unexpected methods
+145
View File
@@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MiraAPI.Utilities;
/// <summary>
/// Enum serializer for converting enums to and from byte arrays.
/// </summary>
public static class EnumSerializer
{
/// <summary>
/// Serializes an enum to an array of bytes.
/// </summary>
/// <param name="value">The enum value to serialize.</param>
/// <typeparam name="T">The enum type.</typeparam>
/// <returns>A byte array representing the enum value.</returns>
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
{
var underlyingType = typeof(T).GetEnumUnderlyingType();
return underlyingType switch
{
not null when underlyingType == typeof(byte) => [(byte)(object)value],
not null when underlyingType == typeof(sbyte) => [(byte)(sbyte)(object)value],
not null when underlyingType == typeof(short) => BitConverter.GetBytes((short)(object)value),
not null when underlyingType == typeof(ushort) => BitConverter.GetBytes((ushort)(object)value),
not null when underlyingType == typeof(int) => BitConverter.GetBytes((int)(object)value),
not null when underlyingType == typeof(uint) => BitConverter.GetBytes((uint)(object)value),
not null when underlyingType == typeof(long) => BitConverter.GetBytes((long)(object)value),
not null when underlyingType == typeof(ulong) => BitConverter.GetBytes((ulong)(object)value),
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
};
}
/// <summary>
/// Converts bytes to an enum using the underlying type.
/// </summary>
/// <param name="bytes">The byte array to convert.</param>
/// <param name="offset">The offset in the byte array to start reading from.</param>
/// <param name="underlyingType">The underlying type of the enum.</param>
/// <typeparam name="T">The enum type.</typeparam>
/// <returns>The enum value represented by the bytes.</returns>
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
public static T EnumFromBytes<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum
{
return underlyingType switch
{
{ } t when t == typeof(byte) || t == typeof(sbyte) => (T)(object)bytes[offset],
{ } t when t == typeof(short) => (T)(object)BitConverter.ToInt16(bytes, offset),
{ } t when t == typeof(ushort) => (T)(object)BitConverter.ToUInt16(bytes, offset),
{ } t when t == typeof(int) => (T)(object)BitConverter.ToInt32(bytes, offset),
{ } t when t == typeof(uint) => (T)(object)BitConverter.ToUInt32(bytes, offset),
{ } t when t == typeof(long) => (T)(object)BitConverter.ToInt64(bytes, offset),
{ } t when t == typeof(ulong) => (T)(object)BitConverter.ToUInt64(bytes, offset),
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
};
}
/// <inheritdoc cref="EnumFromBytes{T}(byte[], int, Type)"/>
public static T EnumFromBytes<T>(byte[] bytes, int offset) where T : struct, Enum => EnumFromBytes<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
/// <summary>
/// Converts a byte array into a collection of enum values.
/// </summary>
/// <param name="bytes">The byte array to convert.</param>
/// <typeparam name="T">The enum type.</typeparam>
/// <returns>An enumerable collection of enum values.</returns>
/// <exception cref="ArgumentException">Thrown when the byte array length is not a multiple of the enum's underlying type size.</exception>
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
{
var underlyingType = typeof(T).GetEnumUnderlyingType();
var size = GetManagedSize(underlyingType);
if (bytes.Length % size != 0)
{
throw new ArgumentException("Byte array length must be a multiple of " + size + ".");
}
return EnumsFromBytesIterator<T>(bytes, underlyingType, size);
}
private static IEnumerable<T> EnumsFromBytesIterator<T>(byte[] bytes, Type underlyingType, int size) where T : struct, Enum
{
var count = bytes.Length / size;
var underlyingArray = Array.CreateInstance(underlyingType, count);
Buffer.BlockCopy(bytes, 0, underlyingArray, 0, bytes.Length);
foreach (var value in underlyingArray)
yield return (T)value;
}
/// <summary>
/// Converts a collection of enum values to a byte array.
/// </summary>
/// <param name="values"> The collection of enum values to convert.</param>
/// <typeparam name="T">The enum type.</typeparam>
/// <returns>A byte array representing the collection of enum values.</returns>
public static byte[] EnumsToBytes<T>(IEnumerable<T> values) where T : struct, Enum
{
var underlyingType = typeof(T).GetEnumUnderlyingType();
var size = GetManagedSize(underlyingType);
var valuesArray = values.ToArray();
var count = valuesArray.Length;
var underlyingArray = Array.CreateInstance(underlyingType, count);
for (var i = 0; i < count; i++)
{
underlyingArray.SetValue(ConvertToUnderlyingType(valuesArray[i], underlyingType), i);
}
var bytes = new byte[count * size];
Buffer.BlockCopy(underlyingArray, 0, bytes, 0, bytes.Length);
return bytes;
}
private static object ConvertToUnderlyingType<T>(T value, Type underlyingType) where T : struct, Enum
{
return underlyingType switch
{
not null when underlyingType == typeof(byte) => (byte)(object)value,
not null when underlyingType == typeof(sbyte) => (sbyte)(object)value,
not null when underlyingType == typeof(short) => (short)(object)value,
not null when underlyingType == typeof(ushort) => (ushort)(object)value,
not null when underlyingType == typeof(int) => (int)(object)value,
not null when underlyingType == typeof(uint) => (uint)(object)value,
not null when underlyingType == typeof(long) => (long)(object)value,
not null when underlyingType == typeof(ulong) => (ulong)(object)value,
_ => throw new InvalidOperationException("Unsupported underlying type."),
};
}
private static int GetManagedSize(Type type)
{
return Type.GetTypeCode(type) switch
{
TypeCode.Byte or TypeCode.SByte => 1,
TypeCode.Int16 or TypeCode.UInt16 => 2,
TypeCode.Int32 or TypeCode.UInt32 => 4,
TypeCode.Int64 or TypeCode.UInt64 => 8,
_ => throw new ArgumentException("Unsupported underlying type: " + type.Name),
};
}
}
-89
View File
@@ -431,93 +431,4 @@ public static class Helpers
items.Do(x => result += $"{separator}{x}");
return result[separator.Length..];
}
/// <summary>
/// Serializes an enum to an array of bytes.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="value">The value to serialize.</param>
/// <returns>An array of bytes representing the enum value.</returns>
/// <exception cref="ArgumentException">The enum type had an unknown underlying type.</exception>
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
{
var enumType = typeof(T);
var underlyingType = enumType.GetEnumUnderlyingType();
if (underlyingType == typeof(byte))
return [Convert.ToByte(value, NumberFormatInfo.InvariantInfo)];
else if (underlyingType == typeof(sbyte))
return [(byte)Convert.ToSByte(value, NumberFormatInfo.InvariantInfo)];
else if (underlyingType == typeof(short))
return BitConverter.GetBytes(Convert.ToInt16(value, NumberFormatInfo.InvariantInfo));
else if (underlyingType == typeof(ushort))
return BitConverter.GetBytes(Convert.ToUInt16(value, NumberFormatInfo.InvariantInfo));
else if (underlyingType == typeof(int))
return BitConverter.GetBytes(Convert.ToInt32(value, NumberFormatInfo.InvariantInfo));
else if (underlyingType == typeof(uint))
return BitConverter.GetBytes(Convert.ToUInt32(value, NumberFormatInfo.InvariantInfo));
else if (underlyingType == typeof(long))
return BitConverter.GetBytes(Convert.ToInt64(value, NumberFormatInfo.InvariantInfo));
else if (underlyingType == typeof(ulong))
return BitConverter.GetBytes(Convert.ToUInt64(value, NumberFormatInfo.InvariantInfo));
else
throw new ArgumentException("Unknown underlying type for " + enumType.Name);
}
/// <summary>
/// Converts an array of bytes to an enum using the provided offset and the enum's underlying type.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="bytes">The array of bytes to deserialize from.</param>
/// <param name="offset">The index to convert from.</param>
/// <param name="underlyingType">The underlying type of the enum type.</param>
/// <returns>An enum value from the provided byte array.</returns>
/// <exception cref="ArgumentException">The enum type had an unknown underlying type.</exception>
public static T EnumFromBytes<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum // This version exists purely for performance and optimisation
{
var enumType = typeof(T);
if (underlyingType == typeof(byte) || underlyingType == typeof(sbyte))
return (T)(object)bytes[offset];
else if (underlyingType == typeof(short))
return (T)(object)BitConverter.ToInt16(bytes, offset);
else if (underlyingType == typeof(ushort))
return (T)(object)BitConverter.ToUInt16(bytes, offset);
else if (underlyingType == typeof(int))
return (T)(object)BitConverter.ToInt32(bytes, offset);
else if (underlyingType == typeof(uint))
return (T)(object)BitConverter.ToUInt32(bytes, offset);
else if (underlyingType == typeof(long))
return (T)(object)BitConverter.ToInt64(bytes, offset);
else if (underlyingType == typeof(ulong))
return (T)(object)BitConverter.ToUInt64(bytes, offset);
else
throw new ArgumentException("Unknown underlying type for " + enumType.Name);
}
/// <inheritdoc cref="EnumFromBytes{T}(byte[], int, Type)"/>
public static T EnumFromBytes<T>(byte[] bytes, int offset) where T : struct, Enum => EnumFromBytes<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
/// <summary>
/// Converts the provided byte array into a collection of enum values.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="bytes">The array of bytes to deserialize from.</param>
/// <returns>A collection of enum values.</returns>
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
{
var underlyingType = typeof(T).GetEnumUnderlyingType();
var size = underlyingType.GetManagedSize();
for (var i = 0; i < bytes.Length; i += size)
yield return EnumFromBytes<T>(bytes, i, underlyingType);
}
/// <summary>
/// Converts a collection of enum values to an array of bytes.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
/// <param name="values">The values to serialize to bytes to.</param>
/// <returns>An array of bytes serialized from the collection of enum values.</returns>
public static byte[] EnumsToBytes<T>(IEnumerable<T> values) where T : struct, Enum => [.. values.Select(EnumToBytes).SelectMany(x => x)];
}