Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

1
vendor/bevy_app/.cargo-checksum.json vendored Normal file
View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"87992f5f8feb2e5884932ceb8d1621d387ed865a5765480e1f45dcef0970a81b","Cargo.toml":"46d46345af2e847cf682546ab61621cefa444efe435984e0f67019c7cd52e440","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"83d974a9d731f77ebf218bdd6a886cc01c96f09431fca8d4e50946206bb66105","src/app.rs":"ed6d5fce0cb964ea87ae95a6ff7ed0a8e6f8eeff436de0c26472f2153938da43","src/lib.rs":"f139f8d58a205cecc8e0728068b102b726fc348eb50f352bfdf4ae5c4724bc57","src/main_schedule.rs":"aa145fbb30361e4c82091e2d6b6e03d07ae5d547f0a71eabe4a50b20ca152022","src/panic_handler.rs":"eb51dc8711d605cc0a12181cb4a09125c3565f61bed53e97d06f2aeeefcdcd35","src/plugin.rs":"292bbdba4179db8ce4c58313965f9c957be3f389fedc91de47204b30bf7811c3","src/plugin_group.rs":"1d87a2e14e12805a4e7dc4c05c70923b71ffd167e652e249692d66062bed9569","src/schedule_runner.rs":"ec1866fb27ab4dfb2f18cd9a16df394dbcea090011edb8b0bb51ea4de132c6d7","src/sub_app.rs":"6900f05d35c9eda6acdd5b855ca47042eda110eeac78eedc73ca8c88fbe7abfc","src/task_pool_plugin.rs":"ce894664976294738c15d1c827b5eae0043bc4bba4c09fa39893b96e323fdacd","src/terminal_ctrl_c_handler.rs":"d871023d0db4d3edae98290a63fc4e87b7ce993486ccf39f2f6fb8cb8e3ebcc2"},"package":"4491cc4c718ae76b4c6883df58b94cc88b32dcd894ea8d5b603c7c7da72ca967"}

1070
vendor/bevy_app/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

