RequestResendAsync
For streaming widgets — today, drawing_canvas — input arrives as a sequence of stroke_progress events with a per-stroke seq number. If the host detects a gap (e.g. seq 12 followed by seq 15), it can ask the phone to replay the missing events from its in-memory buffer.
RequestResendAsync does exactly that, with a positive ACK — the phone tells you whether it can replay or not, so you don’t sit indefinitely waiting for events that will never come.
Two variants
// Fire-and-forget. Result is delivered via OnResendFailed (or by the events// themselves arriving on OnInput if the replay succeeds).void RequestResend(string playerId, string widgetId, int strokeId, int fromSeq);
// Awaitable. Returns a ResendResult so you know up-front whether to expect events.Task<ResendResult> RequestResendAsync( string playerId, string widgetId, int strokeId, int fromSeq, int timeoutMs = 2000, CancellationToken ct = default);ResendResult
public enum ResendStatus { Accepted, Rejected, TimedOut }
public record ResendResult{ public ResendStatus Status; public int ReplayedCount; // Accepted: how many events the phone is replaying. public string? RejectReason; // Rejected: BufferExpired | WidgetNotReady | ...}Example — gap-fill in a Pictionary host
var result = await manager.RequestResendAsync( playerId: drawer.Id, widgetId: "canvas", strokeId: 7, fromSeq: 12, timeoutMs: 2000);
switch (result.Status){ case ResendStatus.Accepted: // Phone is replaying — events arrive on OnInput like any other stroke_progress. Debug.Log($"Replaying {result.ReplayedCount} events"); break;
case ResendStatus.Rejected: // RejectReason will be one of: BufferExpired (phone pruned it), // WidgetNotReady (canvas was unmounted), etc. AbandonGapSlot(7); break;
case ResendStatus.TimedOut: // Neither phone nor relay replied within timeoutMs. AbandonGapSlot(7); break;}When this matters
Drawing-canvas events are emitted at ~60ms cadence; under spotty cellular, packets can drop. Without resend, the host’s reconstruction of the drawing has gaps. With resend, you get every stroke or a definitive “no, you don’t” answer in ≤ 2 seconds.
For a drawing_canvas host:
- Track the per-stroke
seqyou’ve seen on eachstroke_progress. - If you detect
seq_njumping, queue aRequestResendAsync(strokeId, lastSeenSeq + 1). - On
Accepted, expectReplayedCountmore events. - On
Rejected/TimedOut, give up on that range and continue rendering — typically the player’s stroke will still look fine; the buffer is small.
Pitfalls
- Don’t await it on the input thread. The replay may take longer than your frame budget.
awaitit inside anasync voidUpdate or queue the call as a background task. - The phone’s buffer is small. Roughly the last 2 seconds of
stroke_progressevents. If you ask for older data you’ll getRejectedwithBufferExpired. - One outstanding resend per
(playerId, widgetId, strokeId). A second concurrent call may cancel the first.
Next
- Drawing canvas widget reference: drawing_canvas.
- Single-shot ask:
RequestAsync.