6054b489fd7d43ffafc3ebc5ed2d64a96321b130da7788d8a715e83dd3d71cf1.source 5.4 KB

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