namespace ScadaLink.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". /// Reads block on the actor Ask; async variants are provided for callers /// that prefer to await explicitly. /// public class AttributeAccessor { private readonly ScriptRuntimeContext _ctx; /// Canonical-name prefix, e.g. "" for root or "TempSensor" for a composition. public string ScopePrefix { get; } public AttributeAccessor(ScriptRuntimeContext ctx, string prefix) { _ctx = ctx; ScopePrefix = prefix; } public string Resolve(string key) => ScopePrefix.Length == 0 ? key : ScopePrefix + "." + key; public object? this[string key] { get => _ctx.GetAttribute(Resolve(key)).GetAwaiter().GetResult(); set => _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty); } public Task GetAsync(string key) => _ctx.GetAttribute(Resolve(key)); public Task SetAsync(string key, object? value) { _ctx.SetAttribute(Resolve(key), value?.ToString() ?? string.Empty); return Task.CompletedTask; } } /// /// 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; } public AttributeAccessor Attributes { get; } public CompositionAccessor(ScriptRuntimeContext ctx, string path) { _ctx = ctx; Path = path; Attributes = new AttributeAccessor(ctx, path); } public string ResolveScript(string scriptName) => Path.Length == 0 ? scriptName : Path + "." + scriptName; public Task CallScript(string scriptName, IReadOnlyDictionary? 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; public ChildrenAccessor(ScriptRuntimeContext ctx, string selfPath) { _ctx = ctx; _selfPath = selfPath; } public CompositionAccessor this[string compositionName] { get { var path = _selfPath.Length == 0 ? compositionName : _selfPath + "." + compositionName; return new CompositionAccessor(_ctx, path); } } } internal static class ScopeAccessorFactory { public static AttributeAccessor AttributesFor(ScriptRuntimeContext ctx, string selfPath) => new(ctx, selfPath); public static ChildrenAccessor ChildrenFor(ScriptRuntimeContext ctx, string selfPath) => new(ctx, selfPath); public static CompositionAccessor? ParentFor(ScriptRuntimeContext ctx, string? parentPath) => parentPath == null ? null : new CompositionAccessor(ctx, parentPath); }