Allow bevy_utils in no_std Contexts (#15279)

# Objective

- Adjust `bevy_utils` to make it `no_std` compatible
- Partially replaces #6581
- Contributes to #8161
- Contributes to #6370

## Solution

Added `alloc` and `std` features to `bevy_utils` (`std` is enabled by
default), allowing the crate's use in `no_std` contexts.

## Testing

- CI passed locally.
- Used `bevy_utils` in a `no_std` crate as an experiment and compiled
successfully.

## Migration Guide

If you were importing `bevy_utils` and setting `default_features` to
`false`, but relying on elements which are now gated behind the `std` or
`alloc` features, include the relevant feature in your `Cargo.toml`.

## Notes

- Bevy already includes a single `no_std` crate, `bevy_ptr`, so there is
precedent for this change.
- As `bevy_utils` is widely used across the rest of Bevy, further work
to make Bevy `no_std` compatible would be blocked on this crate, if such
work was to be undertaken.
- Most of the changes in this PR are just the removal of an unnecessary
call to `to_string()` within unit tests.
This commit is contained in:
Zachary Harrold 2024-09-19 02:00:03 +10:00 committed by GitHub
parent b1273d48cb
commit bd489068c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 35 deletions

View file

@ -9,11 +9,16 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"] keywords = ["bevy"]
[features] [features]
default = ["std"]
std = ["alloc", "tracing/std", "ahash/std"]
alloc = []
detailed_trace = [] detailed_trace = []
[dependencies] [dependencies]
ahash = "0.8.7" ahash = { version = "0.8.7", default-features = false, features = [
tracing = { version = "0.1", default-features = false, features = ["std"] } "runtime-rng",
] }
tracing = { version = "0.1", default-features = false }
web-time = { version = "1.1" } web-time = { version = "1.1" }
hashbrown = { version = "0.14.2", features = ["serde"] } hashbrown = { version = "0.14.2", features = ["serde"] }
bevy_utils_proc_macros = { version = "0.15.0-dev", path = "macros" } bevy_utils_proc_macros = { version = "0.15.0-dev", path = "macros" }

View file

@ -1,5 +1,5 @@
//! Utilities for working with [`Future`]s. //! Utilities for working with [`Future`]s.
use std::{ use core::{
future::Future, future::Future,
pin::Pin, pin::Pin,
task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
@ -44,7 +44,7 @@ fn noop(_data: *const ()) {}
const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop);
fn noop_raw_waker() -> RawWaker { fn noop_raw_waker() -> RawWaker {
RawWaker::new(std::ptr::null(), &NOOP_WAKER_VTABLE) RawWaker::new(core::ptr::null(), &NOOP_WAKER_VTABLE)
} }
fn noop_waker() -> Waker { fn noop_waker() -> Waker {

View file

@ -4,12 +4,16 @@
html_logo_url = "https://bevyengine.org/assets/icon.png", html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png" html_favicon_url = "https://bevyengine.org/assets/icon.png"
)] )]
#![cfg_attr(not(feature = "std"), no_std)]
//! General utilities for first-party [Bevy] engine crates. //! General utilities for first-party [Bevy] engine crates.
//! //!
//! [Bevy]: https://bevyengine.org/ //! [Bevy]: https://bevyengine.org/
//! //!
#[cfg(feature = "alloc")]
extern crate alloc;
/// The utilities prelude. /// The utilities prelude.
/// ///
/// This includes the most common types in this crate, re-exported for your convenience. /// This includes the most common types in this crate, re-exported for your convenience.
@ -18,7 +22,9 @@ pub mod prelude {
} }
pub mod futures; pub mod futures;
#[cfg(feature = "alloc")]
mod short_names; mod short_names;
#[cfg(feature = "alloc")]
pub use short_names::get_short_name; pub use short_names::get_short_name;
pub mod synccell; pub mod synccell;
pub mod syncunsafecell; pub mod syncunsafecell;
@ -37,8 +43,10 @@ pub use parallel_queue::*;
pub use tracing; pub use tracing;
pub use web_time::{Duration, Instant, SystemTime, SystemTimeError, TryFromFloatSecsError}; pub use web_time::{Duration, Instant, SystemTime, SystemTimeError, TryFromFloatSecsError};
use hashbrown::hash_map::RawEntryMut; #[cfg(feature = "alloc")]
use std::{ use alloc::boxed::Box;
use core::{
any::TypeId, any::TypeId,
fmt::Debug, fmt::Debug,
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
@ -46,6 +54,7 @@ use std::{
mem::ManuallyDrop, mem::ManuallyDrop,
ops::Deref, ops::Deref,
}; };
use hashbrown::hash_map::RawEntryMut;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
mod conditional_send { mod conditional_send {
@ -66,11 +75,12 @@ pub use conditional_send::*;
/// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. Wasm), /// Use [`ConditionalSendFuture`] for a future with an optional Send trait bound, as on certain platforms (eg. Wasm),
/// futures aren't Send. /// futures aren't Send.
pub trait ConditionalSendFuture: std::future::Future + ConditionalSend {} pub trait ConditionalSendFuture: core::future::Future + ConditionalSend {}
impl<T: std::future::Future + ConditionalSend> ConditionalSendFuture for T {} impl<T: core::future::Future + ConditionalSend> ConditionalSendFuture for T {}
/// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection. /// An owned and dynamically typed Future used when you can't statically type your result or need to add some indirection.
pub type BoxedFuture<'a, T> = std::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>; #[cfg(feature = "alloc")]
pub type BoxedFuture<'a, T> = core::pin::Pin<Box<dyn ConditionalSendFuture<Output = T> + 'a>>;
/// A shortcut alias for [`hashbrown::hash_map::Entry`]. /// A shortcut alias for [`hashbrown::hash_map::Entry`].
pub type Entry<'a, K, V, S = BuildHasherDefault<AHasher>> = hashbrown::hash_map::Entry<'a, K, V, S>; pub type Entry<'a, K, V, S = BuildHasherDefault<AHasher>> = hashbrown::hash_map::Entry<'a, K, V, S>;
@ -192,7 +202,7 @@ impl<V: PartialEq, H> PartialEq for Hashed<V, H> {
} }
impl<V: Debug, H> Debug for Hashed<V, H> { impl<V: Debug, H> Debug for Hashed<V, H> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Hashed") f.debug_struct("Hashed")
.field("hash", &self.hash) .field("hash", &self.hash)
.field("value", &self.value) .field("value", &self.value)
@ -417,7 +427,7 @@ mod tests {
fn fast_typeid_hash() { fn fast_typeid_hash() {
struct Hasher; struct Hasher;
impl std::hash::Hasher for Hasher { impl core::hash::Hasher for Hasher {
fn finish(&self) -> u64 { fn finish(&self) -> u64 {
0 0
} }
@ -430,8 +440,11 @@ mod tests {
Hash::hash(&TypeId::of::<()>(), &mut Hasher); Hash::hash(&TypeId::of::<()>(), &mut Hasher);
} }
#[cfg(feature = "alloc")]
#[test] #[test]
fn stable_hash_within_same_program_execution() { fn stable_hash_within_same_program_execution() {
use alloc::vec::Vec;
let mut map_1 = HashMap::new(); let mut map_1 = HashMap::new();
let mut map_2 = HashMap::new(); let mut map_2 = HashMap::new();
for i in 1..10 { for i in 1..10 {

View file

@ -1,4 +1,7 @@
use std::{cell::RefCell, ops::DerefMut}; #[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::vec::Vec;
use core::{cell::RefCell, ops::DerefMut};
use thread_local::ThreadLocal; use thread_local::ThreadLocal;
/// A cohesive set of thread-local values of a given type. /// A cohesive set of thread-local values of a given type.
@ -56,6 +59,7 @@ where
} }
} }
#[cfg(feature = "alloc")]
impl<T: Send> Parallel<Vec<T>> { impl<T: Send> Parallel<Vec<T>> {
/// Collect all enqueued items from all threads and appends them to the end of a /// Collect all enqueued items from all threads and appends them to the end of a
/// single Vec. /// single Vec.

View file

@ -1,3 +1,5 @@
use alloc::string::String;
/// Shortens a type name to remove all module paths. /// Shortens a type name to remove all module paths.
/// ///
/// The short name of a type is its full name as returned by /// The short name of a type is its full name as returned by
@ -88,43 +90,37 @@ mod name_formatting_tests {
fn path_separated() { fn path_separated() {
assert_eq!( assert_eq!(
get_short_name("bevy_prelude::make_fun_game"), get_short_name("bevy_prelude::make_fun_game"),
"make_fun_game".to_string() "make_fun_game"
); );
} }
#[test] #[test]
fn tuple_type() { fn tuple_type() {
assert_eq!( assert_eq!(get_short_name("(String, String)"), "(String, String)");
get_short_name("(String, String)"),
"(String, String)".to_string()
);
} }
#[test] #[test]
fn array_type() { fn array_type() {
assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]".to_string()); assert_eq!(get_short_name("[i32; 3]"), "[i32; 3]");
} }
#[test] #[test]
fn trivial_generics() { fn trivial_generics() {
assert_eq!(get_short_name("a<B>"), "a<B>".to_string()); assert_eq!(get_short_name("a<B>"), "a<B>");
} }
#[test] #[test]
fn multiple_type_parameters() { fn multiple_type_parameters() {
assert_eq!(get_short_name("a<B, C>"), "a<B, C>".to_string()); assert_eq!(get_short_name("a<B, C>"), "a<B, C>");
} }
#[test] #[test]
fn enums() { fn enums() {
assert_eq!(get_short_name("Option::None"), "Option::None".to_string()); assert_eq!(get_short_name("Option::None"), "Option::None");
assert_eq!( assert_eq!(get_short_name("Option::Some(2)"), "Option::Some(2)");
get_short_name("Option::Some(2)"),
"Option::Some(2)".to_string()
);
assert_eq!( assert_eq!(
get_short_name("bevy_render::RenderSet::Prepare"), get_short_name("bevy_render::RenderSet::Prepare"),
"RenderSet::Prepare".to_string() "RenderSet::Prepare"
); );
} }
@ -132,7 +128,7 @@ mod name_formatting_tests {
fn generics() { fn generics() {
assert_eq!( assert_eq!(
get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"), get_short_name("bevy_render::camera::camera::extract_cameras<bevy_render::camera::bundle::Camera3d>"),
"extract_cameras<Camera3d>".to_string() "extract_cameras<Camera3d>"
); );
} }
@ -140,7 +136,7 @@ mod name_formatting_tests {
fn nested_generics() { fn nested_generics() {
assert_eq!( assert_eq!(
get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"), get_short_name("bevy::mad_science::do_mad_science<mad_science::Test<mad_science::Tube>, bavy::TypeSystemAbuse>"),
"do_mad_science<Test<Tube>, TypeSystemAbuse>".to_string() "do_mad_science<Test<Tube>, TypeSystemAbuse>"
); );
} }
@ -148,15 +144,12 @@ mod name_formatting_tests {
fn sub_path_after_closing_bracket() { fn sub_path_after_closing_bracket() {
assert_eq!( assert_eq!(
get_short_name("bevy_asset::assets::Assets<bevy_scene::dynamic_scene::DynamicScene>::asset_event_system"), get_short_name("bevy_asset::assets::Assets<bevy_scene::dynamic_scene::DynamicScene>::asset_event_system"),
"Assets<DynamicScene>::asset_event_system".to_string() "Assets<DynamicScene>::asset_event_system"
); );
assert_eq!( assert_eq!(
get_short_name("(String, String)::default"), get_short_name("(String, String)::default"),
"(String, String)::default".to_string() "(String, String)::default"
);
assert_eq!(
get_short_name("[i32; 16]::default"),
"[i32; 16]::default".to_string()
); );
assert_eq!(get_short_name("[i32; 16]::default"), "[i32; 16]::default");
} }
} }

View file

@ -2,7 +2,7 @@
//! //!
//! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html //! [`std::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
use std::ptr; use core::ptr;
/// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation, /// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation,
/// which should replace this one entirely. /// which should replace this one entirely.