-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Add no_std
support to bevy_ecs
#16758
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's some comments to assist reviewers with evaluating this PR. It is fairly lengthy so I'd recommend skimming through these first.
critical-section = [ | ||
"dep:critical-section", | ||
"bevy_tasks/critical-section", | ||
"portable-atomic?/critical-section", | ||
] | ||
portable-atomic = [ | ||
"dep:portable-atomic", | ||
"dep:portable-atomic-util", | ||
"bevy_tasks/portable-atomic", | ||
"concurrent-queue/portable-atomic", | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These features allow compiling on platforms without full support for atomics. Currently, they just enable features in our other dependencies and provide a couple of shims for things like Arc
. In the future, we may be able to better leverage critical-section
for other operations synchronisation areas.
tracing = { version = "0.1", default-features = false, optional = true } | ||
log = { version = "0.4", default-features = false } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously, tracing
was being brought in via a re-export from bevy_utils
. I have opted to flatten this dependency, as in general flatter dependency graphs yield faster compilation times. Additionally, I'm bringing in log
to allow the basic logging (warn!
, trace!
, etc.) to work on platforms tracing
does not support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should swap to a workspace dependency IMO: synchronizing these manually is silly. But that probably warrants its own independent discussion.
@@ -26,6 +26,7 @@ use crate::{ | |||
observer::Observers, | |||
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow}, | |||
}; | |||
use alloc::{boxed::Box, vec::Vec}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most files have changed that look like this; just including items that were previously implicitly available.
#[cfg(feature = "portable-atomic")] | ||
use portable_atomic_util::Arc; | ||
|
||
#[cfg(not(feature = "portable-atomic"))] | ||
use alloc::sync::Arc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a handful of atomic types, this kind of feature gating is required to support platforms without full native atomic support. In general, portable-atomic
is a drop-in replacement for alloc::sync
.
// `portable-atomic-util` `Arc` is not able to coerce an unsized | ||
// type like `std::sync::Arc` can. Creating a `Box` first does the | ||
// coercion. | ||
// | ||
// This would be resolved by https://github.com/rust-lang/rust/issues/123430 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This area is a little frustrating. In short, std::sync::Arc
has certain privileges that portable_atomic_util::Arc
doesn't, the key one being unsized coercion. To work around this, we first create a Box
(which can do the coercion), and then create an Arc
from that box. This code is a little verbose to handle the 2×2 feature matrix of tracking changes and portable atomics while also ensuring the Box
workaround is only used when portable-atomic
is enabled.
#[cfg(not(target_has_atomic = "64"))] | ||
#[cfg(all(not(target_has_atomic = "64"), not(feature = "portable-atomic")))] | ||
use core::sync::atomic::AtomicIsize as AtomicIdCursor; | ||
#[cfg(all(not(target_has_atomic = "64"), feature = "portable-atomic"))] | ||
use portable_atomic::AtomicIsize as AtomicIdCursor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tried it, but I believe using portable-atomic
may allow access to an AtomicI64
on all platforms, removing the possible panic. This should be investigated in a follow-up PR.
#[cfg(feature = "std")] | ||
use std::sync::{OnceLock, PoisonError, RwLock}; | ||
|
||
#[cfg(not(feature = "std"))] | ||
use spin::{once::Once as OnceLock, rwlock::RwLock}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A handful of sync primitives are conditionally replaced with alternatives from spin
. I would like to revisit our reliance on spin
at a later date. Spinning is considered a last-resort sync option, and designing it out may improve std
performance too.
CI is passing, so I consider this PR ready for review! |
/// Exports used by macros. | ||
/// | ||
/// These are not meant to be used directly and are subject to breaking changes. | ||
#[doc(hidden)] | ||
pub mod __macro_exports { | ||
// Cannot directly use `alloc::vec::Vec` in macros, as a crate may not have | ||
// included `extern crate alloc;`. This re-export ensures we have access | ||
// to `Vec` in `no_std` and `std` contexts. | ||
pub use alloc::vec::Vec; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new required components recursion check uses a Vec
, which isn't in the global namespace with no_std
. This technique of adding a pub mod __macro_exports { ... }
is how bevy_reflect
gets around this issue, so this is at least consistent, even if it isn't ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could define a feature-flagged type alias here instead right? But yeah, consistent is fine for now.
Objective
no_std
Bevy #15460Solution
std
(default)async_executor
(default)edge_executor
critical-section
portable-atomic
tracing
inbevy_utils
to allow compilation on certain platformstracing
tolog
for simple message logging withinbevy_ecs
. Note thattracing
supports capturing fromlog
so this should be an uncontroversial change.Testing
compile-check-no-std
CI commandcargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section,portable-atomic --target thumbv6m-none-eabi
cargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section
Draft Release Notes
Bevy's core ECS now supports
no_std
platforms.In prior versions of Bevy, it was not possible to work with embedded or niche platforms due to our reliance on the standard library,
std
. This has blocked a number of novel use-cases for Bevy, such as an embedded database for IoT devices, or for creating games on retro consoles.With this release,
bevy_ecs
no longer requiresstd
. To use Bevy on ano_std
platform, you must disable default features and enable the newedge_executor
andcritical-section
features. You may also need to enableportable-atomic
if your platform does not natively support all atomic types and operations used by Bevy.Currently, this has been tested on bare-metal x86 and the Raspberry Pi Pico. If you have trouble using
bevy_ecs
on a particular platform, please reach out either through a GitHub issue or in theno_std
working group on the Bevy Discord server.Keep an eye out for future
no_std
updates as we continue to improve the parity betweenstd
andno_std
. We look forward to seeing what kinds of applications are now possible with Bevy!Notes
no_std
, especially due toNonSend
being unsound if allowed in multithreading. The reason is we cannot check theThreadId
inno_std
, so we have no mechanism to at-runtime determine if access is sound.