186
vendor/bevy_app/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,186 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_app"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides core App functionality for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
bevy_debug_stepping = []
bevy_reflect = [
"dep:bevy_reflect",
"bevy_ecs/bevy_reflect",
]
critical-section = [
"bevy_tasks/critical-section",
"bevy_ecs/critical-section",
"bevy_platform/critical-section",
"bevy_reflect?/critical-section",
]
default = [
"std",
"bevy_reflect",
"bevy_ecs/default",
"error_panic_hook",
]
error_panic_hook = []
reflect_functions = [
"bevy_reflect",
"bevy_reflect/functions",
"bevy_ecs/reflect_functions",
]
std = [
"bevy_reflect?/std",
"bevy_ecs/std",
"dep:ctrlc",
"downcast-rs/std",
"bevy_utils/std",
"bevy_tasks/std",
"bevy_platform/std",
]
trace = ["dep:tracing"]
web = [
"bevy_platform/web",
"bevy_tasks/web",
"bevy_reflect?/web",
"dep:wasm-bindgen",
"dep:web-sys",
"dep:console_error_panic_hook",
]
[lib]
name = "bevy_app"
path = "src/lib.rs"
[dependencies.bevy_derive]
version = "0.16.1"
[dependencies.bevy_ecs]
version = "0.16.1"
default-features = false
[dependencies.bevy_platform]
version = "0.16.1"
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
optional = true
default-features = false
[dependencies.bevy_tasks]
version = "0.16.1"
default-features = false
[dependencies.bevy_utils]
version = "0.16.1"
features = ["alloc"]
default-features = false
[dependencies.cfg-if]
version = "1.0.0"
[dependencies.downcast-rs]
version = "2"
default-features = false
[dependencies.log]
version = "0.4"
default-features = false
[dependencies.thiserror]
version = "2"
default-features = false
[dependencies.tracing]
version = "0.1"
optional = true
default-features = false
[dependencies.variadics_please]
version = "1.1"
[dev-dependencies.crossbeam-channel]
version = "0.5.0"
[target."cfg(any(unix, windows))".dependencies.ctrlc]
version = "3.4.4"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.console_error_panic_hook]
version = "0.1.6"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
version = "0.2"
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3"
features = ["Window"]
optional = true
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_app/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_app/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
vendor/bevy_app/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
# Bevy App
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](https://crates.io/crates/bevy_app)
[![Downloads](https://img.shields.io/crates/d/bevy_app.svg)](https://crates.io/crates/bevy_app)
[![Docs](https://docs.rs/bevy_app/badge.svg)](https://docs.rs/bevy_app/latest/bevy_app/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)
This crate is about everything concerning the highest-level, application layer of a [Bevy](https://crates.io/crates/bevy) app.

1832
vendor/bevy_app/src/app.rs vendored Normal file

File diff suppressed because it is too large Load Diff

63
vendor/bevy_app/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,63 @@
#![cfg_attr(
any(docsrs, docsrs_dep),
expect(
internal_features,
reason = "rustdoc_internals is needed for fake_variadic"
)
)]
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
#![no_std]
//! This crate is about everything concerning the highest-level, application layer of a Bevy app.
#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
// Required to make proc macros work in bevy itself.
extern crate self as bevy_app;
mod app;
mod main_schedule;
mod panic_handler;
mod plugin;
mod plugin_group;
mod schedule_runner;
mod sub_app;
mod task_pool_plugin;
#[cfg(all(any(unix, windows), feature = "std"))]
mod terminal_ctrl_c_handler;
pub use app::*;
pub use main_schedule::*;
pub use panic_handler::*;
pub use plugin::*;
pub use plugin_group::*;
pub use schedule_runner::*;
pub use sub_app::*;
pub use task_pool_plugin::*;
#[cfg(all(any(unix, windows), feature = "std"))]
pub use terminal_ctrl_c_handler::*;
/// The app prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
pub use crate::{
app::{App, AppExit},
main_schedule::{
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop,
RunFixedMainLoopSystem, SpawnScene, Startup, Update,
},
sub_app::SubApp,
NonSendMarker, Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin,
};
}

482
vendor/bevy_app/src/main_schedule.rs vendored Normal file
View File

@@ -0,0 +1,482 @@
use crate::{App, Plugin};
use alloc::{vec, vec::Vec};
use bevy_ecs::{
resource::Resource,
schedule::{
ExecutorKind, InternedScheduleLabel, IntoScheduleConfigs, Schedule, ScheduleLabel,
SystemSet,
},
system::Local,
world::{Mut, World},
};
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
///
/// By default, it will run the following schedules in the given order:
///
/// On the first run of the schedule (and only on the first run), it will run:
/// * [`StateTransition`] [^1]
/// * This means that [`OnEnter(MyState::Foo)`] will be called *before* [`PreStartup`]
/// if `MyState` was added to the app with `MyState::Foo` as the initial state,
/// as well as [`OnEnter(MyComputedState)`] if it `compute`s to `Some(Self)` in `MyState::Foo`.
/// * If you want to run systems before any state transitions, regardless of which state is the starting state,
/// for example, for registering required components, you can add your own custom startup schedule
/// before [`StateTransition`]. See [`MainScheduleOrder::insert_startup_before`] for more details.
/// * [`PreStartup`]
/// * [`Startup`]
/// * [`PostStartup`]
///
/// Then it will run:
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`] [^1]
/// * [`RunFixedMainLoop`]
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`]
/// * [`PostUpdate`]
/// * [`Last`]
///
/// # Rendering
///
/// Note rendering is not executed in the main schedule by default.
/// Instead, rendering is performed in a separate [`SubApp`]
/// which exchanges data with the main app in between the main schedule runs.
///
/// See [`RenderPlugin`] and [`PipelinedRenderingPlugin`] for more details.
///
/// [^1]: [`StateTransition`] is inserted only if you have `bevy_state` feature enabled. It is enabled in `default` features.
///
/// [`StateTransition`]: https://docs.rs/bevy/latest/bevy/prelude/struct.StateTransition.html
/// [`OnEnter(MyState::Foo)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html
/// [`OnEnter(MyComputedState)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html
/// [`RenderPlugin`]: https://docs.rs/bevy/latest/bevy/render/struct.RenderPlugin.html
/// [`PipelinedRenderingPlugin`]: https://docs.rs/bevy/latest/bevy/render/pipelined_rendering/struct.PipelinedRenderingPlugin.html
/// [`SubApp`]: crate::SubApp
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Main;
/// The schedule that runs before [`Startup`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PreStartup;
/// The schedule that runs once when the app starts.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Startup;
/// The schedule that runs once after [`Startup`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PostStartup;
/// Runs first in the schedule.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct First;
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
/// input OS events into an `Events` resource. This enables systems in [`Update`] to consume the events from the `Events`
/// resource without actually knowing about (or taking a direct scheduler dependency on) the "os-level keyboard event system".
///
/// [`PreUpdate`] exists to do "engine/plugin preparation work" that ensures the APIs consumed in [`Update`] are "ready".
/// [`PreUpdate`] abstracts out "pre work implementation details".
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PreUpdate;
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
///
/// If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
///
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct RunFixedMainLoop;
/// Runs first in the [`FixedMain`] schedule.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedFirst;
/// The schedule that contains logic that must run before [`FixedUpdate`].
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedPreUpdate;
/// The schedule that contains most gameplay logic, which runs at a fixed rate rather than every render frame.
/// For logic that should run once per render frame, use the [`Update`] schedule instead.
///
/// Examples of systems that should run at a fixed rate include (but are not limited to):
/// - Physics
/// - AI
/// - Networking
/// - Game rules
///
/// See the [`Update`] schedule for examples of systems that *should not* use this schedule.
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedUpdate;
/// The schedule that runs after the [`FixedUpdate`] schedule, for reacting
/// to changes made in the main update logic.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedPostUpdate;
/// The schedule that runs last in [`FixedMain`]
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedLast;
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
///
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct FixedMain;
/// The schedule that contains any app logic that must run once per render frame.
/// For most gameplay logic, consider using [`FixedUpdate`] instead.
///
/// Examples of systems that should run once per render frame include (but are not limited to):
/// - UI
/// - Input handling
/// - Audio control
///
/// See the [`FixedUpdate`] schedule for examples of systems that *should not* use this schedule.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Update;
/// The schedule that contains scene spawning.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct SpawnScene;
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
/// to "global" absolute transforms. This enables the [`PostUpdate`] transform-sync system to react to "local transform" changes in
/// [`Update`] without the [`Update`] systems needing to know about (or add scheduler dependencies for) the "global transform sync system".
///
/// [`PostUpdate`] exists to do "engine/plugin response work" to things that happened in [`Update`].
/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`].
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct PostUpdate;
/// Runs last in the schedule.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Last;
/// Animation system set. This exists in [`PostUpdate`].
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct Animation;
/// Defines the schedules to be run for the [`Main`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct MainScheduleOrder {
/// The labels to run for the main phase of the [`Main`] schedule (in the order they will be run).
pub labels: Vec<InternedScheduleLabel>,
/// The labels to run for the startup phase of the [`Main`] schedule (in the order they will be run).
pub startup_labels: Vec<InternedScheduleLabel>,
}
impl Default for MainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
First.intern(),
PreUpdate.intern(),
RunFixedMainLoop.intern(),
Update.intern(),
SpawnScene.intern(),
PostUpdate.intern(),
Last.intern(),
],
startup_labels: vec![PreStartup.intern(), Startup.intern(), PostStartup.intern()],
}
}
}
impl MainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule in the main list of schedules.
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule in the main list of schedules.
pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.labels.insert(index, schedule.intern());
}
/// Adds the given `schedule` after the `after` schedule in the list of startup schedules.
pub fn insert_startup_after(
&mut self,
after: impl ScheduleLabel,
schedule: impl ScheduleLabel,
) {
let index = self
.startup_labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.startup_labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule in the list of startup schedules.
pub fn insert_startup_before(
&mut self,
before: impl ScheduleLabel,
schedule: impl ScheduleLabel,
) {
let index = self
.startup_labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.startup_labels.insert(index, schedule.intern());
}
}
impl Main {
/// A system that runs the "main schedule"
pub fn run_main(world: &mut World, mut run_at_least_once: Local<bool>) {
if !*run_at_least_once {
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
for &label in &order.startup_labels {
let _ = world.try_run_schedule(label);
}
});
*run_at_least_once = true;
}
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
for &label in &order.labels {
let _ = world.try_run_schedule(label);
}
});
}
}
/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`].
pub struct MainSchedulePlugin;
impl Plugin for MainSchedulePlugin {
fn build(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new(Main);
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_schedule = Schedule::new(FixedMain);
fixed_main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_loop_schedule = Schedule::new(RunFixedMainLoop);
fixed_main_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
app.add_schedule(main_schedule)
.add_schedule(fixed_main_schedule)
.add_schedule(fixed_main_loop_schedule)
.init_resource::<MainScheduleOrder>()
.init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main)
.configure_sets(
RunFixedMainLoop,
(
RunFixedMainLoopSystem::BeforeFixedMainLoop,
RunFixedMainLoopSystem::FixedMainLoop,
RunFixedMainLoopSystem::AfterFixedMainLoop,
)
.chain(),
);
#[cfg(feature = "bevy_debug_stepping")]
{
use bevy_ecs::schedule::{IntoScheduleConfigs, Stepping};
app.add_systems(Main, Stepping::begin_frame.before(Main::run_main));
}
}
}
/// Defines the schedules to be run for the [`FixedMain`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct FixedMainScheduleOrder {
/// The labels to run for the [`FixedMain`] schedule (in the order they will be run).
pub labels: Vec<InternedScheduleLabel>,
}
impl Default for FixedMainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
FixedFirst.intern(),
FixedPreUpdate.intern(),
FixedUpdate.intern(),
FixedPostUpdate.intern(),
FixedLast.intern(),
],
}
}
}
impl FixedMainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, schedule.intern());
}
/// Adds the given `schedule` before the `before` schedule
pub fn insert_before(&mut self, before: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&before))
.unwrap_or_else(|| panic!("Expected {before:?} to exist"));
self.labels.insert(index, schedule.intern());
}
}
impl FixedMain {
/// A system that runs the fixed timestep's "main schedule"
pub fn run_fixed_main(world: &mut World) {
world.resource_scope(|world, order: Mut<FixedMainScheduleOrder>| {
for &label in &order.labels {
let _ = world.try_run_schedule(label);
}
});
}
}
/// Set enum for the systems that want to run inside [`RunFixedMainLoop`],
/// but before or after the fixed update logic. Systems in this set
/// will run exactly once per frame, regardless of the number of fixed updates.
/// They will also run under a variable timestep.
///
/// This is useful for handling things that need to run every frame, but
/// also need to be read by the fixed update logic. See the individual variants
/// for examples of what kind of systems should be placed in each.
///
/// Note that in contrast to most other Bevy schedules, systems added directly to
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)]
pub enum RunFixedMainLoopSystem {
/// Runs before the fixed update logic.
///
/// A good example of a system that fits here
/// is camera movement, which needs to be updated in a variable timestep,
/// as you want the camera to move with as much precision and updates as
/// the frame rate allows. A physics system that needs to read the camera
/// position and orientation, however, should run in the fixed update logic,
/// as it needs to be deterministic and run at a fixed rate for better stability.
/// Note that we are not placing the camera movement system in `Update`, as that
/// would mean that the physics system already ran at that point.
///
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(
/// RunFixedMainLoop,
/// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop))
/// .add_systems(FixedUpdate, update_physics);
///
/// # fn update_camera_rotation() {}
/// # fn update_physics() {}
/// ```
BeforeFixedMainLoop,
/// Contains the fixed update logic.
/// Runs [`FixedMain`] zero or more times based on delta of
/// [`Time<Virtual>`] and [`Time::overstep`].
///
/// Don't place systems here, use [`FixedUpdate`] and friends instead.
/// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all
/// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`].
///
/// [`Time<Virtual>`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html
/// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(FixedUpdate, update_physics)
/// .add_systems(
/// RunFixedMainLoop,
/// (
/// // This system will be called before all interpolation systems
/// // that third-party plugins might add.
/// prepare_for_interpolation
/// .after(RunFixedMainLoopSystem::FixedMainLoop)
/// .before(RunFixedMainLoopSystem::AfterFixedMainLoop),
/// )
/// );
///
/// # fn prepare_for_interpolation() {}
/// # fn update_physics() {}
/// ```
FixedMainLoop,
/// Runs after the fixed update logic.
///
/// A good example of a system that fits here
/// is a system that interpolates the transform of an entity between the last and current fixed update.
/// See the [fixed timestep example] for more details.
///
/// [fixed timestep example]: https://github.com/bevyengine/bevy/blob/main/examples/movement/physics_in_fixed_timestep.rs
///
/// # Example
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(FixedUpdate, update_physics)
/// .add_systems(
/// RunFixedMainLoop,
/// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop));
///
/// # fn interpolate_transforms() {}
/// # fn update_physics() {}
/// ```
AfterFixedMainLoop,
}

61
vendor/bevy_app/src/panic_handler.rs vendored Normal file
View File

@@ -0,0 +1,61 @@
//! This module provides panic handlers for [Bevy](https://bevyengine.org)
//! apps, and automatically configures platform specifics (i.e. Wasm or Android).
//!
//! By default, the [`PanicHandlerPlugin`] from this crate is included in Bevy's `DefaultPlugins`.
//!
//! For more fine-tuned control over panic behavior, disable the [`PanicHandlerPlugin`] or
//! `DefaultPlugins` during app initialization.
use crate::{App, Plugin};
/// Adds sensible panic handlers to Apps. This plugin is part of the `DefaultPlugins`. Adding
/// this plugin will setup a panic hook appropriate to your target platform:
/// * On Wasm, uses [`console_error_panic_hook`](https://crates.io/crates/console_error_panic_hook), logging
/// to the browser console.
/// * Other platforms are currently not setup.
///
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, PluginGroup, PanicHandlerPlugin};
/// fn main() {
/// App::new()
/// .add_plugins(MinimalPlugins)
/// .add_plugins(PanicHandlerPlugin)
/// .run();
/// }
/// ```
///
/// If you want to setup your own panic handler, you should disable this
/// plugin from `DefaultPlugins`:
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, PanicHandlerPlugin};
/// fn main() {
/// App::new()
/// .add_plugins(DefaultPlugins.build().disable::<PanicHandlerPlugin>())
/// .run();
/// }
/// ```
#[derive(Default)]
pub struct PanicHandlerPlugin;
impl Plugin for PanicHandlerPlugin {
fn build(&self, _app: &mut App) {
#[cfg(feature = "std")]
{
static SET_HOOK: std::sync::Once = std::sync::Once::new();
SET_HOOK.call_once(|| {
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
// This provides better panic handling in JS engines (displays the panic message and improves the backtrace).
std::panic::set_hook(alloc::boxed::Box::new(console_error_panic_hook::hook));
} else if #[cfg(feature = "error_panic_hook")] {
let current_hook = std::panic::take_hook();
std::panic::set_hook(alloc::boxed::Box::new(
bevy_ecs::error::bevy_error_panic_hook(current_hook),
));
}
// Otherwise use the default target panic hook - Do nothing.
}
});
}
}
}

194
vendor/bevy_app/src/plugin.rs vendored Normal file
View File

@@ -0,0 +1,194 @@
use crate::App;
use core::any::Any;
use downcast_rs::{impl_downcast, Downcast};
/// A collection of Bevy app logic and configuration.
///
/// Plugins configure an [`App`]. When an [`App`] registers a plugin,
/// the plugin's [`Plugin::build`] function is run. By default, a plugin
/// can only be added once to an [`App`].
///
/// If the plugin may need to be added twice or more, the function [`is_unique()`](Self::is_unique)
/// should be overridden to return `false`. Plugins are considered duplicate if they have the same
/// [`name()`](Self::name). The default `name()` implementation returns the type name, which means
/// generic plugins with different type parameters will not be considered duplicates.
///
/// ## Lifecycle of a plugin
///
/// When adding a plugin to an [`App`]:
/// * the app calls [`Plugin::build`] immediately, and register the plugin
/// * once the app started, it will wait for all registered [`Plugin::ready`] to return `true`
/// * it will then call all registered [`Plugin::finish`]
/// * and call all registered [`Plugin::cleanup`]
///
/// ## Defining a plugin.
///
/// Most plugins are simply functions that add configuration to an [`App`].
///
/// ```
/// # use bevy_app::{App, Update};
/// App::new().add_plugins(my_plugin).run();
///
/// // This function implements `Plugin`, along with every other `fn(&mut App)`.
/// pub fn my_plugin(app: &mut App) {
/// app.add_systems(Update, hello_world);
/// }
/// # fn hello_world() {}
/// ```
///
/// For more advanced use cases, the `Plugin` trait can be implemented manually for a type.
///
/// ```
/// # use bevy_app::*;
/// pub struct AccessibilityPlugin {
/// pub flicker_damping: bool,
/// // ...
/// }
///
/// impl Plugin for AccessibilityPlugin {
/// fn build(&self, app: &mut App) {
/// if self.flicker_damping {
/// app.add_systems(PostUpdate, damp_flickering);
/// }
/// }
/// }
/// # fn damp_flickering() {}
/// ```
pub trait Plugin: Downcast + Any + Send + Sync {
/// Configures the [`App`] to which this plugin is added.
fn build(&self, app: &mut App);
/// Has the plugin finished its setup? This can be useful for plugins that need something
/// asynchronous to happen before they can finish their setup, like the initialization of a renderer.
/// Once the plugin is ready, [`finish`](Plugin::finish) should be called.
fn ready(&self, _app: &App) -> bool {
true
}
/// Finish adding this plugin to the [`App`], once all plugins registered are ready. This can
/// be useful for plugins that depends on another plugin asynchronous setup, like the renderer.
fn finish(&self, _app: &mut App) {
// do nothing
}
/// Runs after all plugins are built and finished, but before the app schedule is executed.
/// This can be useful if you have some resource that other plugins need during their build step,
/// but after build you want to remove it and send it to another thread.
fn cleanup(&self, _app: &mut App) {
// do nothing
}
/// Configures a name for the [`Plugin`] which is primarily used for checking plugin
/// uniqueness and debugging.
fn name(&self) -> &str {
core::any::type_name::<Self>()
}
/// If the plugin can be meaningfully instantiated several times in an [`App`],
/// override this method to return `false`.
fn is_unique(&self) -> bool {
true
}
}
impl_downcast!(Plugin);
impl<T: Fn(&mut App) + Send + Sync + 'static> Plugin for T {
fn build(&self, app: &mut App) {
self(app);
}
}
/// Plugins state in the application
#[derive(PartialEq, Eq, Debug, Clone, Copy, PartialOrd, Ord)]
pub enum PluginsState {
/// Plugins are being added.
Adding,
/// All plugins already added are ready.
Ready,
/// Finish has been executed for all plugins added.
Finished,
/// Cleanup has been executed for all plugins added.
Cleaned,
}
/// A dummy plugin that's to temporarily occupy an entry in an app's plugin registry.
pub(crate) struct PlaceholderPlugin;
impl Plugin for PlaceholderPlugin {
fn build(&self, _app: &mut App) {}
}
/// Types that represent a set of [`Plugin`]s.
///
/// This is implemented for all types which implement [`Plugin`],
/// [`PluginGroup`](super::PluginGroup), and tuples over [`Plugins`].
pub trait Plugins<Marker>: sealed::Plugins<Marker> {}
impl<Marker, T> Plugins<Marker> for T where T: sealed::Plugins<Marker> {}
mod sealed {
use alloc::boxed::Box;
use variadics_please::all_tuples;
use crate::{App, AppError, Plugin, PluginGroup};
pub trait Plugins<Marker> {
fn add_to_app(self, app: &mut App);
}
pub struct PluginMarker;
pub struct PluginGroupMarker;
pub struct PluginsTupleMarker;
impl<P: Plugin> Plugins<PluginMarker> for P {
#[track_caller]
fn add_to_app(self, app: &mut App) {
if let Err(AppError::DuplicatePlugin { plugin_name }) =
app.add_boxed_plugin(Box::new(self))
{
panic!(
"Error adding plugin {plugin_name}: : plugin was already added in application"
)
}
}
}
impl<P: PluginGroup> Plugins<PluginGroupMarker> for P {
#[track_caller]
fn add_to_app(self, app: &mut App) {
self.build().finish(app);
}
}
macro_rules! impl_plugins_tuples {
($(#[$meta:meta])* $(($param: ident, $plugins: ident)),*) => {
$(#[$meta])*
impl<$($param, $plugins),*> Plugins<(PluginsTupleMarker, $($param,)*)> for ($($plugins,)*)
where
$($plugins: Plugins<$param>),*
{
#[expect(
clippy::allow_attributes,
reason = "This is inside a macro, and as such, may not trigger in all cases."
)]
#[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
#[allow(unused_variables, reason = "`app` is unused when implemented for the unit type `()`.")]
#[track_caller]
fn add_to_app(self, app: &mut App) {
let ($($plugins,)*) = self;
$($plugins.add_to_app(app);)*
}
}
}
}
all_tuples!(
#[doc(fake_variadic)]
impl_plugins_tuples,
0,
15,
P,
S
);
}

876
vendor/bevy_app/src/plugin_group.rs vendored Normal file
View File

