using UniRx; using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace SLSFramework.General { public class CommandQueueManager : Singleton { // --- 新增:游戏状态变化的广播器 --- /// /// 一个全局的 Subject,当任何可能影响指令执行条件的状态发生变化时 /// (例如:角色死亡、抽牌、获得 Buff 等),都应该调用 OnNext。 /// 正在“等待”的指令会监听这个“心跳”来重新检查它们的条件。 /// public readonly ISubject OnStateChanged = new Subject(); // 队列的入口现在需要一个能接收 Context 的指令工厂 // 1. 我们使用一个标准的 Queue 来存储待执行的指令。 // 这比 Subject 更能明确地表达“队列”的意图。 private readonly LinkedList> commandQueue = new LinkedList>(); // 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 AddCommands(List 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; } /// /// 处理队列中的下一个指令。 /// 这是整个系统的“引擎”。 /// private void ProcessNextInQueue() { // 3. 如果当前正在忙,或者队列已空,则直接返回。 // 这确保了同一时间永远只有一个指令在执行。 if (isBusy || commandQueue.Count == 0) { return; } // 4. 标记为“忙碌”,并从队列中取出下一个指令。 isBusy = true; Tuple 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 ... } }