Skip to content
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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

bushrat011899
Copy link
Contributor

@bushrat011899 bushrat011899 commented Dec 10, 2024

Objective

Solution

  • Added the following features:
    • std (default)
    • async_executor (default)
    • edge_executor
    • critical-section
    • portable-atomic
  • Gated tracing in bevy_utils to allow compilation on certain platforms
  • Switched from tracing to log for simple message logging within bevy_ecs. Note that tracing supports capturing from log so this should be an uncontroversial change.
  • Fixed imports and added feature gates as required

Testing

  • Added to compile-check-no-std CI command
  • cargo 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 requires std. To use Bevy on a no_std platform, you must disable default features and enable the new edge_executor and critical-section features. You may also need to enable portable-atomic if your platform does not natively support all atomic types and operations used by Bevy.

[dependencies]
bevy_ecs = { version = "0.16", default-features = false, features = [
  # Required
  "edge_executor",
  "critical-section",

  # Required for platforms with incomplete atomics (e.g., Raspberry Pi Pico)
  "portable-atomic",

  # Optional
  "bevy_reflect",
  "serialize",
  "bevy_debug_stepping"
] }

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 the no_std working group on the Bevy Discord server.

Keep an eye out for future no_std updates as we continue to improve the parity between std and no_std. We look forward to seeing what kinds of applications are now possible with Bevy!

Notes

  • Creating PR in draft to ensure CI is passing before requesting reviews.
  • This implementation has no support for multithreading in no_std, especially due to NonSend being unsound if allowed in multithreading. The reason is we cannot check the ThreadId in no_std, so we have no mechanism to at-runtime determine if access is sound.

@bushrat011899 bushrat011899 added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events X-Contentious There are nontrivial implications that should be thought through D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 10, 2024
@alice-i-cecile alice-i-cecile added the M-Needs-Release-Note Work that should be called out in the blog due to impact label Dec 10, 2024
Copy link
Contributor Author

@bushrat011899 bushrat011899 left a 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.

Comment on lines +39 to +49
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",
]
Copy link
Contributor Author

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.

Comment on lines +95 to +96
tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false }
Copy link
Contributor Author

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.

Copy link
Member

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};
Copy link
Contributor Author

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.

Comment on lines +32 to +36
#[cfg(feature = "portable-atomic")]
use portable_atomic_util::Arc;

#[cfg(not(feature = "portable-atomic"))]
use alloc::sync::Arc;
Copy link
Contributor Author

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.

Comment on lines +1993 to +1997
// `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
Copy link
Contributor Author

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.

Comment on lines -76 to +88
#[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;
Copy link
Contributor Author

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.

Comment on lines +10 to +14
#[cfg(feature = "std")]
use std::sync::{OnceLock, PoisonError, RwLock};

#[cfg(not(feature = "std"))]
use spin::{once::Once as OnceLock, rwlock::RwLock};
Copy link
Contributor Author

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.

@bushrat011899 bushrat011899 marked this pull request as ready for review December 11, 2024 00:54
@bushrat011899
Copy link
Contributor Author

CI is passing, so I consider this PR ready for review!

Comment on lines +92 to +101
/// 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;
}
Copy link
Contributor Author

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.

Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants