ScriptToolPlugin.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading;
  4. using TeamAAS.FlowEditor.Models;
  5. using TeamAAS.FlowEditor.Plugins;
  6. using Plugins.Script.Models;
  7. using Plugins.Script.Views;
  8. using System.Diagnostics;
  9. using Plugins.Script.Core;
  10. namespace Plugins.Script
  11. {
  12. /// <summary>
  13. /// C# 脚本工具节点(稳定宿主):双击弹出 CodeForge 完整代码编辑器(自由写命名空间/多个类、智能补全、
  14. /// 编译诊断波浪线、一键编译运行、添加 DLL 引用)。用户代码是一个完整插件类
  15. /// (约定:<c>public class MyScript : BasePlugin&lt;ScriptToolModel&gt;</c>,PluginRun/DeclareOutputs 已预写好,想咋改咋改)。
  16. ///
  17. /// 运行时本宿主把 Model/Context/Registry 注入编译出的脚本实例并委托执行——脚本因此拥有普通插件的全部能力,
  18. /// 而节点自身是稳定类型(可写入 .aas、可被 PluginLoader 发现),规避了动态类型无法持久化的问题
  19. /// (与 VisionMaster 的 C#脚本 插件同一思路:稳定节点 + 编译用户脚本类 + 反射委托)。
  20. /// </summary>
  21. [Serializable]
  22. [Plugin("C#脚本", PluginCategory.逻辑判断, typeof(ScriptToolModel), typeof(ScriptToolView),
  23. "CodeBraces", Description = "用 CodeForge 完整编辑器编写 C# 脚本节点:脚本类继承 BasePlugin<ScriptToolModel>,可写多个类/添加 DLL 引用/智能补全/编译诊断/一键运行;拥有 GetValue/Log/Service/Model 等普通插件全部能力")]
  24. public class ScriptToolPlugin : BasePlugin<ScriptToolModel>
  25. {
  26. [NonSerialized] private BasePlugin<ScriptToolModel> _script;
  27. [NonSerialized] private string _scriptCode;
  28. [NonSerialized] private List<string> _scriptErrors = new List<string>();
  29. /// <summary>取得(并按需重编译)已注入 Model/Context/Registry 的脚本插件实例;编译失败返回 null。</summary>
  30. private BasePlugin<ScriptToolModel> GetScript()
  31. {
  32. var code = Model != null ? (Model.Code ?? "") : "";
  33. if (_script != null && _scriptCode == code) return _script;
  34. List<string> errs, warns;
  35. var inst = ScriptCompiler.CreateScript(code, Model, Context, Registry, out errs, out warns);
  36. _scriptErrors = errs ?? new List<string>();
  37. _scriptCode = code;
  38. if (inst != null)
  39. {
  40. _script = inst;
  41. try { inst.InitPlugin(); } catch { /* 脚本 InitPlugin 失败不阻断,PluginRun 会再暴露 */ }
  42. }
  43. else _script = null;
  44. return _script;
  45. }
  46. /// <summary>每次执行前刷新注入(Registry 可能在初始化后才由框架注入)。</summary>
  47. private void Inject(BasePlugin<ScriptToolModel> s)
  48. {
  49. if (s == null) return;
  50. s.GetModel = Model;
  51. s.Context = Context;
  52. s.Registry = Registry;
  53. }
  54. public override bool InitPlugin()
  55. {
  56. // 首次拖入节点即走这里(InitRun)。CodeForge 引擎首建 + 全量引用 + Roslyn 编译耗时数秒,
  57. // 同步做会冻结 UI —— 一律转后台编译并立即返回;输出在编译完成后的下一次声明/运行时生效。
  58. var code = Model != null ? (Model.Code ?? "") : "";
  59. System.Threading.Tasks.Task.Run(() =>
  60. {
  61. try
  62. {
  63. List<string> errs, warns;
  64. ScriptCompiler.CompileEntry(code, out errs, out warns);
  65. }
  66. catch { /* 后台预热/编译失败不阻断创建,PluginRun 时会再暴露 */ }
  67. });
  68. return true;
  69. }
  70. public override List<OutputField> DeclareOutputs()
  71. {
  72. // 只用已缓存的编译结果(绝不在这里同步编译——本方法会在创建节点的 UI 线程被调用);
  73. // 后台编译完成后,下次打开属性/保存/运行即按脚本声明输出。
  74. var code = Model != null ? (Model.Code ?? "") : "";
  75. var s = ScriptCompiler.TryCreateCached(code, Model, Context, Registry);
  76. if (s != null)
  77. {
  78. Inject(s);
  79. try { return s.DeclareOutputs(); }
  80. catch { /* 脚本声明输出失败则回退空 */ }
  81. }
  82. return new List<OutputField>();
  83. }
  84. public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary<string, object> results)
  85. {
  86. results = new Dictionary<string, object>();
  87. var s = GetScript();
  88. if (s == null)
  89. {
  90. results["Error"] = "脚本编译失败:" + string.Join(" | ", (_scriptErrors ?? new List<string>()).ToArray());
  91. Log(1, "脚本节点失败: " + results["Error"], TeamAAS.LogLevel.Error);
  92. return NodeRunStatus.Failed;
  93. }
  94. Inject(s);
  95. try
  96. {
  97. Dictionary<string, object> r;
  98. var status = s.PluginRun(token, out r);
  99. if (r != null) results = r;
  100. return status;
  101. }
  102. catch (Exception ex)
  103. {
  104. results["Error"] = ex.Message;
  105. Log(1, "脚本运行异常: " + ex.Message, TeamAAS.LogLevel.Error);
  106. return NodeRunStatus.Failed;
  107. }
  108. }
  109. }
  110. }