Skip to content

Commit a24848d

Browse files
authored
blog: Bevy ECS no_std (#442)
* blog: Bevy ECS no_std
1 parent 3634dde commit a24848d

File tree

2 files changed

+215
-0
lines changed

2 files changed

+215
-0
lines changed
236 KB
Loading
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
---
2+
title: "Bevy Entity Component System on ESP32 with Rust no_std"
3+
date: "2025-04-09"
4+
showAuthor: false
5+
authors:
6+
- "juraj-michalek"
7+
tags: ["Embedded Systems", "ESP32", "ESP32-S3", "ESP32-C3", "Rust", "Bevy", "no_std", "ECS", "WASM"]
8+
---
9+
10+
## Introduction
11+
12+
Embedded development in Rust is rapidly evolving, and one of exciting new developments is the introduction
13+
of [**no_std** support](https://www.youtube.com/live/Ao2gXd_CgUc?si=emIdJlz5fbGJQAx6&t=236)
14+
into the [Bevy Entity Component System (ECS)](https://bevyengine.org/learn/quick-start/getting-started/ecs/).
15+
This improvement allows developers to leverage the powerful and modular design of Bevy ECS
16+
on resource‑constrained devices like the ESP32.
17+
18+
In this article, we demonstrate how to build an embedded application using Rust no_std and Bevy ECS
19+
on an ESP32 device, using a simulation of [Conway’s Game of Life](https://github.com/georgik/esp32-conways-game-of-life-rs) and
20+
[ESP32 Spooky Maze Game](https://github.com/georgik/esp32-spooky-maze-game) as our examples.
21+
22+
<div style="display: flex; justify-content: center; margin: 20px 0;">
23+
<iframe
24+
src="https://developer.espressif.com/persist/rust/esp32-conways-game-of-life-rs/"
25+
width="640"
26+
height="480"
27+
style="border: none; overflow: hidden;"
28+
scrolling="no"
29+
title="Conway's Game of Life WASM Demo">
30+
</iframe>
31+
</div>
32+
33+
Although Conway’s Game of Life is a classic cellular automaton, our primary focus is on structuring embedded applications using Bevy ECS principles.
34+
35+
This approach helps organize code into clean, modular systems, ideal for interactive and data-intensive applications.
36+
37+
{{< youtube BeDaT9ydHU0 >}}
38+
39+
The second example, the Spooky Maze Game, is more complex, demonstrating an event-based approach to integrate peripherals like accelerometers with application logic.
40+
41+
{{< youtube JdYz991F9S8 >}}
42+
43+
## What is Bevy ECS?
44+
45+
Bevy ECS is the core data‑oriented architecture of the Bevy game engine. It provides a way to structure programs by breaking them into:
46+
47+
- **Entities** which represent objects
48+
- **Components** which hold data
49+
- **Systems** which operate on entities with specific components
50+
51+
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
52+
[Rust development for ESP32](https://developer.espressif.com/blog/2025/02/rust-esp-hal-beta/).
53+
54+
## Advantages for Embedded Rust Developers
55+
56+
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/).
57+
58+
Here are some specific advantages of using ECS in Rust applications:
59+
60+
- **Modularity and Maintainability:** ECS encourages the separation of data and behavior into independent systems, which leads to clearer, easier-to-maintain code.
61+
- **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.
62+
- **Scalability:** Even on microcontrollers, ECS allows you to extend your application with additional features or behaviors without significant restructuring.
63+
- **Familiarity:** Developers experienced with ECS on desktop or game development can leverage similar patterns on embedded platforms.
64+
65+
## Hardware and Software Setup
66+
67+
### Hardware Requirements
68+
69+
- **ESP32 Development Board:** ESP32-S3, ESP32-C3, or similar variants.
70+
- **Display Module:** For example, an ILI9486-based display connected via SPI.
71+
72+
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.
73+
74+
### Software Requirements
75+
76+
- [Rust Toolchain](https://github.com/esp-rs/espup) 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).
77+
78+
```sh
79+
cargo install espup
80+
espup install # For ESP32, ESP32-S
81+
```
82+
83+
- [espflash](https://github.com/esp-rs/espflash): Rust implementation of flashing tool for ESP32.
84+
85+
```sh
86+
cargo install espflash
87+
```
88+
Note: The Rust tooling could be also installed by [`cargo binstall`](https://github.com/cargo-bins/cargo-binstall).
89+
90+
### Crates Used in the Project
91+
92+
- [ESP‑HAL](https://github.com/esp-rs/esp-hal) and [mipidsi](https://github.com/almindor/mipidsi): These crates provide the hardware abstraction and display support for ESP32 devices.
93+
- [Bevy ECS (no_std)](https://github.com/bevyengine/bevy/issues/15460) The latest no_std support in Bevy ECS lets you use its powerful ECS model on bare‑metal targets.
94+
95+
## Building the Application
96+
97+
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.
98+
99+
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).
100+
101+
## Running Applications - Conway's Game of Life
102+
103+
- [Source code](https://github.com/georgik/esp32-conways-game-of-life-rs)
104+
105+
### ESP32-S3-BOX-3 - Conway
106+
107+
```sh
108+
cd esp32-s3-box-3
109+
cargo run --release
110+
```
111+
112+
### ESP32-C3 - Conway
113+
114+
Use the upstream Rust toolchain with the RISC‑V target:
115+
```sh
116+
cd esp32-c3-lcdkit
117+
cargo run --release
118+
```
119+
120+
### ESP32 Spooky Maze Game
121+
122+
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.
123+
124+
- [Source code](https://github.com/georgik/esp32-spooky-maze-game)
125+
126+
#### ESP32-S3-BOX-3 - Spooky
127+
128+
```sh
129+
cd spooky-maze-esp32-s3-box-3
130+
cargo run --release
131+
```
132+
133+
#### Desktop - Spooky
134+
135+
```sh
136+
cd spooky-maze-desktop
137+
cargo run
138+
```
139+
140+
### Structure of main.rs
141+
142+
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.
143+
144+
Here's sample code:
145+
146+
```rust
147+
let mut app = App::new();
148+
app.add_plugins((DefaultPlugins,))
149+
.insert_non_send_resource(DisplayResource { display })
150+
.insert_non_send_resource(AccelerometerResource { sensor: icm_sensor })
151+
.insert_resource(FrameBufferResource::new())
152+
.add_systems(Startup, systems::setup::setup)
153+
.add_event::<PlayerInputEvent>()
154+
.add_event::<CoinCollisionEvent>()
155+
.add_event::<NpcCollisionEvent>()
156+
.add_systems(
157+
Update,
158+
(
159+
player_input::dispatch_accelerometer_input::<MyI2c, MyI2cError>,
160+
systems::process_player_input::process_player_input,
161+
collisions::npc::detect_npc_collision,
162+
collisions::npc::handle_npc_collision,
163+
systems::npc_logic::update_npc_movement,
164+
systems::game_logic::update_game,
165+
embedded_systems::render::render_system,
166+
),
167+
)
168+
.run();
169+
170+
```
171+
172+
173+
174+
### Architecture of the Application
175+
176+
#### Shared ECS Core
177+
178+
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.
179+
180+
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.
181+
182+
183+
#### Custom Renderer for Embedded
184+
185+
On embedded devices, the demo uses a custom renderer that:
186+
187+
- Draws the maze background and sprites to an off‑screen framebuffer.
188+
189+
- Applies a filtering layer to skip “magic pink” pixels (which represent transparency). This technique is known from DOS games.
190+
191+
- Flushes the framebuffer to the physical display via SPI using the mipidsi crate.
192+
193+
#### Event‑Based Collision and Input
194+
195+
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.
196+
197+
#### Resource Injection
198+
199+
Resources such as the maze, player position, HUD state and 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.
200+
201+
## Conclusion
202+
203+
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.
204+
205+
## Recommended IDE for Development
206+
207+
[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.
208+
209+
Another great option is [VS Code](https://code.visualstudio.com/) with Rust plugins.
210+
211+
All IDEs mentioned above support simulation of ESP32 using [Wokwi simulator](https://plugins.jetbrains.com/plugin/23826-wokwi-simulator).
212+
213+
## Contributing
214+
215+
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.

0 commit comments

Comments
 (0)