@@ -0,0 +1,876 @@
use crate::{App, AppError, Plugin};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use bevy_platform::collections::hash_map::Entry;
use bevy_utils::TypeIdMap;
use core::any::TypeId;
use log::{debug, warn};
/// A macro for generating a well-documented [`PluginGroup`] from a list of [`Plugin`] paths.
///
/// Every plugin must implement the [`Default`] trait.
///
/// # Example
///
/// ```
/// # use bevy_app::*;
/// #
/// # mod velocity {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct VelocityPlugin;
/// # impl Plugin for VelocityPlugin { fn build(&self, _: &mut App) {} }
/// # }
/// #
/// # mod collision {
/// # pub mod capsule {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct CapsuleCollisionPlugin;
/// # impl Plugin for CapsuleCollisionPlugin { fn build(&self, _: &mut App) {} }
/// # }
/// # }
/// #
/// # #[derive(Default)]
/// # pub struct TickratePlugin;
/// # impl Plugin for TickratePlugin { fn build(&self, _: &mut App) {} }
/// #
/// # mod features {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct ForcePlugin;
/// # impl Plugin for ForcePlugin { fn build(&self, _: &mut App) {} }
/// # }
/// #
/// # mod web {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct WebCompatibilityPlugin;
/// # impl Plugin for WebCompatibilityPlugin { fn build(&self, _: &mut App) {} }
/// # }
/// #
/// # mod audio {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct AudioPlugins;
/// # impl PluginGroup for AudioPlugins {
/// # fn build(self) -> PluginGroupBuilder {
/// # PluginGroupBuilder::start::<Self>()
/// # }
/// # }
/// # }
/// #
/// # mod internal {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct InternalPlugin;
/// # impl Plugin for InternalPlugin { fn build(&self, _: &mut App) {} }
/// # }
/// #
/// plugin_group! {
/// /// Doc comments and annotations are supported: they will be added to the generated plugin
/// /// group.
/// #[derive(Debug)]
/// pub struct PhysicsPlugins {
/// // If referencing a plugin within the same module, you must prefix it with a colon `:`.
/// :TickratePlugin,
/// // If referencing a plugin within a different module, there must be three colons `:::`
/// // between the final module and the plugin name.
/// collision::capsule:::CapsuleCollisionPlugin,
/// velocity:::VelocityPlugin,
/// // If you feature-flag a plugin, it will be automatically documented. There can only be
/// // one automatically documented feature flag, and it must be first. All other
/// // `#[cfg()]` attributes must be wrapped by `#[custom()]`.
/// #[cfg(feature = "external_forces")]
/// features:::ForcePlugin,
/// // More complicated `#[cfg()]`s and annotations are not supported by automatic doc
/// // generation, in which case you must wrap it in `#[custom()]`.
/// #[custom(cfg(target_arch = "wasm32"))]
/// web:::WebCompatibilityPlugin,
/// // You can nest `PluginGroup`s within other `PluginGroup`s, you just need the
/// // `#[plugin_group]` attribute.
/// #[plugin_group]
/// audio:::AudioPlugins,
/// // You can hide plugins from documentation. Due to macro limitations, hidden plugins
/// // must be last.
/// #[doc(hidden)]
/// internal:::InternalPlugin
/// }
/// /// You may add doc comments after the plugin group as well. They will be appended after
/// /// the documented list of plugins.
/// }
/// ```
#[macro_export]
macro_rules! plugin_group {
{
$(#[$group_meta:meta])*
$vis:vis struct $group:ident {
$(
$(#[cfg(feature = $plugin_feature:literal)])?
$(#[custom($plugin_meta:meta)])*
$($plugin_path:ident::)* : $plugin_name:ident
),*
$(
$(,)?$(
#[plugin_group]
$(#[cfg(feature = $plugin_group_feature:literal)])?
$(#[custom($plugin_group_meta:meta)])*
$($plugin_group_path:ident::)* : $plugin_group_name:ident
),+
)?
$(
$(,)?$(
#[doc(hidden)]
$(#[cfg(feature = $hidden_plugin_feature:literal)])?
$(#[custom($hidden_plugin_meta:meta)])*
$($hidden_plugin_path:ident::)* : $hidden_plugin_name:ident
),+
)?
$(,)?
}
$($(#[doc = $post_doc:literal])+)?
} => {
$(#[$group_meta])*
///
$(#[doc = concat!(
" - [`", stringify!($plugin_name), "`](" $(, stringify!($plugin_path), "::")*, stringify!($plugin_name), ")"
$(, " - with feature `", $plugin_feature, "`")?
)])*
$($(#[doc = concat!(
" - [`", stringify!($plugin_group_name), "`](" $(, stringify!($plugin_group_path), "::")*, stringify!($plugin_group_name), ")"
$(, " - with feature `", $plugin_group_feature, "`")?
)]),+)?
$(
///
$(#[doc = $post_doc])+
)?
$vis struct $group;
impl $crate::PluginGroup for $group {
fn build(self) -> $crate::PluginGroupBuilder {
let mut group = $crate::PluginGroupBuilder::start::<Self>();
$(
$(#[cfg(feature = $plugin_feature)])?
$(#[$plugin_meta])*
{
const _: () = {
const fn check_default<T: Default>() {}
check_default::<$($plugin_path::)*$plugin_name>();
};
group = group.add(<$($plugin_path::)*$plugin_name>::default());
}
)*
$($(
$(#[cfg(feature = $plugin_group_feature)])?
$(#[$plugin_group_meta])*
{
const _: () = {
const fn check_default<T: Default>() {}
check_default::<$($plugin_group_path::)*$plugin_group_name>();
};
group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
}
)+)?
$($(
$(#[cfg(feature = $hidden_plugin_feature)])?
$(#[$hidden_plugin_meta])*
{
const _: () = {
const fn check_default<T: Default>() {}
check_default::<$($hidden_plugin_path::)*$hidden_plugin_name>();
};
group = group.add(<$($hidden_plugin_path::)*$hidden_plugin_name>::default());
}
)+)?
group
}
}
};
}
/// Combines multiple [`Plugin`]s into a single unit.
///
/// If you want an easier, but slightly more restrictive, method of implementing this trait, you
/// may be interested in the [`plugin_group!`] macro.
pub trait PluginGroup: Sized {
/// Configures the [`Plugin`]s that are to be added.
fn build(self) -> PluginGroupBuilder;
/// Configures a name for the [`PluginGroup`] which is primarily used for debugging.
fn name() -> String {
core::any::type_name::<Self>().to_string()
}
/// Sets the value of the given [`Plugin`], if it exists
fn set<T: Plugin>(self, plugin: T) -> PluginGroupBuilder {
self.build().set(plugin)
}
}
struct PluginEntry {
plugin: Box<dyn Plugin>,
enabled: bool,
}
impl PluginGroup for PluginGroupBuilder {
fn build(self) -> PluginGroupBuilder {
self
}
}
/// Facilitates the creation and configuration of a [`PluginGroup`].
///
/// Provides a build ordering to ensure that [`Plugin`]s which produce/require a [`Resource`](bevy_ecs::resource::Resource)
/// are built before/after dependent/depending [`Plugin`]s. [`Plugin`]s inside the group
/// can be disabled, enabled or reordered.
pub struct PluginGroupBuilder {
group_name: String,
plugins: TypeIdMap<PluginEntry>,
order: Vec<TypeId>,
}
impl PluginGroupBuilder {
/// Start a new builder for the [`PluginGroup`].
pub fn start<PG: PluginGroup>() -> Self {
Self {
group_name: PG::name(),
plugins: Default::default(),
order: Default::default(),
}
}
/// Checks if the [`PluginGroupBuilder`] contains the given [`Plugin`].
pub fn contains<T: Plugin>(&self) -> bool {
self.plugins.contains_key(&TypeId::of::<T>())
}
/// Returns `true` if the [`PluginGroupBuilder`] contains the given [`Plugin`] and it's enabled.
pub fn enabled<T: Plugin>(&self) -> bool {
self.plugins
.get(&TypeId::of::<T>())
.is_some_and(|e| e.enabled)
}
/// Finds the index of a target [`Plugin`].
fn index_of<Target: Plugin>(&self) -> Option<usize> {
self.order
.iter()
.position(|&ty| ty == TypeId::of::<Target>())
}
// Insert the new plugin as enabled, and removes its previous ordering if it was
// already present
fn upsert_plugin_state<T: Plugin>(&mut self, plugin: T, added_at_index: usize) {
self.upsert_plugin_entry_state(
TypeId::of::<T>(),
PluginEntry {
plugin: Box::new(plugin),
enabled: true,
},
added_at_index,
);
}
// Insert the new plugin entry as enabled, and removes its previous ordering if it was
// already present
fn upsert_plugin_entry_state(
&mut self,
key: TypeId,
plugin: PluginEntry,
added_at_index: usize,
) {
if let Some(entry) = self.plugins.insert(key, plugin) {
if entry.enabled {
warn!(
"You are replacing plugin '{}' that was not disabled.",
entry.plugin.name()
);
}
if let Some(to_remove) = self
.order
.iter()
.enumerate()
.find(|(i, ty)| *i != added_at_index && **ty == key)
.map(|(i, _)| i)
{
self.order.remove(to_remove);
}
}
}
/// Sets the value of the given [`Plugin`], if it exists.
///
/// # Panics
///
/// Panics if the [`Plugin`] does not exist.
pub fn set<T: Plugin>(self, plugin: T) -> Self {
self.try_set(plugin).unwrap_or_else(|_| {
panic!(
"{} does not exist in this PluginGroup",
core::any::type_name::<T>(),
)
})
}
/// Tries to set the value of the given [`Plugin`], if it exists.
///
/// If the given plugin doesn't exist returns self and the passed in [`Plugin`].
pub fn try_set<T: Plugin>(mut self, plugin: T) -> Result<Self, (Self, T)> {
match self.plugins.entry(TypeId::of::<T>()) {
Entry::Occupied(mut entry) => {
entry.get_mut().plugin = Box::new(plugin);
Ok(self)
}
Entry::Vacant(_) => Err((self, plugin)),
}
}
/// Adds the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`]. If the plugin was
/// already in the group, it is removed from its previous place.
// This is not confusing, clippy!
#[expect(
clippy::should_implement_trait,
reason = "This does not emulate the `+` operator, but is more akin to pushing to a stack."
)]
pub fn add<T: Plugin>(mut self, plugin: T) -> Self {
let target_index = self.order.len();
self.order.push(TypeId::of::<T>());
self.upsert_plugin_state(plugin, target_index);
self
}
/// Attempts to add the plugin [`Plugin`] at the end of this [`PluginGroupBuilder`].
///
/// If the plugin was already in the group the addition fails.
pub fn try_add<T: Plugin>(self, plugin: T) -> Result<Self, (Self, T)> {
if self.contains::<T>() {
return Err((self, plugin));
}
Ok(self.add(plugin))
}
/// Adds a [`PluginGroup`] at the end of this [`PluginGroupBuilder`]. If the plugin was
/// already in the group, it is removed from its previous place.
pub fn add_group(mut self, group: impl PluginGroup) -> Self {
let Self {
mut plugins, order, ..
} = group.build();
for plugin_id in order {
self.upsert_plugin_entry_state(
plugin_id,
plugins.remove(&plugin_id).unwrap(),
self.order.len(),
);
self.order.push(plugin_id);
}
self
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
///
/// If the plugin was already the group, it is removed from its previous place.
///
/// # Panics
///
/// Panics if `Target` is not already in this [`PluginGroupBuilder`].
pub fn add_before<Target: Plugin>(self, plugin: impl Plugin) -> Self {
self.try_add_before_overwrite::<Target, _>(plugin)
.unwrap_or_else(|_| {
panic!(
"Plugin does not exist in group: {}.",
core::any::type_name::<Target>()
)
})
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
///
/// If the plugin was already in the group the add fails. If there isn't a plugin
/// of type `Target` in the group the plugin we're trying to insert is returned.
pub fn try_add_before<Target: Plugin, Insert: Plugin>(
self,
plugin: Insert,
) -> Result<Self, (Self, Insert)> {
if self.contains::<Insert>() {
return Err((self, plugin));
}
self.try_add_before_overwrite::<Target, _>(plugin)
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] before the plugin of type `Target`.
///
/// If the plugin was already in the group, it is removed from its previous places.
/// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
/// is returned.
pub fn try_add_before_overwrite<Target: Plugin, Insert: Plugin>(
mut self,
plugin: Insert,
) -> Result<Self, (Self, Insert)> {
let Some(target_index) = self.index_of::<Target>() else {
return Err((self, plugin));
};
self.order.insert(target_index, TypeId::of::<Insert>());
self.upsert_plugin_state(plugin, target_index);
Ok(self)
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
///
/// If the plugin was already the group, it is removed from its previous place.
///
/// # Panics
///
/// Panics if `Target` is not already in this [`PluginGroupBuilder`].
pub fn add_after<Target: Plugin>(self, plugin: impl Plugin) -> Self {
self.try_add_after_overwrite::<Target, _>(plugin)
.unwrap_or_else(|_| {
panic!(
"Plugin does not exist in group: {}.",
core::any::type_name::<Target>()
)
})
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
///
/// If the plugin was already in the group the add fails. If there isn't a plugin
/// of type `Target` in the group the plugin we're trying to insert is returned.
pub fn try_add_after<Target: Plugin, Insert: Plugin>(
self,
plugin: Insert,
) -> Result<Self, (Self, Insert)> {
if self.contains::<Insert>() {
return Err((self, plugin));
}
self.try_add_after_overwrite::<Target, _>(plugin)
}
/// Adds a [`Plugin`] in this [`PluginGroupBuilder`] after the plugin of type `Target`.
///
/// If the plugin was already in the group, it is removed from its previous places.
/// If there isn't a plugin of type `Target` in the group the plugin we're trying to insert
/// is returned.
pub fn try_add_after_overwrite<Target: Plugin, Insert: Plugin>(
mut self,
plugin: Insert,
) -> Result<Self, (Self, Insert)> {
let Some(target_index) = self.index_of::<Target>() else {
return Err((self, plugin));
};
let target_index = target_index + 1;
self.order.insert(target_index, TypeId::of::<Insert>());
self.upsert_plugin_state(plugin, target_index);
Ok(self)
}
/// Enables a [`Plugin`].
///
/// [`Plugin`]s within a [`PluginGroup`] are enabled by default. This function is used to
/// opt back in to a [`Plugin`] after [disabling](Self::disable) it. If there are no plugins
/// of type `T` in this group, it will panic.
pub fn enable<T: Plugin>(mut self) -> Self {
let plugin_entry = self
.plugins
.get_mut(&TypeId::of::<T>())
.expect("Cannot enable a plugin that does not exist.");
plugin_entry.enabled = true;
self
}
/// Disables a [`Plugin`], preventing it from being added to the [`App`] with the rest of the
/// [`PluginGroup`]. The disabled [`Plugin`] keeps its place in the [`PluginGroup`], so it can
/// still be used for ordering with [`add_before`](Self::add_before) or
/// [`add_after`](Self::add_after), or it can be [re-enabled](Self::enable). If there are no
/// plugins of type `T` in this group, it will panic.
pub fn disable<T: Plugin>(mut self) -> Self {
let plugin_entry = self
.plugins
.get_mut(&TypeId::of::<T>())
.expect("Cannot disable a plugin that does not exist.");
plugin_entry.enabled = false;
self
}
/// Consumes the [`PluginGroupBuilder`] and [builds](Plugin::build) the contained [`Plugin`]s
/// in the order specified.
///
/// # Panics
///
/// Panics if one of the plugin in the group was already added to the application.
#[track_caller]
pub fn finish(mut self, app: &mut App) {
for ty in &self.order {
if let Some(entry) = self.plugins.remove(ty) {
if entry.enabled {
debug!("added plugin: {}", entry.plugin.name());
if let Err(AppError::DuplicatePlugin { plugin_name }) =
app.add_boxed_plugin(entry.plugin)
{
panic!(
"Error adding plugin {} in group {}: plugin was already added in application",
plugin_name,
self.group_name
);
}
}
}
}
}
}
/// A plugin group which doesn't do anything. Useful for examples:
/// ```
/// # use bevy_app::prelude::*;
/// use bevy_app::NoopPluginGroup as MinimalPlugins;
///
/// fn main(){
/// App::new().add_plugins(MinimalPlugins).run();
/// }
/// ```
#[doc(hidden)]
pub struct NoopPluginGroup;
impl PluginGroup for NoopPluginGroup {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use core::{any::TypeId, fmt::Debug};
use super::PluginGroupBuilder;
use crate::{App, NoopPluginGroup, Plugin};
struct PluginA;
impl Plugin for PluginA {
fn build(&self, _: &mut App) {}
}
struct PluginB;
impl Plugin for PluginB {
fn build(&self, _: &mut App) {}
}
struct PluginC;
impl Plugin for PluginC {
fn build(&self, _: &mut App) {}
}
#[derive(PartialEq, Debug)]
struct PluginWithData(u32);
impl Plugin for PluginWithData {
fn build(&self, _: &mut App) {}
}
fn get_plugin<T: Debug + 'static>(group: &PluginGroupBuilder, id: TypeId) -> &T {
group.plugins[&id]
.plugin
.as_any()
.downcast_ref::<T>()
.unwrap()
}
#[test]
fn contains() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB);
assert!(group.contains::<PluginA>());
assert!(!group.contains::<PluginC>());
let group = group.disable::<PluginA>();
assert!(group.enabled::<PluginB>());
assert!(!group.enabled::<PluginA>());
}
#[test]
fn basic_ordering() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add(PluginC);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginB>(),
TypeId::of::<PluginC>(),
]
);
}
#[test]
fn add_before() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add_before::<PluginB>(PluginC);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
}
#[test]
fn try_add_before() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>().add(PluginA);
let Ok(group) = group.try_add_before::<PluginA, _>(PluginC) else {
panic!("PluginA wasn't in group");
};
assert_eq!(
group.order,
vec![TypeId::of::<PluginC>(), TypeId::of::<PluginA>(),]
);
assert!(group.try_add_before::<PluginA, _>(PluginC).is_err());
}
#[test]
#[should_panic(
expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
)]
fn add_before_nonexistent() {
PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add_before::<PluginB>(PluginC);
}
#[test]
fn add_after() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add_after::<PluginA>(PluginC);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
}
#[test]
fn try_add_after() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB);
let Ok(group) = group.try_add_after::<PluginA, _>(PluginC) else {
panic!("PluginA wasn't in group");
};
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
assert!(group.try_add_after::<PluginA, _>(PluginC).is_err());
}
#[test]
#[should_panic(
expected = "Plugin does not exist in group: bevy_app::plugin_group::tests::PluginB."
)]
fn add_after_nonexistent() {
PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add_after::<PluginB>(PluginC);
}
#[test]
fn add_overwrite() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginWithData(0x0F))
.add(PluginC);
let id = TypeId::of::<PluginWithData>();
assert_eq!(
get_plugin::<PluginWithData>(&group, id),
&PluginWithData(0x0F)
);
let group = group.add(PluginWithData(0xA0));
assert_eq!(
get_plugin::<PluginWithData>(&group, id),
&PluginWithData(0xA0)
);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginWithData>(),
]
);
let Ok(group) = group.try_add_before_overwrite::<PluginA, _>(PluginWithData(0x01)) else {
panic!("PluginA wasn't in group");
};
assert_eq!(
get_plugin::<PluginWithData>(&group, id),
&PluginWithData(0x01)
);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginWithData>(),
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
]
);
let Ok(group) = group.try_add_after_overwrite::<PluginA, _>(PluginWithData(0xdeadbeef))
else {
panic!("PluginA wasn't in group");
};
assert_eq!(
get_plugin::<PluginWithData>(&group, id),
&PluginWithData(0xdeadbeef)
);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginWithData>(),
TypeId::of::<PluginC>(),
]
);
}
#[test]
fn readd() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add(PluginC)
.add(PluginB);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
}
#[test]
fn readd_before() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add(PluginC)
.add_before::<PluginB>(PluginC);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
}
#[test]
fn readd_after() {
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB)
.add(PluginC)
.add_after::<PluginA>(PluginC);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginC>(),
TypeId::of::<PluginB>(),
]
);
}
#[test]
fn add_basic_subgroup() {
let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginB);
let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
.add_group(group_a)
.add(PluginC);
assert_eq!(
group_b.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginB>(),
TypeId::of::<PluginC>(),
]
);
}
#[test]
fn add_conflicting_subgroup() {
let group_a = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginA)
.add(PluginC);
let group_b = PluginGroupBuilder::start::<NoopPluginGroup>()
.add(PluginB)
.add(PluginC);
let group = PluginGroupBuilder::start::<NoopPluginGroup>()
.add_group(group_a)
.add_group(group_b);
assert_eq!(
group.order,
vec![
TypeId::of::<PluginA>(),
TypeId::of::<PluginB>(),
TypeId::of::<PluginC>(),
]
);
}
}

