159 lines
6.9 KiB
C#
159 lines
6.9 KiB
C#
using UniRx;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using UnityEngine;
|
||
|
||
namespace SLSFramework.General
|
||
{
|
||
public class CommandQueueManager : Singleton<CommandQueueManager>
|
||
{
|
||
// --- 新增:游戏状态变化的广播器 ---
|
||
/// <summary>
|
||
/// 一个全局的 Subject,当任何可能影响指令执行条件的状态发生变化时
|
||
/// (例如:角色死亡、抽牌、获得 Buff 等),都应该调用 OnNext。
|
||
/// 正在“等待”的指令会监听这个“心跳”来重新检查它们的条件。
|
||
/// </summary>
|
||
public readonly ISubject<Unit> OnStateChanged = new Subject<Unit>();
|
||
|
||
// 队列的入口现在需要一个能接收 Context 的指令工厂
|
||
// 1. 我们使用一个标准的 Queue<T> 来存储待执行的指令。
|
||
// 这比 Subject 更能明确地表达“队列”的意图。
|
||
private readonly LinkedList<Tuple<CommandBase, CommandContext>> commandQueue = new LinkedList<Tuple<CommandBase, CommandContext>>();
|
||
|
||
// 2. 一个布尔值标志,用于追踪管理器当前是否正在执行一个指令。
|
||
private bool isBusy = false;
|
||
|
||
protected override void Awake()
|
||
{
|
||
// ... 单例模式代码 ...
|
||
base.Awake();
|
||
|
||
commandQueue
|
||
.Select(entry => entry.Item1.Execute(entry.Item2))
|
||
.Concat()
|
||
.Subscribe(
|
||
_ => { /* OnNext */ },
|
||
ex => Debug.LogError($"Command Queue Error: {ex}")
|
||
)
|
||
.AddTo(this);
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
/*
|
||
var context = new CommandContext();
|
||
|
||
// 2. 创建第一个指令组(串行执行)。
|
||
// 这个组会先等待2秒并打印信息,然后设置一个名为 "TestMessage" 的变量。
|
||
var group1 = new CommandGroup(ExecutionMode.Sequential,
|
||
new Cmd_WaitAndLog(2f, "第一个指令组,第一步完成。"),
|
||
new Cmd_SetVariable("TestMessage", "Hello, Command Queue!")
|
||
);
|
||
|
||
var group2 = new CommandGroup(ExecutionMode.Parallel,
|
||
new Cmd_WaitAndLog(1f, "第二个指令组,第一步完成。"),
|
||
new Cmd_WaitAndLog(2f, "第二个指令组,第二步完成。")
|
||
);
|
||
|
||
// 3. 创建第二个指令组。
|
||
// 这个组只有一个任务:读取并打印出 "TestMessage" 变量。
|
||
var group3 = new CommandGroup(ExecutionMode.Parallel,
|
||
new Cmd_WaitAndLog(1f, "第三个指令组,第一步完成。"),
|
||
new Cmd_GetAndLogVariable("TestMessage"),
|
||
new Cmd_WaitAndLog(1f, "第三个指令组,第二步完成。")
|
||
);
|
||
|
||
AddCommand(group1, context); // 传入我们创建的上下文
|
||
AddCommand(group2, context); // 传入完全相同的上下文,这样 group2 才能读取到 group1 设置的变量
|
||
AddCommand(group3, context); // 传入完全相同的上下文,这样 group3 才能读取到 group1 设置的变量
|
||
*/
|
||
}
|
||
|
||
public List<CommandBase> AddCommands(List<CommandBase> command, CommandContext outerContext = null)
|
||
{
|
||
return command.Select(cmd => AddCommand(cmd, outerContext)).ToList();
|
||
}
|
||
|
||
public CommandBase AddCommand(CommandBase command, CommandContext context = null)
|
||
{
|
||
return AddCommand(command, false, context);
|
||
}
|
||
|
||
public CommandBase AddCommand(CommandBase command, bool insertAtFirst, CommandContext context = null)
|
||
{
|
||
context ??= new CommandContext();
|
||
// 将指令和其上下文入队
|
||
if (insertAtFirst || command.insertAtFirst)
|
||
{
|
||
commandQueue.AddFirst(Tuple.Create(command, context));
|
||
}
|
||
else
|
||
{
|
||
commandQueue.AddLast(Tuple.Create(command, context));
|
||
}
|
||
|
||
//Debug.Log($"[Queue] 添加指令: {command.GetType()},队列长度: {commandQueue.Count}");
|
||
// 尝试启动队列处理。
|
||
// 如果队列当前不忙,这个调用会立即开始处理我们刚刚添加的指令。
|
||
// 如果队列正忙,这个调用什么也不做,因为当前指令完成后会自动处理下一个。
|
||
ProcessNextInQueue();
|
||
|
||
return command;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 处理队列中的下一个指令。
|
||
/// 这是整个系统的“引擎”。
|
||
/// </summary>
|
||
private void ProcessNextInQueue()
|
||
{
|
||
// 3. 如果当前正在忙,或者队列已空,则直接返回。
|
||
// 这确保了同一时间永远只有一个指令在执行。
|
||
if (isBusy || commandQueue.Count == 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 4. 标记为“忙碌”,并从队列中取出下一个指令。
|
||
isBusy = true;
|
||
Tuple<CommandBase, CommandContext> nextEntry = commandQueue.First.Value;
|
||
commandQueue.RemoveFirst();
|
||
//Debug.Log($"[Queue] 开始执行指令: {nextEntry.Item1.GetType()},队列剩余长度: {commandQueue.Count}");
|
||
var commandToExecute = nextEntry.Item1;
|
||
var context = nextEntry.Item2;
|
||
|
||
//Debug.Log($"--- [Queue] 开始执行指令: {commandToExecute.GetType()} ---");
|
||
|
||
// 5. 【核心】在这里,我们才真正调用 Execute 方法。
|
||
// 此时,可以保证所有之前的指令都已经彻底完成了。
|
||
commandToExecute.Execute(context)
|
||
.Subscribe(
|
||
_ => { /* OnNext: 忽略 */ },
|
||
ex => {
|
||
// 错误处理:即使出错,也要解锁队列,避免卡死
|
||
Debug.LogError($"[Queue] 指令 {commandToExecute.GetType()} 执行出错: {ex}");
|
||
isBusy = false;
|
||
ProcessNextInQueue(); // 尝试执行下一个
|
||
},
|
||
() => {
|
||
// 6. OnCompleted: 当指令成功完成时执行。
|
||
//Debug.Log($"--- [Queue] 指令 {commandToExecute.GetType()} 执行完毕 ---");
|
||
|
||
// 7. 解除“忙碌”状态,并立即尝试处理队列中的下一个指令。
|
||
// 这就形成了“一个接一个”的链式反应。
|
||
isBusy = false;
|
||
ProcessNextInQueue();
|
||
}
|
||
).AddTo(this); // 确保订阅在对象销毁时被清理
|
||
}
|
||
|
||
public void NotifyStateChanged()
|
||
{
|
||
OnStateChanged.OnNext(Unit.Default);
|
||
}
|
||
|
||
// ... OnDestroy ...
|
||
}
|
||
} |