Add no_std Support to bevy_time (#17491)

# Objective

- Contributes to #15460

## Solution

- Switched `tracing` for `log` for the atomically challenged platforms
- Setup feature flags as required
- Added to `compile-check-no-std` CI task
- Made `crossbeam-channel` optional depending on `std`.

## Testing

- CI

---

## Notes

- `crossbeam-channel` provides a MPMC channel type which isn't readily
replicable in `no_std`, and is only used for a `bevy_render`
integration. As such, I've feature-gated the `TimeReceiver` and
`TimeSender` types.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Zachary Harrold 2025-01-23 07:02:43 +11:00 committed by GitHub
parent f32a6fb205
commit d921fdc376
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 105 additions and 35 deletions

View File

@ -126,7 +126,6 @@ impl_reflect_opaque!(::core::time::Duration(
Deserialize, Deserialize,
Default Default
)); ));
#[cfg(any(target_arch = "wasm32", feature = "std"))]
impl_reflect_opaque!(::bevy_platform_support::time::Instant( impl_reflect_opaque!(::bevy_platform_support::time::Instant(
Debug, Hash, PartialEq Debug, Hash, PartialEq
)); ));

View File

@ -9,26 +9,69 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"] keywords = ["bevy"]
[features] [features]
default = ["bevy_reflect"] default = ["std", "bevy_reflect", "bevy_app/default"]
serialize = ["serde"]
# Functionality
## Adds runtime reflection support using `bevy_reflect`.
bevy_reflect = [
"dep:bevy_reflect",
"bevy_ecs/bevy_reflect",
"bevy_app/bevy_reflect",
]
## Adds serialization support through `serde`.
serialize = ["dep:serde", "bevy_ecs/serialize"]
# 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 = [
"serde?/std",
"bevy_reflect?/std",
"bevy_ecs/std",
"bevy_app/std",
"bevy_platform_support/std",
"dep:crossbeam-channel",
]
## `critical-section` provides the building blocks for synchronization primitives
## on all platforms, including `no_std`.
critical-section = [
"bevy_ecs/critical-section",
"bevy_platform_support/critical-section",
"bevy_reflect?/critical-section",
"bevy_app/critical-section",
]
## `portable-atomic` provides additional platform support for atomic types and
## operations, even on targets without native support.
portable-atomic = [
"bevy_ecs/portable-atomic",
"bevy_platform_support/portable-atomic",
"bevy_reflect?/portable-atomic",
"bevy_app/portable-atomic",
]
[dependencies] [dependencies]
# bevy # bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" } bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", features = [ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
"bevy_reflect", bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
"bevy", "bevy",
], optional = true } ], optional = true }
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [ bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false }
"std",
] }
# other # other
crossbeam-channel = "0.5.0" crossbeam-channel = { version = "0.5.0", default-features = false, features = [
serde = { version = "1", features = ["derive"], optional = true } "std",
tracing = { version = "0.1", default-features = false, features = ["std"] } ], optional = true }
serde = { version = "1", features = [
"derive",
], default-features = false, optional = true }
log = { version = "0.4", default-features = false }
[lints] [lints]
workspace = true workspace = true

View File