175
vendor/bevy_app/src/schedule_runner.rs vendored Normal file
View File

@@ -0,0 +1,175 @@
use crate::{
app::{App, AppExit},
plugin::Plugin,
PluginsState,
};
use bevy_platform::time::Instant;
use core::time::Duration;
#[cfg(all(target_arch = "wasm32", feature = "web"))]
use {
alloc::{boxed::Box, rc::Rc},
core::cell::RefCell,
wasm_bindgen::{prelude::*, JsCast},
};
/// Determines the method used to run an [`App`]'s [`Schedule`](bevy_ecs::schedule::Schedule).
///
/// It is used in the [`ScheduleRunnerPlugin`].
#[derive(Copy, Clone, Debug)]
pub enum RunMode {
/// Indicates that the [`App`]'s schedule should run repeatedly.
Loop {
/// The minimum [`Duration`] to wait after a [`Schedule`](bevy_ecs::schedule::Schedule)
/// has completed before repeating. A value of [`None`] will not wait.
wait: Option<Duration>,
},
/// Indicates that the [`App`]'s schedule should run only once.
Once,
}
impl Default for RunMode {
fn default() -> Self {
RunMode::Loop { wait: None }
}
}
/// Configures an [`App`] to run its [`Schedule`](bevy_ecs::schedule::Schedule) according to a given
/// [`RunMode`].
///
/// [`ScheduleRunnerPlugin`] is included in the
/// [`MinimalPlugins`](https://docs.rs/bevy/latest/bevy/struct.MinimalPlugins.html) plugin group.
///
/// [`ScheduleRunnerPlugin`] is *not* included in the
/// [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html) plugin group
/// which assumes that the [`Schedule`](bevy_ecs::schedule::Schedule) will be executed by other means:
/// typically, the `winit` event loop
/// (see [`WinitPlugin`](https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html))
/// executes the schedule making [`ScheduleRunnerPlugin`] unnecessary.
#[derive(Default)]
pub struct ScheduleRunnerPlugin {
/// Determines whether the [`Schedule`](bevy_ecs::schedule::Schedule) is run once or repeatedly.
pub run_mode: RunMode,
}
impl ScheduleRunnerPlugin {
/// See [`RunMode::Once`].
pub fn run_once() -> Self {
ScheduleRunnerPlugin {
run_mode: RunMode::Once,
}
}
/// See [`RunMode::Loop`].
pub fn run_loop(wait_duration: Duration) -> Self {
ScheduleRunnerPlugin {
run_mode: RunMode::Loop {
wait: Some(wait_duration),
},
}
}
}
impl Plugin for ScheduleRunnerPlugin {
fn build(&self, app: &mut App) {
let run_mode = self.run_mode;
app.set_runner(move |mut app: App| {
let plugins_state = app.plugins_state();
if plugins_state != PluginsState::Cleaned {
while app.plugins_state() == PluginsState::Adding {
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
bevy_tasks::tick_global_task_pools_on_main_thread();
}
app.finish();
app.cleanup();
}
match run_mode {
RunMode::Once => {
app.update();
if let Some(exit) = app.should_exit() {
return exit;
}
AppExit::Success
}
RunMode::Loop { wait } => {
let tick = move |app: &mut App,
_wait: Option<Duration>|
-> Result<Option<Duration>, AppExit> {
let start_time = Instant::now();
app.update();
if let Some(exit) = app.should_exit() {
return Err(exit);
};
let end_time = Instant::now();
if let Some(wait) = _wait {
let exe_time = end_time - start_time;
if exe_time < wait {
return Ok(Some(wait - exe_time));
}
}
Ok(None)
};
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "wasm32", feature = "web"))] {
fn set_timeout(callback: &Closure<dyn FnMut()>, dur: Duration) {
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
callback.as_ref().unchecked_ref(),
dur.as_millis() as i32,
)
.expect("Should register `setTimeout`.");
}
let asap = Duration::from_millis(1);
let exit = Rc::new(RefCell::new(AppExit::Success));
let closure_exit = exit.clone();
let mut app = Rc::new(app);
let moved_tick_closure = Rc::new(RefCell::new(None));
let base_tick_closure = moved_tick_closure.clone();
let tick_app = move || {
let app = Rc::get_mut(&mut app).unwrap();
let delay = tick(app, wait);
match delay {
Ok(delay) => set_timeout(
moved_tick_closure.borrow().as_ref().unwrap(),
delay.unwrap_or(asap),
),
Err(code) => {
closure_exit.replace(code);
}
}
};
*base_tick_closure.borrow_mut() =
Some(Closure::wrap(Box::new(tick_app) as Box<dyn FnMut()>));
set_timeout(base_tick_closure.borrow().as_ref().unwrap(), asap);
exit.take()
} else {
loop {
match tick(&mut app, wait) {
Ok(Some(delay)) => {
bevy_platform::thread::sleep(delay);
}
Ok(None) => continue,
Err(exit) => return exit,
}
}
}
}
}
}
});
}
}

