// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using GFramework.Game.Abstractions.Input; namespace GFramework.Game.Input; /// /// 提供基于内存快照的默认输入绑定存储实现。 /// /// /// 该实现聚焦于框架级动作绑定管理语义:默认值恢复、主绑定替换、冲突交换与快照导入导出。 /// 它不依赖具体宿主输入事件,适合作为 `Game` 层默认运行时与单元测试基线。 /// public sealed class InputBindingStore : IInputBindingStore { private readonly Dictionary> _defaultBindings; private readonly Dictionary> _currentBindings; /// /// 初始化输入绑定存储。 /// /// 默认绑定快照。 public InputBindingStore(InputBindingSnapshot defaultSnapshot) { _defaultBindings = ToDictionary(defaultSnapshot); _currentBindings = CloneDictionary(_defaultBindings); } /// public InputActionBinding GetBindings(string actionName) { var bindings = GetOrCreateBindings(actionName); return new InputActionBinding(actionName, bindings.ToArray()); } /// public InputBindingSnapshot ExportSnapshot() { var actions = _currentBindings .OrderBy(static pair => pair.Key, StringComparer.Ordinal) .Select(static pair => new InputActionBinding(pair.Key, pair.Value.ToArray())) .ToArray(); return new InputBindingSnapshot(actions); } /// public void ImportSnapshot(InputBindingSnapshot snapshot) { ArgumentNullException.ThrowIfNull(snapshot); _currentBindings.Clear(); foreach (var action in snapshot.Actions) { _currentBindings[action.ActionName] = [..action.Bindings]; } } /// public void SetPrimaryBinding(string actionName, InputBindingDescriptor binding, bool swapIfTaken = true) { ArgumentException.ThrowIfNullOrWhiteSpace(actionName); ArgumentNullException.ThrowIfNull(binding); var targetBindings = GetOrCreateBindings(actionName); var existingOwner = FindOwner(actionName, binding); if (existingOwner is not null) { if (!swapIfTaken) { return; } var previousPrimary = targetBindings.Count > 0 ? targetBindings[0] : null; var ownerBindings = GetOrCreateBindings(existingOwner); ReplaceBinding(ownerBindings, binding, previousPrimary); } RemoveBinding(targetBindings, binding); targetBindings.Insert(0, binding); } /// public void ResetAction(string actionName) { ArgumentException.ThrowIfNullOrWhiteSpace(actionName); if (_defaultBindings.TryGetValue(actionName, out var bindings)) { _currentBindings[actionName] = [..bindings]; return; } _currentBindings.Remove(actionName); } /// public void ResetAll() { _currentBindings.Clear(); foreach (var pair in _defaultBindings) { _currentBindings[pair.Key] = [..pair.Value]; } } private static Dictionary> ToDictionary(InputBindingSnapshot snapshot) { ArgumentNullException.ThrowIfNull(snapshot); return snapshot.Actions.ToDictionary( static action => action.ActionName, static action => action.Bindings.ToList(), StringComparer.Ordinal); } private static Dictionary> CloneDictionary( IReadOnlyDictionary> source) { return source.ToDictionary( static pair => pair.Key, static pair => pair.Value.ToList(), StringComparer.Ordinal); } private static void RemoveBinding(List bindings, InputBindingDescriptor binding) { bindings.RemoveAll(existing => AreEquivalent(existing, binding)); } private static void ReplaceBinding( List bindings, InputBindingDescriptor bindingToReplace, InputBindingDescriptor? replacement) { var index = bindings.FindIndex(existing => AreEquivalent(existing, bindingToReplace)); if (index < 0) { return; } bindings.RemoveAt(index); if (replacement is not null) { bindings.Insert(index, replacement); } } private static bool AreEquivalent(InputBindingDescriptor left, InputBindingDescriptor right) { return left.DeviceKind == right.DeviceKind && left.BindingKind == right.BindingKind && string.Equals(left.Code, right.Code, StringComparison.Ordinal) && Nullable.Equals(left.AxisDirection, right.AxisDirection); } private List GetOrCreateBindings(string actionName) { if (!_currentBindings.TryGetValue(actionName, out var bindings)) { bindings = []; _currentBindings[actionName] = bindings; } return bindings; } private string? FindOwner(string excludedActionName, InputBindingDescriptor binding) { foreach (var pair in _currentBindings) { if (string.Equals(pair.Key, excludedActionName, StringComparison.Ordinal)) { continue; } if (pair.Value.Any(existing => AreEquivalent(existing, binding))) { return pair.Key; } } return null; } }