@ -5,6 +5,12 @@
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"
)] )]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
/// Common run conditions /// Common run conditions
pub mod common_conditions; pub mod common_conditions;
@ -37,9 +43,12 @@ use bevy_ecs::{
}; };
use bevy_platform_support::time::Instant; use bevy_platform_support::time::Instant;
use core::time::Duration; use core::time::Duration;
#[cfg(feature = "std")]
pub use crossbeam_channel::TrySendError; pub use crossbeam_channel::TrySendError;
#[cfg(feature = "std")]
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use tracing::warn;
/// Adds time functionality to Apps. /// Adds time functionality to Apps.
#[derive(Default)] #[derive(Default)]
@ -92,8 +101,9 @@ impl Plugin for TimePlugin {
/// networking or similar, you may prefer to set the next [`Time`] value manually. /// networking or similar, you may prefer to set the next [`Time`] value manually.
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub enum TimeUpdateStrategy { pub enum TimeUpdateStrategy {
/// [`Time`] will be automatically updated each frame using an [`Instant`] sent from the render world via a [`TimeSender`]. /// [`Time`] will be automatically updated each frame using an [`Instant`] sent from the render world.
/// If nothing is sent, the system clock will be used instead. /// If nothing is sent, the system clock will be used instead.
#[cfg_attr(feature = "std", doc = "See [`TimeSender`] for more details.")]
#[default] #[default]
Automatic, Automatic,
/// [`Time`] will be updated to the specified [`Instant`] value each frame. /// [`Time`] will be updated to the specified [`Instant`] value each frame.
@ -106,14 +116,17 @@ pub enum TimeUpdateStrategy {
} }
/// Channel resource used to receive time from the render world. /// Channel resource used to receive time from the render world.
#[cfg(feature = "std")]
#[derive(Resource)] #[derive(Resource)]
pub struct TimeReceiver(pub Receiver<Instant>); pub struct TimeReceiver(pub Receiver<Instant>);
/// Channel resource used to send time from the render world. /// Channel resource used to send time from the render world.
#[cfg(feature = "std")]
#[derive(Resource)] #[derive(Resource)]
pub struct TimeSender(pub Sender<Instant>); pub struct TimeSender(pub Sender<Instant>);
/// Creates channels used for sending time between the render world and the main world. /// Creates channels used for sending time between the render world and the main world.
#[cfg(feature = "std")]
pub fn create_time_channels() -> (TimeSender, TimeReceiver) { pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
// bound the channel to 2 since when pipelined the render phase can finish before // bound the channel to 2 since when pipelined the render phase can finish before
// the time system runs. // the time system runs.
@ -128,26 +141,32 @@ pub fn time_system(
mut virtual_time: ResMut<Time<Virtual>>, mut virtual_time: ResMut<Time<Virtual>>,
mut time: ResMut<Time>, mut time: ResMut<Time>,
update_strategy: Res<TimeUpdateStrategy>, update_strategy: Res<TimeUpdateStrategy>,
time_recv: Option<Res<TimeReceiver>>, #[cfg(feature = "std")] time_recv: Option<Res<TimeReceiver>>,
mut has_received_time: Local<bool>, #[cfg(feature = "std")] mut has_received_time: Local<bool>,
) { ) {
let new_time = if let Some(time_recv) = time_recv {
// TODO: Figure out how to handle this when using pipelined rendering.
if let Ok(new_time) = time_recv.0.try_recv() {
*has_received_time = true;
new_time
} else {
if *has_received_time {
warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
}
Instant::now()
}
} else {
Instant::now()
};
match update_strategy.as_ref() { match update_strategy.as_ref() {
TimeUpdateStrategy::Automatic => real_time.update_with_instant(new_time), TimeUpdateStrategy::Automatic => {
#[cfg(feature = "std")]
let new_time = if let Some(time_recv) = time_recv {
// TODO: Figure out how to handle this when using pipelined rendering.
if let Ok(new_time) = time_recv.0.try_recv() {
*has_received_time = true;
new_time
} else {
if *has_received_time {
log::warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
}
Instant::now()
}
} else {
Instant::now()
};
#[cfg(not(feature = "std"))]
let new_time = Instant::now();
real_time.update_with_instant(new_time);
}
TimeUpdateStrategy::ManualInstant(instant) => real_time.update_with_instant(*instant), TimeUpdateStrategy::ManualInstant(instant) => real_time.update_with_instant(*instant),
TimeUpdateStrategy::ManualDuration(duration) => real_time.update_with_duration(*duration), TimeUpdateStrategy::ManualDuration(duration) => real_time.update_with_duration(*duration),
} }
@ -166,6 +185,7 @@ mod tests {
}; };
use core::error::Error; use core::error::Error;
use core::time::Duration; use core::time::Duration;
use std::println;
#[derive(Event)] #[derive(Event)]
struct TestEvent<T: Default> { struct TestEvent<T: Default> {

View File

@ -1,7 +1,7 @@
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use core::time::Duration; use core::time::Duration;
use tracing::debug; use log::debug;
use crate::{real::Real, time::Time}; use crate::{real::Real, time::Time};

View File

@ -142,6 +142,14 @@ impl Prepare for CompileCheckNoStdCommand {
"Please fix compiler errors in output above for bevy_transform no_std compatibility.", "Please fix compiler errors in output above for bevy_transform no_std compatibility.",
)); ));
commands.push(PreparedCommand::new::<Self>(
cmd!(
sh,
"cargo check -p bevy_time --no-default-features --features bevy_reflect,serialize --target {target}"
),
"Please fix compiler errors in output above for bevy_transform no_std compatibility.",
));
commands.push(PreparedCommand::new::<Self>( commands.push(PreparedCommand::new::<Self>(
cmd!( cmd!(
sh, sh,