Architecture Idea: Imperative shell, functional core using push-based stream #1323
Replies: 2 comments
-
There are some major components that could be pipeline-ized; for better organization, i.e.
The core design of the Kanata internals though is that it is a state machine with transitions being key input and 1-ms-ticks. I don't currently envision a way for something like:
to cleanly fit into this model. Looking specifically at rxRust, my initial thought like it uses an async framework and would have too much overhead compared to the processing loop that currently runs in a single thread today. Looking at both libs, I don't see how the operations within either of rxRust or pushgen can help to implement an action like |
Beta Was this translation helpful? Give feedback.
-
@jtroo thank you for the explanation! |
Beta Was this translation helpful? Give feedback.
-
Kanata is an amazing piece of work and achieves everything I've ever dreamed of from a keyboard remapper:
I would love to contribute more to it, even though at the moment I don't even know what else one could desire. 😉
One thing that might make future development easier is to isolate side effects from the key transformation logic.
In Scala, one design pattern that is often used is the "imperative shell + functional core". For Kanata, this would mean isolating the two main side effects "reading scancodes" and "emitting keyboard / mouse events" into one or two specific classes or modules. Then, all the transformation logic (mapping a scancode to a keycode, grouping multiple keycodes in a chord, etc.) could remain purely functional and side-effect-free (i.e., it would not directly invoke
kb.press_key
orkb.release_key
).The key transformation logic could be viewed as a push-based stream, where scancodes are pushed into a "source" and processed through a series of transformations before finally arriving at a "sink" that handles the actual side effects (e.g.,
kb.press_key
).The (very much simplified) resulting code would look something like this:
Stream operations
The following stream operations would probably be required:
partition
can be seen as two filters based on a predicate and its negation.Advantages
Disadvantages
Implementation
rxRust is a Rust implementation of ReactiveX and seems to have implemented the required operations.
Alternatively there's a less actively maintained library called pushgen with a
Generator
trait which looks suitable for the use case and has some of the required stream operations already implemented.There's nothing like
buffer
though, or any other time-based operation.Beta Was this translation helpful? Give feedback.
All reactions