feat(siteruntime): event-driven Attributes.WriteBatchAndWaitAsync (batched DCL write + trigger + existing WaitForAttribute waiter) + compile mirror
This commit is contained in:
@@ -74,6 +74,40 @@ public class AttributeAccessor
|
||||
public Task SetAsync(string key, object? value)
|
||||
=> _ctx.SetAttribute(Resolve(key), AttributeValueCodec.Encode(value) ?? string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a SET of data-sourced attributes to the device in ONE gateway batch
|
||||
/// round-trip, then writes <paramref name="flagKey"/>=<paramref name="flagValue"/>,
|
||||
/// then waits EVENT-DRIVEN (reusing the existing <c>WaitForAttribute</c> waiter — not a
|
||||
/// poll) for <paramref name="responseKey"/> to reach <paramref name="responseValue"/>,
|
||||
/// bounded by <paramref name="timeout"/>. Replaces N sequential per-attribute writes
|
||||
/// with one batched call before the wait. All keys are scope-resolved (<see cref="Resolve"/>)
|
||||
/// and all values codec-encoded just like the other accessors. Returns <c>true</c> if
|
||||
/// the response was observed within the timeout, <c>false</c> on timeout (no throw on
|
||||
/// timeout); throws <see cref="System.InvalidOperationException"/> if the batch/trigger
|
||||
/// write itself fails.
|
||||
/// </summary>
|
||||
/// <param name="values">Attribute key → value to batch-write (keys scope-resolved, values codec-encoded).</param>
|
||||
/// <param name="flagKey">Trigger attribute key written AFTER the batch.</param>
|
||||
/// <param name="flagValue">Value to write to the trigger.</param>
|
||||
/// <param name="responseKey">Attribute key to wait on.</param>
|
||||
/// <param name="responseValue">Target value to wait for (<c>null</c> ⇒ any change).</param>
|
||||
/// <param name="timeout">How long to wait before returning false.</param>
|
||||
/// <returns><c>true</c> on response match within the timeout; <c>false</c> on timeout.</returns>
|
||||
public Task<bool> WriteBatchAndWaitAsync(
|
||||
IReadOnlyDictionary<string, object?> values, string flagKey, object? flagValue,
|
||||
string responseKey, object? responseValue, TimeSpan timeout)
|
||||
{
|
||||
var encoded = new Dictionary<string, string?>(values.Count);
|
||||
foreach (var kv in values)
|
||||
encoded[Resolve(kv.Key)] = AttributeValueCodec.Encode(kv.Value);
|
||||
|
||||
return _ctx.WriteBatchAndWait(
|
||||
encoded,
|
||||
Resolve(flagKey), AttributeValueCodec.Encode(flagValue),
|
||||
Resolve(responseKey), AttributeValueCodec.Encode(responseValue),
|
||||
timeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WaitForAttribute (spec §3-§5): waits event-driven until the attribute equals
|
||||
/// <paramref name="targetValue"/> (value-equality, codec-normalized), bounded by
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user