521
vendor/bevy_app/src/sub_app.rs vendored Normal file
View File

@@ -0,0 +1,521 @@
use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState};
use alloc::{boxed::Box, string::String, vec::Vec};
use bevy_ecs::{
event::EventRegistry,
prelude::*,
schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
system::{ScheduleSystem, SystemId, SystemInput},
};
use bevy_platform::collections::{HashMap, HashSet};
use core::fmt::Debug;
#[cfg(feature = "trace")]
use tracing::info_span;
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
/// A secondary application with its own [`World`]. These can run independently of each other.
///
/// These are useful for situations where certain processes (e.g. a render thread) need to be kept
/// separate from the main application.
///
/// # Example
///
/// ```
/// # use bevy_app::{App, AppLabel, SubApp, Main};
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::schedule::ScheduleLabel;
///
/// #[derive(Resource, Default)]
/// struct Val(pub i32);
///
/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
/// struct ExampleApp;
///
/// // Create an app with a certain resource.
/// let mut app = App::new();
/// app.insert_resource(Val(10));
///
/// // Create a sub-app with the same resource and a single schedule.
/// let mut sub_app = SubApp::new();
/// sub_app.update_schedule = Some(Main.intern());
/// sub_app.insert_resource(Val(100));
///
/// // Setup an extract function to copy the resource's value in the main world.
/// sub_app.set_extract(|main_world, sub_world| {
/// sub_world.resource_mut::<Val>().0 = main_world.resource::<Val>().0;
/// });
///
/// // Schedule a system that will verify extraction is working.
/// sub_app.add_systems(Main, |counter: Res<Val>| {
/// // The value will be copied during extraction, so we should see 10 instead of 100.
/// assert_eq!(counter.0, 10);
/// });
///
/// // Add the sub-app to the main app.
/// app.insert_sub_app(ExampleApp, sub_app);
///
/// // Update the application once (using the default runner).
/// app.run();
/// ```
pub struct SubApp {
/// The data of this application.
world: World,
/// List of plugins that have been added.
pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
/// The names of plugins that have been added to this app. (used to track duplicates and
/// already-registered plugins)
pub(crate) plugin_names: HashSet<String>,
/// Panics if an update is attempted while plugins are building.
pub(crate) plugin_build_depth: usize,
pub(crate) plugins_state: PluginsState,
/// The schedule that will be run by [`update`](Self::update).
pub update_schedule: Option<InternedScheduleLabel>,
/// A function that gives mutable access to two app worlds. This is primarily
/// intended for copying data from the main world to secondary worlds.
extract: Option<ExtractFn>,
}
impl Debug for SubApp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "SubApp")
}
}
impl Default for SubApp {
fn default() -> Self {
let mut world = World::new();
world.init_resource::<Schedules>();
Self {
world,
plugin_registry: Vec::default(),
plugin_names: HashSet::default(),
plugin_build_depth: 0,
plugins_state: PluginsState::Adding,
update_schedule: None,
extract: None,
}
}
}
impl SubApp {
/// Returns a default, empty [`SubApp`].
pub fn new() -> Self {
Self::default()
}
/// This method is a workaround. Each [`SubApp`] can have its own plugins, but [`Plugin`]
/// works on an [`App`] as a whole.
fn run_as_app<F>(&mut self, f: F)
where
F: FnOnce(&mut App),
{
let mut app = App::empty();
core::mem::swap(self, &mut app.sub_apps.main);
f(&mut app);
core::mem::swap(self, &mut app.sub_apps.main);
}
/// Returns a reference to the [`World`].
pub fn world(&self) -> &World {
&self.world
}
/// Returns a mutable reference to the [`World`].
pub fn world_mut(&mut self) -> &mut World {
&mut self.world
}
/// Runs the default schedule.
///
/// Does not clear internal trackers used for change detection.
pub fn run_default_schedule(&mut self) {
if self.is_building_plugins() {
panic!("SubApp::update() was called while a plugin was building.");
}
if let Some(label) = self.update_schedule {
self.world.run_schedule(label);
}
}
/// Runs the default schedule and updates internal component trackers.
pub fn update(&mut self) {
self.run_default_schedule();
self.world.clear_trackers();
}
/// Extracts data from `world` into the app's world using the registered extract method.
///
/// **Note:** There is no default extract method. Calling `extract` does nothing if
/// [`set_extract`](Self::set_extract) has not been called.
pub fn extract(&mut self, world: &mut World) {
if let Some(f) = self.extract.as_mut() {
f(world, &mut self.world);
}
}
/// Sets the method that will be called by [`extract`](Self::extract).
///
/// The first argument is the `World` to extract data from, the second argument is the app `World`.
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
where
F: Fn(&mut World, &mut World) + Send + 'static,
{
self.extract = Some(Box::new(extract));
self
}
/// Take the function that will be called by [`extract`](Self::extract) out of the app, if any was set,
/// and replace it with `None`.
///
/// If you use Bevy, `bevy_render` will set a default extract function used to extract data from
/// the main world into the render world as part of the Extract phase. In that case, you cannot replace
/// it with your own function. Instead, take the Bevy default function with this, and install your own
/// instead which calls the Bevy default.
///
/// ```
/// # use bevy_app::SubApp;
/// # let mut app = SubApp::new();
/// let default_fn = app.take_extract();
/// app.set_extract(move |main, render| {
/// // Do pre-extract custom logic
/// // [...]
///
/// // Call Bevy's default, which executes the Extract phase
/// if let Some(f) = default_fn.as_ref() {
/// f(main, render);
/// }
///
/// // Do post-extract custom logic
/// // [...]
/// });
/// ```
pub fn take_extract(&mut self) -> Option<ExtractFn> {
self.extract.take()
}
/// See [`App::insert_resource`].
pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
self.world.insert_resource(resource);
self
}
/// See [`App::init_resource`].
pub fn init_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
self.world.init_resource::<R>();
self
}
/// See [`App::add_systems`].
pub fn add_systems<M>(
&mut self,
schedule: impl ScheduleLabel,
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
schedules.add_systems(schedule, systems);
self
}
/// See [`App::register_system`].
pub fn register_system<I, O, M>(
&mut self,
system: impl IntoSystem<I, O, M> + 'static,
) -> SystemId<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
self.world.register_system(system)
}
/// See [`App::configure_sets`].
#[track_caller]
pub fn configure_sets<M>(
&mut self,
schedule: impl ScheduleLabel,
sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
schedules.configure_sets(schedule, sets);
self
}
/// See [`App::add_schedule`].
pub fn add_schedule(&mut self, schedule: Schedule) -> &mut Self {
let mut schedules = self.world.resource_mut::<Schedules>();
schedules.insert(schedule);
self
}
/// See [`App::init_schedule`].
pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self {
let label = label.intern();
let mut schedules = self.world.resource_mut::<Schedules>();
if !schedules.contains(label) {
schedules.insert(Schedule::new(label));
}
self
}
/// See [`App::get_schedule`].
pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
let schedules = self.world.get_resource::<Schedules>()?;
schedules.get(label)
}
/// See [`App::get_schedule_mut`].
pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
let schedules = self.world.get_resource_mut::<Schedules>()?;
// We must call `.into_inner` here because the borrow checker only understands reborrows
// using ordinary references, not our `Mut` smart pointers.
schedules.into_inner().get_mut(label)
}
/// See [`App::edit_schedule`].
pub fn edit_schedule(
&mut self,
label: impl ScheduleLabel,
mut f: impl FnMut(&mut Schedule),
) -> &mut Self {
let label = label.intern();
let mut schedules = self.world.resource_mut::<Schedules>();
if !schedules.contains(label) {
schedules.insert(Schedule::new(label));
}
let schedule = schedules.get_mut(label).unwrap();
f(schedule);
self
}
/// See [`App::configure_schedules`].
pub fn configure_schedules(
&mut self,
schedule_build_settings: ScheduleBuildSettings,
) -> &mut Self {
self.world_mut()
.resource_mut::<Schedules>()
.configure_schedules(schedule_build_settings);
self
}
/// See [`App::allow_ambiguous_component`].
pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
self.world_mut().allow_ambiguous_component::<T>();
self
}
/// See [`App::allow_ambiguous_resource`].
pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
self.world_mut().allow_ambiguous_resource::<T>();
self
}
/// See [`App::ignore_ambiguity`].
#[track_caller]
pub fn ignore_ambiguity<M1, M2, S1, S2>(
&mut self,
schedule: impl ScheduleLabel,
a: S1,
b: S2,
) -> &mut Self
where
S1: IntoSystemSet<M1>,
S2: IntoSystemSet<M2>,
{
let schedule = schedule.intern();
let mut schedules = self.world.resource_mut::<Schedules>();
schedules.ignore_ambiguity(schedule, a, b);
self
}
/// See [`App::add_event`].
pub fn add_event<T>(&mut self) -> &mut Self
where
T: Event,
{
if !self.world.contains_resource::<Events<T>>() {
EventRegistry::register_event::<T>(self.world_mut());
}
self
}
/// See [`App::add_plugins`].
pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
self.run_as_app(|app| plugins.add_to_app(app));
self
}
/// See [`App::is_plugin_added`].
pub fn is_plugin_added<T>(&self) -> bool
where
T: Plugin,
{
self.plugin_names.contains(core::any::type_name::<T>())
}
/// See [`App::get_added_plugins`].
pub fn get_added_plugins<T>(&self) -> Vec<&T>
where
T: Plugin,
{
self.plugin_registry
.iter()
.filter_map(|p| p.downcast_ref())
.collect()
}
/// Returns `true` if there is no plugin in the middle of being built.
pub(crate) fn is_building_plugins(&self) -> bool {
self.plugin_build_depth > 0
}
/// Return the state of plugins.
#[inline]
pub fn plugins_state(&mut self) -> PluginsState {
match self.plugins_state {
PluginsState::Adding => {
let mut state = PluginsState::Ready;
let plugins = core::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| {
for plugin in &plugins {
if !plugin.ready(app) {
state = PluginsState::Adding;
return;
}
}
});
self.plugin_registry = plugins;
state
}
state => state,
}
}
/// Runs [`Plugin::finish`] for each plugin.
pub fn finish(&mut self) {
let plugins = core::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| {
for plugin in &plugins {
plugin.finish(app);
}
});
self.plugin_registry = plugins;
self.plugins_state = PluginsState::Finished;
}
/// Runs [`Plugin::cleanup`] for each plugin.
pub fn cleanup(&mut self) {
let plugins = core::mem::take(&mut self.plugin_registry);
self.run_as_app(|app| {
for plugin in &plugins {
plugin.cleanup(app);
}
});
self.plugin_registry = plugins;
self.plugins_state = PluginsState::Cleaned;
}
/// See [`App::register_type`].
#[cfg(feature = "bevy_reflect")]
pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register::<T>();
self
}
/// See [`App::register_type_data`].
#[cfg(feature = "bevy_reflect")]
pub fn register_type_data<
T: bevy_reflect::Reflect + bevy_reflect::TypePath,
D: bevy_reflect::TypeData + bevy_reflect::FromType<T>,
>(
&mut self,
) -> &mut Self {
let registry = self.world.resource_mut::<AppTypeRegistry>();
registry.write().register_type_data::<T, D>();
self
}
/// See [`App::register_function`].
#[cfg(feature = "reflect_functions")]
pub fn register_function<F, Marker>(&mut self, function: F) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<'static, Marker> + 'static,
{
let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register(function).unwrap();
self
}
/// See [`App::register_function_with_name`].
#[cfg(feature = "reflect_functions")]
pub fn register_function_with_name<F, Marker>(
&mut self,
name: impl Into<alloc::borrow::Cow<'static, str>>,
function: F,
) -> &mut Self
where
F: bevy_reflect::func::IntoFunction<'static, Marker> + 'static,
{
let registry = self.world.resource_mut::<AppFunctionRegistry>();
registry.write().register_with_name(name, function).unwrap();
self
}
}
/// The collection of sub-apps that belong to an [`App`].
#[derive(Default)]
pub struct SubApps {
/// The primary sub-app that contains the "main" world.
pub main: SubApp,
/// Other, labeled sub-apps.
pub sub_apps: HashMap<InternedAppLabel, SubApp>,
}
impl SubApps {
/// Calls [`update`](SubApp::update) for the main sub-app, and then calls
/// [`extract`](SubApp::extract) and [`update`](SubApp::update) for the rest.
pub fn update(&mut self) {
#[cfg(feature = "trace")]
let _bevy_update_span = info_span!("update").entered();
{
#[cfg(feature = "trace")]
let _bevy_frame_update_span = info_span!("main app").entered();
self.main.run_default_schedule();
}
for (_label, sub_app) in self.sub_apps.iter_mut() {
#[cfg(feature = "trace")]
let _sub_app_span = info_span!("sub app", name = ?_label).entered();
sub_app.extract(&mut self.main.world);
sub_app.update();
}
self.main.world.clear_trackers();
}
/// Returns an iterator over the sub-apps (starting with the main one).
pub fn iter(&self) -> impl Iterator<Item = &SubApp> + '_ {
core::iter::once(&self.main).chain(self.sub_apps.values())
}
/// Returns a mutable iterator over the sub-apps (starting with the main one).
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SubApp> + '_ {
core::iter::once(&mut self.main).chain(self.sub_apps.values_mut())
}
/// Extract data from the main world into the [`SubApp`] with the given label and perform an update if it exists.
pub fn update_subapp_by_label(&mut self, label: impl AppLabel) {
if let Some(sub_app) = self.sub_apps.get_mut(&label.intern()) {
sub_app.extract(&mut self.main.world);
sub_app.update();
}
}
}

