Loading layouts
This is a design document for a way to load layouts in the current system of event→state→outcome→command.
Background: Application state wants to be immutable for easy inspection, manipulation, and snapshotting. On the other hand, layout selection and loading is a core part of Squeekboard, and a lot of state logic depends on it. However, it makes Squeekboard state depend on the layouts residing on the very mutable file system, and so, the file system needs to be accessible as part of state manipulation.
There's no escaping that Squeekboard's state includes some of its mutable environment, unless we want to load all layouts at startup (we don't).
Solution 1: simple but bad
When calculating state updates, inspect the file system as if it was part of the state structure.
Cons:
- Harder to debug: the event + state (printed in debug logs) no longer describe the next state
- reading from the file system is synchronous and slow, will lead to stuttering
Solution 2: layout cache
Add explicit piece of state in a separate actor called "layout loader", which contains the logic for layout selection, as well as loading.
The state logic sends a request to load a layout based on some relevant conditions (output size, hints, etc). Application state/outcome needs a field: Option<LoadingLayout(desired_conditions)>. It sends a command to the dispatcher: LoadLayout(desired_conditions). The dispatcher launches a task to load the layout. Then state receives an event: LayoutLoaded(layout, desired_conditions), checks if the conditions under which the request was made are still applicable, and either accepts or rejects the loaded layout.
Cons:
- It's complex
- layout loader stops being part of the main state
- layout loader must decide the best available layout, which is nontrivial logic for something not in main state