bevy/examples/no_std/library/src/lib.rs
Zachary Harrold 958c9bb652
Add no_std Library Example (#18333)
# Objective

- Fixes #17506
- Fixes #16258

## Solution

- Added a new folder of examples, `no_std`, similar to the `mobile`
folder.
- Added a single example, `no_std_library`, which demonstrates how to
make a `no_std` compatible Bevy library.
- Added a new CI task, `check-compiles-no-std-examples`, which checks
that `no_std` examples compile on `no_std` targets.
- Added `bevy_platform_support::prelude` to `bevy::prelude`.

## Testing

- CI

---

## Notes

- I've structured the folders here to permit further `no_std` examples
(e.g., GameBoy Games, ESP32 firmware, etc.), but I am starting with the
simplest and least controversial example.
- I've tried to be as clear as possible with the documentation for this
example, catering to an audience who may not have even heard of `no_std`
before.

---------

Co-authored-by: Greeble <166992735+greeble-dev@users.noreply.github.com>
2025-03-18 00:45:25 +00:00

138 lines
4.8 KiB
Rust

//! Example `no_std` compatible Bevy library.
// The first step to a `no_std` library is to add this annotation:
#![no_std]
// This does 2 things to your crate:
// 1. It prevents automatically linking the `std` crate with yours.
// 2. It switches to `core::prelude` instead of `std::prelude` for what is implicitly
// imported in all modules in your crate.
// It is common to want to use `std` when it's available, and fall-back to an alternative
// implementation which may make compromises for the sake of compatibility.
// To do this, you can conditionally re-include the standard library:
#[cfg(feature = "std")]
extern crate std;
// This still uses the `core` prelude, so items such as `std::println` aren't implicitly included
// in all your modules, but it does make them available to import.
// Because Bevy requires access to an allocator anyway, you are free to include `alloc` regardless
// of what features are enabled.
// This gives you access to `Vec`, `String`, `Box`, and many other allocation primitives.
extern crate alloc;
// Here's our first example of using something from `core` instead of `std`.
// Since `std` re-exports `core` items, they are the same type just with a different name.
// This means any 3rd party code written for `std::time::Duration` will work identically for
// `core::time::Duration`.
use core::time::Duration;
// With the above boilerplate out of the way, everything below should look very familiar to those
// who have worked with Bevy before.
use bevy::prelude::*;
// While this example doesn't need it, a lot of fundamental types which are exclusively in `std`
// have alternatives in `bevy::platform_support`.
// If you find yourself needing a `HashMap`, `RwLock`, or `Instant`, check there first!
#[expect(unused_imports, reason = "demonstrating some available items")]
use bevy::platform_support::{
collections::{HashMap, HashSet},
hash::DefaultHasher,
sync::{
atomic::{AtomicBool, AtomicUsize},
Arc, Barrier, LazyLock, Mutex, Once, OnceLock, RwLock, Weak,
},
time::Instant,
};
// Note that `bevy::platform_support::sync::Arc` exists, despite `alloc::sync::Arc` being available.
// The reason is not every platform has full support for atomic operations, so `Arc`, `AtomicBool`,
// etc. aren't always available.
// You can test for their inclusion with `#[cfg(target_has_atomic = "ptr")]` and other related flags.
// You can get a more cross-platform alternative from `portable-atomic`, but Bevy handles this for you!
// Simply use `bevy::platform_support::sync` instead of `core::sync` and `alloc::sync` when possible,
// and Bevy will handle selecting the fallback from `portable-atomic` when it is required.
/// Plugin for working with delayed components.
///
/// You can delay the insertion of a component by using [`insert_delayed`](EntityCommandsExt::insert_delayed).
pub struct DelayedComponentPlugin;
impl Plugin for DelayedComponentPlugin {
fn build(&self, app: &mut App) {
app.register_type::<DelayedComponentTimer>()
.add_systems(Update, tick_timers);
}
}
/// Extension trait providing [`insert_delayed`](EntityCommandsExt::insert_delayed).
pub trait EntityCommandsExt {
/// Insert the provided [`Bundle`] `B` with a provided `delay`.
fn insert_delayed<B: Bundle>(&mut self, bundle: B, delay: Duration) -> &mut Self;
}
impl EntityCommandsExt for EntityCommands<'_> {
fn insert_delayed<B: Bundle>(&mut self, bundle: B, delay: Duration) -> &mut Self {
self.insert((
DelayedComponentTimer(Timer::new(delay, TimerMode::Once)),
DelayedComponent(bundle),
))
.observe(unwrap::<B>)
}
}
impl EntityCommandsExt for EntityWorldMut<'_> {
fn insert_delayed<B: Bundle>(&mut self, bundle: B, delay: Duration) -> &mut Self {
self.insert((
DelayedComponentTimer(Timer::new(delay, TimerMode::Once)),
DelayedComponent(bundle),
))
.observe(unwrap::<B>)
}
}
#[derive(Component, Deref, DerefMut, Reflect, Debug)]
#[reflect(Component)]
struct DelayedComponentTimer(Timer);
#[derive(Component)]
#[component(immutable)]
struct DelayedComponent<B: Bundle>(B);
#[derive(Event)]
struct Unwrap;
fn tick_timers(
mut commands: Commands,
mut query: Query<(Entity, &mut DelayedComponentTimer)>,
time: Res<Time>,
) {
for (entity, mut timer) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
commands
.entity(entity)
.remove::<DelayedComponentTimer>()
.trigger(Unwrap);
}
}
}
fn unwrap<B: Bundle>(trigger: Trigger<Unwrap>, world: &mut World) {
if let Ok(mut target) = world.get_entity_mut(trigger.target()) {
if let Some(DelayedComponent(bundle)) = target.take::<DelayedComponent<B>>() {
target.insert(bundle);
}
}
world.despawn(trigger.observer());
}