| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- 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
- {
- /// <summary>
- /// C# 脚本工具节点(稳定宿主):双击弹出 CodeForge 完整代码编辑器(自由写命名空间/多个类、智能补全、
- /// 编译诊断波浪线、一键编译运行、添加 DLL 引用)。用户代码是一个完整插件类
- /// (约定:<c>public class MyScript : BasePlugin<ScriptToolModel></c>,PluginRun/DeclareOutputs 已预写好,想咋改咋改)。
- ///
- /// 运行时本宿主把 Model/Context/Registry 注入编译出的脚本实例并委托执行——脚本因此拥有普通插件的全部能力,
- /// 而节点自身是稳定类型(可写入 .aas、可被 PluginLoader 发现),规避了动态类型无法持久化的问题
- /// (与 VisionMaster 的 C#脚本 插件同一思路:稳定节点 + 编译用户脚本类 + 反射委托)。
- /// </summary>
- [Serializable]
- [Plugin("C#脚本", PluginCategory.逻辑判断, typeof(ScriptToolModel), typeof(ScriptToolView),
- "CodeBraces", Description = "用 CodeForge 完整编辑器编写 C# 脚本节点:脚本类继承 BasePlugin<ScriptToolModel>,可写多个类/添加 DLL 引用/智能补全/编译诊断/一键运行;拥有 GetValue/Log/Service/Model 等普通插件全部能力")]
- public class ScriptToolPlugin : BasePlugin<ScriptToolModel>
- {
- [NonSerialized] private BasePlugin<ScriptToolModel> _script;
- [NonSerialized] private string _scriptCode;
- [NonSerialized] private List<string> _scriptErrors = new List<string>();
- /// <summary>取得(并按需重编译)已注入 Model/Context/Registry 的脚本插件实例;编译失败返回 null。</summary>
- private BasePlugin<ScriptToolModel> GetScript()
- {
- var code = Model != null ? (Model.Code ?? "") : "";
- if (_script != null && _scriptCode == code) return _script;
- List<string> errs, warns;
- var inst = ScriptCompiler.CreateScript(code, Model, Context, Registry, out errs, out warns);
- _scriptErrors = errs ?? new List<string>();
- _scriptCode = code;
- if (inst != null)
- {
- _script = inst;
- try { inst.InitPlugin(); } catch { /* 脚本 InitPlugin 失败不阻断,PluginRun 会再暴露 */ }
- }
- else _script = null;
- return _script;
- }
- /// <summary>每次执行前刷新注入(Registry 可能在初始化后才由框架注入)。</summary>
- private void Inject(BasePlugin<ScriptToolModel> 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<string> errs, warns;
- ScriptCompiler.CompileEntry(code, out errs, out warns);
- }
- catch { /* 后台预热/编译失败不阻断创建,PluginRun 时会再暴露 */ }
- });
- return true;
- }
- public override List<OutputField> 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<OutputField>();
- }
- public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary<string, object> results)
- {
- results = new Dictionary<string, object>();
- var s = GetScript();
- if (s == null)
- {
- results["Error"] = "脚本编译失败:" + string.Join(" | ", (_scriptErrors ?? new List<string>()).ToArray());
- Log(1, "脚本节点失败: " + results["Error"], TeamAAS.LogLevel.Error);
- return NodeRunStatus.Failed;
- }
- Inject(s);
- try
- {
- Dictionary<string, object> 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;
- }
- }
- }
- }
|