using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using TeamAAS.FlowEditor.Execution; // ResultRegistry
using TeamAAS.FlowEditor.Plugins; // BasePlugin
using Plugins.Script.Models;
namespace Plugins.Script.Core
{
///
/// 完整 C# 代码的 headless 编译 / 缓存 / 入口定位 / 实例化注入(无 UI 依赖,可在工作线程执行)。
/// 用户代码是一个完整插件类(约定入口:继承 BasePlugin<ScriptToolModel> 的类,含 PluginRun/DeclareOutputs)。
/// 编译用 CodeForge.CodeForgeEngine.CompileCode(整段代码)(切勿用 Compile/CompileAndRun,那编译的是编辑器文本),
/// 加载后定位入口类型并 Activator 实例化,注入 Model/Context/Registry,使其行为与普通插件一致。
/// 按代码全文缓存入口 Type(编译一次、多次运行,避免每次 Assembly.Load 泄漏);引用集变化时清空缓存重编。
///
internal static class ScriptCompiler
{
/// 约定的入口类名(优先匹配;找不到则取任一 BasePlugin<ScriptToolModel> 子类)。
public const string EntryClassName = "MyScript";
private static readonly ConcurrentDictionary _typeCache = new ConcurrentDictionary();
private static readonly ConcurrentDictionary> _errorCache = new ConcurrentDictionary>();
private static readonly HashSet _addedRefPaths = new HashSet(StringComparer.OrdinalIgnoreCase);
private static CodeForge.CodeForgeEngine _engine;
private static readonly object _engineGate = new object();
/// 仅查缓存(不触发编译):命中返回入口 Type。
public static bool TryGetEntry(string code, out Type entry)
{
entry = null;
if (string.IsNullOrWhiteSpace(code)) return false;
return _typeCache.TryGetValue(code, out entry);
}
/// 用缓存好的入口 Type 快速创建脚本实例(绝不编译)。未命中缓存返回 null。
public static BasePlugin TryCreateCached(string code, ScriptToolModel model,
TeamAAS.Global.IPluginContext context, ResultRegistry registry)
{
Type entry;
if (!TryGetEntry(code, out entry)) return null;
try
{
var inst = (BasePlugin)Activator.CreateInstance(entry);
inst.GetModel = model;
inst.Context = context;
inst.Registry = registry;
return inst;
}
catch { return null; }
}
///
/// 后台预热引擎(首次创建 CodeForge 引擎 + 全量基础引用 + 用户引用同步)。
/// 这一步在 UI 线程上做会卡顿数秒——务必只在后台线程调用(节点创建时 Task.Run 触发)。
///
public static void WarmupEngine()
{
lock (_engineGate)
{
var engine = GetEngine();
SyncUserReferences(engine);
}
}
///
/// 编译整段代码并定位入口类型(命中缓存则跳过编译)。成功返回入口 Type;失败返回 null 并给出 errors。
///
public static Type CompileEntry(string code, out List errors, out List warnings)
{
errors = new List();
warnings = new List();
if (string.IsNullOrWhiteSpace(code)) { errors.Add("脚本代码为空"); return null; }
Type cached;
if (_typeCache.TryGetValue(code, out cached)) return cached;
List cachedErr;
if (_errorCache.TryGetValue(code, out cachedErr)) { errors = cachedErr; return null; }
CodeForge.CodeCompileResult compile;
lock (_engineGate)
{
var engine = GetEngine();
if (SyncUserReferences(engine)) { _typeCache.Clear(); _errorCache.Clear(); }
compile = engine.CompileCode(code);
}
if (compile == null || !compile.Success || compile.AssemblyBytes == null)
{
errors = (compile != null && compile.Errors != null)
? new List(compile.Errors) : new List { "编译失败(无详情)" };
_errorCache[code] = errors;
return null;
}
if (compile.Warnings != null) warnings = new List(compile.Warnings);
var asm = Assembly.Load(compile.AssemblyBytes);
var entry = FindEntryType(asm);
if (entry == null)
{
errors = new List
{
"编译成功但未找到入口类:请保留 public class MyScript : BasePlugin { public override NodeRunStatus PluginRun(...) {...} }"
};
_errorCache[code] = errors;
return null;
}
_typeCache[code] = entry;
return entry;
}
///
/// 编译(命中缓存则跳过)并创建已注入 Model/Context/Registry 的脚本插件实例;失败返回 null 并给出 errors。
/// 返回的实例即普通 BasePlugin,可直接调用其 PluginRun / DeclareOutputs / InitPlugin。
///
public static BasePlugin CreateScript(
string code, ScriptToolModel model,
TeamAAS.Global.IPluginContext context, ResultRegistry registry,
out List errors, out List warnings)
{
var type = CompileEntry(code, out errors, out warnings);
if (type == null) return null;
try
{
var inst = (BasePlugin)Activator.CreateInstance(type);
inst.GetModel = model;
inst.Context = context;
inst.Registry = registry;
return inst;
}
catch (Exception ex)
{
errors = new List { "创建脚本实例失败:" + ex.Message };
return null;
}
}
/// 定位入口类型:继承 BasePlugin<ScriptToolModel> 的非抽象类;优先名为 MyScript 的。
private static Type FindEntryType(Assembly asm)
{
Type[] types;
try { types = asm.GetTypes(); }
catch (ReflectionTypeLoadException ex) { types = ex.Types.Where(t => t != null).ToArray(); }
var baseType = typeof(BasePlugin);
Type first = null;
foreach (var t in types)
{
if (t == null || t.IsAbstract || t == baseType) continue;
if (!baseType.IsAssignableFrom(t)) continue;
if (t.Name == EntryClassName) return t;
if (first == null) first = t;
}
return first;
}
private static CodeForge.CodeForgeEngine GetEngine()
{
if (_engine == null)
{
_engine = new CodeForge.CodeForgeEngine();
AddBaseReferences(_engine);
}
return _engine;
}
/// 基础引用:本插件(ScriptToolModel)+ 所有已加载的 TeamAAS.* 程序集(跟普通插件一样)。
private static void AddBaseReferences(CodeForge.CodeForgeEngine engine)
{
AddRef(engine, typeof(ScriptToolModel).Assembly);
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
if (asm.IsDynamic) continue;
var name = asm.GetName().Name ?? "";
if (!name.StartsWith("TeamAAS", StringComparison.OrdinalIgnoreCase)) continue;
AddRef(engine, asm);
}
catch { /* 忽略单个引用失败 */ }
}
}
///
/// 用户 DLL 归档目录候选(都基于 exe 目录):根目录 References\ 是 CodeForge.InstallAndReferenceDll
/// 的归档约定处;Runtime\References\ 是历史版本 PostBuild 把 References 整体搬入 Runtime 后的遗留位置。
/// 两处都增量扫描,保证旧文件也能被编译环境引用。
///
private static IEnumerable UserReferenceDirs()
{
yield return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "References");
yield return Path.Combine(TeamAAS.PathHelper.RuntimeDir, "References");
}
/// 扫描用户 DLL 归档目录,增量加入引擎;有新增返回 true。
private static bool SyncUserReferences(CodeForge.CodeForgeEngine engine)
{
bool changed = false;
foreach (var dir in UserReferenceDirs())
{
try
{
if (!Directory.Exists(dir)) continue;
foreach (var dll in Directory.GetFiles(dir, "*.dll"))
{
if (_addedRefPaths.Contains(dll)) continue;
try { engine.AddReferenceFromFile(dll); _addedRefPaths.Add(dll); changed = true; }
catch { /* 忽略坏 DLL */ }
}
}
catch { /* 忽略扫描失败 */ }
}
return changed;
}
private static void AddRef(CodeForge.CodeForgeEngine engine, Assembly asm)
{
try
{
if (asm == null || asm.IsDynamic) return;
var loc = asm.Location;
if (string.IsNullOrEmpty(loc) || !File.Exists(loc)) return;
if (_addedRefPaths.Contains(loc)) return;
engine.AddReferenceFromFile(loc);
_addedRefPaths.Add(loc);
}
catch { /* 忽略单个引用失败 */ }
}
}
}