Oh, the UI already has its own copy of the values, stored on a few @MainActor
view models, so all UI controls see changes reflected immediately, even if the visualization takes a few ms longer to update. So when the UI is updated -> @MainActor
View Model is updated -> a task to mutate the renderer
is spawned. It's even possible to keep track of those tasks to disable the specific control while the action takes place.
There are a few reasons I went with shared state with reference semantics instead of copying all state from the @MainActor
View Models at the start of every frame:
- The combined state is not lightweight enough to copy all of it up to 120 times per second.
- It allows rendering to happen only when a value has changed, using minimal energy if there are no changes.
- It completely avoids requiring a switch to the main thread to render new frames. So even if the main thread is blocked for a few milliseconds, no frames are dropped.