1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-12-25 17:59:24 +01:00

Added DynamicEnum IsDefined and EnumHelper and DynamicEnum GetUniqueFlags

This commit is contained in:
Ell 2022-10-26 23:34:30 +02:00
parent 7ab76d239d
commit 8745a3237e
5 changed files with 151 additions and 38 deletions

View file

@ -14,7 +14,7 @@ Jump to version:
### MLEM
Additions
- Added TokenizedString.Realign
- Added EnumHelper.GetFlags
- Added GetFlags and GetUniqueFlags to EnumHelper
- **Added the ability to find paths to one of multiple goals using AStar**
Improvements
@ -60,7 +60,7 @@ Fixes
Additions
- Added data, from, and copy instructions to DataTextureAtlas
- Added the ability to add additional regions to a RuntimeTexturePacker after packing
- Added DynamicEnum.GetFlags
- Added GetFlags, GetUniqueFlags and IsDefined to DynamicEnum
Improvements
- Allow data texture atlas pivots and offsets to be negative

View file

@ -134,7 +134,7 @@ namespace MLEM.Data {
/// <returns>The newly created enum value</returns>
public static T AddValue<T>(string name) where T : DynamicEnum {
BigInteger value = 0;
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
while (DynamicEnum.IsDefined(typeof(T), value))
value++;
return DynamicEnum.Add<T>(name, value);
}
@ -149,7 +149,7 @@ namespace MLEM.Data {
/// <returns>The newly created enum value</returns>
public static T AddFlag<T>(string name) where T : DynamicEnum {
BigInteger value = 1;
while (DynamicEnum.GetStorage(typeof(T)).Values.ContainsKey(value))
while (DynamicEnum.IsDefined(typeof(T), value))
value <<= 1;
return DynamicEnum.Add<T>(name, value);
}
@ -189,6 +189,27 @@ namespace MLEM.Data {
}
}
/// <summary>
/// Returns all of the defined unique flags from the given dynamic enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
/// Any combined flags (flags that aren't powers of two) which are defined in <typeparamref name="T"/> will not be returned.
/// </summary>
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
/// <typeparam name="T">The type of enum.</typeparam>
/// <returns>All of the unique flags that make up <paramref name="combinedFlag"/>.</returns>
public static IEnumerable<T> GetUniqueFlags<T>(T combinedFlag) where T : DynamicEnum {
// we can't use the same method here as EnumHelper.GetUniqueFlags since DynamicEnum doesn't guarantee sorted values
var max = DynamicEnum.GetValues<T>().Max(DynamicEnum.GetValue);
var uniqueFlag = BigInteger.One;
while (uniqueFlag <= max) {
if (DynamicEnum.IsDefined(typeof(T), uniqueFlag)) {
var uniqueFlagValue = DynamicEnum.GetEnumValue<T>(uniqueFlag);
if (combinedFlag.HasFlag(uniqueFlagValue))
yield return uniqueFlagValue;
}
uniqueFlag <<= 1;
}
}
/// <summary>
/// Returns the bitwise OR (|) combination of the two dynamic enum values
/// </summary>
@ -307,7 +328,8 @@ namespace MLEM.Data {
/// <summary>
/// Parses the given <see cref="string"/> into a dynamic enum value and returns the result.
/// This method supports defined enum values as well as values combined using the pipe (|) character and any number of spaces.
/// If no enum value can be parsed, null is returned. /// </summary>
/// If no enum value can be parsed, null is returned.
/// </summary>
/// <param name="type">The type of the dynamic enum value to parse</param>
/// <param name="strg">The string to parse into a dynamic enum value</param>
/// <returns>The parsed enum value, or null if parsing fails</returns>
@ -330,6 +352,27 @@ namespace MLEM.Data {
return cached;
}
/// <summary>
/// Returns whether the given <paramref name="value"/> is defined in the given dynamic enum <paramref name="type"/>.
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
/// </summary>
/// <param name="type">The dynamic enum type to query.</param>
/// <param name="value">The value to query.</param>
/// <returns>Whether the <paramref name="value"/> is defined.</returns>
public static bool IsDefined(Type type, BigInteger value) {
return DynamicEnum.GetStorage(type).Values.ContainsKey(value);
}
/// <summary>
/// Returns whether the given <paramref name="value"/> is defined in its dynamic enum type.
/// A value counts as explicitly defined if it has been added using <see cref="Add{T}"/>, <see cref="AddValue{T}"/> or <see cref="AddFlag{T}"/>.
/// </summary>
/// <param name="value">The value to query.</param>
/// <returns>Whether the <paramref name="value"/> is defined.</returns>
public static bool IsDefined(DynamicEnum value) {
return value != null && DynamicEnum.IsDefined(value.GetType(), DynamicEnum.GetValue(value));
}
private static Storage GetStorage(Type type) {
if (!DynamicEnum.Storages.TryGetValue(type, out var storage)) {
storage = new Storage();

View file

@ -46,5 +46,24 @@ namespace MLEM.Misc {
}
}
/// <summary>
/// Returns all of the defined unique flags from the given enum type <typeparamref name="T"/> which are contained in <paramref name="combinedFlag"/>.
/// Any combined flags (flags that aren't powers of two) which are defined in <typeparamref name="T"/> will not be returned.
/// </summary>
/// <param name="combinedFlag">The combined flags whose individual flags to return.</param>
/// <typeparam name="T">The type of enum.</typeparam>
/// <returns>All of the unique flags that make up <paramref name="combinedFlag"/>.</returns>
public static IEnumerable<T> GetUniqueFlags<T>(T combinedFlag) where T : struct, Enum {
var uniqueFlag = 1;
foreach (var flag in EnumHelper.GetValues<T>()) {
var flagValue = Convert.ToInt64(flag);
// GetValues is always ordered by binary value, so we can be sure that the next flag is bigger than the last
while (uniqueFlag < flagValue)
uniqueFlag <<= 1;
if (flagValue == uniqueFlag && combinedFlag.HasFlag(flag))
yield return flag;
}
}
}
}

View file

@ -36,33 +36,6 @@ namespace Tests {
Assert.AreEqual(this.testObject, read);
}
[Test]
public void TestDynamicEnum() {
var flags = new TestEnum[100];
for (var i = 0; i < flags.Length; i++)
flags[i] = DynamicEnum.AddFlag<TestEnum>("Flag" + i);
Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7);
Assert.AreEqual(DynamicEnum.GetEnumValue<TestEnum>(BigInteger.One << 75), flags[75]);
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Or(flags[2], flags[17])), BigInteger.One << 2 | BigInteger.One << 17);
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.And(flags[2], flags[3])), BigInteger.Zero);
Assert.AreEqual(DynamicEnum.And(DynamicEnum.Or(flags[24], flags[52]), DynamicEnum.Or(flags[52], flags[75])), flags[52]);
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], flags[73]), flags[73]), flags[85]);
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], DynamicEnum.Or(flags[73], flags[12])), flags[73]), DynamicEnum.Or(flags[85], flags[12]));
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Neg(flags[74])), ~(BigInteger.One << 74));
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasFlag(flags[24]), true);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlag(flags[24]), true);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasFlag(DynamicEnum.Or(flags[24], flags[26])), false);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlag(DynamicEnum.Or(flags[24], flags[26])), true);
Assert.AreEqual(DynamicEnum.Parse<TestEnum>("Flag24"), flags[24]);
Assert.AreEqual(DynamicEnum.Parse<TestEnum>("Flag24 | Flag43"), DynamicEnum.Or(flags[24], flags[43]));
Assert.AreEqual(flags[24].ToString(), "Flag24");
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
}
[Test]
public void TestJsonTypeSafety() {
var serializer = new JsonSerializer {TypeNameHandling = TypeNameHandling.Auto};
@ -109,11 +82,5 @@ namespace Tests {
}
private class TestEnum : DynamicEnum {
public TestEnum(string name, BigInteger value) : base(name, value) {}
}
}
}

