using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using TeamAAS.FlowEditor.Execution;
using TeamAAS.FlowEditor.Models;
using TeamAAS.FlowEditor.ProductModels;
using TeamAAS.FlowEngine.Execution;
using TeamAAS.Communication;
using TeamAAS;
namespace TeamAAS.FlowEditor
{
///
/// 产品与流程文件的持久化管理器。
/// 一个产品可以包含多个流程文件,当前编辑产品和实际运行产品分别维护,
/// 以支持编辑态与运行态的切换。
/// 相关数据模型(ProductMeta / HomeFrameItem / FrameArrangeMode / FlowMeta)
/// 见 TeamAAS.FlowEditor.ProductModels 命名空间。
///
public class ProductManager
{
private static ProductManager _instance;
public static ProductManager Instance => _instance ?? (_instance = new ProductManager());
///
/// 产品列表发生变化(新增/删除/重命名)时触发,供首页等订阅方刷新产品型号下拉。
///
public event Action ProductListChanged;
private void RaiseProductListChanged()
{
try { ProductListChanged?.Invoke(); } catch { }
}
/// Products 根目录(统一由 PathHelper 管理,跟随 PathHelper.Root)。
public static string ProductsPath => PathHelper.ProductsDir;
/// 流程文件统一后缀(导入/导出与每个流程存储都用它)。
public const string FlowExtension = ".aas";
public string CurrentProductName { get; private set; } = "";
private string _runProductName = "";
public string RunProductName
{
get => _runProductName;
private set => _runProductName = value;
}
public bool IsEditMode => CurrentProductName != RunProductName;
/// 当前编辑器外壳(切换产品时登记;FlowRunner 用它判断是否与编辑态共享流程图)
public FlowEditorShellViewModel ActiveEditorShell { get; private set; }
///
/// 编辑器切换产品时要显示的流程列表:始终从磁盘加载编辑副本。
/// 监控模式(FlowEditorShellViewModel.IsMonitorMode=true)由 Shell 主动挂接
/// FlowRunner.RunTabs,不再由这里隐式挂接,避免编辑态被运行态覆盖。
///
private List LoadEditorTabs(string productName)
{
return LoadAllFlows(productName);
}
public string GetProductPath(string productName) => Path.Combine(ProductsPath, productName);
public string GetProductMetaPath(string productName) =>
Path.Combine(ProductsPath, productName, "product.json");
public ProductMeta LoadProductMeta(string productName)
{
try
{
var path = GetProductMetaPath(productName);
if (File.Exists(path))
{
var json = File.ReadAllText(path);
var meta = JsonConvert.DeserializeObject(json);
if (meta != null)
{
meta.Name = productName; // 目录名即产品名(防历史 json 里名称不一致)
meta.MigrateLegacyFrames(); // 旧版画面配置自动迁移为画面列表
return meta;
}
}
}
catch { }
return new ProductMeta { Name = productName };
}
public void SaveProductMeta(ProductMeta meta)
{
try
{
if (string.IsNullOrWhiteSpace(meta.Name)) return;
var dir = Path.Combine(ProductsPath, meta.Name);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
var path = GetProductMetaPath(meta.Name);
// 旧版画面字段已迁移进 HomeFrames,置空避免再次触发迁移
meta.HomeFrameCount = 0;
meta.HomeFrameNames = "";
meta.HomeFrameShowDot = "";
var json = JsonConvert.SerializeObject(meta, Formatting.Indented);
File.WriteAllText(path, json);
}
catch { }
}
public List GetProductList()
{
var list = new List();
try
{
if (Directory.Exists(ProductsPath))
{
list = Directory.GetDirectories(ProductsPath)
.Select(Path.GetFileName)
.OrderBy(n => n)
.ToList();
}
}
catch { }
return list;
}
public List GetProductMetaList()
{
var result = new List();
try
{
if (Directory.Exists(ProductsPath))
{
foreach (var dir in Directory.GetDirectories(ProductsPath))
{
var name = Path.GetFileName(dir);
var meta = LoadProductMeta(name);
if (meta == null) meta = new ProductMeta { Name = name };
result.Add(meta);
}
result = result.OrderBy(m => m.Sequence).ThenBy(m => m.Name).ToList();
}
}
catch { }
return result;
}
public ProductMeta GetProductBySequence(int sequence)
{
return GetProductMetaList().FirstOrDefault(m => m.Sequence == sequence);
}
public bool CreateProduct(string productName, int sequence = 0)
{
if (string.IsNullOrWhiteSpace(productName)) return false;
try
{
string safeName = SanitizeName(productName);
string path = Path.Combine(ProductsPath, safeName);
if (Directory.Exists(path)) return false;
Directory.CreateDirectory(path);
var meta = new ProductMeta { Name = safeName, Sequence = sequence };
SaveProductMeta(meta);
RaiseProductListChanged();
return true;
}
catch { return false; }
}
public bool DeleteProduct(string productName)
{
if (string.IsNullOrWhiteSpace(productName)) return false;
try
{
string path = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(path)) return false;
Directory.Delete(path, true);
if (CurrentProductName == productName)
CurrentProductName = "";
if (RunProductName == productName)
RunProductName = "";
RaiseProductListChanged();
return true;
}
catch { return false; }
}
public bool RenameProduct(string oldName, string newName)
{
if (string.IsNullOrWhiteSpace(oldName) || string.IsNullOrWhiteSpace(newName)) return false;
try
{
string safeNew = SanitizeName(newName);
string oldPath = Path.Combine(ProductsPath, oldName);
string newPath = Path.Combine(ProductsPath, safeNew);
if (!Directory.Exists(oldPath)) return false;
if (Directory.Exists(newPath)) return false;
Directory.Move(oldPath, newPath);
var oldMeta = LoadProductMeta(oldName);
if (oldMeta != null)
{
oldMeta.Name = safeNew;
SaveProductMeta(oldMeta);
}
if (CurrentProductName == oldName)
CurrentProductName = safeNew;
if (RunProductName == oldName)
RunProductName = safeNew;
RaiseProductListChanged();
return true;
}
catch { return false; }
}
public bool SetProductSequence(string productName, int sequence)
{
var meta = LoadProductMeta(productName);
if (meta == null) return false;
meta.Sequence = sequence;
SaveProductMeta(meta);
return true;
}
/// 主页画面配置变化(当前产品的画面数/排列/名称被修改)通知
public event Action HomeFrameConfigChanged;
/// 当前编辑产品的画面配置(读不到产品时返回默认值;不落盘)
public ProductMeta GetHomeFrameConfig(string productName)
{
var meta = string.IsNullOrEmpty(productName) ? null : LoadProductMeta(productName);
return meta ?? new ProductMeta { Name = productName ?? "" };
}
/// 保存画面配置到产品并广播变化(首页按新配置重建布局)
public bool SaveHomeFrameConfig(ProductMeta meta)
{
if (meta == null || string.IsNullOrWhiteSpace(meta.Name)) return false;
SaveProductMeta(meta);
HomeFrameConfigChanged?.Invoke();
return true;
}
public List GetFlowFiles(string productName)
{
var list = new List();
try
{
string path = Path.Combine(ProductsPath, productName);
if (Directory.Exists(path))
{
var flowNames = new HashSet(StringComparer.OrdinalIgnoreCase);
foreach (var f in Directory.GetFiles(path, "*" + FlowExtension))
flowNames.Add(Path.GetFileNameWithoutExtension(f));
list = flowNames.OrderBy(n => n).ToList();
}
}
catch { }
return list;
}
///
/// 产品流程文件的变更指纹(文件数 + 最新修改时间Ticks),用于判断运行态流程是否需要从磁盘重载,
/// 避免高频触发时每次都反序列化。涵盖 .aas 流程文件与 flows.meta.json。
///
public string GetProductFlowStamp(string productName)
{
try
{
if (string.IsNullOrWhiteSpace(productName)) return "";
var dir = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(dir)) return "";
long maxTicks = 0;
int count = 0;
foreach (var f in Directory.GetFiles(dir))
{
var name = Path.GetFileName(f);
var ext = Path.GetExtension(f);
bool isFlow = ext.Equals(FlowExtension, StringComparison.OrdinalIgnoreCase);
bool isMeta = name.Equals("flows.meta.json", StringComparison.OrdinalIgnoreCase);
if (!isFlow && !isMeta) continue;
count++;
var t = File.GetLastWriteTimeUtc(f).Ticks;
if (t > maxTicks) maxTicks = t;
}
return count.ToString() + "|" + maxTicks.ToString();
}
catch { return ""; }
}
public string GetFlowMetaPath(string productName) =>
Path.Combine(ProductsPath, productName, "flows.meta.json");
///
/// 把所有流程 Tab 的元数据(触发类型/循环次数/画布尺寸)写入 flows.meta.json。
/// 在 SaveAllFlows / SaveFlow 末尾调用,保证 Tab 属性变化能持久化。
///
public void SaveFlowMetaMap(string productName, IEnumerable tabs)
{
// 空产品名会让 Path.Combine(ProductsPath, "", "flows.meta.json") 退化为 Products/flows.meta.json
// (根目录凭空多一份),必须在写文件前拦截。
if (string.IsNullOrWhiteSpace(productName)) return;
try
{
var dir = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
var list = new List();
foreach (var tab in tabs ?? Enumerable.Empty())
{
var meta = new FlowMeta
{
Name = tab.Name,
TriggerType = (int)tab.TriggerType,
LoopCount = tab.LoopCount,
CanvasWidth = tab.CanvasWidth,
CanvasHeight = tab.CanvasHeight,
LoopIntervalMs = tab.LoopIntervalMs
};
list.Add(meta);
// 调试日志:记录保存时的 TriggerType 值
AppLogger.Info($"SaveFlowMeta: {tab.Name} TriggerType={tab.TriggerType} (int={(int)tab.TriggerType})", nameof(ProductManager));
}
var json = JsonConvert.SerializeObject(list, Formatting.Indented);
File.WriteAllText(GetFlowMetaPath(productName), json);
AppLogger.Info($"SaveFlowMeta: wrote {list.Count} entries to {GetFlowMetaPath(productName)}", nameof(ProductManager));
}
catch (Exception ex) { AppLogger.Error("SaveFlowMeta 异常", ex, nameof(ProductManager)); }
}
///
/// 读取 flows.meta.json,按 Name 索引返回字典。文件不存在返回空字典(兼容老产品目录)。
///
public Dictionary LoadFlowMetaMap(string productName)
{
var map = new Dictionary(StringComparer.Ordinal);
if (string.IsNullOrWhiteSpace(productName)) return map; // 空名会误读 Products 根目录的残留文件
try
{
var path = GetFlowMetaPath(productName);
if (!File.Exists(path))
{
AppLogger.Info($"LoadFlowMeta: 文件不存在 {path}", nameof(ProductManager));
return map;
}
var json = File.ReadAllText(path);
var list = JsonConvert.DeserializeObject>(json);
if (list != null)
{
foreach (var m in list)
{
if (!string.IsNullOrEmpty(m?.Name))
{
map[m.Name] = m;
AppLogger.Info($"LoadFlowMeta: {m.Name} TriggerType={m.TriggerType} (int={m.TriggerType})", nameof(ProductManager));
}
}
}
AppLogger.Info($"LoadFlowMeta: loaded {map.Count} entries from {path}", nameof(ProductManager));
}
catch (Exception ex) { AppLogger.Error("LoadFlowMeta 异常", ex, nameof(ProductManager)); }
return map;
}
public bool SaveFlow(string productName, FlowTabItem tab)
{
if (string.IsNullOrWhiteSpace(productName) || tab == null) return false;
try
{
string dir = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
string filePath = Path.Combine(dir, tab.Name + FlowExtension); // 统一 .aas
bool ok = tab.EditorVm?.ExportFlow(tab.Name, filePath) ?? false;
if (ok)
{
SaveFlowMetaSingle(productName, tab); // 同步更新单条流程元数据
}
return ok;
}
catch { return false; }
}
///
/// 单条流程保存时更新其元数据:读现有 flows.meta.json,按 Name 替换/添加后写回。
///
public void SaveFlowMetaSingle(string productName, FlowTabItem tab)
{
if (tab == null) return;
try
{
var map = LoadFlowMetaMap(productName);
map[tab.Name] = new FlowMeta
{
Name = tab.Name,
TriggerType = (int)tab.TriggerType,
LoopCount = tab.LoopCount,
CanvasWidth = tab.CanvasWidth,
CanvasHeight = tab.CanvasHeight,
LoopIntervalMs = tab.LoopIntervalMs
};
var dir = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
var json = JsonConvert.SerializeObject(map.Values.ToList(), Formatting.Indented);
File.WriteAllText(GetFlowMetaPath(productName), json);
}
catch { }
}
public bool SaveAllFlows(string productName, IEnumerable tabs)
{
if (string.IsNullOrWhiteSpace(productName) || tabs == null) return false;
try
{
string dir = Path.Combine(ProductsPath, productName);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
var tabNames = new HashSet(tabs.Select(t => t.Name), StringComparer.OrdinalIgnoreCase);
foreach (var tab in tabs)
{
string filePath = Path.Combine(dir, tab.Name + FlowExtension); // 统一 .aas
tab.EditorVm?.ExportFlow(tab.Name, filePath);
}
// 删除磁盘上已不存在于 Tab 列表中的流程文件
// (例如用户删掉了"流程2"后点保存,切换产品再切回来就不会复活了)
foreach (var file in Directory.GetFiles(dir, "*" + FlowExtension))
{
string name = Path.GetFileNameWithoutExtension(file);
if (!tabNames.Contains(name))
{
try { File.Delete(file); }
catch { /* 文件被占用等异常忽略,下次保存再清理 */ }
}
}
// 同步写入流程 Tab 元数据(触发类型/循环次数/画布尺寸)
SaveFlowMetaMap(productName, tabs);
// 同步写入该产品的全局变量快照(Product 作用域变量值)
GlobalVariableManager.Instance.SaveSnapshot(GetGlobalVarsPath(productName));
return true;
}
catch { return false; }
}
public FlowTabItem LoadFlow(string productName, string flowName)
=> LoadFlow(productName, flowName, null, null);
public FlowTabItem LoadFlow(string productName, string flowName, FlowMeta meta, ResultRegistry registry = null)
{
if (string.IsNullOrWhiteSpace(productName) || string.IsNullOrWhiteSpace(flowName)) return null;
try
{
string filePath = Path.Combine(ProductsPath, productName, flowName + FlowExtension); // 统一 .aas
if (!File.Exists(filePath)) return null;
var tab = new FlowTabItem(flowName);
// 应用磁盘上保存的 Tab 元数据(触发类型/循环次数/画布尺寸/循环间隔);meta 为 null 时用默认值
if (meta != null)
{
tab.TriggerType = (TriggerType)meta.TriggerType;
tab.LoopCount = meta.LoopCount;
tab.CanvasWidth = meta.CanvasWidth;
tab.CanvasHeight = meta.CanvasHeight;
tab.LoopIntervalMs = meta.LoopIntervalMs;
}
FlowGraph result = FlowFileStore.Load(filePath);
if (result != null && tab.EditorVm != null)
{
var graph = tab.EditorVm.Graph;
// 绑定结果注册表:运行态=Main,编辑/调试态=Debug
graph.Registry = registry ?? ResultRegistry.Debug;
graph.Registry.ClearFlow(graph.GraphId);
result.GraphId = graph.GraphId;
result.GraphName = graph.GraphName;
var idMap = new Dictionary();
foreach (var item in result.Nodes)
{
string oldNodeId = item.NodeId;
item.InitializeNode(graph.GraphId, graph.GraphName, graph.Registry);
if (!string.IsNullOrEmpty(oldNodeId))
idMap[oldNodeId] = item.NodeId;
}
if (result.Connections != null && idMap.Count > 0)
{
foreach (var conn in result.Connections)
{
if (idMap.TryGetValue(conn.SourceNodeId, out var newSrcId))
conn.SourceNodeId = newSrcId;
if (idMap.TryGetValue(conn.TargetNodeId, out var newTgtId))
conn.TargetNodeId = newTgtId;
}
}
graph.Nodes.Clear();
foreach (var n in result.Nodes)
graph.Nodes.Add(n);
graph.Connections.Clear();
foreach (var c in result.Connections)
graph.Connections.Add(c);
tab.EditorVm.GraphDataChanged?.Invoke(graph, null);
tab.EditorVm?.MarkClean(); // 刚加载的内容视为"已保存"状态
}
return tab;
}
catch { return null; }
}
public List LoadAllFlows(string productName, ResultRegistry registry = null)
{
var tabs = new List();
var flowFiles = GetFlowFiles(productName);
// 读一次元数据字典,按 Name 应用到对应 Tab(兼容老产品目录:无文件则全用默认值)
var metaMap = LoadFlowMetaMap(productName);
foreach (var flowName in flowFiles)
{
metaMap.TryGetValue(flowName, out var meta);
if (meta != null)
AppLogger.Info($"LoadAllFlows: {flowName} 找到 meta,TriggerType={meta.TriggerType}", nameof(ProductManager));
else
AppLogger.Info($"LoadAllFlows: {flowName} 未找到 meta,使用默认值", nameof(ProductManager));
var tab = LoadFlow(productName, flowName, meta, registry);
if (tab != null)
{
AppLogger.Info($"LoadAllFlows: {flowName} 加载完成,TriggerType={tab.TriggerType}", nameof(ProductManager));
tabs.Add(tab);
}
}
return tabs;
}
public string GetGlobalVarsPath(string productName) =>
Path.Combine(ProductsPath, productName, "global_vars.json");
///
/// 当前内存里的全局变量归属哪个产品。加载快照/切换产品时更新。
/// 内存变量是进程级共享的,编辑态与运行态都可能改它,
/// 只按 CurrentProductName 存会存错产品(例如主页已切到运行产品但编辑态还停在另一个)。
///
public string GlobalVarsOwnerProduct { get; private set; } = "";
///
/// 保存“当前归属产品”的全局变量快照。
/// 产品切换前、以及设置页手动保存时调用;无归属产品时回退到编辑态当前产品。
///
public bool SaveGlobalVarsForOwner()
{
var owner = !string.IsNullOrEmpty(GlobalVarsOwnerProduct) ? GlobalVarsOwnerProduct : CurrentProductName;
return SaveGlobalVarsFor(owner);
}
///
/// 把 Product 作用域变量保存到指定产品的快照文件(Products\<名>\global_vars.json)。
/// 产品切换前、以及产品变量弹窗点确定时调用。
///
public bool SaveGlobalVarsFor(string productName)
{
if (string.IsNullOrEmpty(productName)) return false;
try
{
GlobalVariableManager.Instance.SaveSnapshot(GetGlobalVarsPath(productName));
GlobalVarsOwnerProduct = productName;
return true;
}
catch { return false; }
}
///
/// 从目标产品快照加载 Product 作用域变量到 GlobalVariableManager(Global 子集不受影响)。
/// 快照不存在时清空 Product 子集(切到新产品应从空开始,而非继承上个产品的变量)。
/// Variables 是 ObservableCollection 绑定 UI,必须在 UI 线程调用。
///
public bool LoadProductGlobalVars(string productName)
{
if (string.IsNullOrEmpty(productName)) return false;
try
{
GlobalVarsOwnerProduct = productName;
return GlobalVariableManager.Instance.LoadSnapshot(GetGlobalVarsPath(productName));
}
catch { return false; }
}
///
/// 释放一组 FlowTab 持有的资源:节点插件、结果字典、图节点引用。
/// 切换产品 / 卸载运行产品 / 清理编辑态时统一调用。
/// 安全保证:跳过仍被 FlowRunner.RunTabs 持有的运行态 Tab——
/// 监控模式下编辑器的 FlowTabs 挂的就是这批对象,若在此清空会导致
/// 切换产品时正在运行的流程画布变空、主页面运行也被打断。
///
internal static void DisposeFlowTabs(IEnumerable tabs)
{
if (tabs == null) return;
var runTabs = FlowRunner.Instance.RunTabs;
foreach (var tab in tabs)
{
if (tab == null) continue;
if (runTabs != null && runTabs.Contains(tab)) continue;
// 1. 释放节点插件(相机句柄、连接流、大对象等)
if (tab.Graph != null)
{
foreach (var node in tab.Graph.Nodes)
{
try { node.PluginModel?.Dispose(); }
catch { /* 单个插件释放失败不影响整体 */ }
try
{
node.ResultItems?.Clear();
node.ExecutionHistory?.Clear();
}
catch { }
}
// 2. 清掉该流程绑定的 ResultRegistry 实例中本流程的全部结果
// (运行态图 → Main,编辑/调试态图 → Debug,互不干扰)
if (!string.IsNullOrEmpty(tab.Graph.GraphId))
tab.Graph.Registry?.ClearFlow(tab.Graph.GraphId);
tab.Graph.Nodes.Clear();
tab.Graph.Connections.Clear();
}
// 3. 清 EditorVm 引用
if (tab.EditorVm != null)
{
try { tab.EditorVm.SelectedNode = null; } catch { }
}
}
// 释放完成后强制全代 GC,确保插件引用的大对象被回收
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced, true);
}
///
/// 编辑模式:切换产品进行编辑(不影响主程序运行)。
/// 注意:不再自动保存——是否保存由调用方(ProductViewModel)根据脏标记提示用户确认。
///
public void SwitchProduct(string productName, FlowEditorShellViewModel shellVm)
{
if (shellVm == null) return;
// 切换前:保存当前产品的全局变量快照和流程元数据
SaveGlobalVarsForOwner();
SaveFlowMetaMap(CurrentProductName, shellVm.FlowTabs);
// 释放旧产品的资源(节点插件 Dispose + ResultRegistry 清理 + 图引用解除)
DisposeFlowTabs(shellVm.FlowTabs);
// 切换编辑产品:完全清空 Debug(含上一产品各流程与子流程残留),避免跨产品数据堆积
ResultRegistry.Debug.ClearAllFlows();
shellVm.FlowTabs.Clear();
CurrentProductName = productName;
ActiveEditorShell = shellVm;
if (!string.IsNullOrEmpty(productName))
{
var flows = LoadEditorTabs(productName);
foreach (var flow in flows)
{
shellVm.FlowTabs.Add(flow);
}
}
if (shellVm.FlowTabs.Count == 0)
{
var defaultTab = new FlowTabItem("流程1");
shellVm.FlowTabs.Add(defaultTab);
}
shellVm.ActiveTab = shellVm.FlowTabs.FirstOrDefault();
// 加载目标产品的全局变量快照(Variables 是 ObservableCollection,UI 线程同步修改)
LoadProductGlobalVars(productName);
// 切换产品后刷新监控选项可用性 + 自动进入/退出监控(运行产品且运行中→默认监控)
shellVm.ApplyMonitorAvailability();
// 强制全代 GC + 等待终结器,确保 LOH 大对象(图像/位图源/CogRecord 等)被回收
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced, true);
}
///
/// 编辑模式:切换产品(异步版)。文件 IO(加载新产品)在后台线程执行,
/// FlowTabs 等绑定 UI 的集合变更始终留在调用线程(UI 线程)。
/// 不自动保存;是否保存由调用方在切换前询问用户。
///
public async System.Threading.Tasks.Task SwitchProductAsync(string productName, FlowEditorShellViewModel shellVm)
{
if (shellVm == null) return;
// 切换前:在 UI 线程保存当前产品全局变量快照和流程元数据
SaveGlobalVarsForOwner();
SaveFlowMetaMap(CurrentProductName, shellVm.FlowTabs);
// 释放旧产品的资源(节点插件 Dispose + ResultRegistry 清理 + 图引用解除)
DisposeFlowTabs(shellVm.FlowTabs);
// 切换编辑产品:完全清空 Debug(含上一产品各流程与子流程残留),避免跨产品数据堆积
ResultRegistry.Debug.ClearAllFlows();
shellVm.FlowTabs.Clear();
// 后台线程:解析目标产品流程(始终读盘,监控挂接由 Shell 主动控制)
List flows = null;
await System.Threading.Tasks.Task.Run(() =>
{
if (!string.IsNullOrEmpty(productName))
flows = LoadEditorTabs(productName);
});
// 调用线程(UI):应用集合变更
shellVm.FlowTabs.Clear();
CurrentProductName = productName;
ActiveEditorShell = shellVm;
if (flows != null)
{
foreach (var flow in flows)
shellVm.FlowTabs.Add(flow);
}
if (shellVm.FlowTabs.Count == 0)
shellVm.FlowTabs.Add(new FlowTabItem("流程1"));
shellVm.ActiveTab = shellVm.FlowTabs.FirstOrDefault();
// UI 线程:加载目标产品全局变量快照
LoadProductGlobalVars(productName);
// 切换产品后刷新监控选项可用性 + 自动进入/退出监控(运行产品且运行中→默认监控)
shellVm.ApplyMonitorAvailability();
// 强制全代 GC + 等待终结器,确保 LOH 大对象(图像/位图源/CogRecord 等)被回收
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced, true);
}
///
/// 运行模式:加载产品作为当前运行程序(从编辑显式加载)
///
public void LoadProductToRun(string productName)
{
// 切换前先把上一个归属产品的全局变量落盘:
// 设置页改的值只存在内存里,不先存就会随产品切换丢失
if (!string.IsNullOrEmpty(GlobalVarsOwnerProduct) && GlobalVarsOwnerProduct != productName)
SaveGlobalVarsForOwner();
RunProductName = productName;
// 主页加载运行产品:同步加载该产品的全局变量快照(与编辑模式一致)
LoadProductGlobalVars(productName);
}
///
/// 基于序号切换产品(供外部调用,如 PLC 触发)。
/// 产品切换经 LoadRunProduct → LoadProductGlobalVars 自动载入该产品的 Product 变量,
/// 故此处无需再单独切换全局变量配方(配方机制已退休)。
///
public bool SwitchProductBySequence(int sequence)
{
var meta = GetProductBySequence(sequence);
if (meta == null) return false;
RunProductName = meta.Name;
// 加载为运行产品(FlowRunner 会复制一份运行态流程,不影响编辑态)
TeamAAS.FlowEngine.Execution.FlowRunner.Instance.LoadRunProduct(meta.Name);
return true;
}
private static string SanitizeName(string name)
=> TeamAAS.PathHelper.SanitizeFileName(name);
}
}