135 lines
4.8 KiB
C#
135 lines
4.8 KiB
C#
using System.Collections.Generic;
|
||
using Cysharp.Threading.Tasks;
|
||
using UnityEngine;
|
||
|
||
namespace SLSUtilities.General
|
||
{
|
||
/// <summary>
|
||
/// 全局命令队列管理器。
|
||
/// 内部按 <see cref="CommandLane"/> 优先级分桶,同桶内保持 FIFO 顺序。
|
||
/// 使用 <see cref="AddCommand"/> 将命令加入队列;队列空闲时自动休眠,有新命令时唤醒。
|
||
/// </summary>
|
||
public class CommandQueueManager : Singleton<CommandQueueManager>
|
||
{
|
||
// 每个 Lane 独立维护一个 FIFO 队列,key 数值越小优先级越高
|
||
private readonly SortedDictionary<CommandLane, Queue<CommandBase>> lanes =
|
||
new SortedDictionary<CommandLane, Queue<CommandBase>>();
|
||
|
||
// 全局 outerContext,在整个战斗生命周期内共享
|
||
private readonly CommandContext globalContext = new CommandContext();
|
||
|
||
private bool isLoopRunning = false;
|
||
|
||
/// <summary>当前是否有命令正在执行或等待执行。</summary>
|
||
public bool IsBusy => HasPending || isProcessing;
|
||
|
||
private bool isProcessing = false;
|
||
|
||
private bool HasPending
|
||
{
|
||
get
|
||
{
|
||
foreach (var queue in lanes.Values)
|
||
if (queue.Count > 0) return true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
protected override void Awake()
|
||
{
|
||
base.Awake();
|
||
|
||
// 预建所有 Lane 的队列,避免运行时动态创建
|
||
foreach (CommandLane lane in System.Enum.GetValues(typeof(CommandLane)))
|
||
lanes[lane] = new Queue<CommandBase>();
|
||
|
||
StartProcessLoop().Forget();
|
||
}
|
||
|
||
// ── 公开 API ─────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 将命令追加到指定 Lane 的队列末尾。
|
||
/// 默认 Lane 为 <see cref="CommandLane.Main"/>,Mod 制作者通常不需要指定 Lane。
|
||
/// </summary>
|
||
public CommandBase AddCommand(CommandBase command, CommandLane lane = CommandLane.Main)
|
||
{
|
||
if (command == null) return null;
|
||
lanes[lane].Enqueue(command);
|
||
return command;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将多条命令批量追加到指定 Lane。
|
||
/// </summary>
|
||
public void AddCommands(IEnumerable<CommandBase> commands, CommandLane lane = CommandLane.Main)
|
||
{
|
||
if (commands == null) return;
|
||
foreach (var cmd in commands)
|
||
AddCommand(cmd, lane);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将命令插入到 <see cref="CommandLane.Interrupt"/> Lane 的队首(最高优先级)。
|
||
/// 仅供框架内部使用(死亡判定、强制打断等)。
|
||
/// </summary>
|
||
public CommandBase AddCommandNext(CommandBase command)
|
||
{
|
||
if (command == null) return null;
|
||
|
||
// Interrupt Lane 的队列本身是 FIFO,但它的整体优先级最高,
|
||
// 因此直接 Enqueue 到 Interrupt 即可保证在 Main 之前执行。
|
||
lanes[CommandLane.Interrupt].Enqueue(command);
|
||
return command;
|
||
}
|
||
|
||
/// <summary>等待直到队列完全空闲(所有 Lane 均无待执行命令)。</summary>
|
||
public async UniTask WaitUntilIdle()
|
||
{
|
||
await UniTask.WaitUntil(() => !IsBusy);
|
||
}
|
||
|
||
// ── 内部处理循环 ─────────────────────────────────────────────────
|
||
|
||
private async UniTaskVoid StartProcessLoop()
|
||
{
|
||
if (isLoopRunning) return;
|
||
isLoopRunning = true;
|
||
|
||
while (true)
|
||
{
|
||
// 队列为空时休眠,避免空转
|
||
await UniTask.WaitUntil(() => HasPending);
|
||
|
||
CommandBase next = DequeueHighestPriority();
|
||
if (next == null) continue;
|
||
|
||
isProcessing = true;
|
||
try
|
||
{
|
||
await next.RunAsync(globalContext);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"[CommandQueue] 命令 {next.GetType().Name} 执行出错:{ex}");
|
||
}
|
||
finally
|
||
{
|
||
isProcessing = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>从优先级最高的非空 Lane 取出队首命令。</summary>
|
||
private CommandBase DequeueHighestPriority()
|
||
{
|
||
foreach (var queue in lanes.Values)
|
||
{
|
||
if (queue.Count > 0)
|
||
return queue.Dequeue();
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
}
|