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,
Default
));
#[cfg(any(target_arch = "wasm32", feature = "std"))]
impl_reflect_opaque!(::bevy_platform_support::time::Instant(
Debug, Hash, PartialEq
));

View File

@ -9,26 +9,69 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
default = ["bevy_reflect"]
serialize = ["serde"]
default = ["std", "bevy_reflect", "bevy_app/default"]
# 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]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", features = [
"bevy_reflect",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
"bevy",
], optional = true }
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
"std",
] }
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false }
# other
crossbeam-channel = "0.5.0"
serde = { version = "1", features = ["derive"], optional = true }
tracing = { version = "0.1", default-features = false, features = ["std"] }
crossbeam-channel = { version = "0.5.0", 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]
workspace = true

View File

@ -5,6 +5,12 @@
html_logo_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
pub mod common_conditions;
@ -37,9 +43,12 @@ use bevy_ecs::{
};
use bevy_platform_support::time::Instant;
use core::time::Duration;
#[cfg(feature = "std")]
pub use crossbeam_channel::TrySendError;
#[cfg(feature = "std")]
use crossbeam_channel::{Receiver, Sender};
use tracing::warn;
/// Adds time functionality to Apps.
#[derive(Default)]
@ -92,8 +101,9 @@ impl Plugin for TimePlugin {
/// networking or similar, you may prefer to set the next [`Time`] value manually.
#[derive(Resource, Default)]
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.
#[cfg_attr(feature = "std", doc = "See [`TimeSender`] for more details.")]
#[default]
Automatic,
/// [`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.
#[cfg(feature = "std")]
#[derive(Resource)]
pub struct TimeReceiver(pub Receiver<Instant>);
/// Channel resource used to send time from the render world.
#[cfg(feature = "std")]
#[derive(Resource)]
pub struct TimeSender(pub Sender<Instant>);
/// Creates channels used for sending time between the render world and the main world.
#[cfg(feature = "std")]
pub fn create_time_channels() -> (TimeSender, TimeReceiver) {
// bound the channel to 2 since when pipelined the render phase can finish before
// the time system runs.
@ -128,26 +141,32 @@ pub fn time_system(
mut virtual_time: ResMut<Time<Virtual>>,
mut time: ResMut<Time>,
update_strategy: Res<TimeUpdateStrategy>,
time_recv: Option<Res<TimeReceiver>>,
mut has_received_time: Local<bool>,
#[cfg(feature = "std")] time_recv: Option<Res<TimeReceiver>>,
#[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() {
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::ManualDuration(duration) => real_time.update_with_duration(*duration),
}
@ -166,6 +185,7 @@ mod tests {
};
use core::error::Error;
use core::time::Duration;
use std::println;
#[derive(Event)]
struct TestEvent<T: Default> {

View File

@ -1,7 +1,7 @@
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use core::time::Duration;
use tracing::debug;
use log::debug;
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.",
));
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>(
cmd!(
sh,