using System; using System.Collections.Generic; using System.Threading; using TeamAAS.FlowEditor.Models; using TeamAAS.FlowEditor.Plugins; using Plugins.Script.Models; using Plugins.Script.Views; using System.Diagnostics; using Plugins.Script.Core; namespace Plugins.Script { /// /// C# 脚本工具节点(稳定宿主):双击弹出 CodeForge 完整代码编辑器(自由写命名空间/多个类、智能补全、 /// 编译诊断波浪线、一键编译运行、添加 DLL 引用)。用户代码是一个完整插件类 /// (约定:public class MyScript : BasePlugin<ScriptToolModel>,PluginRun/DeclareOutputs 已预写好,想咋改咋改)。 /// /// 运行时本宿主把 Model/Context/Registry 注入编译出的脚本实例并委托执行——脚本因此拥有普通插件的全部能力, /// 而节点自身是稳定类型(可写入 .aas、可被 PluginLoader 发现),规避了动态类型无法持久化的问题 /// (与 VisionMaster 的 C#脚本 插件同一思路:稳定节点 + 编译用户脚本类 + 反射委托)。 /// [Serializable] [Plugin("C#脚本", PluginCategory.逻辑判断, typeof(ScriptToolModel), typeof(ScriptToolView), "CodeBraces", Description = "用 CodeForge 完整编辑器编写 C# 脚本节点:脚本类继承 BasePlugin,可写多个类/添加 DLL 引用/智能补全/编译诊断/一键运行;拥有 GetValue/Log/Service/Model 等普通插件全部能力")] public class ScriptToolPlugin : BasePlugin { [NonSerialized] private BasePlugin _script; [NonSerialized] private string _scriptCode; [NonSerialized] private List _scriptErrors = new List(); /// 取得(并按需重编译)已注入 Model/Context/Registry 的脚本插件实例;编译失败返回 null。 private BasePlugin GetScript() { var code = Model != null ? (Model.Code ?? "") : ""; if (_script != null && _scriptCode == code) return _script; List errs, warns; var inst = ScriptCompiler.CreateScript(code, Model, Context, Registry, out errs, out warns); _scriptErrors = errs ?? new List(); _scriptCode = code; if (inst != null) { _script = inst; try { inst.InitPlugin(); } catch { /* 脚本 InitPlugin 失败不阻断,PluginRun 会再暴露 */ } } else _script = null; return _script; } /// 每次执行前刷新注入(Registry 可能在初始化后才由框架注入)。 private void Inject(BasePlugin s) { if (s == null) return; s.GetModel = Model; s.Context = Context; s.Registry = Registry; } public override bool InitPlugin() { // 首次拖入节点即走这里(InitRun)。CodeForge 引擎首建 + 全量引用 + Roslyn 编译耗时数秒, // 同步做会冻结 UI —— 一律转后台编译并立即返回;输出在编译完成后的下一次声明/运行时生效。 var code = Model != null ? (Model.Code ?? "") : ""; System.Threading.Tasks.Task.Run(() => { try { List errs, warns; ScriptCompiler.CompileEntry(code, out errs, out warns); } catch { /* 后台预热/编译失败不阻断创建,PluginRun 时会再暴露 */ } }); return true; } public override List DeclareOutputs() { // 只用已缓存的编译结果(绝不在这里同步编译——本方法会在创建节点的 UI 线程被调用); // 后台编译完成后,下次打开属性/保存/运行即按脚本声明输出。 var code = Model != null ? (Model.Code ?? "") : ""; var s = ScriptCompiler.TryCreateCached(code, Model, Context, Registry); if (s != null) { Inject(s); try { return s.DeclareOutputs(); } catch { /* 脚本声明输出失败则回退空 */ } } return new List(); } public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary results) { results = new Dictionary(); var s = GetScript(); if (s == null) { results["Error"] = "脚本编译失败:" + string.Join(" | ", (_scriptErrors ?? new List()).ToArray()); Log(1, "脚本节点失败: " + results["Error"], TeamAAS.LogLevel.Error); return NodeRunStatus.Failed; } Inject(s); try { Dictionary r; var status = s.PluginRun(token, out r); if (r != null) results = r; return status; } catch (Exception ex) { results["Error"] = ex.Message; Log(1, "脚本运行异常: " + ex.Message, TeamAAS.LogLevel.Error); return NodeRunStatus.Failed; } } } }