using System; using System.Collections.Generic; using System.Text; using System.Threading; using BITKit.Mod; using BITKit.StateMachine; using Cysharp.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Unity.Mathematics; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace BITKit.UX { /// /// 适用于Unity的UX Service /// public class UXService : IUXService { private readonly IAfterTicker _ticker; private readonly IServiceProvider _serviceProvider; private readonly CancellationTokenSource _cancellationTokenSource; public UXService(IAfterTicker ticker, IServiceProvider serviceProvider, CancellationTokenSource cancellationTokenSource) { _ticker = ticker; _serviceProvider = serviceProvider; _cancellationTokenSource = cancellationTokenSource; _entryGroup.OnStateChanged += OnEntry; _windowEntryGroup.OnStateChanged += OnWindowEntry; _ticker.Add(OnTick); } private void OnWindowEntry(IUXPanel prev, IUXPanel next) { BITAppForUnity.AllowCursor.SetElements(_windowEntryGroup, next is { AllowCursor: true }); BITInputSystem.AllowInput.SetDisableElements(_windowEntryGroup, next is { AllowInput: false }); } private readonly AsyncStateMachine _entryGroup = new(); private readonly AsyncStateMachine _windowEntryGroup = new(); /// /// 内部注册面板队列 /// private readonly Queue _registryQueue = new(); /// /// 内部注销面板队列 /// private readonly Queue _unRegistryQueue = new(); /// /// 已注册面板字典 /// private readonly Dictionary _panels = new(); /// /// 等待启用的面板队列 /// private readonly Stack _entryQueue = new(); private readonly List _entryQueueByName = new(); /// /// 返回面板缓冲区 /// private readonly DoubleBuffer _returnBuffer = new(); /// /// 已启用面板 /// private IUXPanel _currentPanel; /// /// 历史面板 /// private static readonly Stack History = new(); /// /// 清空历史面板,通常用于关闭返回上一步 /// public static void ClearHistory() => History.Clear(); public string SettingsPath { get; set; } = "ux_panel_settings"; public object Root { get; private set; } public static VisualElement RootVisualElement { get; private set; } public async UniTask InitializeAsync() { var gameObject = new GameObject("UXService"); Object.DontDestroyOnLoad(gameObject); _cancellationTokenSource.Token.Register(() => { Object.Destroy(gameObject); }); var document = gameObject.AddComponent(); try { if (Touchscreen.current is not null && SettingsPath == "ux_panel_settings") { SettingsPath = "ux_panel_settings_mobile"; } var panelSettings =await ModService.LoadAsset(SettingsPath); document.panelSettings = panelSettings; } catch (Exception e) { BIT4Log.Warning("未找到ux_panel_settings"); throw; } Root = RootVisualElement= document.rootVisualElement; if (Touchscreen.current is not null) { RootVisualElement.AddToClassList("mobile"); } } public void Register(IUXPanel panel) => _registryQueue.Enqueue(panel); public void UnRegister(IUXPanel panel) => _unRegistryQueue.Enqueue(panel); public void Entry() where T : IUXPanel { var panel = _serviceProvider.GetRequiredService(); Entry(panel); //Entry(typeof(T).Name); } public void Entry(IUXPanel panel) => _entryQueue.Push(panel); public void Entry(string panelName) => _entryQueueByName.TryAdd(panelName); public IUXPanel CurrentPanel => _currentPanel; public event Action OnPanelChanged; public bool TryPick(float2 position, out object obj) { obj = null; if (!EventSystem.current.IsPointerOverGameObject()) { return false; } position.y = Screen.height - position.y; var ve = RootVisualElement.panel.Pick(RuntimePanelUtils.ScreenToPanel(RootVisualElement.panel, position)); obj = ve; return obj is not null; } public void Return() { if (_windowEntryGroup.CurrentState is not null) { _windowEntryGroup.DisposeState(); return; } if (History.TryPop(out var returnPanel)) { _returnBuffer.Release(returnPanel); } } private bool _initialized; private void OnEntry(IUXPanel prev,IUXPanel next) { OnPanelChanged?.Invoke(_currentPanel,next); _currentPanel = next; } private void OnTick(float delta) { try { while (_registryQueue.TryDequeue(out var result)) { if (result is null) continue; _entryGroup.Register(result); _panels.Set(result.Index, result); } while (_unRegistryQueue.TryDequeue(out var result)) { if (result is null) continue; _entryGroup.UnRegister(result); _panels.Remove(result.Index); } if (_returnBuffer.TryGetRelease(out var returnPanel)) { _entryGroup.TransitionState(returnPanel); BITAppForUnity.AllowCursor.SetElements(this, returnPanel.AllowCursor); BITInputSystem.AllowInput.SetElements(this, returnPanel.AllowInput); } foreach (var panelName in _entryQueueByName) { if (!_panels.TryGetValue(panelName, out var panel))continue; _entryQueue.Push(panel); _entryQueueByName.TryRemove(panelName); break; } if (_entryQueue.TryPop(out var nextPanel)) { if (nextPanel is {IsWindow:true}) { _windowEntryGroup.TransitionState(nextPanel); return; } _windowEntryGroup.DisposeState(); History.Push(_currentPanel); _entryGroup.TransitionState(nextPanel); BITAppForUnity.AllowCursor.SetElements(this, nextPanel.AllowCursor); BITInputSystem.AllowInput.SetElements(this, nextPanel.AllowInput); } if (_entryGroup.CurrentState is {Enabled:true}) { _entryGroup.CurrentState.OnTick(delta); } if (_windowEntryGroup.CurrentState is {Enabled:true}) { _windowEntryGroup.CurrentState.OnTick(delta); } } catch (Exception e) { BIT4Log.LogException(e); } } public async void Dispose() { foreach (var panelsValue in _panels.Values) { if (panelsValue is IDisposable disposable) { disposable.Dispose(); } } _ticker.Remove(OnTick); await UniTask.SwitchToMainThread(); _entryGroup.Dispose(); _windowEntryGroup.Dispose(); } } }