-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Create bevy_platform::cfg
for viral feature management
#18822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create bevy_platform::cfg
for viral feature management
#18822
Conversation
Assists in managing pervasive features across Bevy.
Marking as |
/// } | ||
/// } | ||
/// ``` | ||
#[doc(inline)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using doc(inline)
on this pub use
statement, but doc(hidden)
on the declaration gives us full documentation on docs.rs, and keeps the macro contained to this module, rather than immediately going to the crate root. This lets us use shorter named like switch
and std
, since they're already scoped to the cfg
namespace, avoiding the cfg_if::cfg_if
ugliness.
|
||
#[doc(hidden)] | ||
#[macro_export] | ||
macro_rules! switch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is heavily based on cfg_match
from the nightly standard library. It includes an extra case to integration with macros created with define_alias
though.
crates/bevy_platform/src/cfg.rs
Outdated
#[doc(hidden)] | ||
#[macro_export] | ||
macro_rules! noop { | ||
() => { false }; | ||
(if { $($p:tt)* } else { $($n:tt)* }) => { $($n)* }; | ||
($($p:tt)*) => {}; | ||
} | ||
|
||
#[doc(hidden)] | ||
#[macro_export] | ||
macro_rules! pass { | ||
() => { true }; | ||
(if { $($p:tt)* } else { $($n:tt)* }) => { $($p)* }; | ||
($($p:tt)*) => { $($p)* }; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We define this macros and then re-export them inside define_alias
because it is not possible to define a macro from within a macro if the inner macro uses repetition in its pattern (since the outer macro sees that as a repetition for itself). The usage of doc(inline)
obfuscates this issue and presents documentation on docs.rs as-if the macro is defined every time.
crates/bevy_platform/src/cfg.rs
Outdated
define_alias! { | ||
feature = "alloc" => { | ||
/// Indicates the `alloc` crate is available and can be used. | ||
alloc | ||
} | ||
feature = "std" => { | ||
/// Indicates the `std` crate is available and can be used. | ||
std | ||
} | ||
panic = "unwind" => { | ||
/// Indicates that a [`panic`] will be unwound, and can be potentially caught. | ||
panic_unwind | ||
} | ||
panic = "abort" => { | ||
/// Indicates that a [`panic`] will lead to an abort, and cannot be caught. | ||
panic_abort | ||
} | ||
all(target_arch = "wasm32", feature = "web") => { | ||
/// Indicates that this target has access to browser APIs. | ||
web | ||
} | ||
all(feature = "alloc", target_has_atomic = "ptr") => { | ||
/// Indicates that this target has access to a native implementation of `Arc`. | ||
arc | ||
} | ||
feature = "critical-section" => { | ||
/// Indicates `critical-section` is available. | ||
critical_section | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are mostly examples right now. How we actually use aliases will depend on the rest of Bevy's crates. For example, I believe Cart was interested in moving some critical platform-dependent dependencies to bevy_platform
so that features here could be inherited elsewhere.
@@ -46,7 +46,6 @@ critical-section = ["dep:critical-section", "portable-atomic/critical-section"] | |||
web = ["dep:web-time", "dep:getrandom"] | |||
|
|||
[dependencies] | |||
cfg-if = "1.0.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe bevy_platform::cfg::switch
is superior to cfg-if
so no reason to keep it around IMO.
#[cfg(feature = "alloc")] | ||
pub use alloc::{ | ||
borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, | ||
}; | ||
crate::cfg::alloc! { | ||
pub use alloc::{ | ||
borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The one downside of these macros is it's common to go one indentation level higher. Not impossible to work with, but annoying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't mind this, as it's easier to tell if a large function is feature-gated without having to scroll all the way up! :)
This |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about it, the greater I find it, especially in crates/bevy_platform/src/sync/mod.rs
: I find the refactor (alloc!
followed by nested conditional arc!
) really neat and much more comprehensive!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the ergonomic improvements this brings along! I have some notes, but it's generally only good things :)
#[cfg(feature = "alloc")] | ||
pub use alloc::{ | ||
borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, | ||
}; | ||
crate::cfg::alloc! { | ||
pub use alloc::{ | ||
borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally don't mind this, as it's easier to tell if a large function is feature-gated without having to scroll all the way up! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the right general path forward. I'm not sure we'll be able to use it to completely remove viral features (I suspect removing "std" completely will be a challenge), but my branch doing a similar thing here proves that it is viable for "web" (which is one of the bigger causes of viral nastiness).
The macros also just generally improve maintainability and legibility, which I like.
The big "risk" of using this approach is that bevy_platform
will become a dumping ground for types and re-exported crates, creating a bottleneck in our compiles (ex: see the web
module on my branch), as dependent crates can no longer conditionally pull in new dependencies based on these features. Every time we move a thing into bevy_platform
, we'll need to ask ourselves "is this worth it?".
In the case of "web", I think the answer is probably "yes it is worth it", as the cost to downstream developers is very high. We should measure the full bevy
compile time cost after that consolidation though.
Co-Authored-By: BD103 <59022059+BD103@users.noreply.github.com>
For other reviewers, I played around with this PR on this branch to see how far we could go with a bottom-up approach to features. There's definitely some design considerations to be had, but with a handful of controversial choices, we can definitely eliminate the Lots of interesting designs to consider! |
Did a quick pass over that branch. The clarity / terseness wins there are huge! Very exciting. |
Exciting! I look forward to further work here! |
Objective
bevy_app
#18170Solution
cfg
module inbevy_platform
which contains two macros to aid in working with features likeweb
,std
, andalloc
.switch
is a stable implementation ofcfg_match
, which itself is acore
alternative tocfg_if
.define_alias
is abuild.rs
-free alternative tocfg_aliases
with the ability to share feature information between crates.bevy_platform
to demonstrate usage.Testing
Showcase
Consider the typical
std
feature as an example of a "virality". With justbevy_platform
,bevy_utils
, andbevy_ecs
, we have 3 crates in a chain where activatingstd
in any of them should really activate it everywhere. The status-quo for this is for each crate to define its ownstd
feature, and ensure it includes thestd
feature of every dependency in that feature. For crates which don't even interact withstd
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.The above
web
andstd
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 inbevy_utils
andbevy_ecs
without those crates including their ownstd
features.To aid in more complex configurations,
switch
is provided to provide acfg_if
alternative that is compatible withdefine_alias
: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
, andbevy
(sincebevy
/_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:If
define_alias
was used inbevy_image
,bevy_render
would not need to re-export thektx2
feature, andbevy_core_pipeline
could directly probebevy_image
for the status ofktx2
andzstd
features to determine if it should compile thetonemapping_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.