created_at2024-01-04 06:33:06.161197
updated_at2024-04-05 16:55:17.880158
descriptionHierarchical state machine
Mikhail Katychev (mkatychev)



# rex-sm :crown: ### Design Features - Make state machines modular units that can be reused - State machines that use other state machines are aware of who they can use, but state machines that are being used are not aware of state machines that use them - No tasking/threading - Handle multiple state machines concurrently - Trigger other state machines, and wait for their completion, then resume - Create/delete timers - Send notifications outside the state machine group - Send events to themselves - Accept events from outside the state machine group - Encourage state machine implementations to be represented in a `(, )` format ### Inspiration - [message-io]( - [async-hsm]( - [event-driven finite-state machine]( ### Overview `StateMachine`s are first registered with the `StateMachineManager`, which I will refer to as simply the `Manager`. Every call to `Manager::cycle()` processes a single event. A single event corresponds to running on a single state machine. The `Manager` accesses the contents of the `Controller` and manipulates it. A single `Controller` is shared amongst all state machines registered with the `Manager`. There are two types of events `UserEvent`s and `SystemEvent`s. `UserEvent`s are passed to `StateMachine::cycle()` while `SystemEvent`s are not. `StateMachine::cycle()` accepts a `&mut Controller` and a `UserEvent`. The `StateMachine` uses the functions in the `Controller` to add/remove events from the event queue; all functions do this except for timer related functions. `SystemEvent`s are consumed by the manager and used to modify the `Controller` internals or send data or notifications to outside the state machine group. ## Node based StateMachine Manager (in development) ## Goals * decoupling state machine input processing from a given **state’s current enumerations** * state signaling that all feeds into the same sink (the manager’s `signal_queue`) ; this allows lifts and transits to be processed **homogeneously** thus avoiding type opacity through `Box` ## In practice the design should give at most three message streams connected to a particular state machine down: I/O * One `Input` (handles both external and internal events) ```rust Signal { id: StateId, input: I, } ``` Two outputs: * `Signal` output (events meant to be processed as inputs for other state machines) * `Notification` output (events meant to be processed by _anything_ that is not a state machine fed by a given `signal_queue`) ### The `StateMachineManager` owns: * The state storage layer (using `StateStore`) * the input event stream * The state machine processors ### The `StateMachineManager` is responsible for: * routing `Signal`s to the appropriate state machines * Injecting `ProcessorContext`s into the state machines: this action is what allows state machines to cycle concurrently ### `StateStore` is responsible for: * inserting & updating various state hierarchies * operations are done concurrently by holding all node trees in `Arc>` containers. ### This allows `StateStore` storage to: * Create multiple indices (Through fresh `DashMap` key insertions) pointing to the same tree by incrementing the `Arc` count and inserting a new entry per child node * Allows independent interior mutability per state tree, decoupling unrelated states from resource contention ## Node based `TimeoutManager` (in development) Considerations: - only one timer is active per `StateId`, State machines should not have to keep track of `Operation::Set(Instant::now())` emitted to notifications. Thus, all timers should be indexable by `StateId`. - A newer `Operation::Set` for the same `StateId` should override an old timer. - A timeout should emit a `Signal` that is pertinent to the related state machine. Approach: * `TimeoutManager` implements a per tick polling approach to resolving timeouts * TimeoutManager accepts two types of inputs, set and cancel (timeout) * Timeouts are stored within the `TimeoutLedger` * `TimeoutLedger` contains a `BTreeMap` that indexes IDs by `Instant` and a `HashMap` that indexes `Instant`s by ID This double indexing allows timeout cancellations to go through without providing the `Instant` that they were meant to remove
Commit count: 0

cargo fmt