Files
Continentis/Assets/Scripts/ScriptExtensions/CommandQueue/CommandQueueManager.cs
SoulliesOfficial c09dae7783 彻底修好了
2025-11-13 10:42:05 -05:00

159 lines
6.9 KiB
C#
Raw 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 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 ...
}
}