PluginLoader.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Reflection;
  7. using TeamAAS.FlowEditor.Execution;
  8. using TeamAAS.FlowEditor.Models;
  9. using TeamAAS.FlowEditor.Plugins;
  10. using TeamAAS.FlowEngine.Execution;
  11. using TeamAAS.FlowEngine.Interfaces;
  12. using TeamAAS;
  13. namespace TeamAAS.FlowEngine
  14. {
  15. /// <summary>
  16. /// 流程节点插件注册中心 + 流程 Tab 统一容器。懒汉单例:<c>PluginLoader.Instance</c>。
  17. /// 启动时从 Plugins 目录扫描带有 <c>PluginAttribute</c> 的类型,
  18. /// 建立「显示名称 → 插件类型」的索引;流程编辑器和执行器通过该索引创建节点实例;
  19. /// 同时通过 <see cref="FlowTabs"/> 统一管理所有打开的流程,支撑组合模块的跨流程查询。
  20. /// 管理类按架构规则保留在根命名空间 TeamAAS.FlowEngine。
  21. /// </summary>
  22. public class PluginLoader : IPluginLoader
  23. {
  24. #region 懒汉单例
  25. private static readonly Lazy<PluginLoader> _instance =
  26. new Lazy<PluginLoader>(() => new PluginLoader(), isThreadSafe: true);
  27. /// <summary>
  28. /// 懒汉单例入口。首次访问时初始化,线程安全。
  29. /// </summary>
  30. public static PluginLoader Instance => _instance.Value;
  31. #endregion
  32. private readonly List<PluginDescriptor> _descriptors = new List<PluginDescriptor>();
  33. private readonly Dictionary<string, PluginDescriptor> _byName = new Dictionary<string, PluginDescriptor>(StringComparer.OrdinalIgnoreCase);
  34. private bool _loaded = false;
  35. private readonly object _scanLock = new object();
  36. /// <summary>
  37. /// 组合模块编辑时,记录当前子流程对应的组合模块 PluginModel;
  38. /// 公式编辑器据此区分「编辑子流程节点属性」和「编辑组合模块输出」场景。
  39. /// </summary>
  40. public FlowGraph SetGroupPluginModel { get; set; }
  41. /// <summary>
  42. /// 组合模块编辑时,记录外层组合模块的 Model;
  43. /// InputSourceDataProvider 用它查询主流程中组合模块的前置节点结果。
  44. /// </summary>
  45. public BasePluginModel ParentGroupModel { get; set; }
  46. /// <summary>
  47. /// 当前在 PropertyGrid 中选中的节点(公式编辑器用它判断上下文)。
  48. /// </summary>
  49. public FlowNode SelectFlow { get; set; }
  50. #region 流程统一管理
  51. /// <summary>
  52. /// 当前打开的所有流程 Tab(编辑器/执行器共用,可绑定到 UI)。
  53. /// </summary>
  54. public ObservableCollection<FlowTabItem> FlowTabs { get; set; } = new ObservableCollection<FlowTabItem>();
  55. /// <summary>
  56. /// 通过名称获取对应的 FlowTabItem(不存在返回 null)。
  57. /// </summary>
  58. public FlowTabItem GetFlowTab(string flowName)
  59. {
  60. if (string.IsNullOrEmpty(flowName)) return null;
  61. return FlowTabs.FirstOrDefault(t => t.Name == flowName);
  62. }
  63. /// <summary>
  64. /// 通过流程名称获取一个新的 FlowExecutor 执行器(基于 FlowGraph 构造)。
  65. /// 流程或图不存在返回 null。
  66. /// </summary>
  67. public FlowExecutor GetFlowExecutor(string flowName)
  68. {
  69. var tab = GetFlowTab(flowName);
  70. if (tab?.Graph == null) return null;
  71. return new FlowExecutor(tab.Graph);
  72. }
  73. /// <summary>
  74. /// 获取所有已打开流程的名称列表(用于下拉框、日志输出等)。
  75. /// </summary>
  76. public List<string> GetFlowNames()
  77. {
  78. return FlowTabs.Select(t => t.Name).ToList();
  79. }
  80. #endregion
  81. /// <summary>
  82. /// 所有已发现插件的描述(只读快照,顺序与扫描顺序一致)。
  83. /// </summary>
  84. public IReadOnlyList<PluginDescriptor> Descriptors => _descriptors;
  85. /// <summary>
  86. /// 当前插件目录是否已加载过(防止重复扫描)。
  87. /// </summary>
  88. public bool IsLoaded => _loaded;
  89. /// <summary>
  90. /// 从指定目录加载插件:仅把文件名形如 <c>Plugins.*.dll</c> 的程序集反射用作插件;
  91. /// 目录下的其它 DLL 属于运行环境/依赖,交给 CLR 与 AssemblyResolve 按需加载,这里不主动加载、也不清理。
  92. /// 加载或扫描某个插件出错时,通过 <see cref="AppLogger"/> 写入主页实时日志提示。
  93. /// 只在首次调用时执行(<see cref="IsLoaded"/> 为 true 时直接返回)。
  94. /// </summary>
  95. /// <param name="pluginsPath">插件目录路径(通常是 Runtime\Plugins)</param>
  96. public void LoadFrom(string pluginsPath)
  97. {
  98. if (_loaded) return;
  99. lock (_scanLock)
  100. {
  101. if (_loaded) return;
  102. _loaded = true;
  103. if (Directory.Exists(pluginsPath))
  104. {
  105. // 递归扫描子目录(视觉平台分包:HalconPlugins/VmPlugins/VppPlugins 等),
  106. // 同名 DLL 只装载一次(防止多目录重复部署导致插件类型双注册)
  107. var seenAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
  108. foreach (var dll in Directory.GetFiles(pluginsPath, "*.dll", SearchOption.AllDirectories))
  109. {
  110. var fileName = Path.GetFileName(dll);
  111. // 只把 Plugins.* 命名的程序集当插件反射;其它 DLL 是环境/依赖,会被别处按需加载,这里不管
  112. if (!fileName.StartsWith("Plugins.", StringComparison.OrdinalIgnoreCase))
  113. continue;
  114. if (!seenAssemblies.Add(fileName))
  115. {
  116. AppLogger.Warning($"插件 {fileName} 在多个子目录中重复出现,跳过: {dll}", "插件加载");
  117. continue;
  118. }
  119. try
  120. {
  121. var asm = Assembly.LoadFrom(dll);
  122. if (!ScanAssembly(asm))
  123. AppLogger.Warning($"插件 {fileName} 未发现任何插件类型(检查 [Plugin] 标注或依赖是否齐全)", "插件加载");
  124. }
  125. catch (Exception ex)
  126. {
  127. // 插件加载/扫描失败 → 主页实时日志提示(含异常详情)
  128. AppLogger.Error($"插件加载失败 {fileName}: {ex.Message}", ex, "插件加载");
  129. System.Diagnostics.Debug.WriteLine($"[PluginLoader] {dll}: {ex}");
  130. }
  131. }
  132. }
  133. else
  134. {
  135. AppLogger.Warning($"插件目录不存在,已跳过插件扫描: {pluginsPath}", "插件加载");
  136. }
  137. // 兜底:扫描当前 AppDomain 内已加载的非动态程序集(如被主程序直接引用的插件)
  138. foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
  139. {
  140. try { if (!asm.IsDynamic) ScanAssembly(asm); } catch { }
  141. }
  142. AppLogger.Info($"插件加载完成,共 {_descriptors.Count} 个流程节点插件", "插件加载");
  143. }
  144. }
  145. /// <summary>
  146. /// 在一个程序集内扫描所有带有 <c>[PluginAttribute]</c> 且实现 <c>IFlowNodePlugin</c>
  147. /// 的非抽象类型,写入描述列表与名称索引。
  148. /// </summary>
  149. /// <returns>程序集中是否至少发现一个插件类型</returns>
  150. private bool ScanAssembly(Assembly asm)
  151. {
  152. bool found = false;
  153. Type[] types;
  154. try { types = asm.GetTypes(); }
  155. catch (ReflectionTypeLoadException ex)
  156. {
  157. // 部分类型加载失败时,仍扫描已成功加载的类型
  158. types = ex.Types?.Where(t => t != null).ToArray() ?? Type.EmptyTypes;
  159. }
  160. catch { return false; }
  161. foreach (var type in types)
  162. {
  163. try
  164. {
  165. var attr = type.GetCustomAttribute<PluginAttribute>();
  166. if (attr != null && typeof(IFlowNodePlugin).IsAssignableFrom(type) && !type.IsAbstract)
  167. {
  168. found = true;
  169. if (!_descriptors.Any(d => d.PluginType == type))
  170. {
  171. var desc = new PluginDescriptor { PluginType = type, Attribute = attr };
  172. _descriptors.Add(desc);
  173. _byName[attr.DisplayName] = desc;
  174. }
  175. }
  176. }
  177. catch { }
  178. }
  179. return found;
  180. }
  181. /// <summary>
  182. /// 按显示名称创建插件实例(不带模型初始化)。找不到或实例化失败返回 null。
  183. /// </summary>
  184. public IFlowNodePlugin CreateInstance(string displayName)
  185. {
  186. if (string.IsNullOrEmpty(displayName)) return null;
  187. if (_byName.TryGetValue(displayName, out var desc))
  188. {
  189. try { return Activator.CreateInstance(desc.PluginType) as IFlowNodePlugin; }
  190. catch (Exception ex)
  191. {
  192. System.Diagnostics.Debug.WriteLine($"[PluginLoader] Create {displayName}: {ex.Message}");
  193. return null;
  194. }
  195. }
  196. return null;
  197. }
  198. /// <summary>
  199. /// 按显示名称获取插件描述信息(含 PluginType、Attribute 元数据)。找不到返回 null。
  200. /// </summary>
  201. public PluginDescriptor GetDescriptor(string displayName)
  202. {
  203. if (string.IsNullOrEmpty(displayName)) return null;
  204. _byName.TryGetValue(displayName, out var desc);
  205. return desc;
  206. }
  207. /// <summary>
  208. /// 获取所有已注册插件的 UI 展示信息(工具箱列表绑定用)。
  209. /// </summary>
  210. public List<NodePluginInfo> GetAllPluginInfos()
  211. {
  212. return _descriptors.Select(d => new NodePluginInfo
  213. {
  214. PluginId = GuidGenerator.GenerateGuidFromValue(d.DisplayName).ToString(),
  215. DisplayName = d.DisplayName,
  216. Category = d.NodeShape,
  217. IconGeometry = d.IconGeometry,
  218. Description = d.Attribute.Description,
  219. Group = d.Category,
  220. IsSubFlowNode = d.IsSubFlowNode,
  221. VisionCategory = d.VisionCategory
  222. }).ToList();
  223. }
  224. /// <summary>
  225. /// 获取单个插件的 UI 展示信息。找不到返回 null。
  226. /// </summary>
  227. public NodePluginInfo GetPluginInfo(string displayName)
  228. {
  229. var desc = GetDescriptor(displayName);
  230. if (desc == null) return null;
  231. return new NodePluginInfo
  232. {
  233. PluginId = desc.DisplayName,
  234. DisplayName = desc.DisplayName,
  235. Category = desc.NodeShape,
  236. IconGeometry = desc.IconGeometry,
  237. Description = desc.Attribute.Description,
  238. Group = desc.Category
  239. };
  240. }
  241. /// <summary>
  242. /// 查找指定节点的首个后继节点插件实例(按连线先后顺序,只返回第一个)。
  243. /// 用于条件跳转、组合模块子流程等只取一条后继的场景。
  244. /// </summary>
  245. /// <param name="flowName">流程名称(匹配 FlowTabItem.Name)</param>
  246. /// <param name="nodeId">起点节点 NodeId</param>
  247. public IFlowNodePlugin GetPluginTool(string flowName, string nodeId)
  248. {
  249. var graph = FlowTabs.FirstOrDefault(x => x.Graph.GraphName == flowName)?.Graph;
  250. var outgoing = graph?.Connections
  251. .Where(c => c.SourceNodeId == nodeId)
  252. .ToList();
  253. foreach (var conn in outgoing)
  254. {
  255. var target = graph?.GetNode(conn.TargetNodeId);
  256. if (target != null) return target.PluginModel;
  257. }
  258. return default(IFlowNodePlugin);
  259. }
  260. /// <summary>
  261. /// 通过 FlowId + NodeId 获取所有后继节点列表。
  262. /// 自动在 FlowTabs 顶层图以及所有组合模块 SubGraph 中递归查找;
  263. /// 找不到目标图时返回空列表。
  264. /// </summary>
  265. /// <param name="flowId">目标图 GraphId(主流程或组合模块子流程皆可)</param>
  266. /// <param name="nodeId">起点节点 NodeId</param>
  267. public List<FlowNode> IDGetNextPlugins(string flowId, string nodeId)
  268. {
  269. var flows = new List<FlowNode>();
  270. try
  271. {
  272. // 1) 先查顶层 FlowTabs(主流程场景)
  273. var graph = FlowTabs.FirstOrDefault(x => x.Graph.GraphId == flowId)?.Graph;
  274. // 2) 没找到 → 在所有组合模块的子流程里查(子流程场景,用反射避免 FlowEngine 依赖具体插件类型)
  275. if (graph == null) graph = FindSubGraph(FlowTabs, flowId);
  276. if (graph == null) return flows;
  277. var outgoing = graph.Connections
  278. .Where(c => c.SourceNodeId == nodeId)
  279. .ToList();
  280. foreach (var conn in outgoing)
  281. {
  282. var target = graph.GetNode(conn.TargetNodeId);
  283. if (target != null) flows.Add(target);
  284. }
  285. return flows;
  286. }
  287. catch (Exception) { return flows; }
  288. }
  289. /// <summary>
  290. /// 递归在所有 Tab 的节点 PluginModel.SubGraph 里查找 GraphId 匹配的子流程。
  291. /// 用反射访问 SubGraph 属性,避免 FlowEngine 直接依赖具体插件类型(如 GroupPluginModel)。
  292. /// </summary>
  293. private static FlowGraph FindSubGraph(ObservableCollection<FlowTabItem> tabs, string subFlowId)
  294. {
  295. if (tabs == null || string.IsNullOrEmpty(subFlowId)) return null;
  296. foreach (var tab in tabs)
  297. {
  298. if (tab?.Graph?.Nodes == null) continue;
  299. var found = FindSubGraphInGraph(tab.Graph, subFlowId);
  300. if (found != null) return found;
  301. }
  302. return null;
  303. }
  304. /// <summary>
  305. /// 在单个图内递归查找 SubGraph(支持任意嵌套组合模块)。
  306. /// </summary>
  307. private static FlowGraph FindSubGraphInGraph(FlowGraph graph, string subFlowId)
  308. {
  309. if (graph?.Nodes == null) return null;
  310. foreach (var node in graph.Nodes)
  311. {
  312. // node.PluginModel 是 IFlowNodePlugin(插件实例),SubGraph 属性在它的 GetModel(BasePluginModel 派生)上
  313. var model = node.PluginModel?.GetModel;
  314. var subGraph = TryGetSubGraph(model);
  315. if (subGraph != null)
  316. {
  317. if (subGraph.GraphId == subFlowId) return subGraph;
  318. var nested = FindSubGraphInGraph(subGraph, subFlowId);
  319. if (nested != null) return nested;
  320. }
  321. }
  322. return null;
  323. }
  324. /// <summary>
  325. /// 反射读 PluginModel 的 SubGraph 属性(兼容 GroupPluginModel 及其他组合模块插件)。
  326. /// 属性不存在或为 null 返回 null。
  327. /// </summary>
  328. private static FlowGraph TryGetSubGraph(object pluginModel)
  329. {
  330. if (pluginModel == null) return null;
  331. try
  332. {
  333. var prop = pluginModel.GetType().GetProperty("SubGraph");
  334. return prop?.GetValue(pluginModel) as FlowGraph;
  335. }
  336. catch { return null; }
  337. }
  338. }
  339. }