From f45e78e6583d16686b8693f296b63c12dec527ec Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Thu, 19 Dec 2024 09:04:45 +1100 Subject: [PATCH] Add `no_std` support to `bevy_app` (#16874) # Objective - Contributes to #15460 ## Solution - Added the following features: - `std` (default) - `bevy_tasks` (default) - `downcast ` (default) - `portable-atomic` - `critical-section` - `downcast` and `bevy_tasks` are now optional dependencies for `bevy_app`. ## Testing - CI - Personal UEFI and Raspberry Pi Pico demo applications compile and run against this branch ## Draft Release Notes Bevy's application framework now supports `no_std` platforms. Following up on `bevy_ecs` gaining `no_std` support, `bevy_app` extends the functionality available on these targets to include the powerful `App` and `Plugin` abstractions. With this, library authors now have the option of making their plugins `no_std` compatible, or even offering plugins specifically to improve Bevy on certain embedded platforms! To start making a `no_std` compatible plugin, simply disable default features when including `bevy_app`: ```toml [dependencies] bevy_app = { version = "0.16", default-features = false } ``` We encourage library authors to do this anyway, as it can also help with compile times and binary size on all platforms. 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 - `downcast-rs` is optional as it isn't compatible with `portable-atomic`. I will investigate making a PR upstream to add support for this functionality, as it should be very straightforward. - In line with the `bevy_ecs` no-std-ification, I've added documentation to all features, and grouped them as well. - ~~Creating this PR in draft while CI runs and so I can polish before review.~~ --------- Co-authored-by: Alice Cecile --- crates/bevy_app/Cargo.toml | 62 ++++++++++++++++--- crates/bevy_app/src/app.rs | 34 ++++++++-- crates/bevy_app/src/lib.rs | 5 +- crates/bevy_app/src/main_schedule.rs | 1 + crates/bevy_app/src/plugin.rs | 15 +++++ crates/bevy_app/src/plugin_group.rs | 9 ++- crates/bevy_app/src/schedule_runner.rs | 19 ++++-- crates/bevy_app/src/sub_app.rs | 8 ++- .../bevy_app/src/terminal_ctrl_c_handler.rs | 4 +- crates/bevy_ecs/Cargo.toml | 5 +- tools/ci/src/commands/compile_check_no_std.rs | 8 +++ 11 files changed, 139 insertions(+), 31 deletions(-) diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index a42deb8efd74b..fc32ff4191dad 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -9,31 +9,77 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [features] -trace = [] -bevy_debug_stepping = [] -default = ["bevy_reflect"] +default = ["std", "bevy_reflect", "bevy_tasks", "bevy_ecs/default", "downcast"] + +# Functionality + +## Adds runtime reflection support using `bevy_reflect`. bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"] + +## Extends reflection support to functions. reflect_functions = [ "bevy_reflect", "bevy_reflect/functions", "bevy_ecs/reflect_functions", ] +## Adds support for running async background tasks +bevy_tasks = ["dep:bevy_tasks"] + +## Adds `downcast-rs` integration for `Plugin` +downcast = ["dep:downcast-rs"] + +# Debugging Features + +## Enables `tracing` integration, allowing spans and other metrics to be reported +## through that framework. +trace = ["dep:tracing"] + +## Provides system stepping support, allowing them to be paused, stepped, and +## other debug operations which can help with diagnosing certain behaviors. +bevy_debug_stepping = [] + +# Platform Compatibility + +## Allows access to the `std` crate. Enabling this feature will prevent compilation +## on `no_std` targets, but provides access to certain additional features on +## supported platforms. +std = [ + "bevy_reflect?/std", + "bevy_ecs/std", + "dep:ctrlc", + "downcast-rs?/std", + "bevy_utils/std", + "bevy_tasks?/std", +] + +## `critical-section` provides the building blocks for synchronization primitives +## on all platforms, including `no_std`. +critical-section = ["bevy_tasks?/critical-section", "bevy_ecs/critical-section"] + +## `portable-atomic` provides additional platform support for atomic types and +## operations, even on targets without native support. +portable-atomic = ["bevy_tasks?/portable-atomic", "bevy_ecs/portable-atomic"] + [dependencies] # bevy bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false } -bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } -bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", default-features = false, optional = true } +bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev", default-features = false, features = [ + "alloc", +] } +bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev", default-features = false, optional = true } # other -downcast-rs = "1.2.0" +downcast-rs = { version = "1.2.0", default-features = false, optional = true } thiserror = { version = "2", default-features = false } variadics_please = "1.1" +tracing = { version = "0.1", default-features = false, optional = true } +log = { version = "0.4", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ctrlc = "3.4.4" +ctrlc = { version = "3.4.4", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4ff12abd74a5b..668ebc9118ef5 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -2,6 +2,10 @@ use crate::{ First, Main, MainSchedulePlugin, PlaceholderPlugin, Plugin, Plugins, PluginsState, SubApp, SubApps, }; +use alloc::{ + boxed::Box, + string::{String, ToString}, +}; pub use bevy_derive::AppLabel; use bevy_ecs::{ component::RequiredComponentsError, @@ -11,15 +15,22 @@ use bevy_ecs::{ schedule::{ScheduleBuildSettings, ScheduleLabel}, system::{IntoObserverSystem, SystemId, SystemInput}, }; -#[cfg(feature = "trace")] -use bevy_utils::tracing::info_span; -use bevy_utils::{tracing::debug, HashMap}; +use bevy_utils::HashMap; use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe}; +use log::debug; +use thiserror::Error; + +#[cfg(feature = "trace")] +use tracing::info_span; + +#[cfg(feature = "std")] use std::{ panic::{catch_unwind, resume_unwind}, process::{ExitCode, Termination}, }; -use thiserror::Error; + +#[cfg(feature = "downcast")] +use alloc::vec::Vec; bevy_ecs::define_label!( /// A strongly-typed class of labels used to identify an [`App`]. @@ -458,12 +469,21 @@ impl App { .push(Box::new(PlaceholderPlugin)); self.main_mut().plugin_build_depth += 1; - let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self))); + + let f = AssertUnwindSafe(|| plugin.build(self)); + + #[cfg(feature = "std")] + let result = catch_unwind(f); + + #[cfg(not(feature = "std"))] + f(); + self.main_mut() .plugin_names .insert(plugin.name().to_string()); self.main_mut().plugin_build_depth -= 1; + #[cfg(feature = "std")] if let Err(payload) = result { resume_unwind(payload); } @@ -499,6 +519,7 @@ impl App { /// # app.add_plugins(ImagePlugin::default()); /// let default_sampler = app.get_added_plugins::()[0].default_sampler; /// ``` + #[cfg(feature = "downcast")] pub fn get_added_plugins(&self) -> Vec<&T> where T: Plugin, @@ -1294,7 +1315,7 @@ type RunnerFn = Box AppExit>; fn run_once(mut app: App) -> AppExit { while app.plugins_state() == PluginsState::Adding { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "bevy_tasks"))] bevy_tasks::tick_global_task_pools_on_main_thread(); } app.finish(); @@ -1364,6 +1385,7 @@ impl From for AppExit { } } +#[cfg(feature = "std")] impl Termination for AppExit { fn report(self) -> ExitCode { match self { diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index c7dd7414aab80..e9c1054b9e4e1 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -11,6 +11,7 @@ html_logo_url = "https://bevyengine.org/assets/icon.png", html_favicon_url = "https://bevyengine.org/assets/icon.png" )] +#![cfg_attr(not(feature = "std"), no_std)] //! This crate is about everything concerning the highest-level, application layer of a Bevy app. @@ -23,7 +24,7 @@ mod plugin; mod plugin_group; mod schedule_runner; mod sub_app; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] mod terminal_ctrl_c_handler; pub use app::*; @@ -33,7 +34,7 @@ pub use plugin::*; pub use plugin_group::*; pub use schedule_runner::*; pub use sub_app::*; -#[cfg(not(target_arch = "wasm32"))] +#[cfg(all(not(target_arch = "wasm32"), feature = "std"))] pub use terminal_ctrl_c_handler::*; /// The app prelude. diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 834205cf5347f..227a976de7e97 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -1,4 +1,5 @@ use crate::{App, Plugin}; +use alloc::{vec, vec::Vec}; use bevy_ecs::{ schedule::{ ExecutorKind, InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel, diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 73c2e452a81ee..724dbfde93e27 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -1,8 +1,21 @@ +// TODO: Upstream `portable-atomic` support to `downcast_rs` and unconditionally +// include it as a dependency. +// See https://github.com/marcianx/downcast-rs/pull/22 for details +#[cfg(feature = "downcast")] use downcast_rs::{impl_downcast, Downcast}; use crate::App; use core::any::Any; +/// Dummy trait with the same name as `downcast_rs::Downcast`. This is to ensure +/// the `Plugin: Downcast` bound can remain even when `downcast` isn't enabled. +#[cfg(not(feature = "downcast"))] +#[doc(hidden)] +pub trait Downcast {} + +#[cfg(not(feature = "downcast"))] +impl Downcast for T {} + /// A collection of Bevy app logic and configuration. /// /// Plugins configure an [`App`]. When an [`App`] registers a plugin, @@ -92,6 +105,7 @@ pub trait Plugin: Downcast + Any + Send + Sync { } } +#[cfg(feature = "downcast")] impl_downcast!(Plugin); impl Plugin for T { @@ -129,6 +143,7 @@ pub trait Plugins: sealed::Plugins {} impl Plugins for T where T: sealed::Plugins {} mod sealed { + use alloc::boxed::Box; use variadics_please::all_tuples; use crate::{App, AppError, Plugin, PluginGroup}; diff --git a/crates/bevy_app/src/plugin_group.rs b/crates/bevy_app/src/plugin_group.rs index e828a012d0400..ce78f52315c62 100644 --- a/crates/bevy_app/src/plugin_group.rs +++ b/crates/bevy_app/src/plugin_group.rs @@ -1,9 +1,12 @@ use crate::{App, AppError, Plugin}; -use bevy_utils::{ - tracing::{debug, warn}, - TypeIdMap, +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, }; +use bevy_utils::TypeIdMap; use core::any::TypeId; +use log::{debug, warn}; /// A macro for generating a well-documented [`PluginGroup`] from a list of [`Plugin`] paths. /// diff --git a/crates/bevy_app/src/schedule_runner.rs b/crates/bevy_app/src/schedule_runner.rs index 01c43fde6f2ae..d1e3865b52ac8 100644 --- a/crates/bevy_app/src/schedule_runner.rs +++ b/crates/bevy_app/src/schedule_runner.rs @@ -3,7 +3,10 @@ use crate::{ plugin::Plugin, PluginsState, }; -use bevy_utils::{Duration, Instant}; +use bevy_utils::Duration; + +#[cfg(any(target_arch = "wasm32", feature = "std"))] +use bevy_utils::Instant; #[cfg(target_arch = "wasm32")] use { @@ -76,7 +79,7 @@ impl Plugin for ScheduleRunnerPlugin { let plugins_state = app.plugins_state(); if plugins_state != PluginsState::Cleaned { while app.plugins_state() == PluginsState::Adding { - #[cfg(not(target_arch = "wasm32"))] + #[cfg(all(not(target_arch = "wasm32"), feature = "bevy_tasks"))] bevy_tasks::tick_global_task_pools_on_main_thread(); } app.finish(); @@ -95,8 +98,9 @@ impl Plugin for ScheduleRunnerPlugin { } RunMode::Loop { wait } => { let tick = move |app: &mut App, - wait: Option| + _wait: Option| -> Result, AppExit> { + #[cfg(any(target_arch = "wasm32", feature = "std"))] let start_time = Instant::now(); app.update(); @@ -105,9 +109,11 @@ impl Plugin for ScheduleRunnerPlugin { return Err(exit); }; + #[cfg(any(target_arch = "wasm32", feature = "std"))] let end_time = Instant::now(); - if let Some(wait) = wait { + #[cfg(any(target_arch = "wasm32", feature = "std"))] + if let Some(wait) = _wait { let exe_time = end_time - start_time; if exe_time < wait { return Ok(Some(wait - exe_time)); @@ -121,7 +127,10 @@ impl Plugin for ScheduleRunnerPlugin { { loop { match tick(&mut app, wait) { - Ok(Some(delay)) => std::thread::sleep(delay), + Ok(Some(_delay)) => { + #[cfg(feature = "std")] + std::thread::sleep(_delay); + } Ok(None) => continue, Err(exit) => return exit, } diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 93cbd089483f8..b921956a6d317 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -1,16 +1,17 @@ use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState}; +use alloc::{boxed::Box, string::String, vec::Vec}; use bevy_ecs::{ event::EventRegistry, prelude::*, schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel}, system::{SystemId, SystemInput}, }; - -#[cfg(feature = "trace")] -use bevy_utils::tracing::info_span; use bevy_utils::{HashMap, HashSet}; use core::fmt::Debug; +#[cfg(feature = "trace")] +use tracing::info_span; + type ExtractFn = Box; /// A secondary application with its own [`World`]. These can run independently of each other. @@ -332,6 +333,7 @@ impl SubApp { } /// See [`App::get_added_plugins`]. + #[cfg(feature = "downcast")] pub fn get_added_plugins(&self) -> Vec<&T> where T: Plugin, diff --git a/crates/bevy_app/src/terminal_ctrl_c_handler.rs b/crates/bevy_app/src/terminal_ctrl_c_handler.rs index b42fb47f6f676..0eb34ccdbe98e 100644 --- a/crates/bevy_app/src/terminal_ctrl_c_handler.rs +++ b/crates/bevy_app/src/terminal_ctrl_c_handler.rs @@ -63,9 +63,9 @@ impl Plugin for TerminalCtrlCHandlerPlugin { match result { Ok(()) => {} Err(ctrlc::Error::MultipleHandlers) => { - bevy_utils::tracing::info!("Skipping installing `Ctrl+C` handler as one was already installed. Please call `TerminalCtrlCHandlerPlugin::gracefully_exit` in your own `Ctrl+C` handler if you want Bevy to gracefully exit on `Ctrl+C`."); + log::info!("Skipping installing `Ctrl+C` handler as one was already installed. Please call `TerminalCtrlCHandlerPlugin::gracefully_exit` in your own `Ctrl+C` handler if you want Bevy to gracefully exit on `Ctrl+C`."); } - Err(err) => bevy_utils::tracing::warn!("Failed to set `Ctrl+C` handler: {err}"), + Err(err) => log::warn!("Failed to set `Ctrl+C` handler: {err}"), } app.add_systems(Update, TerminalCtrlCHandlerPlugin::exit_on_flag); diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index bb5e2424d230f..3db26b9a2c4b4 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -79,7 +79,7 @@ std = [ ## on all platforms, including `no_std`. critical-section = [ "dep:critical-section", - "bevy_tasks/critical-section", + "bevy_tasks?/critical-section", "portable-atomic?/critical-section", ] @@ -88,8 +88,9 @@ critical-section = [ portable-atomic = [ "dep:portable-atomic", "dep:portable-atomic-util", - "bevy_tasks/portable-atomic", + "bevy_tasks?/portable-atomic", "concurrent-queue/portable-atomic", + "spin/portable_atomic", ] [dependencies] diff --git a/tools/ci/src/commands/compile_check_no_std.rs b/tools/ci/src/commands/compile_check_no_std.rs index 02bfd7c58790f..7ca5d6b2b5905 100644 --- a/tools/ci/src/commands/compile_check_no_std.rs +++ b/tools/ci/src/commands/compile_check_no_std.rs @@ -102,6 +102,14 @@ impl Prepare for CompileCheckNoStdCommand { "Please fix compiler errors in output above for bevy_ecs no_std compatibility.", )); + commands.push(PreparedCommand::new::( + cmd!( + sh, + "cargo check -p bevy_app --no-default-features --features bevy_reflect --target {target}" + ), + "Please fix compiler errors in output above for bevy_app no_std compatibility.", + )); + commands } }