BasePlugin.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. using PropertyGridLib.Controls;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Threading;
  5. using TeamAAS.FlowEditor.Execution;
  6. using TeamAAS.FlowEditor.Models;
  7. namespace TeamAAS.FlowEditor.Plugins
  8. {
  9. /// <summary>
  10. /// 插件抽象基类 - 参照 VisionKit 的 BaseAbstractPlugin&lt;TModel&gt;
  11. /// 泛型 TModel 自动实例化,子类只需实现 InitPlugin 和 PluginRun
  12. /// </summary>
  13. [Serializable]
  14. public abstract class BasePlugin<TModel> : IFlowNodePlugin where TModel : BasePluginModel, new()
  15. {
  16. public TModel Model { get; protected set; }
  17. public BasePluginModel GetModel { get => Model; set => Model = (TModel)value; }
  18. /// <summary>
  19. /// 所属流程绑定的结果注册表(运行态=Main,编辑/调试态=Debug)。
  20. /// 由 FlowNode 在初始化/换图时注入;未注入时回退到 Debug 注册表。
  21. /// </summary>
  22. [field: NonSerialized]
  23. public ResultRegistry Registry { get; set; }
  24. /// <summary>实际使用的注册表(保证非 null)</summary>
  25. protected ResultRegistry RegistryOrDebug => Registry ?? ResultRegistry.Debug;
  26. [NonSerialized]
  27. private TeamAAS.Global.IPluginContext _context;
  28. /// <summary>
  29. /// 插件运行上下文:获取平台服务(相机/机器人/通讯/供料/运动)的正规渠道。
  30. /// 未注入时回落到全局 ServiceRegistry.Default,永不为 null;
  31. /// 插件经 Context.GetService 取服务,无需直接摸 XxxManager.Instance。
  32. /// 宿主将来可提供自定义实现(如远程代理)注入覆盖。
  33. /// </summary>
  34. public TeamAAS.Global.IPluginContext Context
  35. {
  36. get => _context ?? TeamAAS.Global.ServiceRegistry.Default;
  37. set => _context = value;
  38. }
  39. /// <summary>按类型获取平台服务(工厂方法):相机/机器人/通讯/供料/运动管理器等;未注册返回 null。</summary>
  40. protected T Service<T>() => Context.GetService<T>();
  41. public int CostTime { get; protected set; }
  42. public NodeRunStatus IsSuccess { get; set; } = NodeRunStatus.NotStarted;
  43. public bool IsEnable { get; set; } = true;
  44. public Dictionary<string, object> LastResults { get; private set; }
  45. /// <summary>分支节点:返回下一个要执行的节点名列表(null=非分支节点)</summary>
  46. public List<string> BranchTargets { get; protected set; }
  47. protected BasePlugin()
  48. {
  49. Model = new TModel();
  50. }
  51. /// <summary>
  52. /// 释放插件持有的资源。
  53. /// 子类有相机句柄/连接流/大对象等需释放的资源时,重写并调用 base.Dispose()。
  54. /// </summary>
  55. public virtual void Dispose()
  56. {
  57. try
  58. {
  59. LastResults?.Clear();
  60. LastResults = null;
  61. BranchTargets?.Clear();
  62. BranchTargets = null;
  63. Model = null;
  64. }
  65. catch { }
  66. }
  67. public virtual bool InitPlugin()
  68. {
  69. return true;
  70. }
  71. /// <summary>
  72. /// 声明插件输出字段。静态插件重写此方法声明固定输出;动态插件可从 Model 动态生成。
  73. /// </summary>
  74. public virtual List<OutputField> DeclareOutputs() => new List<OutputField>();
  75. public abstract NodeRunStatus PluginRun(CancellationToken token, out Dictionary<string, object> results);
  76. public bool InitRun()
  77. {
  78. Dictionary<string, object> results = new Dictionary<string, object>();
  79. try
  80. {
  81. bool ret = InitPlugin();
  82. foreach (var field in DeclareOutputs())
  83. {
  84. results[field.Key] = field.ValueType != null && field.ValueType.IsValueType
  85. ? Activator.CreateInstance(field.ValueType)
  86. : null;
  87. }
  88. results["状态"] = NodeRunStatus.NotStarted;
  89. results["结果"] = false;
  90. results["耗时"] = 0;
  91. results["模型"] = this;
  92. if (!ret)
  93. results["Error"] = GetModel.FlowName + "插件初始化失败";
  94. RegistryOrDebug.SetResult(GetModel.FlowId, GetModel.NodeId, GetModel.NodeName, results);
  95. LastResults = results;
  96. return ret;
  97. }
  98. catch (System.Exception ex)
  99. {
  100. results["Error"] = GetModel.FlowName + ex.Message;
  101. RegistryOrDebug.SetResult(GetModel.FlowId, GetModel.NodeId, GetModel.NodeName, results);
  102. LastResults = results;
  103. System.Diagnostics.Debug.WriteLine($"[InitRun] {GetModel.NodeName}: {ex}");
  104. return false;
  105. }
  106. }
  107. public NodeRunStatus Run(CancellationToken token)
  108. {
  109. IsSuccess = NodeRunStatus.Running;
  110. NodeRunStatus status;
  111. try
  112. {
  113. Dictionary<string, object> results;
  114. status = PluginRun(token, out results);
  115. LastResults = results;
  116. IsSuccess = status;
  117. }
  118. catch (System.Exception)
  119. {
  120. IsSuccess = NodeRunStatus.Failed;
  121. status = NodeRunStatus.Failed;
  122. }
  123. return status;
  124. }
  125. /// <summary>
  126. /// 通过公式获取节点执行结果值
  127. /// </summary>
  128. /// <typeparam name="T"></typeparam>
  129. /// <param name="formula"></param>
  130. /// <returns></returns>
  131. public T GetValue<T>(FormulaBound<T> formula)
  132. {
  133. if (formula == null)
  134. return default(T);
  135. object _result = new object();
  136. _result = RegistryOrDebug.ResolveFormula(GetModel.FlowId, formula.Formula);
  137. if (_result == null || _result?.ToString() == "")
  138. return formula.Value;
  139. else if (typeof(T).IsInterface)
  140. {
  141. if (_result is T tResult)
  142. return tResult;
  143. else
  144. return default(T);
  145. }
  146. else
  147. return (T)System.Convert.ChangeType(_result, typeof(T));
  148. }
  149. public object GetResolve(FormulaBound<object> formula)
  150. {
  151. if (formula == null)
  152. return default(object);
  153. object _result = new object();
  154. _result = RegistryOrDebug.ResolveFormula(GetModel.FlowId, formula.Formula);
  155. if (_result == null || _result?.ToString() == "")
  156. return formula.Value;
  157. else
  158. return _result;
  159. }
  160. /// <summary>
  161. /// 保存一条插件日志(像 GetValue 一样直接调用)。
  162. /// level 是详细度等级(1低/2中/3高/4终):超过本插件设置的"日志等级"就跳过对应通道(存储/显示各一道,详见下行);
  163. /// 两道独立闸门:level ≤ 节点「存储详细度」才写文件(1/2 公共日志,3/4 流程/任务 独立路径);level ≤ 节点「显示详细度」才进实时日志控件(可存而不显、显而不存)。category 是类别(Info/Warning/Error),只作标记不影响存/显。
  164. /// 例:Log(1, "开始执行"); Log(3, "通讯超时", TeamAAS.LogLevel.Warning);
  165. /// </summary>
  166. public void Log(int level, string message, TeamAAS.LogLevel category = TeamAAS.LogLevel.Info)
  167. => TeamAAS.Logging.PluginLogger.Log(
  168. Model.FlowName, Model.NodeName, Model.StoreVerbosity, Model.DisplayVerbosity, level, message, category);
  169. }
  170. }