| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using TeamAAS.FlowEditor.Execution;
- using TeamAAS.FlowEditor.Models;
- using TeamAAS.FlowEditor.Plugins;
- using TeamAAS.FlowEngine.Execution;
- using TeamAAS.FlowEngine.Interfaces;
- using TeamAAS;
- namespace TeamAAS.FlowEngine
- {
- /// <summary>
- /// 流程节点插件注册中心 + 流程 Tab 统一容器。懒汉单例:<c>PluginLoader.Instance</c>。
- /// 启动时从 Plugins 目录扫描带有 <c>PluginAttribute</c> 的类型,
- /// 建立「显示名称 → 插件类型」的索引;流程编辑器和执行器通过该索引创建节点实例;
- /// 同时通过 <see cref="FlowTabs"/> 统一管理所有打开的流程,支撑组合模块的跨流程查询。
- /// 管理类按架构规则保留在根命名空间 TeamAAS.FlowEngine。
- /// </summary>
- public class PluginLoader : IPluginLoader
- {
- #region 懒汉单例
- private static readonly Lazy<PluginLoader> _instance =
- new Lazy<PluginLoader>(() => new PluginLoader(), isThreadSafe: true);
- /// <summary>
- /// 懒汉单例入口。首次访问时初始化,线程安全。
- /// </summary>
- public static PluginLoader Instance => _instance.Value;
- #endregion
- private readonly List<PluginDescriptor> _descriptors = new List<PluginDescriptor>();
- private readonly Dictionary<string, PluginDescriptor> _byName = new Dictionary<string, PluginDescriptor>(StringComparer.OrdinalIgnoreCase);
- private bool _loaded = false;
- private readonly object _scanLock = new object();
- /// <summary>
- /// 组合模块编辑时,记录当前子流程对应的组合模块 PluginModel;
- /// 公式编辑器据此区分「编辑子流程节点属性」和「编辑组合模块输出」场景。
- /// </summary>
- public FlowGraph SetGroupPluginModel { get; set; }
- /// <summary>
- /// 组合模块编辑时,记录外层组合模块的 Model;
- /// InputSourceDataProvider 用它查询主流程中组合模块的前置节点结果。
- /// </summary>
- public BasePluginModel ParentGroupModel { get; set; }
- /// <summary>
- /// 当前在 PropertyGrid 中选中的节点(公式编辑器用它判断上下文)。
- /// </summary>
- public FlowNode SelectFlow { get; set; }
- #region 流程统一管理
- /// <summary>
- /// 当前打开的所有流程 Tab(编辑器/执行器共用,可绑定到 UI)。
- /// </summary>
- public ObservableCollection<FlowTabItem> FlowTabs { get; set; } = new ObservableCollection<FlowTabItem>();
- /// <summary>
- /// 通过名称获取对应的 FlowTabItem(不存在返回 null)。
- /// </summary>
- public FlowTabItem GetFlowTab(string flowName)
- {
- if (string.IsNullOrEmpty(flowName)) return null;
- return FlowTabs.FirstOrDefault(t => t.Name == flowName);
- }
- /// <summary>
- /// 通过流程名称获取一个新的 FlowExecutor 执行器(基于 FlowGraph 构造)。
- /// 流程或图不存在返回 null。
- /// </summary>
- public FlowExecutor GetFlowExecutor(string flowName)
- {
- var tab = GetFlowTab(flowName);
- if (tab?.Graph == null) return null;
- return new FlowExecutor(tab.Graph);
- }
- /// <summary>
- /// 获取所有已打开流程的名称列表(用于下拉框、日志输出等)。
- /// </summary>
- public List<string> GetFlowNames()
- {
- return FlowTabs.Select(t => t.Name).ToList();
- }
- #endregion
- /// <summary>
- /// 所有已发现插件的描述(只读快照,顺序与扫描顺序一致)。
- /// </summary>
- public IReadOnlyList<PluginDescriptor> Descriptors => _descriptors;
- /// <summary>
- /// 当前插件目录是否已加载过(防止重复扫描)。
- /// </summary>
- public bool IsLoaded => _loaded;
- /// <summary>
- /// 从指定目录加载插件:仅把文件名形如 <c>Plugins.*.dll</c> 的程序集反射用作插件;
- /// 目录下的其它 DLL 属于运行环境/依赖,交给 CLR 与 AssemblyResolve 按需加载,这里不主动加载、也不清理。
- /// 加载或扫描某个插件出错时,通过 <see cref="AppLogger"/> 写入主页实时日志提示。
- /// 只在首次调用时执行(<see cref="IsLoaded"/> 为 true 时直接返回)。
- /// </summary>
- /// <param name="pluginsPath">插件目录路径(通常是 Runtime\Plugins)</param>
- public void LoadFrom(string pluginsPath)
- {
- if (_loaded) return;
- lock (_scanLock)
- {
- if (_loaded) return;
- _loaded = true;
- if (Directory.Exists(pluginsPath))
- {
- // 递归扫描子目录(视觉平台分包:HalconPlugins/VmPlugins/VppPlugins 等),
- // 同名 DLL 只装载一次(防止多目录重复部署导致插件类型双注册)
- var seenAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- foreach (var dll in Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories))
- {
- var fileName = Path.GetFileName(dll);
- // 只把 Plugins.* 命名的程序集当插件反射;其它 DLL 是环境/依赖,会被别处按需加载,这里不管
- if (!fileName.StartsWith("Plugins.", StringComparison.OrdinalIgnoreCase))
- continue;
- if (!seenAssemblies.Add(fileName))
- {
- AppLogger.Warning($"插件 {fileName} 在多个子目录中重复出现,跳过: {dll}", "插件加载");
- continue;
- }
- try
- {
- var asm = Assembly.LoadFrom(dll);
- if (!ScanAssembly(asm))
- AppLogger.Warning($"插件 {fileName} 未发现任何插件类型(检查 [Plugin] 标注或依赖是否齐全)", "插件加载");
- }
- catch (Exception ex)
- {
- // 插件加载/扫描失败 → 主页实时日志提示(含异常详情)
- AppLogger.Error($"插件加载失败 {fileName}: {ex.Message}", ex, "插件加载");
- System.Diagnostics.Debug.WriteLine($"[PluginLoader] {dll}: {ex}");
- }
- }
- }
- else
- {
- AppLogger.Warning($"插件目录不存在,已跳过插件扫描: {pluginsPath}", "插件加载");
- }
- // 兜底:扫描当前 AppDomain 内已加载的非动态程序集(如被主程序直接引用的插件)
- foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
- {
- try { if (!asm.IsDynamic) ScanAssembly(asm); } catch { }
- }
- AppLogger.Info($"插件加载完成,共 {_descriptors.Count} 个流程节点插件", "插件加载");
- }
- }
- /// <summary>
- /// 在一个程序集内扫描所有带有 <c>[PluginAttribute]</c> 且实现 <c>IFlowNodePlugin</c>
- /// 的非抽象类型,写入描述列表与名称索引。
- /// </summary>
- /// <returns>程序集中是否至少发现一个插件类型</returns>
- private bool ScanAssembly(Assembly asm)
- {
- bool found = false;
- Type[] types;
- try { types = asm.GetTypes(); }
- catch (ReflectionTypeLoadException ex)
- {
- // 部分类型加载失败时,仍扫描已成功加载的类型
- types = ex.Types?.Where(t => t != null).ToArray() ?? Type.EmptyTypes;
- }
- catch { return false; }
- foreach (var type in types)
- {
- try
- {
- var attr = type.GetCustomAttribute<PluginAttribute>();
- if (attr != null && typeof(IFlowNodePlugin).IsAssignableFrom(type) && !type.IsAbstract)
- {
- found = true;
- if (!_descriptors.Any(d => d.PluginType == type))
- {
- var desc = new PluginDescriptor { PluginType = type, Attribute = attr };
- _descriptors.Add(desc);
- _byName[attr.DisplayName] = desc;
- }
- }
- }
- catch { }
- }
- return found;
- }
- /// <summary>
- /// 按显示名称创建插件实例(不带模型初始化)。找不到或实例化失败返回 null。
- /// </summary>
- public IFlowNodePlugin CreateInstance(string displayName)
- {
- if (string.IsNullOrEmpty(displayName)) return null;
- if (_byName.TryGetValue(displayName, out var desc))
- {
- try { return Activator.CreateInstance(desc.PluginType) as IFlowNodePlugin; }
- catch (Exception ex)
- {
- System.Diagnostics.Debug.WriteLine($"[PluginLoader] Create {displayName}: {ex.Message}");
- return null;
- }
- }
- return null;
- }
- /// <summary>
- /// 按显示名称获取插件描述信息(含 PluginType、Attribute 元数据)。找不到返回 null。
- /// </summary>
- public PluginDescriptor GetDescriptor(string displayName)
- {
- if (string.IsNullOrEmpty(displayName)) return null;
- _byName.TryGetValue(displayName, out var desc);
- return desc;
- }
- /// <summary>
- /// 获取所有已注册插件的 UI 展示信息(工具箱列表绑定用)。
- /// </summary>
- public List<NodePluginInfo> GetAllPluginInfos()
- {
- return _descriptors.Select(d => new NodePluginInfo
- {
- PluginId = GuidGenerator.GenerateGuidFromValue(d.DisplayName).ToString(),
- DisplayName = d.DisplayName,
- Category = d.NodeShape,
- IconGeometry = d.IconGeometry,
- Description = d.Attribute.Description,
- Group = d.Category,
- IsSubFlowNode = d.IsSubFlowNode,
- VisionCategory = d.VisionCategory
- }).ToList();
- }
- /// <summary>
- /// 获取单个插件的 UI 展示信息。找不到返回 null。
- /// </summary>
- public NodePluginInfo GetPluginInfo(string displayName)
- {
- var desc = GetDescriptor(displayName);
- if (desc == null) return null;
- return new NodePluginInfo
- {
- PluginId = desc.DisplayName,
- DisplayName = desc.DisplayName,
- Category = desc.NodeShape,
- IconGeometry = desc.IconGeometry,
- Description = desc.Attribute.Description,
- Group = desc.Category
- };
- }
- /// <summary>
- /// 查找指定节点的首个后继节点插件实例(按连线先后顺序,只返回第一个)。
- /// 用于条件跳转、组合模块子流程等只取一条后继的场景。
- /// </summary>
- /// <param name="flowName">流程名称(匹配 FlowTabItem.Name)</param>
- /// <param name="nodeId">起点节点 NodeId</param>
- public IFlowNodePlugin GetPluginTool(string flowName, string nodeId)
- {
- var graph = FlowTabs.FirstOrDefault(x => x.Graph.GraphName == flowName)?.Graph;
- var outgoing = graph?.Connections
- .Where(c => c.SourceNodeId == nodeId)
- .ToList();
- foreach (var conn in outgoing)
- {
- var target = graph?.GetNode(conn.TargetNodeId);
- if (target != null) return target.PluginModel;
- }
- return default(IFlowNodePlugin);
- }
- /// <summary>
- /// 通过 FlowId + NodeId 获取所有后继节点列表。
- /// 自动在 FlowTabs 顶层图以及所有组合模块 SubGraph 中递归查找;
- /// 找不到目标图时返回空列表。
- /// </summary>
- /// <param name="flowId">目标图 GraphId(主流程或组合模块子流程皆可)</param>
- /// <param name="nodeId">起点节点 NodeId</param>
- public List<FlowNode> IDGetNextPlugins(string flowId, string nodeId)
- {
- var flows = new List<FlowNode>();
- try
- {
- // 1) 先查顶层 FlowTabs(主流程场景)
- var graph = FlowTabs.FirstOrDefault(x => x.Graph.GraphId == flowId)?.Graph;
- // 2) 没找到 → 在所有组合模块的子流程里查(子流程场景,用反射避免 FlowEngine 依赖具体插件类型)
- if (graph == null) graph = FindSubGraph(FlowTabs, flowId);
- if (graph == null) return flows;
- var outgoing = graph.Connections
- .Where(c => c.SourceNodeId == nodeId)
- .ToList();
- foreach (var conn in outgoing)
- {
- var target = graph.GetNode(conn.TargetNodeId);
- if (target != null) flows.Add(target);
- }
- return flows;
- }
- catch (Exception) { return flows; }
- }
- /// <summary>
- /// 递归在所有 Tab 的节点 PluginModel.SubGraph 里查找 GraphId 匹配的子流程。
- /// 用反射访问 SubGraph 属性,避免 FlowEngine 直接依赖具体插件类型(如 GroupPluginModel)。
- /// </summary>
- private static FlowGraph FindSubGraph(ObservableCollection<FlowTabItem> tabs, string subFlowId)
- {
- if (tabs == null || string.IsNullOrEmpty(subFlowId)) return null;
- foreach (var tab in tabs)
- {
- if (tab?.Graph?.Nodes == null) continue;
- var found = FindSubGraphInGraph(tab.Graph, subFlowId);
- if (found != null) return found;
- }
- return null;
- }
- /// <summary>
- /// 在单个图内递归查找 SubGraph(支持任意嵌套组合模块)。
- /// </summary>
- private static FlowGraph FindSubGraphInGraph(FlowGraph graph, string subFlowId)
- {
- if (graph?.Nodes == null) return null;
- foreach (var node in graph.Nodes)
- {
- // node.PluginModel 是 IFlowNodePlugin(插件实例),SubGraph 属性在它的 GetModel(BasePluginModel 派生)上
- var model = node.PluginModel?.GetModel;
- var subGraph = TryGetSubGraph(model);
- if (subGraph != null)
- {
- if (subGraph.GraphId == subFlowId) return subGraph;
- var nested = FindSubGraphInGraph(subGraph, subFlowId);
- if (nested != null) return nested;
- }
- }
- return null;
- }
- /// <summary>
- /// 反射读 PluginModel 的 SubGraph 属性(兼容 GroupPluginModel 及其他组合模块插件)。
- /// 属性不存在或为 null 返回 null。
- /// </summary>
- private static FlowGraph TryGetSubGraph(object pluginModel)
- {
- if (pluginModel == null) return null;
- try
- {
- var prop = pluginModel.GetType().GetProperty("SubGraph");
- return prop?.GetValue(pluginModel) as FlowGraph;
- }
- catch { return null; }
- }
- }
- }
|