03 · 脚本引擎与运行时脚本宿主 —— Hmi.Scripting / Hmi.ScriptRuntime

Hmi.Scripting 是纯粹的编译/执行引擎和函数库表面;Hmi.ScriptRuntime 把它接到真 RTDB / 历史 / 报警上,驱动"启动/退出/条件/周期/画面/元素事件"各类脚本。


Hmi.Scripting —— Roslyn 引擎 + 函数库

职责:C# 脚本引擎(Roslyn)+ 暴露给用户脚本的函数库表面(ScriptGlobals)+ 安全分析器。依赖 Hmi.Core;Microsoft.CodeAnalysis.CSharp.Scripting 4.9.2。

引擎 ScriptEngine

src/Hmi.Scripting/Engine/ScriptEngine.cs(public sealed class):

  • Compile(name, code) —— 先安全分析 → Roslyn 编译 → 缓存。
  • Task<ScriptRunResult> RunAsync(name, code, ScriptGlobals globals, timeout, ct) —— 带协作式超时执行。
  • ClearCache()。

用 CSharpScript,以 ScriptGlobals 作为脚本的全局类型(脚本里直接写 Tag.Read(...) 就是调它)。

函数库表面 ScriptGlobals

src/Hmi.Scripting/ScriptGlobals.cs(public sealed class)—— 暴露给用户脚本的所有函数库:

Tag  Log  Sys  Timer  Math2  Str  Bit  Fs
Alarm  History  Db  Ui  Comm  Report  Recipe

构造时注入 ITagGateway + 可选宿主服务;没接上的服务给占位实现(调用返回"不可用"而不是崩)。

宿主能力接口 + 安全闸

Hmi.Scripting.Abstractions(HostServices.cs):IScriptLogSink、IAlarmService、IHistoryService、IDbService、IUiService、ICommService、IReportService、IRecipeService。文件访问在 Hmi.Scripting.Api.FileApi 里做围栏;ScriptSafetyAnalyzer 在编译前拦危险代码。


Hmi.ScriptRuntime —— 把脚本接到真实运行

职责:运行时脚本宿主 + 组合运行数据服务。依赖 Core、Scripting、Historian、Alarms、Project、Automation。

宿主 ScriptRuntimeHost : IDisposable

src/Hmi.ScriptRuntime/ScriptRuntimeHost.cs。构造 (ScriptDispatcher dispatcher, LoadedProject project):

  • StartAsync —— 跑启动脚本 + 起全局条件/周期定时器。
  • OpenScreenAsync —— 旧画面退出脚本 → 新画面进入脚本 → 切换画面周期定时器。
  • RaiseElementEventAsync / RunNamedScriptAsync / StopAsync。

单个 SemaphoreSlim 串行化所有脚本执行;每脚本 TickGate 防堆积(周期/条件上一拍没跑完就合并丢弃,生命周期事件永远等待闸门)。AlarmNotifier 发软件启动/停止邮件。

组装器 ScriptRuntimeBuilder

构造 (RtdbCore rtdb, IReadOnlyList<TagDef> tags, IActionDispatcher writer);链式 WithHistorian / WithAlarms / WithLog / WithCurrentUser / WithDatabase / WithUi / WithReport / WithRecipe / WithComm / WithFileRoot;ScriptGlobals Build() 把 RtdbTagGateway + HistorianHistoryService + AlarmEngineService 接好。这就是脚本宿主对接 RTDB/历史/报警的地方。

一键组合 RuntimeDataServices.Compose

RuntimeDataServices : IDisposable。静态 Compose(RtdbCore rtdb, LoadedProject project, string historianDbPath, IHistorian? historian, ...) —— 映射报警/历史配置,建 AlarmEngine + AlarmEvaluationTicker + SqliteHistorian + HistorianRecorder + RetentionSweeper + LinkedVariableMirror,并把它们经 Lossless 车道订阅到 RTDB。暴露 Alarms / Recorder / Historian / Retention / AlarmTicker / …。

编译校验实现 RoslynScriptChecker : IScriptCompiler

接口 IScriptCompiler 声明在 Hmi.Automation(依赖倒置),实现在这里。构造 (LoadedProject project, ScriptEngine? engine = null);CheckFragment / CheckProject,把 ScriptDiagnostic 映射成 ScriptIssue。编辑器和 MCP 两处都注入它做 F7 编译校验。

还有:ScriptDispatcher(触发路由)、ForwardingLogSink、RuntimeWriteGate(写值闸:内部变量放行、通道/PLC 点挡住脚本侧写入) —— 安全边界在脚本这一层的落点。


小结

  • 只想编译校验一段脚本 → ScriptEngine 或 RoslynScriptChecker。
  • 想让脚本在运行时真的读点、记历史、触发报警 → ScriptRuntimeBuilder / RuntimeDataServices.Compose 组好再交给 ScriptRuntimeHost。