297
vendor/bevy_app/src/task_pool_plugin.rs vendored Normal file
View File

@@ -0,0 +1,297 @@
use crate::{App, Plugin};
use alloc::string::ToString;
use bevy_platform::sync::Arc;
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
use core::{fmt::Debug, marker::PhantomData};
use log::trace;
cfg_if::cfg_if! {
if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] {
use {crate::Last, bevy_ecs::prelude::NonSend, bevy_tasks::tick_global_task_pools_on_main_thread};
/// A system used to check and advanced our task pools.
///
/// Calls [`tick_global_task_pools_on_main_thread`],
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
tick_global_task_pools_on_main_thread();
}
}
}
/// Setup of default task pools: [`AsyncComputeTaskPool`], [`ComputeTaskPool`], [`IoTaskPool`].
#[derive(Default)]
pub struct TaskPoolPlugin {
/// Options for the [`TaskPool`](bevy_tasks::TaskPool) created at application start.
pub task_pool_options: TaskPoolOptions,
}
impl Plugin for TaskPoolPlugin {
fn build(&self, _app: &mut App) {
// Setup the default bevy task pools
self.task_pool_options.create_default_pools();
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
_app.add_systems(Last, tick_global_task_pools);
}
}
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
pub struct NonSendMarker(PhantomData<*mut ()>);
/// Defines a simple way to determine how many threads to use given the number of remaining cores
/// and number of total cores
#[derive(Clone)]
pub struct TaskPoolThreadAssignmentPolicy {
/// Force using at least this many threads
pub min_threads: usize,
/// Under no circumstance use more than this many threads for this pool
pub max_threads: usize,
/// Target using this percentage of total cores, clamped by `min_threads` and `max_threads`. It is
/// permitted to use 1.0 to try to use all remaining threads
pub percent: f32,
/// Callback that is invoked once for every created thread as it starts.
/// This configuration will be ignored under wasm platform.
pub on_thread_spawn: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
/// Callback that is invoked once for every created thread as it terminates
/// This configuration will be ignored under wasm platform.
pub on_thread_destroy: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
}
impl Debug for TaskPoolThreadAssignmentPolicy {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaskPoolThreadAssignmentPolicy")
.field("min_threads", &self.min_threads)
.field("max_threads", &self.max_threads)
.field("percent", &self.percent)
.finish()
}
}
impl TaskPoolThreadAssignmentPolicy {
/// Determine the number of threads to use for this task pool
fn get_number_of_threads(&self, remaining_threads: usize, total_threads: usize) -> usize {
assert!(self.percent >= 0.0);
let proportion = total_threads as f32 * self.percent;
let mut desired = proportion as usize;
// Equivalent to round() for positive floats without libm requirement for
// no_std compatibility
if proportion - desired as f32 >= 0.5 {
desired += 1;
}
// Limit ourselves to the number of cores available
desired = desired.min(remaining_threads);
// Clamp by min_threads, max_threads. (This may result in us using more threads than are
// available, this is intended. An example case where this might happen is a device with
// <= 2 threads.
desired.clamp(self.min_threads, self.max_threads)
}
}
/// Helper for configuring and creating the default task pools. For end-users who want full control,
/// set up [`TaskPoolPlugin`]
#[derive(Clone, Debug)]
pub struct TaskPoolOptions {
/// If the number of physical cores is less than `min_total_threads`, force using
/// `min_total_threads`
pub min_total_threads: usize,
/// If the number of physical cores is greater than `max_total_threads`, force using
/// `max_total_threads`
pub max_total_threads: usize,
/// Used to determine number of IO threads to allocate
pub io: TaskPoolThreadAssignmentPolicy,
/// Used to determine number of async compute threads to allocate
pub async_compute: TaskPoolThreadAssignmentPolicy,
/// Used to determine number of compute threads to allocate
pub compute: TaskPoolThreadAssignmentPolicy,
}
impl Default for TaskPoolOptions {
fn default() -> Self {
TaskPoolOptions {
// By default, use however many cores are available on the system
min_total_threads: 1,
max_total_threads: usize::MAX,
// Use 25% of cores for IO, at least 1, no more than 4
io: TaskPoolThreadAssignmentPolicy {
min_threads: 1,
max_threads: 4,
percent: 0.25,
on_thread_spawn: None,
on_thread_destroy: None,
},
// Use 25% of cores for async compute, at least 1, no more than 4
async_compute: TaskPoolThreadAssignmentPolicy {
min_threads: 1,
max_threads: 4,
percent: 0.25,
on_thread_spawn: None,
on_thread_destroy: None,
},
// Use all remaining cores for compute (at least 1)
compute: TaskPoolThreadAssignmentPolicy {
min_threads: 1,
max_threads: usize::MAX,
percent: 1.0, // This 1.0 here means "whatever is left over"
on_thread_spawn: None,
on_thread_destroy: None,
},
}
}
}
impl TaskPoolOptions {
/// Create a configuration that forces using the given number of threads.
pub fn with_num_threads(thread_count: usize) -> Self {
TaskPoolOptions {
min_total_threads: thread_count,
max_total_threads: thread_count,
..Default::default()
}
}
/// Inserts the default thread pools into the given resource map based on the configured values
pub fn create_default_pools(&self) {
let total_threads = bevy_tasks::available_parallelism()
.clamp(self.min_total_threads, self.max_total_threads);
trace!("Assigning {} cores to default task pools", total_threads);
let mut remaining_threads = total_threads;
{
// Determine the number of IO threads we will use
let io_threads = self
.io
.get_number_of_threads(remaining_threads, total_threads);
trace!("IO Threads: {}", io_threads);
remaining_threads = remaining_threads.saturating_sub(io_threads);
IoTaskPool::get_or_init(|| {
let builder = TaskPoolBuilder::default()
.num_threads(io_threads)
.thread_name("IO Task Pool".to_string());
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
let builder = {
let mut builder = builder;
if let Some(f) = self.io.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.io.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
builder
};
builder.build()
});
}
{
// Determine the number of async compute threads we will use
let async_compute_threads = self
.async_compute
.get_number_of_threads(remaining_threads, total_threads);
trace!("Async Compute Threads: {}", async_compute_threads);
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
AsyncComputeTaskPool::get_or_init(|| {
let builder = TaskPoolBuilder::default()
.num_threads(async_compute_threads)
.thread_name("Async Compute Task Pool".to_string());
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
let builder = {
let mut builder = builder;
if let Some(f) = self.async_compute.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.async_compute.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
builder
};
builder.build()
});
}
{
// Determine the number of compute threads we will use
// This is intentionally last so that an end user can specify 1.0 as the percent
let compute_threads = self
.compute
.get_number_of_threads(remaining_threads, total_threads);
trace!("Compute Threads: {}", compute_threads);
ComputeTaskPool::get_or_init(|| {
let builder = TaskPoolBuilder::default()
.num_threads(compute_threads)
.thread_name("Compute Task Pool".to_string());
#[cfg(not(all(target_arch = "wasm32", feature = "web")))]
let builder = {
let mut builder = builder;
if let Some(f) = self.compute.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.compute.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
builder
};
builder.build()
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy_tasks::prelude::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool};
#[test]
fn runs_spawn_local_tasks() {
let mut app = App::new();
app.add_plugins(TaskPoolPlugin::default());
let (async_tx, async_rx) = crossbeam_channel::unbounded();
AsyncComputeTaskPool::get()
.spawn_local(async move {
async_tx.send(()).unwrap();
})
.detach();
let (compute_tx, compute_rx) = crossbeam_channel::unbounded();
ComputeTaskPool::get()
.spawn_local(async move {
compute_tx.send(()).unwrap();
})
.detach();
let (io_tx, io_rx) = crossbeam_channel::unbounded();
IoTaskPool::get()
.spawn_local(async move {
io_tx.send(()).unwrap();
})
.detach();
app.run();
async_rx.try_recv().unwrap();
compute_rx.try_recv().unwrap();
io_rx.try_recv().unwrap();
}
}

View File

@@ -0,0 +1,73 @@
use core::sync::atomic::{AtomicBool, Ordering};
use bevy_ecs::event::EventWriter;
use crate::{App, AppExit, Plugin, Update};
pub use ctrlc;
/// Indicates that all [`App`]'s should exit.
static SHOULD_EXIT: AtomicBool = AtomicBool::new(false);
/// Gracefully handles `Ctrl+C` by emitting a [`AppExit`] event. This plugin is part of the `DefaultPlugins`.
///
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as MinimalPlugins, PluginGroup, TerminalCtrlCHandlerPlugin};
/// fn main() {
/// App::new()
/// .add_plugins(MinimalPlugins)
/// .add_plugins(TerminalCtrlCHandlerPlugin)
/// .run();
/// }
/// ```
///
/// If you want to setup your own `Ctrl+C` handler, you should call the
/// [`TerminalCtrlCHandlerPlugin::gracefully_exit`] function in your handler if you want bevy to gracefully exit.
/// ```no_run
/// # use bevy_app::{App, NoopPluginGroup as DefaultPlugins, PluginGroup, TerminalCtrlCHandlerPlugin, ctrlc};
/// fn main() {
/// // Your own `Ctrl+C` handler
/// ctrlc::set_handler(move || {
/// // Other clean up code ...
///
/// TerminalCtrlCHandlerPlugin::gracefully_exit();
/// });
///
/// App::new()
/// .add_plugins(DefaultPlugins)
/// .run();
/// }
/// ```
#[derive(Default)]
pub struct TerminalCtrlCHandlerPlugin;
impl TerminalCtrlCHandlerPlugin {
/// Sends the [`AppExit`] event to all apps using this plugin to make them gracefully exit.
pub fn gracefully_exit() {
SHOULD_EXIT.store(true, Ordering::Relaxed);
}
/// Sends a [`AppExit`] event when the user presses `Ctrl+C` on the terminal.
pub fn exit_on_flag(mut events: EventWriter<AppExit>) {
if SHOULD_EXIT.load(Ordering::Relaxed) {
events.write(AppExit::from_code(130));
}
}
}
impl Plugin for TerminalCtrlCHandlerPlugin {
fn build(&self, app: &mut App) {
let result = ctrlc::try_set_handler(move || {
Self::gracefully_exit();
});
match result {
Ok(()) => {}
Err(ctrlc::Error::MultipleHandlers) => {
log::info!("Skipping installing `Ctrl+C` handler as one was already installed. Please call `TerminalCtrlCHandlerPlugin::gracefully_exit` in your own `Ctrl+C` handler if you want Bevy to gracefully exit on `Ctrl+C`.");
}
Err(err) => log::warn!("Failed to set `Ctrl+C` handler: {err}"),
}
app.add_systems(Update, TerminalCtrlCHandlerPlugin::exit_on_flag);
}
}