From 3aad252b695d8ea5306b08ed950e1b8603ed5d72 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 5 Jun 2025 17:44:42 -0500 Subject: [PATCH] feat: --- astro.config.mjs | 2 - .../state-encoding.webp | Bin 0 -> 9886 bytes .../refactoring-a-state-machine.mdx | 192 ++++++++++++++++++ src/pages/index.astro | 6 +- 4 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 public/posts/refactoring-a-state-machine/state-encoding.webp create mode 100644 src/content/posts/autonomous-racing/refactoring-a-state-machine.mdx diff --git a/astro.config.mjs b/astro.config.mjs index 3757b91..0a64412 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -14,8 +14,6 @@ export default defineConfig({ }), ], markdown: { - remarkPlugins: [remarkMath], - rehypePlugins: [rehypeKatex], shikiConfig: { theme: "github-light", langs: [], diff --git a/public/posts/refactoring-a-state-machine/state-encoding.webp b/public/posts/refactoring-a-state-machine/state-encoding.webp new file mode 100644 index 0000000000000000000000000000000000000000..ee985b2145cdb20a032bdc9420c3de5a2a3204f6 GIT binary patch literal 9886 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 param_types; + std::vector> params; +}; + +struct Expression { + std::string var, func, value; +}; + +struct Condition { + int left, right; + std::string logic_op = ""; +}; +``` + +A state action could be anything from a variable assignment to a function call, all of which had variable syntax and parsing logic by extension. One method had the following signature: + +```cpp +std::unordered_map>> StateMachine::check_state_change; +``` + +It was time for a change. + +# the refactor + +## testing + +As I was unaware of the internals of every possible state transition in the machine, I simply coded up an exhaustive test across all transitions looking something like below. Invalid/impossible state transitions according to the Marelli flag system are omitted for simplicity. + +```cpp +state_machine_->override_state(current_state); + +for (int tf : track_flags) { + for (int vf : vehicle_flags) { + for (int pl : pit_locations.at(current_state)) { + for (double vs : vehicle_speeds.at(current_state)) { + if (state_machine_->get_current_state() != expected_state) { + FAIL() << "incorrect state change\n"; + ... +``` + +This was integrated into our CI via `CMake` + `colcon test`, giving (near) certainty that any state machine passing the test was exactly what we needed. + +## design considerations + +I knew state machines were ubiquitous in software so I explored a few previous approaches: + +1. One big switch: nested conditions (i.e. all combinations of vehicle + track flags) seemed a bit nightmarish to look at as we continue to build out the state machine + +2. [Boost SMS](https://www.boost.org/library/latest/msm/): this and other existing libraries looked enticing but three things stopped me: + 1. Integrating dependencies into our production code is simple but takes time for other members of the team to vet it + 2. It also bloats our deployment latency and final size because: + 3. It is overkill and writing things from scratch is more fun + +3. A minimalist and expressive approach: + +## final design: templates and bitmasks + +Considering each element of the state machine, I simplified it into the following components: + +- State: uniquely encode vehicle and track flags +- Transitions: perform arbitrary side effects depending on state +- Conditions: limit state transitions according to entry and exit states + +### struct design + +Since we only have $N\ll 32$ vehicle flags and $M\ll 32$ track flags, all of this information (all of the state flags) can be encoded in a `uint32_t`: + +![state-encoding](/public/posts/refactoring-a-state-machine/state-encoding.webp) + +The state must also store a few other relevant details: + +```cpp +using VehicleTrackFlag = uint32_t; + +struct StateInfo { + VehicleTrackFlag flags; + uint8_t pit_location; + bool pit_stop; + double speed; + ... +}; + +enum class StateID { State1, State2, ..., Last }; +``` + +A transition has entry and exit conditions which are compositions of state (i.e. just our bitmask) and an arbitrary action (i.e. a lambda). The entry/exit bits must be set/unset in order to enter/exit the state. + + +```cpp +struct Transition { + VehicleTrackFlags entry; + VehicleTrackFlags exit; + std::function action{}; + StateID to; +}; +``` + +### the event loop + +Now that some of the low-level details are resolved, I considered the design top-down. I wanted the main loop to simply iterate through all connected states and check if they could be entered: + +```cpp +void tick(State& state, StateInfo& state_id) { + auto mask = state.flags; + for (auto&& prospective_state : state_table[current]) { + bool enter = (mask & prospective_state.entry) == prospective_state.entry; + bool exit = (mask & prospective_state.exit ) == 0; + if (enter && exit) { + prospective_state.action(state); + state_id = prospective_state.to; + break; + } + } +} +``` + +The `state_table` would need to be a "master lookup" mapping a state id (enum index) to several `Transition`s[^1]: + +```cpp +constexpr std::array, + static_cast(StateID::Last)> +state_table { + red_track_flags_transitions, + switch_to_pits_transitions, + ... +}; +``` + +Finally, the collection of transitions from some state to another are declaratively defined. I inlined each `Transition` with [anonymous struct syntax](https://en.cppreference.com/w/c/language/struct_initialization.html) for the purposes of brevity: + +```cpp +constexpr Transition red_track_flag_transitions[] = { + // entry exit action destination + ... + // example: stay in the same state + { 0, StateID::RedTrack, null_action, make_vtf(TrackFlag::Red) } +}; +``` + +## final considerations + +The final state machine is not bad: + +- Expressive and (relatively) succinct considering the massive amounts of logic encoded +- Easily extendible +- Memory efficient (no heap allocations, compressed state) +- Quick: the [tight event loop](https://www.wikiwand.com/en/dictionary/tight_loop) is fast, especially given the outdegree of any state machine node is $\leq5$ + +but it of course has some downsides: + +- Inflexible for new logic: entry/exit conditions are mere bits. More comprehensive conditions (i.e. "perform at least 3 laps before entering this state") would require a refactor with lambdas +- Harder to read than the YAML custom language (we're not going back to that) + +A large improvement if you ask me. + +[^1]: Why a [std::span](https://www.cppreference.com/w/cpp/container/span.html)? Here, the number of transitions for each array is known at compile-time. A `std::vector` would heap-allocate the structs and a `std::array` would be difficult given that the `Transition` arrays are each of varying sizes. In contrast, one can extend this state machine by adding an entry to the array and nothing more—the `std::span` performs the magic. diff --git a/src/pages/index.astro b/src/pages/index.astro index b9ec17e..f0491ba 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -39,12 +39,12 @@ Object.keys(postsByCategory).forEach((category) => {
  • software
  • -
  • - meditations -
  • autonomous racing
  • +
  • + meditations +