1092 ported the optimizer to Rust and we can now run the optimizer as a WebAssembly module in the browser. One benefit is that the optimizer can run in Web Workers since WASM modules are cloneable. Since the TS port, we've been running the optimizer on the UI thread with various hacks to unblock the UI (e.g. #1017). This issue attempts to map out a new design for the optimization API for the multi-threaded usage. Much of this is learned through experimentation in #1207.
Make the optimizer interruptible
Running the optimizer synchronously is not ideal because it may take an unknown amount of time per step. For a sufficiently complex diagram, the default 10000 steps might take seconds. With autostep on, the UI loop will request optimizer steps at a much faster rate. Similar to how requestAnimationFrame can be cancelled, in a multi-threaded context, we'd also like to cancel an optimization request, no matter where the optimizer is in its on loop. A few implementation possibilities:
wasm-mt: not sure how applicable it is but worth taking a look
Maintain a single source of truth in the UI
Instead of including the optimization steps in the UI state machine, the UI should consider the optimizer as an asynchronous service that may or may not return results in time. This requires the UI to make and resolve requests to the optimizer asynchronously while maintaining a consistent state within the UI thread. For instance, consider running the optimizer continuously as the user is dragging a shape. Both the optimizer and the user alter the diagram, and the UI needs to decide which to render. Currently for dragging, we freeze the interacted shape during optimization (#1143), but this might not be possible in all circumstances. A couple of considerations:
When the user interaction is in the feasible region (the optimizer can return a solution given the current diagram state after user interaction), the user input should be preferred. It's okay for the optimization output to lag a bit because the optimizer will eventually find a solution.
Otherwise (i.e. the user interaction will not lead to any solution), it's perhaps more reasonable to return to the previous feasible state by backtracking the user interaction in time and/or preferring the last update from the optimizer. Alternatively, we may also relax the requirement on preferring user inputs by un-freezing user inputs during optimization. In this "correction" step, the optimizer output will be preferred in the UI, as the optimizer is taking the initiative to course-correct user interaction.
Background
1092 ported the optimizer to Rust and we can now run the optimizer as a WebAssembly module in the browser. One benefit is that the optimizer can run in Web Workers since WASM modules are cloneable. Since the TS port, we've been running the optimizer on the UI thread with various hacks to unblock the UI (e.g. #1017). This issue attempts to map out a new design for the optimization API for the multi-threaded usage. Much of this is learned through experimentation in #1207.
Make the optimizer interruptible
Running the optimizer synchronously is not ideal because it may take an unknown amount of time per step. For a sufficiently complex diagram, the default 10000 steps might take seconds. With
autostep
on, the UI loop will request optimizer steps at a much faster rate. Similar to howrequestAnimationFrame
can be cancelled, in a multi-threaded context, we'd also like to cancel an optimization request, no matter where the optimizer is in its on loop. A few implementation possibilities:Atomics
andSharedArrayBuffer
within the optimizer workerwasm-mt
: not sure how applicable it is but worth taking a lookMaintain a single source of truth in the UI
Instead of including the optimization steps in the UI state machine, the UI should consider the optimizer as an asynchronous service that may or may not return results in time. This requires the UI to make and resolve requests to the optimizer asynchronously while maintaining a consistent state within the UI thread. For instance, consider running the optimizer continuously as the user is dragging a shape. Both the optimizer and the user alter the diagram, and the UI needs to decide which to render. Currently for dragging, we freeze the interacted shape during optimization (#1143), but this might not be possible in all circumstances. A couple of considerations: