-
Notifications
You must be signed in to change notification settings - Fork 43
blog: Bevy ECS no_std #442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
60e4bdc
blog: Bevy ECS no_std - initial draft
georgik 189ea0b
blog: update Bevy article
georgik da091ac
bevy: udpate article, add Spooky
georgik 29163ec
bevy: clean up some parts
georgik 70b5733
bevy: add featured image
georgik 5ebc172
blog: bevy - include demo as iframe
georgik bc354ec
blog: bevy - update embedded code
georgik 0af44c4
blog: bevy - include demo as iframe
georgik 57228f8
blog: bevy use developer portal location for Bevy app and Youtube for…
georgik aa97a58
update comments from review
georgik a823878
relocate the article
georgik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
211 changes: 211 additions & 0 deletions
211
content/blog/bevy-ecs-on-esp32-with-rust-no-std/index.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| --- | ||
| title: "Bevy Entity Component System on ESP32 with Rust no_std" | ||
| date: "2025-04-06" | ||
| showAuthor: false | ||
| authors: | ||
| - "juraj-michalek" | ||
| tags: ["Embedded Systems", "ESP32", "ESP32-S3", "ESP32-C3", "Rust", "Bevy", "no_std", "ECS", "WASM"] | ||
| --- | ||
|
|
||
| ## Introduction | ||
|
|
||
| Embedded development in Rust is rapidly evolving, and one of exciting new developments is the introduction | ||
| of [**no_std** support](https://www.youtube.com/live/Ao2gXd_CgUc?si=emIdJlz5fbGJQAx6&t=236) | ||
| into the [Bevy Entity Component System (ECS)](https://bevyengine.org/learn/quick-start/getting-started/ecs/). | ||
| This improvement allows developers to leverage the powerful and modular design of Bevy ECS | ||
| on resource‑constrained devices like the ESP32. | ||
|
|
||
| In this article, we demonstrate how to build an embedded application using Rust no_std and Bevy ECS | ||
| on an ESP32 device, using a simulation of [Conway’s Game of Life](https://github.com/georgik/esp32-conways-game-of-life-rs) and | ||
| [ESP32 Spooky Maze Game](https://github.com/georgik/esp32-spooky-maze-game) as our examples. | ||
|
|
||
| <div style="display: flex; justify-content: center; margin: 20px 0;"> | ||
| <iframe | ||
| src="https://developer.espressif.com/persist/rust/esp32-conways-game-of-life-rs/" | ||
| width="640" | ||
| height="480" | ||
| style="border: none; overflow: hidden;" | ||
| scrolling="no" | ||
| title="Conway's Game of Life WASM Demo"> | ||
| </iframe> | ||
| </div> | ||
|
|
||
| Although Conway’s Game of Life is a classic cellular automaton, our primary focus is on structuring embedded applications using Bevy ECS principles. | ||
|
|
||
| This approach helps organize code into clean, modular systems, ideal for interactive and data-intensive applications. | ||
|
|
||
| {{< youtube BeDaT9ydHU0 >}} | ||
|
|
||
| The second example, the Spooky Maze Game, is more complex, demonstrating an event-based approach to integrate peripherals like accelerometers with application logic. | ||
|
|
||
| {{< youtube JdYz991F9S8 >}} | ||
|
|
||
| ## What is Bevy ECS? | ||
|
|
||
| Bevy ECS is the core data‑oriented architecture of the Bevy game engine. It provides a way to structure programs by breaking them into entities (which represent objects), components (which hold data), and systems (which operate on entities with specific components). With the introduction of [no_std support](https://www.youtube.com/live/Ao2gXd_CgUc?si=emIdJlz5fbGJQAx6&t=236), Bevy ECS can now be used in bare‑metal environments where the standard library is not available—making it a compelling choice for embedded | ||
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| [Rust development for ESP32](https://developer.espressif.com/blog/2025/02/rust-esp-hal-beta/). | ||
|
|
||
| ## Advantages for Embedded Rust Developers | ||
|
|
||
| Many advantages of Rust for embedded development were described in the article [Rust + Embedded: A Development Power Duo](https://developer.espressif.com/blog/rust-embedded-a-development-power-duo/). | ||
|
|
||
| Here are some specific advantages of using ECS in Rust applications: | ||
|
|
||
| - **Modularity and Maintainability:** ECS encourages the separation of data and behavior into independent systems, which leads to clearer, easier-to-maintain code. | ||
| - **Efficient Resource Management:** The data‑oriented design can lead to better cache utilization and efficient processing, critical for devices with limited memory and processing power. | ||
| - **Scalability:** Even on microcontrollers, ECS allows you to extend your application with additional features or behaviors without significant restructuring. | ||
| - **Familiarity:** Developers experienced with ECS on desktop or game development can leverage similar patterns on embedded platforms. | ||
|
|
||
| ## Hardware and Software Setup | ||
|
|
||
| ### Hardware Requirements | ||
|
|
||
| - **ESP32 Development Board:** ESP32-S3, ESP32-C3, or similar variants. | ||
| - **Display Module:** For example, an ILI9486-based display connected via SPI. | ||
|
|
||
| If you're uncertain which hardware to choose, we recommend the [ESP32-S3-BOX-3](https://github.com/espressif/esp-box?tab=readme-ov-file#esp-box-aiot-development-framework) featuring ESP32-S3 with PSRAM and a display with touch control. | ||
|
|
||
| ### Software Requirements | ||
|
|
||
| - **Rust Toolchain:** Use upstream Rust toolchain (version 1.85.0.0 or later) for RISC-V targets (ESP32-C, ESP32-P, ESP32-H) or installation via [espup](https://github.com/esp-rs/espup) for Xtensa targets (ESP32, ESP32-S). | ||
| - **ESP‑HAL and mipidsi:** These crates provide the hardware abstraction and display support for ESP32 devices. | ||
| - **Bevy ECS (no_std):** The latest no_std support in Bevy ECS lets you use its powerful ECS model on bare‑metal targets. | ||
|
|
||
| ## Building the Application | ||
|
|
||
| The Conway’s Game of Life example manages a simulation grid as an ECS resource. Systems update the game state and render to an off-screen framebuffer, which is then output to a physical display. A WASM version also simulates the display using an HTML canvas in a web browser. | ||
|
|
||
| For ESP32-based projects, the code is compiled as a bare‑metal Rust application using no_std. Flashing the binary onto your hardware is done using [espflash](https://github.com/esp-rs/espflash) or [probe-rs](https://github.com/probe-rs/probe-rs) configured in [`.config/cargo.toml`](https://github.com/georgik/esp32-conways-game-of-life-rs/blob/main/esp32-c3-lcdkit/.cargo/config.toml). | ||
|
|
||
| ## Installing tooling | ||
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The tooling could be installed by [`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) or simply from source code: | ||
|
|
||
| ```sh | ||
| cargo install espup espflash | ||
| espup install # For ESP32, ESP32-S | ||
| ``` | ||
| ## ESP32 Conway's Game of Life | ||
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Running the Application | ||
|
|
||
| #### ESP32-S3-BOX-3 | ||
|
|
||
| ```sh | ||
| cd esp32-s3-box-3 | ||
| cargo run --release | ||
| ``` | ||
|
|
||
| #### ESP32-C3 | ||
|
|
||
| Use the upstream Rust toolchain with the RISC‑V target: | ||
| ```sh | ||
| cd esp32-c3-lcdkit | ||
| cargo run --release | ||
| ``` | ||
|
|
||
| ### ESP32 Spooky Maze Game | ||
|
|
||
| In this small application, a player navigates a maze collecting coins while using special power‑ups to overcome obstacles. When collisions occur (with coins, NPCs, etc.), events are dispatched so that game logic remains decoupled from hardware‑specific input. | ||
|
|
||
|
|
||
| #### ESP32-S3-BOX-3 | ||
|
|
||
| ```sh | ||
| cd spooky-maze-esp32-s3-box-3 | ||
| cargo run --release | ||
| ``` | ||
|
|
||
| #### Desktop | ||
|
|
||
| ```sh | ||
| cd spooky-maze-desktop | ||
| cargo run | ||
| ``` | ||
|
|
||
| ### Structure of main.rs | ||
|
|
||
| Bevy has support for Builder pattern which greatly simplifies the way how the application needs to be structured and allows easy connection between different systems. | ||
|
|
||
| Here's sample code: | ||
|
|
||
| ```rust | ||
| let mut app = App::new(); | ||
| app.add_plugins((DefaultPlugins,)) | ||
| .insert_non_send_resource(DisplayResource { display }) | ||
| .insert_non_send_resource(AccelerometerResource { sensor: icm_sensor }) | ||
| .insert_resource(FrameBufferResource::new()) | ||
| .add_systems(Startup, systems::setup::setup) | ||
| .add_event::<PlayerInputEvent>() | ||
| .add_event::<CoinCollisionEvent>() | ||
| .add_event::<NpcCollisionEvent>() | ||
| .add_systems( | ||
| Update, | ||
| ( | ||
| player_input::dispatch_accelerometer_input::<MyI2c, MyI2cError>, | ||
| systems::process_player_input::process_player_input, | ||
| collisions::npc::detect_npc_collision, | ||
| collisions::npc::handle_npc_collision, | ||
| systems::npc_logic::update_npc_movement, | ||
| systems::game_logic::update_game, | ||
| embedded_systems::render::render_system, | ||
| ), | ||
| ) | ||
| .run(); | ||
|
|
||
| ``` | ||
|
|
||
| ## Key Technical Decisions | ||
|
|
||
| ### Custom Renderer for Embedded Devices | ||
|
|
||
| Because Bevy’s built‑in rendering and UI systems aren’t available in no_std mode, we implemented a custom renderer using the Embedded Graphics crate. This renderer draws the maze, sprites, and HUD elements to an off‑screen framebuffer, then flushes the output to the physical display. In addition, a sprite filtering layer (implemented via a custom SpriteBuf wrapper) discards “magic pink” pixels that denote transparency in our sprite assets. | ||
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### Event‑Driven Architecture | ||
|
|
||
| Input events (keyboard on desktop and accelerometer on embedded) are dispatched via ECS events and then processed by dedicated systems. This design decouples hardware input from game rules and collision detection, making the overall system modular and maintainable. | ||
|
|
||
| ### Peripheral Resource Injection | ||
|
|
||
| Hardware peripherals like the ICM42670 accelerometer are injected as Bevy resources (using NonSend for non‑Sync hardware drivers). This allows our ECS systems to access sensor data seamlessly without directly coupling to hardware APIs. | ||
|
|
||
|
|
||
| ### Architecture of the Application | ||
georgik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| #### Shared ECS Core | ||
|
|
||
| The app’s core is implemented in the spooky-core crate using Bevy ECS. This core contains all app logic (maze generation, collision detection, event handling, etc.) and is compiled with no_std for embedded targets and with std for desktop. | ||
|
|
||
| #### Custom Renderer for Embedded | ||
|
|
||
| On embedded devices, the demo uses a custom renderer that: | ||
|
|
||
| Draws the maze background and sprites to an off‑screen framebuffer. | ||
|
|
||
| Applies a filtering layer to skip “magic pink” pixels (which represent transparency). This technique is known from DOS games. | ||
|
|
||
| Flushes the framebuffer to the physical display via SPI using the mipidsi crate. | ||
|
|
||
| #### Event‑Based Collision and Input | ||
|
|
||
| All input (keyboard or accelerometer) is dispatched as events into the ECS. Separate systems process these events to update game state (for example, moving the player or handling collisions with coins, NPCs, etc.). This design makes it easier to add new types of interactions without tightly coupling the game logic with the underlying hardware. | ||
|
|
||
| #### Resource Injection | ||
|
|
||
| Resources such as the maze, player position, HUD state, and hardware peripherals are injected into the ECS world. This approach allows systems to share data without global variables and ensures a clean separation between hardware drivers and game logic. | ||
|
|
||
| ## Conclusion | ||
|
|
||
| The integration of no_std support into Bevy ECS opens up exciting new possibilities for embedded development in Rust. By leveraging modern ECS design patterns on devices like the ESP32, developers can create modular, efficient, and scalable applications—even in resource‑constrained environments. Whether you’re a seasoned embedded developer or a game developer exploring new hardware, this approach demonstrates that you can build powerful applications with Rust and Bevy ECS on ESP32 devices. | ||
|
|
||
| ## Recommended IDE for Development | ||
|
|
||
| [Rust Rover](https://www.jetbrains.com/rust/) or [CLion with Rust Rover plugin](https://developer.espressif.com/blog/clion/) are great tools for Rust Embedded Development. | ||
|
|
||
| Another great option is [VS Code](https://code.visualstudio.com/) with Rust plugins. | ||
|
|
||
| All IDEs mentioned above supports simulation of ESP32 using [Wokwi simulator](https://plugins.jetbrains.com/plugin/23826-wokwi-simulator). | ||
georgik marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Contributing | ||
|
|
||
| Contributions are welcome! If you’d like to help improve the demo, add new features, or extend hardware support, please submit a pull request. We especially encourage contributions that further refine the embedded no_std integration or improve the custom rendering pipeline. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.