BITKit/Src/Unity/Scripts/UX/Service/UXService.cs

257 lines
6.6 KiB
C#

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
{
/// <summary>
/// 适用于Unity的UX Service
/// </summary>
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<IUXPanel> _entryGroup = new();
private readonly AsyncStateMachine<IUXPanel> _windowEntryGroup = new();
/// <summary>
/// 内部注册面板队列
/// </summary>
private readonly Queue<IUXPanel> _registryQueue = new();
/// <summary>
/// 内部注销面板队列
/// </summary>
private readonly Queue<IUXPanel> _unRegistryQueue = new();
/// <summary>
/// 已注册面板字典
/// </summary>
private readonly Dictionary<string, IUXPanel> _panels = new();
/// <summary>
/// 等待启用的面板队列
/// </summary>
private readonly Stack<IUXPanel> _entryQueue = new();
private readonly List<string> _entryQueueByName = new();
/// <summary>
/// 返回面板缓冲区
/// </summary>
private readonly DoubleBuffer<IUXPanel> _returnBuffer = new();
/// <summary>
/// 已启用面板
/// </summary>
private IUXPanel _currentPanel;
/// <summary>
/// 历史面板
/// </summary>
private static readonly Stack<IUXPanel> History = new();
/// <summary>
/// 清空历史面板,通常用于关闭返回上一步
/// </summary>
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<UIDocument>();
try
{
if (Touchscreen.current is not null && SettingsPath == "ux_panel_settings")
{
SettingsPath = "ux_panel_settings_mobile";
}
var panelSettings =await ModService.LoadAsset<PanelSettings>(SettingsPath);
document.panelSettings = panelSettings;
}
catch (Exception e)
{
BIT4Log.Warning<UXService>("未找到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<T>() where T : IUXPanel
{
var panel = _serviceProvider.GetRequiredService<T>();
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<IUXPanel, IUXPanel> 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.IsWindow)
{
_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();
}
}
}