Files
Continentis/Assets/Scripts/SLSUtilities/CommandQueue/CommandQueueManager.cs
SoulliesOfficial ac98ec3aef 更新
2026-04-17 12:01:50 -04:00

135 lines
4.8 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}