Skip to content

Use client-generated request_async_id for Unity FFI async requests and replace delegate fan-out with pending callback dispatch#195

Merged
xianshijing-lk merged 2 commits intomainfrom
sxian/CLT-2649/improve_unity_ffi_requests
Mar 20, 2026
Merged

Use client-generated request_async_id for Unity FFI async requests and replace delegate fan-out with pending callback dispatch#195
xianshijing-lk merged 2 commits intomainfrom
sxian/CLT-2649/improve_unity_ffi_requests

Conversation

@xianshijing-lk
Copy link
Contributor

Description
This PR updates the Unity FFI async request path to use client-generated request_async_id end-to-end instead of relying on Rust-generated async_id for request/callback matching.

What changed

  • Generate request_async_id on the Unity side before sending async-capable FFI requests.
  • Register async completions before crossing the FFI boundary to remove the callback-listener race.
  • Replace per-request event subscription/fan-out with a single pending-callback map keyed by request_async_id.
  • Dispatch async completions through one generic lookup path in FfiClient.
  • Add cancellation/cleanup for pending async requests on send failure and client disposal.
  • Keep unsolicited/event-stream callbacks (RoomEvent, TrackEvent, incremental stream events, RPC invocation events) on the existing event path.

Why
Previously Unity sent the request first, then relied on Rust’s returned async_id to subscribe for the async callback. If Rust completed quickly, the callback could arrive before Unity had registered its listener.

This PR removes that race by:

  • generating the ID on the client,
  • registering the pending completion first,
  • then sending the request.

It also improves callback dispatch efficiency by avoiding multicast delegate scans when many async requests are in flight.

Behavioral impact

  • Async request/response flows now match on request_async_id.
  • Multiple concurrent async requests of the same type can complete independently and out of order.
  • Pending one-shot completions are resolved or canceled at most once.
  • Failed sends no longer leave hanging pending waiters.

Affected areas

  • Room connect async completion
  • Track stats async completion
  • Local participant async operations:
    • publish/unpublish track
    • set metadata/name/attributes
    • perform RPC
    • send text/file
    • open text/byte streams
  • Data stream read-all / write / close / write-to-file operations
  • Audio frame capture async completion

Implementation notes

  • FfiRequestExtensions.InitializeRequestAsyncId now assigns RequestAsyncId generically using cached runtime reflection rather than a large generated-type switch.
  • FfiClient now owns a generic pending callback registry keyed by request_async_id.
  • FfiRequestWrap.Send() cancels the pending callback if the send path throws before completion can arrive.

Testing

@MaxHeimbrock
Copy link
Contributor

Works for me, though I don't have a full picture yet how the whole life cycle of requests and response and callbacks work. I tested on my Mac.

Copy link
Contributor

@MaxHeimbrock MaxHeimbrock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

@xianshijing-lk
Copy link
Contributor Author

Thanks @MaxHeimbrock.

We will need to do more tests once your example gets landed, and we will make more tests / examples before making a new release

@xianshijing-lk xianshijing-lk merged commit 54635a8 into main Mar 20, 2026
14 checks passed
@xianshijing-lk xianshijing-lk deleted the sxian/CLT-2649/improve_unity_ffi_requests branch March 20, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants