From 65bb3dd2f7ddb6f3fdb62020b16ddb41966fa2d2 Mon Sep 17 00:00:00 2001 From: "everLEEst(SangHyeon Lee)" Date: Fri, 27 Dec 2024 15:21:31 +0900 Subject: [PATCH] [NUI] Refactoring ControlState to use bitmask ControlState was implemented inefficently on the memory and the performance. This patch purposed to reduce inefficency by using bitflags on the state instead of string list. [https://github.sec.samsung.net/NUI/OneUIComponents/issues/15] long type bitmask will be represent each states, 1 1 1 1 1 O S D P F Normal : 0L Focused : 1L Pressed : 2L Disabled : 4L Selected : 8L Other : 16L and All : 31L This concept is based on VisualState of NUI2, https://github.sec.samsung.net/dotnet/nui2/blob/main/src/Tizen.NUI2.Components/Base/ViewState.cs but we had to modified few states to keep backward compatibility of NUI ControlState. --- .../internal/Common/ControlStateUtility.cs | 72 ++++++++ .../src/public/BaseComponents/ControlState.cs | 169 ++++++++---------- 2 files changed, 149 insertions(+), 92 deletions(-) create mode 100644 src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs diff --git a/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs b/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs new file mode 100644 index 00000000000..c87903425fe --- /dev/null +++ b/src/Tizen.NUI/src/internal/Common/ControlStateUtility.cs @@ -0,0 +1,72 @@ +/* + * Copyright(c) 2025 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +using System; +using System.Collections.Generic; + +namespace Tizen.NUI +{ + /// + /// Manages state name and bit mask. + /// + internal static class ControlStateUtility + { + private const int MaxBitWidth = 32; + private static readonly Dictionary registeredStates = new Dictionary(); + private static int nextBitPosition = 0; + + /// + /// + public static long FullMask => (1L << MaxBitWidth) - 1L; + + /// + /// + public static IEnumerable<(string, long)> RegisteredStates() + { + foreach (var (key, value) in registeredStates) + { + yield return (key, value); + } + } + + public static long Register(string stateName) + { + if (stateName == null) + throw new ArgumentNullException($"{nameof(stateName)} cannot be null.", nameof(stateName)); + + if (string.IsNullOrWhiteSpace(stateName)) + throw new ArgumentException($"{nameof(stateName)} cannot be whitespace.", nameof(stateName)); + + string trimmed = stateName.Trim().ToLowerInvariant(); + + if (!registeredStates.TryGetValue(trimmed, out long bitMask)) + { + if (nextBitPosition + 1 > MaxBitWidth) + { + throw new ArgumentException($"The given state name '{stateName}' is not acceptable since there is no more room to register a new state."); + } + + bitMask = 1L << nextBitPosition; + registeredStates.Add(trimmed, bitMask); + + nextBitPosition++; + } + + return bitMask; + } + } +} \ No newline at end of file diff --git a/src/Tizen.NUI/src/public/BaseComponents/ControlState.cs b/src/Tizen.NUI/src/public/BaseComponents/ControlState.cs index 9c557e14b74..9b633b49144 100755 --- a/src/Tizen.NUI/src/public/BaseComponents/ControlState.cs +++ b/src/Tizen.NUI/src/public/BaseComponents/ControlState.cs @@ -1,5 +1,5 @@ /* - * Copyright(c) 2020-2021 Samsung Electronics Co., Ltd. + * Copyright(c) 2020-2025 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,8 @@ */ using System; -using System.Collections.Generic; +using System.Text; using System.ComponentModel; -using System.Linq; namespace Tizen.NUI.BaseComponents { @@ -30,38 +29,37 @@ namespace Tizen.NUI.BaseComponents [Binding.TypeConverter(typeof(ControlStateTypeConverter))] public class ControlState : IEquatable { - private static readonly Dictionary stateDictionary = new Dictionary(); //Default States /// /// The All state is used in a selector class. It represents all states, so if this state is defined in a selector, the other states are ignored. /// /// 9 - public static readonly ControlState All = Create("All"); + public static readonly ControlState All = new ControlState(ControlStateUtility.FullMask); /// /// Normal State. /// /// 9 - public static readonly ControlState Normal = Create("Normal"); + public static readonly ControlState Normal = new ControlState(0L); /// /// Focused State. /// /// 9 - public static readonly ControlState Focused = Create("Focused"); + public static readonly ControlState Focused = new ControlState(nameof(Focused)); /// /// Pressed State. /// /// 9 - public static readonly ControlState Pressed = Create("Pressed"); + public static readonly ControlState Pressed = new ControlState(nameof(Pressed)); /// /// Disabled State. /// /// 9 - public static readonly ControlState Disabled = Create("Disabled"); + public static readonly ControlState Disabled = new ControlState(nameof(Disabled)); /// /// Selected State. /// /// 9 - public static readonly ControlState Selected = Create("Selected"); + public static readonly ControlState Selected = new ControlState(nameof(Selected)); /// /// SelectedPressed State. /// @@ -86,20 +84,24 @@ public class ControlState : IEquatable /// This is used in a selector class. It represents all other states except for states that are already defined in a selector. /// /// 9 - public static readonly ControlState Other = Create("Other"); + public static readonly ControlState Other = new ControlState(nameof(Other)); - private List stateList = new List(); - private readonly string name = ""; + readonly long bitFlags; /// /// Gets or sets a value indicating whether it has combined states. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsCombined => stateList.Count > 1; + public bool IsCombined => bitFlags != 0L && Math.Ceiling(Math.Log2(bitFlags)) != Math.Floor(Math.Log2(bitFlags)); - private ControlState() { } + ControlState(long bitMask) + { + bitFlags = bitMask; + } - private ControlState(string name) : this() => this.name = name; + private ControlState(string name) : this(ControlStateUtility.Register(name)) + { + } /// /// Create an instance of the with state name. @@ -111,20 +113,7 @@ private ControlState() { } /// 9 public static ControlState Create(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentException("name cannot be empty string", nameof(name)); - - name = name.Trim(); - - if (stateDictionary.TryGetValue(name, out ControlState state)) - return state; - - state = new ControlState(name); - state.stateList.Add(state); - stateDictionary.Add(name, state); - return state; + return new ControlState(name); } /// @@ -138,7 +127,8 @@ public static ControlState Create(params ControlState[] states) if (states.Length == 1) return states[0]; - ControlState newState = new ControlState(); + var newState = new ControlState(0L); + for (int i = 0; i < states.Length; i++) { if (states[i] == Normal) @@ -147,17 +137,7 @@ public static ControlState Create(params ControlState[] states) if (states[i] == All) return All; - newState.stateList.AddRange(states[i].stateList); - } - - if (newState.stateList.Count == 0) - return Normal; - - newState.stateList = newState.stateList.Distinct().ToList(); - - if (newState.stateList.Count == 1) - { - return newState.stateList[0]; + newState += states[i]; } return newState; @@ -172,38 +152,16 @@ public static ControlState Create(params ControlState[] states) /// 9 public bool Contains(ControlState state) { - if (state == null) - throw new ArgumentNullException(nameof(state)); - - if (!IsCombined) - return ReferenceEquals(this, state); - - bool found; - for (int i = 0; i < state.stateList.Count; i++) - { - found = false; - for (int j = 0; j < stateList.Count; j++) - { - if (ReferenceEquals(state.stateList[i], stateList[j])) - { - found = true; - break; - } - } - if (!found) return false; - } - - return true; + if (state is null) return false; + return (bitFlags & state.bitFlags) == state.bitFlags; } /// [EditorBrowsable(EditorBrowsableState.Never)] public bool Equals(ControlState other) { - if (other is null || stateList.Count != other.stateList.Count) - return false; - - return Contains(other); + if (other is null) return false; + return this.bitFlags == other.bitFlags; } /// @@ -212,18 +170,24 @@ public bool Equals(ControlState other) /// [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() => (name.GetHashCode() * 397) ^ IsCombined.GetHashCode(); + public override int GetHashCode() => bitFlags.GetHashCode(); /// [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() { - string name = ""; - for (int i = 0; i < stateList.Count; i++) + var sbuilder = new StringBuilder(); + + foreach (var (name, bitMask) in ControlStateUtility.RegisteredStates()) { - name += ((i == 0) ? "" : ", ") + stateList[i].name; + if ((bitFlags & bitMask) > 0) + { + if (sbuilder.Length != 0) sbuilder.Append(", "); + sbuilder.Append(name); + } } - return name; + + return sbuilder.ToString(); } /// @@ -247,7 +211,6 @@ public override string ToString() // Only the left side is null. return false; } - // Equals handles case of null on right side. return lhs.Equals(rhs); } @@ -267,7 +230,10 @@ public override string ToString() /// A on the right hand side. /// The containing the result of the addition. /// 9 - public static ControlState operator +(ControlState lhs, ControlState rhs) => Create(lhs, rhs); + public static ControlState operator +(ControlState lhs, ControlState rhs) + { + return Add(lhs, rhs); + } /// /// The substraction operator. @@ -279,36 +245,55 @@ public override string ToString() [EditorBrowsable(EditorBrowsableState.Never)] public static ControlState operator -(ControlState lhs, ControlState rhs) { - if (null == lhs) + return Remove(lhs, rhs); + } + + /// + /// Add multiple states. + /// + /// The first operand object. + /// The second operand object. + /// The rest operand objects. + /// The operation result. + static ControlState Add(ControlState operand1, ControlState operand2, params ControlState[] rest) + { + if (operand1 is null) { - throw new ArgumentNullException(nameof(lhs)); + throw new ArgumentNullException(nameof(operand1)); } - else if (null == rhs) + if (operand2 is null) { - throw new ArgumentNullException(nameof(rhs)); + throw new ArgumentNullException(nameof(operand2)); } - if (!lhs.IsCombined) + long newBitFlags = operand1.bitFlags | operand2.bitFlags; + + foreach (var state in rest) { - return ReferenceEquals(lhs, rhs) ? Normal : lhs; + newBitFlags |= state.bitFlags; } - var rest = lhs.stateList.Except(rhs.stateList); - var count = rest.Count(); + return new ControlState(newBitFlags); + } - if (count == 0) + /// + /// Remove a state from another. + /// + /// The first operand object. + /// The second operand object. + /// The operation result. + static ControlState Remove(ControlState operand1, ControlState operand2) + { + if (operand1 is null) { - return Normal; + throw new ArgumentNullException(nameof(operand1)); } - - if (count == 1) + if (operand2 is null) { - return rest.First(); + throw new ArgumentNullException(nameof(operand2)); } - ControlState newState = new ControlState(); - newState.stateList.AddRange(rest); - return newState; + return new ControlState(operand1.bitFlags & ~(operand2.bitFlags)); } class ControlStateTypeConverter : Binding.TypeConverter @@ -319,7 +304,7 @@ public override object ConvertFromInvariantString(string value) { value = value.Trim(); - ControlState convertedState = new ControlState(); + ControlState convertedState = new ControlState(0L); string[] parts = value.Split(','); foreach (string part in parts) {