Skip to content

Instantly share code, notes, and snippets.

@Khitiara
Created February 12, 2024 19:01
Show Gist options
  • Save Khitiara/7c4cf8ba73255fdf46aa4b7191081c76 to your computer and use it in GitHub Desktop.
Save Khitiara/7c4cf8ba73255fdf46aa4b7191081c76 to your computer and use it in GitHub Desktop.
SilkX Async Design "Velvet"

Design Constraints

  1. Should be possible to use async/await in a single- or dual-threaded graphics context.
  2. Basic implementation can take inspiration therefore from the windows forms and/or wpf synchronization contexts.
  3. Keep heap allocations down where possible to avoid GC hits.
  4. Therefore, ValueTask everywhere.

In order to control async/await control flow, two mechanisms can be used:

  • Custom SynchronizationContext
  • Custom GetAwaiters

A custom SynchronizationContext allows propogation of async void unhandled exceptions and use of BCL synchronization primitives. Custom GetAwaiters can be used for custom synchronization primitives.

Proposed Design

The HLU windowing context owns two threads, one for rendering and one for window events. Each thread is associated with a Queue<> of structs encapsulating a pair of a SendOrPostCallback and its associated object state argument. The Queue<> can have an initial capacity set to reduce unneeded allocations when not overloaded with large numbers of work items.

Each thread routine proceeds by repeated draining the queue into a temporary array and processing each work item in order. The temporary array is needed so new queued items do not prevent non-work-items from processing, such as window events or the action of swapping buffers and frame timing. Access to the Queue<> is synchronized with a BreakneckLock. Each o the two managed threads has a custom SynchronizationContext set which queues items to the associated thread's Queue<>.

Custom Awaiter structs are implemented, returned by methods on the Window or View context, which schedule their continuations on the relevant thread. Three such awaiters are implemented, one each for scheduling on the window thread, render thread, and worker thread pool. Additional awaiters may be implemented which schedule work for the next frame or other such constructs, but these are not required or detailed by this proposal.

Each Awaiter struct implements both the await-able and the awaiter in one, exposing a single

public SomeAwaiter GetAwaiter() => this;

and then implementing the standard awaiter api surface; see MS docs for that.

Note that the details of the implementation of the queue management may be done in other ways if applicable; for example both win32 windowing and sdl provide the option to use an existing queue, through the windows dispatcher api in System.Windows.Threading and the SDL_PushEvent function respectively, and implementations may queue custom work items to those rather than use a dotnet Queue<> as appropriate to make handling windowing events easier. Any implementation of this design MUST take care that the process of draining the work item queue does not prevent windowing events from running.

Further Questions

This design presents the option to allow users to write a single main loop rather than rely on event callbacks, as the async boundaries allow native windowing events to process through the above design.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment