{"id":16021267,"url":"https://github.com/ewpratten/dirty-fsm","last_synced_at":"2025-08-03T03:34:56.046Z","repository":{"id":57619741,"uuid":"410964571","full_name":"ewpratten/dirty-fsm","owner":"ewpratten","description":"A quick and dirty state machine library","archived":false,"fork":false,"pushed_at":"2023-04-12T21:36:30.000Z","size":23,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-10T15:48:54.553Z","etag":null,"topics":["fsm","statemachine","utility"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/dirty-fsm","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ewpratten.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"ewpratten"}},"created_at":"2021-09-27T16:40:38.000Z","updated_at":"2022-03-27T10:09:37.000Z","dependencies_parsed_at":"2025-01-09T22:21:18.398Z","dependency_job_id":"f3e06839-a492-49f7-8765-a9fb6b85a196","html_url":"https://github.com/ewpratten/dirty-fsm","commit_stats":{"total_commits":8,"total_committers":1,"mean_commits":8.0,"dds":0.0,"last_synced_commit":"a1afe835539685b3735bfe59038ae0a0caa4a6ce"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":"ewpratten/rust-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewpratten%2Fdirty-fsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewpratten%2Fdirty-fsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewpratten%2Fdirty-fsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ewpratten%2Fdirty-fsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ewpratten","download_url":"https://codeload.github.com/ewpratten/dirty-fsm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240956061,"owners_count":19884499,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fsm","statemachine","utility"],"created_at":"2024-10-08T18:02:48.491Z","updated_at":"2025-02-27T00:31:34.865Z","avatar_url":"https://github.com/ewpratten.png","language":"Rust","funding_links":["https://github.com/sponsors/ewpratten"],"categories":[],"sub_categories":[],"readme":"# Dirty FSM\n[![Crates.io](https://img.shields.io/crates/v/dirty-fsm)](https://crates.io/crates/dirty-fsm) \n[![Docs.rs](https://docs.rs/dirty-fsm/badge.svg)](https://docs.rs/dirty-fsm) \n[![Build](https://github.com/Ewpratten/dirty-fsm/actions/workflows/build.yml/badge.svg)](https://github.com/Ewpratten/dirty-fsm/actions/workflows/build.yml)\n[![Clippy](https://github.com/Ewpratten/dirty-fsm/actions/workflows/clippy.yml/badge.svg)](https://github.com/Ewpratten/dirty-fsm/actions/workflows/clippy.yml)\n[![Audit](https://github.com/Ewpratten/dirty-fsm/actions/workflows/audit.yml/badge.svg)](https://github.com/Ewpratten/dirty-fsm/actions/workflows/audit.yml)\n\n\n`dirty-fsm` is a \"Quick and Dirty\" implementation of a finite state machine. Most of the concepts come from code I wrote as part of [`io.github.frc5024.lib5k.libkontrol`](https://github.com/frc5024/lib5k/tree/d1c53dcbda38824866e4117461315b26ba51905e/lib5k/src/main/java/io/github/frc5024/libkontrol/statemachines).\n\n## Example\n\nIn the following example, I model a state machine that represents a claw and a button. When the button is pressed, the claw will toggle between open and closed.\n\nWe start by setting a feature flag and loading the library.\n\n```rust ignore\n// This feature is required to use the new `#[default]` macro on enum variants\n#![feature(derive_default_enum)]\n\nuse dirty_fsm::*;\nuse thiserror::Error;\n```\n\nNext, we define the states of the machine.\n\n```rust ignore\n/// The possible states of the claw\n#[derive(Debug, Default, PartialEq, Eq, Clone, Hash)]\nenum ClawState {\n    /// The claw is closed\n    #[default]\n    ClawClosed,\n\n    /// The claw is open\n    ClawOpen\n}\n```\n\nAlong with the states, we need to define some kind of error type (although if not needed *at all*, we can just use `()`).\n\n```rust ignore\n/// Defines errors that can occur while running actions\n#[derive(Debug, Error)]\nenum ClawError {\n    /// An example error\n    #[error(\"Example error\")]\n    ExampleError,\n}\n```\n\nNext, we define the code to actually run during our first state (`ClawClosed`). This is a regular Rust struct, that implements the `Action` trait.\n\n`Action` contains a few simple functions that are called at various points throughout the action's life:\n\n- `on_register`: Called *once* when the action is registered with a state machine (via `StateMachine::add_action`)\n- `on_first_run`: Called *once* right before the first `execute` call after this state has been started or switched to. This should be treated like an initializer function. Usually used to save information about the environment before performing an operation in `execute`.\n- `execute`: Called multiple times during the action's life. This is where the action's code should go. It should be treated as the body of a `while true` loop, since it will be run over and over until it returns an `ActionFlag` that indicates the action is done.\n- `on_finish`: Called *once* right after the last `execute` call once this action is finished.\n\n```rust ignore\n/// Action that actually handles the claw being closed\n#[derive(Debug)]\nstruct ClawClosedAction;\n\nimpl Action\u003cClawState, ClawError, bool\u003e for ClawClosedAction {\n    fn on_register(\u0026mut self) -\u003e Result\u003c(), ClawError\u003e {\n        println!(\"ClawClosedAction has been registered with the state machine\");\n        Ok(())\n    }\n\n    fn on_first_run(\u0026mut self, context: \u0026bool) -\u003e Result\u003c(), ClawError\u003e {\n        println!(\"Button has been pressed, claw is closing\");\n        Ok(())\n    }\n\n    fn execute(\n        \u0026mut self,\n        delta: \u0026chrono::Duration,\n        context: \u0026bool,\n    ) -\u003e Result\u003ccrate::action::ActionFlag\u003cClawState\u003e, ClawError\u003e {\n        println!(\"Claw code is running now\");\n\n        // If the button is pressed, switch to the next claw state\n        if context {\n            Ok(ActionFlag::SwitchState(ClawState::ClawOpen))\n        } else {\n            Ok(ActionFlag::Continue)\n        }\n    }\n\n    fn on_finish(\u0026mut self, interrupted: bool) -\u003e Result\u003c(), ClawError\u003e {\n        println!(\"ClawClosedAction is done executing\");\n        Ok(())\n    }\n}\n```\n\nSince we have two states, this needs to be done again for the other state.\n\n```rust ignore\n/// Action that actually handles the claw being opened\n#[derive(Debug)]\nstruct ClawOpenedAction;\n\nimpl Action\u003cClawState, ClawError, bool\u003e for ClawOpenedAction {\n    fn on_register(\u0026mut self) -\u003e Result\u003c(), ()\u003e {\n        println!(\"ClawOpenedAction has been registered with the state machine\");\n        Ok(())\n    }\n\n    fn on_first_run(\u0026mut self, context: \u0026bool) -\u003e Result\u003c(), ClawError\u003e {\n        println!(\"Button has been pressed, claw is opening\");\n        Ok(())\n    }\n\n    fn execute(\n        \u0026mut self,\n        delta: \u0026chrono::Duration,\n        context: \u0026bool,\n    ) -\u003e Result\u003ccrate::action::ActionFlag\u003cClawState\u003e, ClawError\u003e {\n        println!(\"Claw code is running now\");\n\n        // If the button is pressed, throw an error as an example\n        if *context {\n            Err(ClawError::ExampleError)\n        } else {\n            Ok(ActionFlag::Continue)\n        }\n    }\n\n    fn on_finish(\u0026mut self, interrupted: bool) -\u003e Result\u003c(), ClawError\u003e {\n        println!(\"ClawOpenedAction is done executing\");\n        Ok(())\n    }\n}\n```\n\nFinally, the code to start and run the state machine:\n\n```rust ignore\nfn main() {\n    // Create the state machine\n    let mut claw_machine = StateMachine::new();\n    claw_machine.add_action(ClawState::ClawClosed, ClawClosedAction {}).unwrap();\n    claw_machine.add_action(ClawState::ClawOpen, ClawOpenedAction {}).unwrap();\n\n    // State. This example assumes some outside \"force\" is changing this value\n    let mut button_pressed = false;\n\n    // Run the state machine\n    loop {\n        claw_machine.run(\u0026mut button_pressed).unwrap();\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fewpratten%2Fdirty-fsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fewpratten%2Fdirty-fsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fewpratten%2Fdirty-fsm/lists"}