feat(scripts): self/child/parent attribute and script accessors
Phases 1+2 of the design at
docs/plans/2026-05-12-script-scope-access-design.md.
Adds ergonomic scope-aware accessors to compiled scripts. A script
on a composed TempSensor reads its own attribute via
Attributes["Temperature"]; reaches up to the parent via
Parent.Attributes["SpeedRPM"]; invokes a child script via
Children["TempSensor"].CallScript("Sample"). All resolve to the
existing flat Instance.GetAttribute / SetAttribute / CallScript
delegates by prepending the script's canonical path prefix.
Runtime types (SiteRuntime.Scripts.ScopeAccessors):
AttributeAccessor sync indexer + GetAsync / SetAsync
CompositionAccessor Attributes + CallScript
ChildrenAccessor Children["name"] => CompositionAccessor
ScriptGlobals gains Scope, Attributes, Children, Parent properties.
Sync indexer blocks on the Instance Actor Ask; explicit GetAsync /
SetAsync are also available for callers that want to await.
Plumbing:
- Commons.Types.Scripts.ScriptScope record (SelfPath / ParentPath).
- ResolvedScript.Scope (defaults to ScriptScope.Root for back-compat).
- FlatteningService emits new ScriptScope(prefix, "") for each
composed script so a script defined on TempSensor composed under
a parent gets SelfPath = "TempSensor".
- ScriptActor reads the Scope from its ResolvedScript and forwards
it through ScriptExecutionActor into ScriptGlobals on each call.
RevisionHashService not touched: the per-script canonical name
already encodes the composition path, so any structural change
already flips the hash.
10 new unit tests on the path arithmetic. Site/Template engine
suites stay green (129 + 199).
Editor surface (Phase 3: metadata fetch, Phase 4: completion +
SCADA006 / SCADA007 diagnostics) follows in the next commits.
This commit is contained in:
@@ -126,4 +126,12 @@ public sealed record ResolvedScript
|
||||
|
||||
public TimeSpan? MinTimeBetweenRuns { get; init; }
|
||||
public string Source { get; init; } = "Template";
|
||||
|
||||
/// <summary>
|
||||
/// Where the script sits in the composition tree. Seeded into ScriptGlobals
|
||||
/// so the script's <c>Attributes</c> / <c>Children</c> / <c>Parent</c>
|
||||
/// accessors resolve canonical names with the right path-prefix.
|
||||
/// </summary>
|
||||
public ScadaLink.Commons.Types.Scripts.ScriptScope Scope { get; init; }
|
||||
= ScadaLink.Commons.Types.Scripts.ScriptScope.Root;
|
||||
}
|
||||
|
||||
17
src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs
Normal file
17
src/ScadaLink.Commons/Types/Scripts/ScriptScope.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace ScadaLink.Commons.Types.Scripts;
|
||||
|
||||
/// <summary>
|
||||
/// Where a compiled script sits in the composition tree. Computed at
|
||||
/// flattening time and seeded into the script's globals at execution time
|
||||
/// so <c>Attributes["X"]</c> / <c>Parent.X</c> can prepend the right
|
||||
/// path-prefix when delegating to the existing flat
|
||||
/// <c>Instance.GetAttribute</c> / <c>Instance.SetAttribute</c> /
|
||||
/// <c>Instance.CallScript</c> APIs.
|
||||
/// </summary>
|
||||
public sealed record ScriptScope(string SelfPath, string? ParentPath)
|
||||
{
|
||||
/// <summary>Scope for a script directly on the root template (no compositions).</summary>
|
||||
public static readonly ScriptScope Root = new("", null);
|
||||
|
||||
public bool HasParent => ParentPath != null;
|
||||
}
|
||||
Reference in New Issue
Block a user