From 569066edc41d172b6a0f378adea391078c741077 Mon Sep 17 00:00:00 2001 From: Drew Tingen Date: Wed, 14 Jun 2023 06:11:37 -0400 Subject: [PATCH] feat[Types]: Add NotifyFlagsChanged, with abstract and generic classes --- .../ICD.Common.Utils.Tests_NetStandard.csproj | 2 +- .../Types/GenericNotifyFlagsChangedTest.cs | 131 ++++++++++++++++++ .../ICD.Common.Utils_SimplSharp.csproj | 2 + .../Types/AbstractNotifyFlagsChanged.cs | 79 +++++++++++ .../Types/GenericNotifyFlagsChanged.cs | 34 +++++ 5 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 ICD.Common.Utils.Tests/Types/GenericNotifyFlagsChangedTest.cs create mode 100644 ICD.Common.Utils/Types/AbstractNotifyFlagsChanged.cs create mode 100644 ICD.Common.Utils/Types/GenericNotifyFlagsChanged.cs diff --git a/ICD.Common.Utils.Tests/ICD.Common.Utils.Tests_NetStandard.csproj b/ICD.Common.Utils.Tests/ICD.Common.Utils.Tests_NetStandard.csproj index 1563c31..173bb35 100644 --- a/ICD.Common.Utils.Tests/ICD.Common.Utils.Tests_NetStandard.csproj +++ b/ICD.Common.Utils.Tests/ICD.Common.Utils.Tests_NetStandard.csproj @@ -26,7 +26,7 @@ - + diff --git a/ICD.Common.Utils.Tests/Types/GenericNotifyFlagsChangedTest.cs b/ICD.Common.Utils.Tests/Types/GenericNotifyFlagsChangedTest.cs new file mode 100644 index 0000000..dc7ec7d --- /dev/null +++ b/ICD.Common.Utils.Tests/Types/GenericNotifyFlagsChangedTest.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using ICD.Common.Utils.EventArguments; +using ICD.Common.Utils.Types; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests.Types +{ + [TestFixture] + public class GenericNotifyFlagsChangedTest + { + [Flags] + private enum eTestFlagsEnum + { + None = 0, + A = 1, + B = 2, + C = 4, + D = 32, + BandC = B | C + } + + [Test] + public void TestSingleFlags() + { + List> addedFlags = new List>(); + List> removedFlags = new List>(); + + GenericNotifyFlagsChanged genericNotify = new GenericNotifyFlagsChanged(); + + genericNotify.OnFlagsSet += (sender, args) => addedFlags.Add(args); + genericNotify.OnFlagsUnset += (sender, args) => removedFlags.Add(args); + + // Initial State + Assert.AreEqual(0, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + + // Add No flags + genericNotify.Data = eTestFlagsEnum.None; + Assert.AreEqual(0, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + + // Add Flag + genericNotify.Data = eTestFlagsEnum.B; + Assert.AreEqual(1, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.B, addedFlags[0].Data); + Assert.AreEqual(eTestFlagsEnum.B, genericNotify.Data); + + // Add Another Flag + genericNotify.Data |= eTestFlagsEnum.C; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.C, addedFlags[1].Data); + Assert.AreEqual(eTestFlagsEnum.B | eTestFlagsEnum.C, genericNotify.Data); + + // Remove a Flag + genericNotify.Data = genericNotify.Data & ~ eTestFlagsEnum.B; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(1, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.B, removedFlags[0].Data); + Assert.AreEqual(eTestFlagsEnum.C, genericNotify.Data); + + // Add Already Existing Flags + genericNotify.Data |= eTestFlagsEnum.C; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(1, removedFlags.Count); + + // Clear Flags + genericNotify.Data = eTestFlagsEnum.None; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(2, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.C, removedFlags[1].Data); + Assert.AreEqual(eTestFlagsEnum.None, genericNotify.Data); + } + + [Test] + public void TestMultipleFlags() + { + List> addedFlags = new List>(); + List> removedFlags = new List>(); + + GenericNotifyFlagsChanged genericNotify = new GenericNotifyFlagsChanged(); + + genericNotify.OnFlagsSet += (sender, args) => addedFlags.Add(args); + genericNotify.OnFlagsUnset += (sender, args) => removedFlags.Add(args); + + // Initial State + Assert.AreEqual(0, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + + // Add No flags + genericNotify.Data = eTestFlagsEnum.None; + Assert.AreEqual(0, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + + // Add Flag + genericNotify.Data = eTestFlagsEnum.B | eTestFlagsEnum.D; + Assert.AreEqual(1, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.B | eTestFlagsEnum.D, addedFlags[0].Data); + Assert.AreEqual(eTestFlagsEnum.B | eTestFlagsEnum.D, genericNotify.Data); + + // Add Another Flag + genericNotify.Data |= eTestFlagsEnum.C; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(0, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.C, addedFlags[1].Data); + Assert.AreEqual(eTestFlagsEnum.B | eTestFlagsEnum.C | eTestFlagsEnum.D, genericNotify.Data); + + // Remove a Flag + genericNotify.Data = eTestFlagsEnum.D; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(1, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.B | eTestFlagsEnum.C, removedFlags[0].Data); + Assert.AreEqual(eTestFlagsEnum.D, genericNotify.Data); + + // Add Already Existing Flags + genericNotify.Data |= eTestFlagsEnum.D; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(1, removedFlags.Count); + + // Clear Flags + genericNotify.Data = eTestFlagsEnum.None; + Assert.AreEqual(2, addedFlags.Count); + Assert.AreEqual(2, removedFlags.Count); + Assert.AreEqual(eTestFlagsEnum.D, removedFlags[1].Data); + Assert.AreEqual(eTestFlagsEnum.None, genericNotify.Data); + } + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index dd01d46..a9ae916 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -242,6 +242,8 @@ + + diff --git a/ICD.Common.Utils/Types/AbstractNotifyFlagsChanged.cs b/ICD.Common.Utils/Types/AbstractNotifyFlagsChanged.cs new file mode 100644 index 0000000..94af1c4 --- /dev/null +++ b/ICD.Common.Utils/Types/AbstractNotifyFlagsChanged.cs @@ -0,0 +1,79 @@ +using System; +using ICD.Common.Utils.EventArguments; + +namespace ICD.Common.Utils.Types +{ + public abstract class AbstractNotifyFlagsChanged where T : struct, IConvertible + { + private T m_Data; + + /// + /// Raised when flags are set on the data + /// + public event EventHandler> OnFlagsSet; + + /// + /// Raised when flags are unset on the data + /// + public event EventHandler> OnFlagsUnset; + + /// + /// Raised when the data changes + /// + public event EventHandler> OnChange; + + /// + /// Data + /// + public T Data + { + get { return m_Data; } + set + { + if (DataIsEqual(m_Data, value)) + return; + + int intData = GetIntValue(m_Data); + int intValue = GetIntValue(value); + + int setFlags = intValue & ~intData; + int unsetFlags = intData & ~intValue; + + m_Data = value; + + OnChange.Raise(this, value); + + if (setFlags != 0) + OnFlagsSet.Raise(this, GetEnumValue(setFlags)); + if (unsetFlags != 0) + OnFlagsUnset.Raise(this, GetEnumValue(unsetFlags)); + } + } + + /// + /// Converts the enum to the backing int value + /// + /// + /// + protected abstract int GetIntValue(T value); + + /// + /// Converts the backing int value to enum + /// + /// + /// + protected abstract T GetEnumValue(int value); + + /// + /// Checks enums for equality + /// Override for performance improvements + /// + /// + /// + /// + protected virtual bool DataIsEqual(T a, T b) + { + return GetIntValue(a) == GetIntValue(b); + } + } +} \ No newline at end of file diff --git a/ICD.Common.Utils/Types/GenericNotifyFlagsChanged.cs b/ICD.Common.Utils/Types/GenericNotifyFlagsChanged.cs new file mode 100644 index 0000000..4db8905 --- /dev/null +++ b/ICD.Common.Utils/Types/GenericNotifyFlagsChanged.cs @@ -0,0 +1,34 @@ +using System; +using ICD.Common.Utils.EventArguments; + +namespace ICD.Common.Utils.Types +{ + /// + /// Class to raise events when flags are set and unset on the data + /// + /// + public sealed class GenericNotifyFlagsChanged : AbstractNotifyFlagsChanged where T : struct, IConvertible + { + /// + /// Converts the enum to the backing int value + /// Override for performance improvements + /// + /// + /// + protected override int GetIntValue(T value) + { + return (int)(object)value; + } + + /// + /// Converts the backing int value to enum + /// Override for performance improvements + /// + /// + /// + protected override T GetEnumValue(int value) + { + return (T)(object)value; + } + } +} \ No newline at end of file