84
Tests/EnumTests.cs Normal file
View file

@ -0,0 +1,84 @@
using System;
using System.Linq;
using System.Numerics;
using MLEM.Data;
using MLEM.Misc;
using NUnit.Framework;
namespace Tests;
public class EnumTests {
[Test]
public void TestRegularEnums() {
Assert.AreEqual(
new[] {TestEnum.One, TestEnum.Two, TestEnum.Eight, TestEnum.Sixteen, TestEnum.EightSixteen},
EnumHelper.GetFlags(TestEnum.One | TestEnum.Sixteen | TestEnum.Eight | TestEnum.Two));
Assert.AreEqual(
new[] {TestEnum.One, TestEnum.Two, TestEnum.Eight, TestEnum.Sixteen},
EnumHelper.GetUniqueFlags(TestEnum.One | TestEnum.Sixteen | TestEnum.Eight | TestEnum.Two));
}
[Test]
public void TestDynamicEnums() {
var flags = new TestDynamicEnum[100];
for (var i = 0; i < flags.Length; i++)
flags[i] = DynamicEnum.AddFlag<TestDynamicEnum>("Flag" + i);
var combined = DynamicEnum.Add<TestDynamicEnum>("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13])));
Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7);
Assert.AreEqual(DynamicEnum.GetEnumValue<TestDynamicEnum>(BigInteger.One << 75), flags[75]);
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Or(flags[2], flags[17])), BigInteger.One << 2 | BigInteger.One << 17);
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.And(flags[2], flags[3])), BigInteger.Zero);
Assert.AreEqual(DynamicEnum.And(DynamicEnum.Or(flags[24], flags[52]), DynamicEnum.Or(flags[52], flags[75])), flags[52]);
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], flags[73]), flags[73]), flags[85]);
Assert.AreEqual(DynamicEnum.Xor(DynamicEnum.Or(flags[85], DynamicEnum.Or(flags[73], flags[12])), flags[73]), DynamicEnum.Or(flags[85], flags[12]));
Assert.AreEqual(DynamicEnum.GetValue(DynamicEnum.Neg(flags[74])), ~(BigInteger.One << 74));
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasFlag(flags[24]), true);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlag(flags[24]), true);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasFlag(DynamicEnum.Or(flags[24], flags[26])), false);
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[52]).HasAnyFlag(DynamicEnum.Or(flags[24], flags[26])), true);
Assert.AreEqual(DynamicEnum.Parse<TestDynamicEnum>("Flag24"), flags[24]);
Assert.AreEqual(DynamicEnum.Parse<TestDynamicEnum>("Flag24 | Flag43"), DynamicEnum.Or(flags[24], flags[43]));
Assert.AreEqual(flags[24].ToString(), "Flag24");
Assert.AreEqual(DynamicEnum.Or(flags[24], flags[43]).ToString(), "Flag24 | Flag43");
Assert.True(DynamicEnum.IsDefined(flags[27]));
Assert.True(DynamicEnum.IsDefined(combined));
Assert.False(DynamicEnum.IsDefined(DynamicEnum.Or(flags[17], flags[49])));
Assert.False(DynamicEnum.IsDefined(DynamicEnum.Or(combined, flags[49])));
Assert.AreEqual(
new[] {flags[0], flags[7], flags[13], combined},
DynamicEnum.GetFlags(DynamicEnum.Or(DynamicEnum.Or(flags[0], flags[13]), flags[7])));
Assert.AreEqual(
new[] {flags[0], flags[7], flags[13]},
DynamicEnum.GetUniqueFlags(DynamicEnum.Or(DynamicEnum.Or(flags[0], flags[13]), flags[7])));
}
[Flags]
private enum TestEnum {
One = 1,
Two = 2,
Eight = 8,
Sixteen = 16,
EightSixteen = TestEnum.Eight | TestEnum.Sixteen,
ThirtyTwo = 32,
OneTwentyEight = 128,
OneTwentyEightTwoOne = TestEnum.OneTwentyEight | TestEnum.Two | TestEnum.One
}
private class TestDynamicEnum : DynamicEnum {
public TestDynamicEnum(string name, BigInteger value) : base(name, value) {}
}
}