using Microsoft.CodeAnalysis.Scripting; using Microsoft.Extensions.Logging; using ZB.MOM.WW.ScadaBridge.Commons.Types; namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; /// /// Shared Script Library — stores compiled shared script delegates in memory. /// Shared scripts are compiled when received from central and executed inline /// (direct method call, not actor message). NOT available on central. /// Recompiled on update when new artifacts arrive. /// public class SharedScriptLibrary { private readonly ScriptCompilationService _compilationService; private readonly ILogger _logger; private readonly Dictionary> _compiledScripts = new(); private readonly object _lock = new(); /// /// Initializes a new with the compilation service and logger. /// /// Service used to compile Roslyn scripts. /// Logger for compilation warnings and diagnostics. public SharedScriptLibrary( ScriptCompilationService compilationService, ILogger logger) { _compilationService = compilationService; _logger = logger; } /// /// Compiles and registers a shared script. Replaces any existing script with the same name. /// Returns true if compilation succeeded, false otherwise. /// /// Unique name for the shared script. /// C# source code of the shared script. /// true if compilation succeeded and the script was registered; false if compilation failed. public bool CompileAndRegister(string name, string code) { var result = _compilationService.Compile(name, code); if (!result.IsSuccess) { _logger.LogWarning( "Shared script '{Name}' failed to compile: {Errors}", name, string.Join("; ", result.Errors)); return false; } lock (_lock) { _compiledScripts[name] = result.CompiledScript!; } _logger.LogInformation("Shared script '{Name}' compiled and registered", name); return true; } /// /// Removes a shared script from the library. /// /// Name of the shared script to remove. /// true if the script was found and removed; false if no script with that name was registered. public bool Remove(string name) { lock (_lock) { return _compiledScripts.Remove(name); } } /// /// Executes a shared script inline with the given runtime context. /// This is a direct method call, not an actor message — executes on the calling thread. /// /// Name of the shared script to execute. /// Runtime context providing instance state and services to the script globals. /// Optional input parameters passed to the script. /// Cancellation token for the execution. /// A task that resolves to the script's return value, or null if the script returns nothing. public async Task ExecuteAsync( string scriptName, ScriptRuntimeContext context, IReadOnlyDictionary? parameters = null, CancellationToken cancellationToken = default) { Script script; lock (_lock) { if (!_compiledScripts.TryGetValue(scriptName, out script!)) { throw new InvalidOperationException( $"Shared script '{scriptName}' not found in library."); } } var globals = new ScriptGlobals { Instance = context, Parameters = new ScriptParameters(parameters ?? new Dictionary()), CancellationToken = cancellationToken }; var state = await script.RunAsync(globals, cancellationToken); return state.ReturnValue; } /// /// Returns the names of all currently registered shared scripts. /// /// A snapshot list of all registered script names at the time of the call. public IReadOnlyList GetRegisteredScriptNames() { lock (_lock) { return _compiledScripts.Keys.ToList(); } } /// /// Returns whether a script with the given name is registered. /// /// Name of the shared script to look up. /// true if a script with the given name is registered; otherwise false. public bool Contains(string name) { lock (_lock) { return _compiledScripts.ContainsKey(name); } } }