namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts; /// /// Scope-aware view onto the instance's attributes, anchored at a path prefix. /// Attributes["X"] on the root scope resolves to canonical name "X"; /// on a composition with prefix "TempSensor" it resolves to "TempSensor.X". /// /// /// Thread-model note (SiteRuntime-012): the indexer get/set block synchronously /// on the Instance Actor Ask (and, for data-connected attributes, the DCL /// round-trip). This is safe because script bodies execute on the dedicated /// threads (SiteRuntime-009), not the /// shared — so a blocked accessor /// cannot starve unrelated Akka dispatchers or HTTP request handling. The async /// variants (/) are still preferred /// where the script can await, as they avoid holding a dedicated thread idle for /// the duration of each round-trip. /// /// public class AttributeAccessor { private readonly ScriptRuntimeContext _ctx; /// Canonical-name prefix, e.g. "" for root or "TempSensor" for a composition. public string ScopePrefix { get; } /// /// Initializes a new instance of the AttributeAccessor with the specified context and prefix. /// /// The script runtime context. /// The canonical-name prefix for attribute resolution. public AttributeAccessor(ScriptRuntimeContext ctx, string prefix) { _ctx = ctx; ScopePrefix = prefix; } /// /// Resolves a key to its full canonical name by applying the scope prefix. /// /// The attribute key to resolve. /// The fully qualified canonical name (e.g. "TempSensor.X" or "X" for the root scope). public string Resolve(string key) => ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key; /// /// Gets or sets an attribute value synchronously by canonical name. /// /// The attribute key. public object? this[string key] { // Both reads and writes block on the actor Ask; the write also blocks // on the DCL round-trip for data-connected attributes. The async // variants (GetAsync/SetAsync) are preferred where awaiting is possible. get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult(); set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty).GetAwaiter().GetResult(); } /// /// Gets an attribute value asynchronously. /// /// The attribute key. /// A task that resolves to the attribute value, or null if not set. public Task GetAsync(string key) => _ctx.GetAttribute(Resolve(key)); /// /// Sets an attribute value asynchronously. /// /// The attribute key. /// The value to set. /// A task that represents the asynchronous operation. public Task SetAsync(string key, object? value) => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty); } /// /// A view of one composition at a path. Exposes its attributes via /// and an invokable CallScript. /// public class CompositionAccessor { private readonly ScriptRuntimeContext _ctx; /// Canonical-name path this composition is rooted at. public string Path { get; } /// /// Accessor for attributes within this composition. /// public AttributeAccessor Attributes { get; } /// /// Initializes a new instance of the CompositionAccessor with the specified context and path. /// /// The script runtime context. /// The canonical-name path for the composition. public CompositionAccessor(ScriptRuntimeContext ctx, string path) { _ctx = ctx; Path = path; Attributes = new AttributeAccessor(ctx, path); } /// /// Resolves a script name to its full canonical name by applying the composition path. /// /// The script name to resolve. /// The fully qualified canonical script name (e.g. "Module.MyScript" or "MyScript" at root). public string ResolveScript(string scriptName) => Path.Length == 0 ? scriptName : Path + "." + scriptName; /// /// Calls a script within this composition. /// /// The name of the script to call. /// Optional parameters to pass to the script. /// A task that resolves to the script's return value, or null if none. public Task CallScript(string scriptName, object? parameters = null) => _ctx.CallScript(ResolveScript(scriptName), parameters); } /// /// Dictionary-style accessor for the script's child compositions. Indexing /// returns a rooted at the child's path. /// public class ChildrenAccessor { private readonly ScriptRuntimeContext _ctx; private readonly string _selfPath; /// /// Initializes a new instance of the ChildrenAccessor with the specified context and path. /// /// The script runtime context. /// The canonical-name path of the parent composition. public ChildrenAccessor(ScriptRuntimeContext ctx, string selfPath) { _ctx = ctx; _selfPath = selfPath; } /// /// Gets a composition accessor for a child by name. /// /// The name of the child composition. public CompositionAccessor this[string compositionName] { get { var path = _selfPath.Length == 0 ? compositionName : _selfPath + "." + compositionName; return new CompositionAccessor(_ctx, path); } } } internal static class ScopeAccessorFactory { /// /// Creates an AttributeAccessor for the specified context and path. /// /// The script runtime context. /// The canonical-name path. /// A new rooted at . public static AttributeAccessor AttributesFor(ScriptRuntimeContext ctx, string selfPath) => new(ctx, selfPath); /// /// Creates a ChildrenAccessor for the specified context and path. /// /// The script runtime context. /// The canonical-name path. /// A new rooted at . public static ChildrenAccessor ChildrenFor(ScriptRuntimeContext ctx, string selfPath) => new(ctx, selfPath); /// /// Creates a CompositionAccessor for the specified context and path, or null if no parent exists. /// /// The script runtime context. /// The parent path, or null if no parent. /// A for the parent, or null when is null. public static CompositionAccessor? ParentFor(ScriptRuntimeContext ctx, string? parentPath) => parentPath == null ? null : new CompositionAccessor(ctx, parentPath); }