feat(siteruntime): event-driven Attributes.WriteBatchAndWaitAsync (batched DCL write + trigger + existing WaitForAttribute waiter) + compile mirror

This commit is contained in:
Joseph Doherty
2026-06-17 12:13:02 -04:00
parent e8db6c71a8
commit 0e989c867d
8 changed files with 363 additions and 0 deletions
@@ -525,6 +525,44 @@ public class ScriptRuntimeContext
}
}
/// <summary>
/// Writes a SET of data-sourced attributes to the device in ONE DCL batch round-trip,
/// then writes a trigger flag, then waits EVENT-DRIVEN (reusing the existing
/// <see cref="WaitAttribute"/> waiter — not a poll) for <paramref name="responseAttr"/>
/// to reach <paramref name="responseEncoded"/>, bounded by <paramref name="timeout"/>.
/// Replaces N sequential per-attribute writes with a single gateway call before the
/// wait. Throws <see cref="InvalidOperationException"/> if the write/trigger leg fails
/// (resolution error, multi-connection batch, device error); returns the wait result
/// otherwise (true = matched, false = timeout, never throws on timeout).
/// </summary>
/// <param name="encodedValues">Scope-resolved attribute name → codec-encoded value to batch-write.</param>
/// <param name="triggerAttr">Scope-resolved trigger attribute name written AFTER the batch.</param>
/// <param name="triggerEncoded">Codec-encoded value for the trigger.</param>
/// <param name="responseAttr">Scope-resolved attribute to wait on.</param>
/// <param name="responseEncoded">Codec-encoded target value (null ⇒ any change).</param>
/// <param name="timeout">How long to wait for the response before returning false.</param>
/// <returns><c>true</c> on response match within the timeout; <c>false</c> on timeout.</returns>
public async Task<bool> WriteBatchAndWait(
IReadOnlyDictionary<string, string?> encodedValues, string triggerAttr, string? triggerEncoded,
string responseAttr, string? responseEncoded, TimeSpan timeout)
{
var cid = Guid.NewGuid().ToString();
var batchReq = new WriteAttributeBatchRequest(
cid, _instanceName, encodedValues, triggerAttr, triggerEncoded, DateTimeOffset.UtcNow);
// 35s: the InstanceActor's DCL Ask is internally bounded at 30s, so allow a small
// margin so the DCL's own typed failure/timeout reply is the one we observe rather
// than an AskTimeoutException here. Honors the script execution-timeout token.
var batchResp = await _instanceActor.Ask<WriteAttributeBatchResponse>(
batchReq, TimeSpan.FromSeconds(35), _scriptTimeoutToken);
if (!batchResp.Success)
throw new InvalidOperationException(
$"WriteBatchAndWait write failed: {batchResp.ErrorMessage}");
return await WaitAttribute(responseAttr, responseEncoded, null, timeout);
}
/// <summary>
/// Calls a sibling script on the same instance by name (Ask pattern).
/// WP-20: Enforces recursion limit.