From 8745a3237eef4f0742831df1c08e9b7cb1367aa4 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Wed, 26 Oct 2022 23:34:30 +0200 Subject: [PATCH] Added DynamicEnum IsDefined and EnumHelper and DynamicEnum GetUniqueFlags --- CHANGELOG.md | 4 +- MLEM.Data/DynamicEnum.cs | 49 +++++++++++++++++++++-- MLEM/Misc/EnumHelper.cs | 19 +++++++++ Tests/DataTests.cs | 33 ---------------- Tests/EnumTests.cs | 84 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 Tests/EnumTests.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index e4eeb56..f38ab0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/MLEM.Data/DynamicEnum.cs b/MLEM.Data/DynamicEnum.cs index e22107b..ff8e8dc 100644 --- a/MLEM.Data/DynamicEnum.cs +++ b/MLEM.Data/DynamicEnum.cs @@ -134,7 +134,7 @@ namespace MLEM.Data { /// The newly created enum value public static T AddValue(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(name, value); } @@ -149,7 +149,7 @@ namespace MLEM.Data { /// The newly created enum value public static T AddFlag(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(name, value); } @@ -189,6 +189,27 @@ namespace MLEM.Data { } } + /// + /// Returns all of the defined unique flags from the given dynamic enum type which are contained in . + /// Any combined flags (flags that aren't powers of two) which are defined in will not be returned. + /// + /// The combined flags whose individual flags to return. + /// The type of enum. + /// All of the unique flags that make up . + public static IEnumerable GetUniqueFlags(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().Max(DynamicEnum.GetValue); + var uniqueFlag = BigInteger.One; + while (uniqueFlag <= max) { + if (DynamicEnum.IsDefined(typeof(T), uniqueFlag)) { + var uniqueFlagValue = DynamicEnum.GetEnumValue(uniqueFlag); + if (combinedFlag.HasFlag(uniqueFlagValue)) + yield return uniqueFlagValue; + } + uniqueFlag <<= 1; + } + } + /// /// Returns the bitwise OR (|) combination of the two dynamic enum values /// @@ -307,7 +328,8 @@ namespace MLEM.Data { /// /// Parses the given 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. /// + /// If no enum value can be parsed, null is returned. + /// /// The type of the dynamic enum value to parse /// The string to parse into a dynamic enum value /// The parsed enum value, or null if parsing fails @@ -330,6 +352,27 @@ namespace MLEM.Data { return cached; } + /// + /// Returns whether the given is defined in the given dynamic enum . + /// A value counts as explicitly defined if it has been added using , or . + /// + /// The dynamic enum type to query. + /// The value to query. + /// Whether the is defined. + public static bool IsDefined(Type type, BigInteger value) { + return DynamicEnum.GetStorage(type).Values.ContainsKey(value); + } + + /// + /// Returns whether the given is defined in its dynamic enum type. + /// A value counts as explicitly defined if it has been added using , or . + /// + /// The value to query. + /// Whether the is defined. + 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(); diff --git a/MLEM/Misc/EnumHelper.cs b/MLEM/Misc/EnumHelper.cs index 34a561b..435be1b 100644 --- a/MLEM/Misc/EnumHelper.cs +++ b/MLEM/Misc/EnumHelper.cs @@ -46,5 +46,24 @@ namespace MLEM.Misc { } } + /// + /// Returns all of the defined unique flags from the given enum type which are contained in . + /// Any combined flags (flags that aren't powers of two) which are defined in will not be returned. + /// + /// The combined flags whose individual flags to return. + /// The type of enum. + /// All of the unique flags that make up . + public static IEnumerable GetUniqueFlags(T combinedFlag) where T : struct, Enum { + var uniqueFlag = 1; + foreach (var flag in EnumHelper.GetValues()) { + 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; + } + } + } } diff --git a/Tests/DataTests.cs b/Tests/DataTests.cs index 9e83ee5..d106d4c 100644 --- a/Tests/DataTests.cs +++ b/Tests/DataTests.cs @@ -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("Flag" + i); - - Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7); - Assert.AreEqual(DynamicEnum.GetEnumValue(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("Flag24"), flags[24]); - Assert.AreEqual(DynamicEnum.Parse("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) {} - - } - } } diff --git a/Tests/EnumTests.cs b/Tests/EnumTests.cs new file mode 100644 index 0000000..50430e0 --- /dev/null +++ b/Tests/EnumTests.cs @@ -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("Flag" + i); + var combined = DynamicEnum.Add("Combined", DynamicEnum.GetValue(DynamicEnum.Or(flags[7], flags[13]))); + + Assert.AreEqual(DynamicEnum.GetValue(flags[7]), BigInteger.One << 7); + Assert.AreEqual(DynamicEnum.GetEnumValue(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("Flag24"), flags[24]); + Assert.AreEqual(DynamicEnum.Parse("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) {} + + } + +}