com.alicizax.unity.ui.exten.../Runtime/UXComponent/Hotkey/UXHotkeyRegisterManager.cs
陈思海 526341579a 优化UIExtension扩展系统
优化UXBinding编辑器
优化UXBinding性能
优化UXBinding Bug
优化Hotkey注册器性能
优化UXNavigation导航性能
2026-04-28 20:52:06 +08:00

962 lines
30 KiB
C#

#if INPUTSYSTEM_SUPPORT
using System;
using System.Runtime.CompilerServices;
using AlicizaX.UI.Runtime;
using UnityEngine;
using UnityEngine.InputSystem;
namespace UnityEngine.UI
{
public enum EHotkeyPressType : byte
{
Started = 0,
Performed = 1
}
internal readonly struct HotkeyRegistration
{
public readonly IHotkeyTrigger Trigger;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HotkeyRegistration(IHotkeyTrigger trigger)
{
Trigger = trigger;
}
}
internal sealed class HotkeyActionRegistrations
{
public readonly HotkeyRegistrationList StartedRegistrations = new();
public readonly HotkeyRegistrationList PerformedRegistrations = new();
public bool IsEmpty => StartedRegistrations.Count == 0 && PerformedRegistrations.Count == 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public HotkeyRegistrationList GetRegistrations(EHotkeyPressType pressType)
{
return pressType == EHotkeyPressType.Started ? StartedRegistrations : PerformedRegistrations;
}
}
internal sealed class HotkeyRegistrationList
{
private HotkeyRegistration[] _items = Array.Empty<HotkeyRegistration>();
public int Count { get; private set; }
public HotkeyRegistration this[int index]
{
get => _items[index];
set => _items[index] = value;
}
public void Add(HotkeyRegistration registration)
{
if (Count == _items.Length)
{
int newLength = _items.Length == 0 ? 2 : _items.Length << 1;
Array.Resize(ref _items, newLength);
}
_items[Count++] = registration;
}
public void RemoveLast()
{
Count--;
_items[Count] = default;
}
public void Clear()
{
for (int i = 0; i < Count; i++)
{
_items[i] = default;
}
Count = 0;
}
}
internal sealed class ReferenceMap<TKey, TValue> where TKey : class
{
private int[] _buckets = Array.Empty<int>();
private int[] _next = Array.Empty<int>();
private TKey[] _keys = Array.Empty<TKey>();
private TValue[] _values = Array.Empty<TValue>();
private int _slotCount;
public int Count { get; private set; }
public int SlotCount => _slotCount;
public bool TryGetValue(TKey key, out TValue value)
{
if (key != null && _buckets.Length > 0)
{
int bucket = GetBucket(key, _buckets.Length);
for (int i = _buckets[bucket] - 1; i >= 0; i = _next[i])
{
if (ReferenceEquals(_keys[i], key))
{
value = _values[i];
return true;
}
}
}
value = default;
return false;
}
public void Set(TKey key, TValue value)
{
if (key == null)
{
return;
}
EnsureCapacity(_slotCount + 1);
int bucket = GetBucket(key, _buckets.Length);
for (int i = _buckets[bucket] - 1; i >= 0; i = _next[i])
{
if (ReferenceEquals(_keys[i], key))
{
_values[i] = value;
return;
}
}
int index = _slotCount++;
_keys[index] = key;
_values[index] = value;
_next[index] = _buckets[bucket] - 1;
_buckets[bucket] = index + 1;
Count++;
}
public bool Remove(TKey key)
{
if (key == null || _buckets.Length == 0)
{
return false;
}
int bucket = GetBucket(key, _buckets.Length);
int previous = -1;
for (int i = _buckets[bucket] - 1; i >= 0; i = _next[i])
{
if (!ReferenceEquals(_keys[i], key))
{
previous = i;
continue;
}
if (previous < 0)
{
_buckets[bucket] = _next[i] + 1;
}
else
{
_next[previous] = _next[i];
}
_keys[i] = null;
_values[i] = default;
_next[i] = -1;
Count--;
return true;
}
return false;
}
public void Clear()
{
Array.Clear(_buckets, 0, _buckets.Length);
Array.Clear(_next, 0, _next.Length);
Array.Clear(_keys, 0, _keys.Length);
Array.Clear(_values, 0, _values.Length);
_slotCount = 0;
Count = 0;
}
public bool TryGetValueAtSlot(int slot, out TValue value)
{
if ((uint)slot < (uint)_slotCount && _keys[slot] != null)
{
value = _values[slot];
return true;
}
value = default;
return false;
}
private void EnsureCapacity(int capacity)
{
if (_buckets.Length >= capacity)
{
return;
}
int newLength = _buckets.Length == 0 ? 8 : _buckets.Length << 1;
while (newLength < capacity)
{
newLength <<= 1;
}
TKey[] oldKeys = _keys;
TValue[] oldValues = _values;
int oldSlotCount = _slotCount;
_buckets = new int[newLength];
_next = new int[newLength];
_keys = new TKey[newLength];
_values = new TValue[newLength];
_slotCount = 0;
Count = 0;
for (int i = 0; i < oldSlotCount; i++)
{
if (oldKeys[i] != null)
{
Set(oldKeys[i], oldValues[i]);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetBucket(TKey key, int length)
{
return (RuntimeHelpers.GetHashCode(key) & int.MaxValue) & (length - 1);
}
}
internal sealed class HotkeyScope
{
public HotkeyScope(UIHolderObjectBase holder)
{
Holder = holder;
HierarchyDepth = GetHierarchyDepth(holder.transform);
ParentHolder = UXHotkeyRegisterManager.FindParentHolder(holder);
}
public readonly UIHolderObjectBase Holder;
public readonly UIHolderObjectBase ParentHolder;
public readonly int HierarchyDepth;
public readonly ReferenceMap<InputAction, HotkeyActionRegistrations> RegistrationsByAction = new();
public bool LifecycleActive;
public ulong ActivationSerial;
private Canvas _canvas;
public Canvas Canvas
{
get
{
if (_canvas == null && Holder != null)
{
_canvas = Holder.GetComponent<Canvas>();
}
return _canvas;
}
}
public void OnBeforeShowHandler()
{
UXHotkeyRegisterManager.ActivateScope(Holder);
}
public void OnBeforeClosedHandler()
{
UXHotkeyRegisterManager.DeactivateScope(Holder);
}
public void OnDestroyHandler()
{
UXHotkeyRegisterManager.DestroyScope(Holder);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetHierarchyDepth(Transform current)
{
int depth = 0;
while (current != null)
{
depth++;
current = current.parent;
}
return depth;
}
}
internal sealed class ActionRegistrationBucket
{
public InputAction Action;
public int StartedCount;
public int PerformedCount;
public bool WasEnabledBeforeHotkey;
public bool EnabledByHotkeySystem;
public int TotalCount => StartedCount + PerformedCount;
}
internal readonly struct TriggerRegistration
{
public readonly InputAction Action;
public readonly UIHolderObjectBase Holder;
public readonly EHotkeyPressType PressType;
public readonly int Index;
public readonly IHotkeyTrigger Trigger;
public TriggerRegistration(InputAction action, UIHolderObjectBase holder, EHotkeyPressType pressType, int index, IHotkeyTrigger trigger)
{
Action = action;
Holder = holder;
PressType = pressType;
Index = index;
Trigger = trigger;
}
}
internal static class UXHotkeyRegisterManager
{
private static readonly ReferenceMap<InputAction, ActionRegistrationBucket> _actions = new();
private static readonly ReferenceMap<IHotkeyTrigger, TriggerRegistration> _triggerMap = new();
private static readonly ReferenceMap<UIHolderObjectBase, HotkeyScope> _scopes = new();
private static readonly ReferenceMap<UIHolderObjectBase, UIHolderObjectBase> _ancestorHolders = new();
private static IHotkeyTrigger[] _destroyScopeTriggers = Array.Empty<IHotkeyTrigger>();
private static int _destroyScopeTriggerCount;
private static readonly Action<InputAction.CallbackContext> _startedHandler = OnActionStarted;
private static readonly Action<InputAction.CallbackContext> _performedHandler = OnActionPerformed;
private static ulong _serialCounter;
private static HotkeyScope _topLeafScope;
private static bool _isDestroyingScope;
#if UNITY_EDITOR
[UnityEditor.Callbacks.DidReloadScripts]
internal static void ClearHotkeyRegistry()
{
IHotkeyTrigger[] triggers = new IHotkeyTrigger[_triggerMap.Count];
int index = 0;
for (int i = 0; i < _triggerMap.SlotCount; i++)
{
if (_triggerMap.TryGetValueAtSlot(i, out var registration))
{
triggers[index++] = registration.Trigger;
}
}
for (int i = 0; i < triggers.Length; i++)
{
UnregisterHotkey(triggers[i]);
}
_actions.Clear();
_triggerMap.Clear();
_scopes.Clear();
_ancestorHolders.Clear();
_destroyScopeTriggerCount = 0;
_serialCounter = 0;
_topLeafScope = null;
_isDestroyingScope = false;
RebuildTopLeafScopeImmediate();
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RegisterHotkey(IHotkeyTrigger trigger, UIHolderObjectBase holder, InputActionReference action, EHotkeyPressType pressType)
{
if (trigger == null || holder == null || action == null || action.action == null)
{
return;
}
UnregisterHotkey(trigger);
InputAction inputAction = action.action;
ActionRegistrationBucket bucket = GetOrCreateBucket(inputAction);
HotkeyScope scope = GetOrCreateScope(holder);
int index = AddScopeRegistration(scope, inputAction, trigger, pressType);
AdjustBucketSubscription(bucket, pressType, true);
if (scope.LifecycleActive)
{
scope.ActivationSerial = ++_serialCounter;
RebuildTopLeafScopeImmediate();
}
_triggerMap.Set(trigger, new TriggerRegistration(inputAction, holder, pressType, index, trigger));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void UnregisterHotkey(IHotkeyTrigger trigger)
{
if (trigger == null || !_triggerMap.TryGetValue(trigger, out var triggerRegistration))
{
return;
}
if (!_scopes.TryGetValue(triggerRegistration.Holder, out var scope))
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.LogError("Hotkey registry is inconsistent: scope missing during unregister.");
#endif
return;
}
if (!RemoveScopeRegistration(scope, triggerRegistration.Action, triggerRegistration.PressType, triggerRegistration.Index, trigger))
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.LogError("Hotkey registry is inconsistent: trigger slot missing during unregister.");
#endif
return;
}
_triggerMap.Remove(trigger);
if (_actions.TryGetValue(triggerRegistration.Action, out var bucket))
{
RemoveActionRegistration(bucket, triggerRegistration.PressType, triggerRegistration.Action);
}
ReleaseScopeIfEmpty(scope);
}
internal static void ActivateScope(UIHolderObjectBase holder)
{
if (_scopes.TryGetValue(holder, out var scope))
{
scope.LifecycleActive = true;
scope.ActivationSerial = ++_serialCounter;
RebuildTopLeafScopeImmediate();
}
}
internal static void DeactivateScope(UIHolderObjectBase holder)
{
if (_scopes.TryGetValue(holder, out var scope))
{
scope.LifecycleActive = false;
RebuildTopLeafScopeImmediate();
}
}
internal static void DestroyScope(UIHolderObjectBase holder)
{
if (holder == null || _isDestroyingScope || !_scopes.TryGetValue(holder, out var scope))
{
return;
}
_isDestroyingScope = true;
_destroyScopeTriggerCount = 0;
for (int i = 0; i < scope.RegistrationsByAction.SlotCount; i++)
{
if (scope.RegistrationsByAction.TryGetValueAtSlot(i, out var actionRegistrations))
{
CollectTriggers(actionRegistrations.StartedRegistrations);
CollectTriggers(actionRegistrations.PerformedRegistrations);
}
}
for (int i = 0; i < _destroyScopeTriggerCount; i++)
{
UnregisterHotkey(_destroyScopeTriggers[i]);
}
Array.Clear(_destroyScopeTriggers, 0, _destroyScopeTriggerCount);
_destroyScopeTriggerCount = 0;
DetachScope(scope);
_isDestroyingScope = false;
}
internal static UIHolderObjectBase FindParentHolder(UIHolderObjectBase holder)
{
if (holder == null)
{
return null;
}
Transform current = holder.transform.parent;
while (current != null)
{
if (current.TryGetComponent<UIHolderObjectBase>(out var parentHolder))
{
return parentHolder;
}
current = current.parent;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void OnActionStarted(InputAction.CallbackContext context)
{
Dispatch(context.action, EHotkeyPressType.Started);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void OnActionPerformed(InputAction.CallbackContext context)
{
Dispatch(context.action, EHotkeyPressType.Performed);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ActionRegistrationBucket GetOrCreateBucket(InputAction inputAction)
{
if (_actions.TryGetValue(inputAction, out var bucket))
{
return bucket;
}
bucket = new ActionRegistrationBucket { Action = inputAction };
_actions.Set(inputAction, bucket);
return bucket;
}
private static HotkeyScope GetOrCreateScope(UIHolderObjectBase holder)
{
if (_scopes.TryGetValue(holder, out var scope))
{
return scope;
}
scope = new HotkeyScope(holder)
{
ActivationSerial = ++_serialCounter
};
scope.LifecycleActive = IsScopeVisible(scope);
holder.OnWindowBeforeShowEvent += scope.OnBeforeShowHandler;
holder.OnWindowBeforeClosedEvent += scope.OnBeforeClosedHandler;
holder.OnWindowDestroyEvent += scope.OnDestroyHandler;
_scopes.Set(holder, scope);
RebuildTopLeafScopeImmediate();
return scope;
}
private static void DetachScope(HotkeyScope scope)
{
if (scope == null || scope.Holder == null)
{
return;
}
scope.Holder.OnWindowBeforeShowEvent -= scope.OnBeforeShowHandler;
scope.Holder.OnWindowBeforeClosedEvent -= scope.OnBeforeClosedHandler;
scope.Holder.OnWindowDestroyEvent -= scope.OnDestroyHandler;
_scopes.Remove(scope.Holder);
RebuildTopLeafScopeImmediate();
}
private static void ReleaseScopeIfEmpty(HotkeyScope scope)
{
if (scope != null && scope.RegistrationsByAction.Count == 0)
{
DetachScope(scope);
}
}
private static int AddScopeRegistration(HotkeyScope scope, InputAction action, IHotkeyTrigger trigger, EHotkeyPressType pressType)
{
if (!scope.RegistrationsByAction.TryGetValue(action, out var actionRegistrations))
{
actionRegistrations = new HotkeyActionRegistrations();
scope.RegistrationsByAction.Set(action, actionRegistrations);
}
HotkeyRegistrationList registrations = actionRegistrations.GetRegistrations(pressType);
int index = registrations.Count;
registrations.Add(new HotkeyRegistration(trigger));
return index;
}
private static bool RemoveScopeRegistration(HotkeyScope scope, InputAction action, EHotkeyPressType pressType, int index, IHotkeyTrigger expectedTrigger)
{
if (!scope.RegistrationsByAction.TryGetValue(action, out var actionRegistrations))
{
return false;
}
HotkeyRegistrationList registrations = actionRegistrations.GetRegistrations(pressType);
if ((uint)index >= (uint)registrations.Count)
{
return false;
}
int lastIndex = registrations.Count - 1;
HotkeyRegistration removedRegistration = registrations[index];
if (!ReferenceEquals(removedRegistration.Trigger, expectedTrigger))
{
return false;
}
if (index != lastIndex)
{
HotkeyRegistration movedRegistration = registrations[lastIndex];
registrations[index] = movedRegistration;
UpdateTriggerIndex(movedRegistration.Trigger, index);
}
registrations.RemoveLast();
if (actionRegistrations.IsEmpty)
{
scope.RegistrationsByAction.Remove(action);
}
return true;
}
private static void UpdateTriggerIndex(IHotkeyTrigger trigger, int index)
{
if (trigger == null || !_triggerMap.TryGetValue(trigger, out var registration))
{
return;
}
_triggerMap.Set(trigger, new TriggerRegistration(registration.Action, registration.Holder, registration.PressType, index, trigger));
}
private static void RemoveActionRegistration(ActionRegistrationBucket bucket, EHotkeyPressType pressType, InputAction action)
{
AdjustBucketSubscription(bucket, pressType, false);
if (bucket.TotalCount == 0)
{
_actions.Remove(action);
}
}
private static void AdjustBucketSubscription(ActionRegistrationBucket bucket, EHotkeyPressType pressType, bool add)
{
InputAction inputAction = bucket.Action;
if (inputAction == null)
{
return;
}
int countBefore = bucket.TotalCount;
if (add && countBefore == 0)
{
bucket.WasEnabledBeforeHotkey = inputAction.enabled;
}
switch (pressType)
{
case EHotkeyPressType.Started:
if (add)
{
if (bucket.StartedCount == 0)
{
inputAction.started += _startedHandler;
}
bucket.StartedCount++;
}
else if (bucket.StartedCount > 0)
{
bucket.StartedCount--;
if (bucket.StartedCount == 0)
{
inputAction.started -= _startedHandler;
}
}
break;
case EHotkeyPressType.Performed:
if (add)
{
if (bucket.PerformedCount == 0)
{
inputAction.performed += _performedHandler;
}
bucket.PerformedCount++;
}
else if (bucket.PerformedCount > 0)
{
bucket.PerformedCount--;
if (bucket.PerformedCount == 0)
{
inputAction.performed -= _performedHandler;
}
}
break;
}
if (add && countBefore == 0 && !bucket.WasEnabledBeforeHotkey)
{
inputAction.Enable();
bucket.EnabledByHotkeySystem = true;
}
else if (!add && bucket.TotalCount == 0 && bucket.EnabledByHotkeySystem && !bucket.WasEnabledBeforeHotkey)
{
inputAction.Disable();
bucket.EnabledByHotkeySystem = false;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Dispatch(InputAction action, EHotkeyPressType pressType)
{
HotkeyScope leafScope = _topLeafScope;
if (leafScope == null)
{
return;
}
TryDispatchToScopeChain(leafScope, action, pressType);
}
private static bool TryDispatchToScopeChain(HotkeyScope leafScope, InputAction action, EHotkeyPressType pressType)
{
HotkeyScope current = leafScope;
while (current != null)
{
if (TryGetLatestRegistration(current, action, pressType, out var registration))
{
registration.Trigger?.HotkeyActionTrigger();
return true;
}
UIHolderObjectBase parentHolder = current.ParentHolder;
current = parentHolder != null && _scopes.TryGetValue(parentHolder, out var parentScope)
? parentScope
: null;
}
return false;
}
private static bool TryGetLatestRegistration(HotkeyScope scope, InputAction action, EHotkeyPressType pressType, out HotkeyRegistration registration)
{
if (scope.RegistrationsByAction.TryGetValue(action, out var actionRegistrations))
{
HotkeyRegistrationList registrations = actionRegistrations.GetRegistrations(pressType);
if (registrations.Count > 0)
{
registration = registrations[registrations.Count - 1];
return registration.Trigger != null;
}
}
registration = default;
return false;
}
private static void RebuildTopLeafScopeImmediate()
{
_topLeafScope = RebuildTopLeafScope();
}
private static HotkeyScope RebuildTopLeafScope()
{
_ancestorHolders.Clear();
for (int i = 0; i < _scopes.SlotCount; i++)
{
if (!_scopes.TryGetValueAtSlot(i, out var scope))
{
continue;
}
if (!IsScopeActive(scope)
#if UX_NAVIGATION
|| !UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder)
#endif
)
{
continue;
}
UIHolderObjectBase parentHolder = scope.ParentHolder;
while (parentHolder != null)
{
_ancestorHolders.Set(parentHolder, parentHolder);
if (_scopes.TryGetValue(parentHolder, out var parentScope))
{
parentHolder = parentScope.ParentHolder;
}
else
{
break;
}
}
}
HotkeyScope bestScope = null;
for (int i = 0; i < _scopes.SlotCount; i++)
{
if (!_scopes.TryGetValueAtSlot(i, out var scope))
{
continue;
}
if (IsScopeActive(scope)
#if UX_NAVIGATION
&& UXNavigationRuntime.IsHolderWithinTopScope(scope.Holder)
#endif
&& !_ancestorHolders.TryGetValue(scope.Holder, out _)
&& (bestScope == null || CompareScopePriority(scope, bestScope) < 0))
{
bestScope = scope;
}
}
return bestScope;
}
private static bool IsScopeActive(HotkeyScope scope)
{
if (scope == null || !scope.LifecycleActive)
{
return false;
}
UIHolderObjectBase holder = scope.Holder;
if (holder == null || !holder.IsValid())
{
return false;
}
if (!holder.gameObject.activeInHierarchy)
{
return false;
}
Canvas canvas = scope.Canvas;
return canvas != null && canvas.gameObject.layer == UIComponent.UIShowLayer;
}
private static bool IsScopeVisible(HotkeyScope scope)
{
UIHolderObjectBase holder = scope.Holder;
if (holder == null || !holder.gameObject.activeInHierarchy)
{
return false;
}
Canvas canvas = scope.Canvas;
return canvas != null && canvas.gameObject.layer == UIComponent.UIShowLayer;
}
private static int CompareScopePriority(HotkeyScope left, HotkeyScope right)
{
int leftDepth = left.Canvas != null ? left.Canvas.sortingOrder : int.MinValue;
int rightDepth = right.Canvas != null ? right.Canvas.sortingOrder : int.MinValue;
int depthCompare = rightDepth.CompareTo(leftDepth);
if (depthCompare != 0)
{
return depthCompare;
}
int hierarchyCompare = right.HierarchyDepth.CompareTo(left.HierarchyDepth);
if (hierarchyCompare != 0)
{
return hierarchyCompare;
}
return right.ActivationSerial.CompareTo(left.ActivationSerial);
}
private static void CollectTriggers(HotkeyRegistrationList registrations)
{
for (int i = 0; i < registrations.Count; i++)
{
AddDestroyScopeTrigger(registrations[i].Trigger);
}
}
private static void AddDestroyScopeTrigger(IHotkeyTrigger trigger)
{
if (_destroyScopeTriggerCount == _destroyScopeTriggers.Length)
{
int newLength = _destroyScopeTriggers.Length == 0 ? 8 : _destroyScopeTriggers.Length << 1;
Array.Resize(ref _destroyScopeTriggers, newLength);
}
_destroyScopeTriggers[_destroyScopeTriggerCount++] = trigger;
}
#if UNITY_EDITOR
public static string GetDebugInfo()
{
return $"Actions: {_actions.Count}, Triggers: {_triggerMap.Count}, Scopes: {_scopes.Count}";
}
#endif
}
}
namespace UnityEngine.UI
{
public static class UXHotkeyExtension
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BindHotKey(this IHotkeyTrigger trigger)
{
if (trigger?.HotkeyAction == null)
{
return;
}
UIHolderObjectBase holder = trigger.HotkeyHolder;
if (holder == null)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
Debug.LogWarning("Hotkey trigger could not find a UIHolderObjectBase owner.");
#endif
return;
}
UXHotkeyRegisterManager.RegisterHotkey(trigger, holder, trigger.HotkeyAction, trigger.HotkeyPressType);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnBindHotKey(this IHotkeyTrigger trigger)
{
if (trigger?.HotkeyAction != null)
{
UXHotkeyRegisterManager.UnregisterHotkey(trigger);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BindHotKeyBatch(this IHotkeyTrigger[] triggers)
{
if (triggers == null)
{
return;
}
for (int i = 0; i < triggers.Length; i++)
{
triggers[i]?.BindHotKey();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnBindHotKeyBatch(this IHotkeyTrigger[] triggers)
{
if (triggers == null)
{
return;
}
for (int i = 0; i < triggers.Length; i++)
{
triggers[i]?.UnBindHotKey();
}
}
}
}
#endif