bevy/crates/bevy_platform
Zachary Harrold aadd3a3ec2
Create bevy_platform::cfg for viral feature management (#18822)
# Objective

- Acts on certain elements of #18799
- Closes #1615
- New baseline for #18170

## Solution

- Created a new `cfg` module in `bevy_platform` which contains two
macros to aid in working with features like `web`, `std`, and `alloc`.
- `switch` is a stable implementation of
[`cfg_match`](https://doc.rust-lang.org/std/macro.cfg_match.html), which
itself is a `core` alternative to [`cfg_if`](https://docs.rs/cfg-if).
- `define_alias` is a `build.rs`-free alternative to
[`cfg_aliases`](https://docs.rs/cfg_aliases) with the ability to share
feature information between crates.
- Switched to these macros within `bevy_platform` to demonstrate usage. 

## Testing

- CI

---

## Showcase

Consider the typical `std` feature as an example of a "virality". With
just `bevy_platform`, `bevy_utils`, and `bevy_ecs`, we have 3 crates in
a chain where activating `std` in any of them should really activate it
everywhere. The status-quo for this is for each crate to define its own
`std` feature, and ensure it includes the `std` feature of every
dependency in that feature. For crates which don't even interact with
`std` directly, this can be quite cumbersome. Especially considering
that Bevy has a fundamental crate, `bevy_platform`, which is a
dependency for effectively every crate.

Instead, we can use `define_alias` to create a macro which will
conditionally compile code if and only if the specified configuration
condition is met _in the defining crate_.

```rust
// In `bevy_platform`

define_alias! {
    #[cfg(feature = "std")] => {
        /// Indicates the `std` crate is available and can be used.
        std
    }
    #[cfg(all(target_arch = "wasm32", feature = "web"))] => {
        /// Indicates that this target has access to browser APIs.
        web
    }
}
```

The above `web` and `std` macros will either no-op the provided code if
the conditions are not met, or pass it unmodified if it is met. Since it
is evaluated in the context of the defining crate, `bevy_platform/std`
can be used to conditionally compile code in `bevy_utils` and `bevy_ecs`
_without_ those crates including their own `std` features.

```rust
// In `bevy_utils`
use bevy_platform::cfg;

// If `bevy_platform` has `std`, then we can too!
cfg::std! {
    extern crate std;
}
```

To aid in more complex configurations, `switch` is provided to provide a
`cfg_if` alternative that is compatible with `define_alias`:

```rust
use bevy_platform::cfg;

cfg::switch! {
    #[cfg(feature = "foo")] => { /* use the foo API */ }
    cfg::web => { /* use browser API */ }
    cfg::std => { /* use std */ }
    _ => { /* use a fallback implementation */ }
}
```

This paradigm would allow Bevy's sub-crates to avoid re-exporting viral
features, and also enable functionality in response to availability in
their dependencies, rather than from explicit features (bottom-up
instead of top-down). I imagine that a "full rollout" of this paradigm
would remove most viral features from Bevy's crates, leaving only
`bevy_platform`, `bevy_internal`, and `bevy` (since `bevy`/`_internal`
are explicitly re-exports of all of Bevy's crates).

This bottom-up approach may be useful in other areas of Bevy's features
too. For example, `bevy_core_pipeline/tonemapping_luts` requires:
- bevy_render/ktx2
- bevy_image/ktx2
- bevy_image/zstd

If `define_alias` was used in `bevy_image`, `bevy_render` would not need
to re-export the `ktx2` feature, and `bevy_core_pipeline` could directly
probe `bevy_image` for the status of `ktx2` and `zstd` features to
determine if it should compile the `tonemapping_luts` functionality,
rather than having an explicitly feature. Of course, an explicit feature
is still important for _features_, so this may not be the best example,
but it highlights that with this paradigm crates can reactively provide
functionality, rather than needing to proactively declare feature
combinations up-front and hope the user enables them.

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
2025-05-06 00:52:15 +00:00
..
src Create bevy_platform::cfg for viral feature management (#18822) 2025-05-06 00:52:15 +00:00
Cargo.toml Create bevy_platform::cfg for viral feature management (#18822) 2025-05-06 00:52:15 +00:00
LICENSE-APACHE Rename bevy_platform_support to bevy_platform (#18813) 2025-04-11 23:13:28 +00:00
LICENSE-MIT Rename bevy_platform_support to bevy_platform (#18813) 2025-04-11 23:13:28 +00:00
README.md Rename bevy_platform_support to bevy_platform (#18813) 2025-04-11 23:13:28 +00:00

Bevy Platform Support

License Crates.io Downloads Docs Discord

Rust is a fantastic multi-platform language with extensive support for modern targets through its standard library. However, some items within the standard library have alternatives that are better suited for Bevy and game engines in general. Additionally, to support embedded and other esoteric platforms, it's often necessary to shed reliance on std, making your crate no_std.

These needs are handled by this crate, bevy_platform. The goal of this crate is to provide alternatives and extensions to the Rust standard library which minimize friction when developing with and for Bevy across multiple platforms.

Getting Started

Like any dependency from crates.io, use cargo to add it to your Cargo.toml file:

cargo add bevy_platform

Now, instead of importing from std you can use bevy_platform for items it has alternative for. See the documentation for what items are available, and explanations for why you may want to use them.

no_std Support

By default, bevy_platform will activate the std feature, requiring access to the std crate for whichever platforms you're targeting. To use this crate on no_std platforms, disable default features:

bevy_platform = { version = "x.y.z", default-features = false }

Features

std (default)

Enables usage of the standard library. Note that where this crate has alternatives to the standard library that it considers better than what's provided, it will provide the alternative even when std is enabled. This is explicitly incompatible with no_std targets.

alloc (default)

Enables usage of the alloc crate. Note that this feature is automatically enabled when enabling std. This is compatible with most no_std targets, but not all.

critical-section

Switches to using critical-section as a backend for synchronization. You may need to enable this feature on platforms with little to no support for atomic operations.