diff --git a/CHANGELOG.md b/CHANGELOG.md index 983ef45..45893a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Additions Improvements - Improved RawContentManager's reader loading and added better exception handling - Improved CopyExtensions construction speed +- Improved DynamicEnum caching ## 5.0.0 ### MLEM diff --git a/MLEM.Data/DynamicEnum.cs b/MLEM.Data/DynamicEnum.cs index 2ad9811..dc4f14d 100644 --- a/MLEM.Data/DynamicEnum.cs +++ b/MLEM.Data/DynamicEnum.cs @@ -12,6 +12,9 @@ namespace MLEM.Data { /// A dynamic enum uses as its underlying type, allowing for an arbitrary number of enum values to be created, even when a -like structure is used that would only allow for up to 64 values in a regular enum. /// All enum operations including , , and are supported and can be implemented in derived classes using operator overloads. /// To create a custom dynamic enum, simply create a class that extends . New values can then be added using , or . + /// + /// This class, and its entire concept, are extremely terrible. If you intend on using this, there's probably at least one better solution available. + /// Though if, for some weird reason, you need a way to have more than 64 distinct flags, this is a pretty good solution. /// /// /// To include enum-like operator overloads in a dynamic enum named MyEnum, the following code can be used: @@ -27,10 +30,7 @@ namespace MLEM.Data { [JsonConverter(typeof(DynamicEnumConverter))] public abstract class DynamicEnum { - private static readonly Dictionary> Values = new Dictionary>(); - private static readonly Dictionary> FlagCache = new Dictionary>(); - private static readonly Dictionary> ParseCache = new Dictionary>(); - + private static readonly Dictionary Storages = new Dictionary(); private readonly BigInteger value; private Dictionary allFlagsCache; @@ -59,8 +59,7 @@ namespace MLEM.Data { if (this.allFlagsCache == null) this.allFlagsCache = new Dictionary(); if (!this.allFlagsCache.TryGetValue(flags, out var ret)) { - // & is very memory-intensive, so we cache the return value - ret = (GetValue(this) & GetValue(flags)) == GetValue(flags); + ret = And(this, flags) == flags; this.allFlagsCache.Add(flags, ret); } return ret; @@ -76,8 +75,7 @@ namespace MLEM.Data { if (this.anyFlagsCache == null) this.anyFlagsCache = new Dictionary(); if (!this.anyFlagsCache.TryGetValue(flags, out var ret)) { - // & is very memory-intensive, so we cache the return value - ret = (GetValue(this) & GetValue(flags)) != 0; + ret = GetValue(And(this, flags)) != 0; this.anyFlagsCache.Add(flags, ret); } return ret; @@ -107,24 +105,20 @@ namespace MLEM.Data { /// The newly created enum value /// Thrown if the name or value passed are already present public static T Add(string name, BigInteger value) where T : DynamicEnum { - if (!Values.TryGetValue(typeof(T), out var dict)) { - dict = new Dictionary(); - Values.Add(typeof(T), dict); - } + var storage = GetStorage(typeof(T)); // cached parsed values and names might be incomplete with new values - FlagCache.Remove(typeof(T)); - ParseCache.Remove(typeof(T)); + storage.ClearCaches(); - if (dict.ContainsKey(value)) + if (storage.Values.ContainsKey(value)) throw new ArgumentException($"Duplicate value {value}", nameof(value)); - foreach (var v in dict.Values) { + foreach (var v in storage.Values.Values) { if (v.name == name) throw new ArgumentException($"Duplicate name {name}", nameof(name)); } var ret = Construct(typeof(T), name, value); - dict.Add(value, ret); + storage.Values.Add(value, ret); return (T) ret; } @@ -138,10 +132,8 @@ namespace MLEM.Data { /// The newly created enum value public static T AddValue(string name) where T : DynamicEnum { BigInteger value = 0; - if (Values.TryGetValue(typeof(T), out var defined)) { - while (defined.ContainsKey(value)) - value++; - } + while (GetStorage(typeof(T)).Values.ContainsKey(value)) + value++; return Add(name, value); } @@ -155,10 +147,8 @@ namespace MLEM.Data { /// The newly created enum value public static T AddFlag(string name) where T : DynamicEnum { BigInteger value = 0; - if (Values.TryGetValue(typeof(T), out var defined)) { - while (defined.ContainsKey(value)) - value <<= 1; - } + while (GetStorage(typeof(T)).Values.ContainsKey(value)) + value <<= 1; return Add(name, value); } @@ -179,7 +169,7 @@ namespace MLEM.Data { /// The type whose values to get /// The defined values for the given type public static IEnumerable GetValues(Type type) { - return Values.TryGetValue(type, out var ret) ? ret.Values : Enumerable.Empty(); + return GetStorage(type).Values.Values; } /// @@ -190,7 +180,12 @@ namespace MLEM.Data { /// The type of the values /// The bitwise OR (|) combination public static T Or(T left, T right) where T : DynamicEnum { - return GetEnumValue(GetValue(left) | GetValue(right)); + var cache = GetStorage(typeof(T)).OrCache; + if (!cache.TryGetValue((left, right), out var ret)) { + ret = GetEnumValue(GetValue(left) | GetValue(right)); + cache.Add((left, right), ret); + } + return (T) ret; } /// @@ -201,7 +196,12 @@ namespace MLEM.Data { /// The type of the values /// The bitwise AND (&) combination public static T And(T left, T right) where T : DynamicEnum { - return GetEnumValue(GetValue(left) & GetValue(right)); + var cache = GetStorage(typeof(T)).AndCache; + if (!cache.TryGetValue((left, right), out var ret)) { + ret = GetEnumValue(GetValue(left) & GetValue(right)); + cache.Add((left, right), ret); + } + return (T) ret; } /// @@ -212,7 +212,12 @@ namespace MLEM.Data { /// The type of the values /// The bitwise XOR (^) combination public static T Xor(T left, T right) where T : DynamicEnum { - return GetEnumValue(GetValue(left) ^ GetValue(right)); + var cache = GetStorage(typeof(T)).XorCache; + if (!cache.TryGetValue((left, right), out var ret)) { + ret = GetEnumValue(GetValue(left) ^ GetValue(right)); + cache.Add((left, right), ret); + } + return (T) ret; } /// @@ -222,7 +227,12 @@ namespace MLEM.Data { /// The type of the values /// The bitwise NEG (~) value public static T Neg(T value) where T : DynamicEnum { - return GetEnumValue(~GetValue(value)); + var cache = GetStorage(typeof(T)).NegCache; + if (!cache.TryGetValue(value, out var ret)) { + ret = GetEnumValue(~GetValue(value)); + cache.Add(value, ret); + } + return (T) ret; } /// @@ -251,18 +261,16 @@ namespace MLEM.Data { /// The value whose dynamic enum value to get /// The defined or combined dynamic enum value public static DynamicEnum GetEnumValue(Type type, BigInteger value) { + var storage = GetStorage(type); + // get the defined value if it exists - if (Values.TryGetValue(type, out var values) && values.TryGetValue(value, out var defined)) + if (storage.Values.TryGetValue(value, out var defined)) return defined; // otherwise, cache the combined value - if (!FlagCache.TryGetValue(type, out var cache)) { - cache = new Dictionary(); - FlagCache.Add(type, cache); - } - if (!cache.TryGetValue(value, out var combined)) { + if (!storage.FlagCache.TryGetValue(value, out var combined)) { combined = Construct(type, null, value); - cache.Add(value, combined); + storage.FlagCache.Add(value, combined); } return combined; } @@ -287,10 +295,7 @@ namespace MLEM.Data { /// The string to parse into a dynamic enum value /// The parsed enum value, or null if parsing fails public static DynamicEnum Parse(Type type, string strg) { - if (!ParseCache.TryGetValue(type, out var cache)) { - cache = new Dictionary(); - ParseCache.Add(type, cache); - } + var cache = GetStorage(type).ParseCache; if (!cache.TryGetValue(strg, out var cached)) { BigInteger? accum = null; foreach (var val in strg.Split('|')) { @@ -308,10 +313,39 @@ namespace MLEM.Data { return cached; } + private static Storage GetStorage(Type type) { + if (!Storages.TryGetValue(type, out var storage)) { + storage = new Storage(); + Storages.Add(type, storage); + } + return storage; + } + private static DynamicEnum Construct(Type type, string name, BigInteger value) { var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] {typeof(string), typeof(BigInteger)}, null); return (DynamicEnum) constructor.Invoke(new object[] {name, value}); } + private class Storage { + + public readonly Dictionary Values = new Dictionary(); + public readonly Dictionary FlagCache = new Dictionary(); + public readonly Dictionary ParseCache = new Dictionary(); + public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> OrCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); + public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> AndCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); + public readonly Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum> XorCache = new Dictionary<(DynamicEnum, DynamicEnum), DynamicEnum>(); + public readonly Dictionary NegCache = new Dictionary(); + + public void ClearCaches() { + this.FlagCache.Clear(); + this.ParseCache.Clear(); + this.OrCache.Clear(); + this.AndCache.Clear(); + this.XorCache.Clear(); + this.NegCache.Clear(); + } + + } + } } \ No newline at end of file