using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SHH.ProcessLaunchers
{
///
/// 进程管理器 (核心实现类)
/// 核心职责:作为对外统一入口 (Facade),维护所有受管进程的容器。
/// 主要功能:负责路由外部指令(启动/停止)到具体的进程实例,并处理事件分发。
///
public class ProcessManager : IProcessManager, IDisposable
{
#region --- 1. 字段与事件 (Fields & Events) ---
///
/// 线程安全的进程容器
/// Key: ProcessConfig.Id (唯一标识)
/// Value: ManagedProcess (受管实例)
///
private readonly ConcurrentDictionary _processes
= new ConcurrentDictionary();
///
/// 日志服务接口 (依赖注入)
///
private readonly ILauncherLogger _logger;
// ---------------------------------------------------------
// 对外暴露的事件定义
// ---------------------------------------------------------
///
/// 对外事件:当接收到任意子进程的标准输出/错误流时触发
///
public event EventHandler OnOutputReceived;
///
/// 对外事件:当任意子进程的状态发生变更时触发
///
public event EventHandler OnStateChanged;
#endregion
#region --- 2. 构造与析构 (Constructor & Dispose) ---
///
/// 初始化进程管理器实例
///
/// 日志实现类 (若外部未传入,则内部自动使用 NullLogger 以防止空引用异常)
public ProcessManager(ILauncherLogger logger = null)
{
// 规范化:使用空合并运算符确保 _logger 永不为 null
_logger = logger ?? new NullLogger();
}
///
/// 销毁资源,停止所有进程并清理事件订阅
///
public void Dispose()
{
// 1. 停止所有子进程 (触发 Kill 操作,清理进程树)
StopAll();
// 2. 清空内部容器引用
_processes.Clear();
// 3. 移除所有外部事件订阅,防止 UI 端因未解绑而导致的内存泄露
OnOutputReceived = null;
OnStateChanged = null;
}
#endregion
#region --- 3. 公共 API 实现 (Public Methods) ---
///
/// 注册一个新的进程配置到管理器中
///
/// 进程配置对象 (包含 Exe路径、参数、熔断策略等)
/// 当 Id 为空时抛出
/// 当 Id 已存在时抛出
public void Register(ProcessConfig config)
{
// 1. 基础参数校验:确保 Id 存在
if (string.IsNullOrWhiteSpace(config.Id))
throw new ArgumentException("进程配置无效:必须包含唯一的 Id");
// 2. 防重复注册校验:确保字典中没有相同的 Key
if (_processes.ContainsKey(config.Id))
throw new InvalidOperationException($"进程 Id '{config.Id}' 已存在,禁止重复注册。");
// 3. 实例化受管进程对象 (传入 this 指针是为了后续回调 DispatchXXX 方法)
var process = new ManagedProcess(config, this, _logger);
// 4. 加入线程安全字典
if (_processes.TryAdd(config.Id, process))
{
_logger.LogLifecycle(config.Id, LogAction.Output, LogTrigger.System,
$"进程配置已注册: {config.DisplayName}");
}
}
///
/// 启动指定 ID 的进程
///
/// 进程的唯一标识符 (ProcessConfig.Id)
public void Start(string id)
{
// 尝试获取指定 ID 的进程实例
if (_processes.TryGetValue(id, out var p))
{
// 调用内部实例的启动逻辑,操作归因标记为"User" (用户手动)
p.ExecuteStart(LogTrigger.User, "用户手动启动指令");
}
else
{
// 如果找不到,记录错误日志
_logger.LogLifecycle(id, LogAction.Error, LogTrigger.User, "启动失败:未找到指定 ID 的进程配置");
}
}
///
/// [异步] 有序批量启动所有进程
/// 按照 StartupOrder 从小到大排序启动,并支持启动间隙延时 (PostStartupDelayMs)。
///
/// 异步任务
public async Task StartAllAsync()
{
_logger.LogLifecycle("ALL", LogAction.Start, LogTrigger.User, "执行有序批量启动");
// 1. 数据准备:从字典取出所有进程,并按配置进行排序
// 排序规则:StartupOrder (小->大) -> Id (字母序) 以保证启动顺序的确定性
var sortedList = _processes.Values
.OrderBy(p => p.Config.StartupOrder) // 按用户指定的权重排
.ThenBy(p => p.Config.Id) // 权重一样时按 ID 排
.ToList();
// 2. 顺序执行启动循环
foreach (var p in sortedList)
{
// 同步调用启动指令(注意:这里不等待进程完全 Ready,只负责拉起进程)
p.ExecuteStart(LogTrigger.User, "有序批量启动");
// 3. 处理启动间隙延迟 (错峰启动)
// 作用:防止多个重型进程同时启动导致 CPU/IO 瞬间拥堵
int delay = p.Config.PostStartupDelayMs;
if (delay > 0)
{
// 异步等待指定毫秒数,释放线程控制权
await Task.Delay(delay);
}
}
_logger.LogLifecycle("ALL", LogAction.Start, LogTrigger.User, "有序批量启动完成");
}
///
/// 停止指定 ID 的进程
///
/// 进程的唯一标识符
public void Stop(string id)
{
if (_processes.TryGetValue(id, out var p))
{
p.ExecuteStop(LogTrigger.User, "用户手动停止指令");
}
}
///
/// 批量停止所有进程 (并发执行)
///
public void StopAll()
{
_logger.LogLifecycle("ALL", LogAction.Stop, LogTrigger.User, "执行批量停止");
// 遍历所有进程,使用 Task.Run 并发执行停止,提高效率,无需等待
foreach (var p in _processes.Values)
{
Task.Run(() => p.ExecuteStop(LogTrigger.User, "批量停止"));
}
}
///
/// 重置/复位指定进程的资源报警状态
/// 当用户在 UI 上点击"已处置"后调用此方法,解除报警锁定。
///
/// 进程的唯一标识符
public void ResetGuard(string id)
{
if (_processes.TryGetValue(id, out var p))
{
// 调用内部复位逻辑,清除报警锁定状态
p.ResetGuards();
_logger.LogLifecycle(id, LogAction.ResourceCheck, LogTrigger.User, "用户手动复位资源报警锁");
}
}
///
/// 获取当前所有进程的实时状态快照
/// 用于 UI 列表的数据绑定或定时刷新。
///
/// 进程信息快照列表
public List GetSnapshot()
{
// 将字典中的所有受管对象转为 DTO 快照列表
return _processes.Values.Select(p => p.GetSnapshot()).ToList();
}
#endregion
#region --- 4. 内部事件分发 (Internal Dispatchers) ---
// 说明:C# 的 event 只能在定义类内部 Invoke。
// 为了让内部类 ManagedProcess 也能触发 Manager 的对外事件,我们提供了这几个 internal 方法。
// 这些方法充当了内部类与外部事件之间的桥梁。
///
/// 分发状态变更事件 (供 ManagedProcess 内部调用)
///
/// 进程 ID
/// 新的状态
internal void DispatchStateChange(string processId, ProcessStatus newState)
{
// 线程安全地触发事件
OnStateChanged?.Invoke(this, new ProcessStateEventArgs
{
ProcessId = processId,
State = newState
});
}
///
/// 分发日志输出事件 (供 ManagedProcess 内部调用)
///
/// 进程 ID
/// 日志内容
/// 是否为错误流
internal void DispatchOutput(string processId, string content, bool isError)
{
// 线程安全地触发事件
OnOutputReceived?.Invoke(this, new ProcessOutputEventArgs
{
ProcessId = processId,
Content = content,
IsError = isError
});
}
#endregion
}
}