diff --git a/.cargo/config_fast_builds.toml b/.cargo/config_fast_builds.toml index 4ee56c480c157..2d82ca39a89c0 100644 --- a/.cargo/config_fast_builds.toml +++ b/.cargo/config_fast_builds.toml @@ -30,7 +30,7 @@ rustflags = [ [target.x86_64-pc-windows-msvc] linker = "rust-lld.exe" # Use LLD Linker rustflags = [ - "-Zshare-generics=n", + "-Zshare-generics=n", # (Nightly) "-Zthreads=0", # (Nightly) Use improved multithreading with the recommended amount of threads. ] diff --git a/.github/contributing/engine_style_guide.md b/.github/contributing/engine_style_guide.md index 5ccc1c59a2586..ff656373fdb4d 100644 --- a/.github/contributing/engine_style_guide.md +++ b/.github/contributing/engine_style_guide.md @@ -14,6 +14,7 @@ For more advice on contributing to the engine, see the [relevant section](../../ 4. Use \`variable_name\` code blocks in comments to signify that you're referring to specific types and variables. 5. Start comments with capital letters. End them with a period if they are sentence-like. 3. Use comments to organize long and complex stretches of code that can't sensibly be refactored into separate functions. +4. When using [Bevy error codes](https://bevyengine.org/learn/errors/) include a link to the relevant error on the Bevy website in the returned error message `... See: https://bevyengine.org/learn/errors/#b0003`. ## Rust API guidelines diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58685510aced3..d1d0909cac009 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,8 +144,6 @@ jobs: target: wasm32-unknown-unknown - name: Check wasm run: cargo check --target wasm32-unknown-unknown - env: - RUSTFLAGS: --cfg=web_sys_unstable_apis markdownlint: runs-on: ubuntu-latest @@ -191,7 +189,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.18.2 + uses: crate-ci/typos@v1.19.0 - name: Typos info if: failure() run: | diff --git a/Cargo.toml b/Cargo.toml index 4d77987d6184b..6de2dccefc702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,9 @@ bevy_winit = ["bevy_internal/bevy_winit"] # Adds support for rendering gizmos bevy_gizmos = ["bevy_internal/bevy_gizmos", "bevy_color"] +# Provides a collection of developer tools +bevy_dev_tools = ["bevy_internal/bevy_dev_tools"] + # Tracing support, saving a file in Chrome Tracing format trace_chrome = ["trace", "bevy_internal/trace_chrome"] @@ -303,7 +306,7 @@ pbr_transmission_textures = ["bevy_internal/pbr_transmission_textures"] # Enable some limitations to be able to use WebGL2. Please refer to the [WebGL2 and WebGPU](https://github.com/bevyengine/bevy/tree/latest/examples#webgl2-and-webgpu) section of the examples README for more information on how to run Wasm builds with WebGPU. webgl2 = ["bevy_internal/webgl"] -# Enable support for WebGPU in Wasm. When enabled, this feature will override the `webgl2` feature and you won't be able to run Wasm builds with WebGL2, only with WebGPU. Requires the `RUSTFLAGS` environment variable to be set to `--cfg=web_sys_unstable_apis` when building. +# Enable support for WebGPU in Wasm. When enabled, this feature will override the `webgl2` feature and you won't be able to run Wasm builds with WebGL2, only with WebGPU. webgpu = ["bevy_internal/webgpu"] # Enables the built-in asset processor for processed assets. @@ -318,6 +321,9 @@ embedded_watcher = ["bevy_internal/embedded_watcher"] # Enable stepping-based debugging of Bevy systems bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"] +# Enable support for the ios_simulator by downgrading some rendering capabilities +ios_simulator = ["bevy_internal/ios_simulator"] + [dependencies] bevy_dylib = { path = "crates/bevy_dylib", version = "0.14.0-dev", default-features = false, optional = true } bevy_internal = { path = "crates/bevy_internal", version = "0.14.0-dev", default-features = false } @@ -332,6 +338,7 @@ bytemuck = "1.7" futures-lite = "2.0.1" crossbeam-channel = "0.5.0" argh = "0.1.12" +thiserror = "1.0" [[example]] name = "hello_world" @@ -1105,6 +1112,16 @@ description = "Illustrate how to add custom log layers" category = "Application" wasm = false +[[example]] +name = "log_layers_ecs" +path = "examples/app/log_layers_ecs.rs" + +[package.metadata.example.log_layers_ecs] +name = "Advanced log layers" +description = "Illustrate how to transfer data between log layers and Bevy's ECS" +category = "Application" +wasm = false + [[example]] name = "plugin" path = "examples/app/plugin.rs" @@ -1227,6 +1244,17 @@ description = "Embed an asset in the application binary and load it" category = "Assets" wasm = true +[[example]] +name = "extra_asset_source" +path = "examples/asset/extra_source.rs" +doc-scrape-examples = true + +[package.metadata.example.extra_asset_source] +name = "Extra asset source" +description = "Load an asset from a non-standard asset source" +category = "Assets" +wasm = true + [[example]] name = "hot_asset_reloading" path = "examples/asset/hot_asset_reloading.rs" @@ -1678,6 +1706,17 @@ description = "Displays each contributor as a bouncy bevy-ball!" category = "Games" wasm = true +[[example]] +name = "desk_toy" +path = "examples/games/desk_toy.rs" +doc-scrape-examples = true + +[package.metadata.example.desk_toy] +name = "Desk Toy" +description = "Bevy logo as a desk toy using transparent windows! Now with Googly Eyes!" +category = "Games" +wasm = false + [[example]] name = "game_menu" path = "examples/games/game_menu.rs" @@ -2500,6 +2539,17 @@ description = "Illustrates how to use 9 Slicing in UI" category = "UI (User Interface)" wasm = true +[[example]] +name = "ui_texture_atlas_slice" +path = "examples/ui/ui_texture_atlas_slice.rs" +doc-scrape-examples = true + +[package.metadata.example.ui_texture_atlas_slice] +name = "UI Texture Atlas Slice" +description = "Illustrates how to use 9 Slicing for TextureAtlases in UI" +category = "UI (User Interface)" +wasm = true + [[example]] name = "viewport_debug" path = "examples/ui/viewport_debug.rs" @@ -2680,6 +2730,28 @@ description = "A scene showcasing 3D gizmos" category = "Gizmos" wasm = true +[[example]] +name = "axes" +path = "examples/gizmos/axes.rs" +doc-scrape-examples = true + +[package.metadata.example.axes] +name = "Axes" +description = "Demonstrates the function of axes gizmos" +category = "Gizmos" +wasm = true + +[[example]] +name = "light_gizmos" +path = "examples/gizmos/light_gizmos.rs" +doc-scrape-examples = true + +[package.metadata.example.light_gizmos] +name = "Light Gizmos" +description = "A scene showcasing light gizmos" +category = "Gizmos" +wasm = true + [profile.wasm-release] inherits = "release" opt-level = "z" diff --git a/assets/textures/fantasy_ui_borders/border_sheet.png b/assets/textures/fantasy_ui_borders/border_sheet.png new file mode 100644 index 0000000000000..8ee2d2a9b2d34 Binary files /dev/null and b/assets/textures/fantasy_ui_borders/border_sheet.png differ diff --git a/benches/benches/bevy_ecs/world/commands.rs b/benches/benches/bevy_ecs/world/commands.rs index 70cf1351acfcb..2b3d84195aff8 100644 --- a/benches/benches/bevy_ecs/world/commands.rs +++ b/benches/benches/bevy_ecs/world/commands.rs @@ -1,8 +1,8 @@ use bevy_ecs::{ component::Component, entity::Entity, - system::{Command, CommandQueue, Commands}, - world::World, + system::Commands, + world::{Command, CommandQueue, World}, }; use criterion::{black_box, Criterion}; diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index eb8f48c12179b..e47fefb987848 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" } bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", @@ -27,7 +26,7 @@ bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } # other sha1_smol = { version = "1.0" } -uuid = { version = "1.7", features = ["v5"] } +uuid = { version = "1.7", features = ["v4"] } [lints] workspace = true diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index bef6c87157c30..08f10a167dfd2 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -14,15 +14,15 @@ use bevy_core::Name; use bevy_ecs::entity::MapEntities; use bevy_ecs::prelude::*; use bevy_ecs::reflect::ReflectMapEntities; -use bevy_log::error; use bevy_math::{FloatExt, Quat, Vec3}; use bevy_reflect::Reflect; use bevy_render::mesh::morph::MorphWeights; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_utils::hashbrown::HashMap; -use bevy_utils::{NoOpHash, Uuid}; +use bevy_utils::{tracing::error, NoOpHash}; use sha1_smol::Sha1; +use uuid::Uuid; #[allow(missing_docs)] pub mod prelude { @@ -688,10 +688,6 @@ impl Plugin for AnimationPlugin { app.init_asset::() .register_asset_reflect::() .register_type::() - .register_type::() - .register_type::>() - .register_type::() - .register_type::() .register_type::() .add_systems( PostUpdate, diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index 020245b6449dc..e4f7649325776 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -10,7 +10,6 @@ keywords = ["bevy"] [features] trace = [] -bevy_ci_testing = ["serde", "ron"] bevy_debug_stepping = [] default = ["bevy_reflect", "bevy_debug_stepping"] bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"] @@ -27,7 +26,7 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } serde = { version = "1.0", features = ["derive"], optional = true } ron = { version = "0.8.0", optional = true } downcast-rs = "1.2.0" - +thiserror = "1.0" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = { version = "0.2" } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index f08a3e73bd788..10f567f2f211c 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -7,11 +7,12 @@ use bevy_ecs::{ InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel, }, }; -use bevy_utils::{intern::Interned, thiserror::Error, tracing::debug, HashMap, HashSet}; +use bevy_utils::{intern::Interned, tracing::debug, HashMap, HashSet}; use std::{ fmt::Debug, panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, }; +use thiserror::Error; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; @@ -188,11 +189,6 @@ impl Default for App { app.add_event::(); - #[cfg(feature = "bevy_ci_testing")] - { - crate::ci_testing::setup_app(&mut app); - } - app } } diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 8f6ba7972f272..721cf1618a724 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -6,9 +6,6 @@ mod plugin; mod plugin_group; mod schedule_runner; -#[cfg(feature = "bevy_ci_testing")] -pub mod ci_testing; - pub use app::*; pub use bevy_derive::DynamicPlugin; pub use main_schedule::*; diff --git a/crates/bevy_app/src/plugin.rs b/crates/bevy_app/src/plugin.rs index 2609c9e5042fa..3dfb28b428ab7 100644 --- a/crates/bevy_app/src/plugin.rs +++ b/crates/bevy_app/src/plugin.rs @@ -115,8 +115,7 @@ pub trait Plugins: sealed::Plugins {} impl Plugins for T where T: sealed::Plugins {} mod sealed { - - use bevy_ecs::all_tuples; + use bevy_utils::all_tuples; use crate::{App, AppError, Plugin, PluginGroup}; diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index 49534ba2a5c1d..ddec0a6b6a59b 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -21,8 +21,9 @@ watch = [] bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } bevy_asset_macros = { path = "macros", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } -bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ + "uuid", +] } bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } @@ -38,6 +39,7 @@ parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] } ron = "0.8" serde = { version = "1", features = ["derive"] } thiserror = "1.0" +uuid = { version = "1.0", features = ["v4"] } [target.'cfg(target_os = "android")'.dependencies] bevy_winit = { path = "../bevy_winit", version = "0.14.0-dev" } @@ -53,6 +55,7 @@ notify-debouncer-full = { version = "0.3.1", optional = true } [dev-dependencies] bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } +bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } [lints] workspace = true diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index c0f76d90c117d..9cf1b4e5386b7 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ system::{Res, ResMut, Resource}, }; use bevy_reflect::{Reflect, TypePath}; -use bevy_utils::{HashMap, Uuid}; +use bevy_utils::HashMap; use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; use std::{ @@ -17,6 +17,7 @@ use std::{ sync::{atomic::AtomicU32, Arc}, }; use thiserror::Error; +use uuid::Uuid; /// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime /// usage and is not suitable for identifying assets across app runs. diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index c89bf4b656a53..d14c325973c77 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -3,8 +3,8 @@ use crate::{ UntypedAssetId, }; use bevy_ecs::prelude::*; -use bevy_reflect::{Reflect, TypePath}; -use bevy_utils::{get_short_name, Uuid}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; +use bevy_utils::get_short_name; use crossbeam_channel::{Receiver, Sender}; use std::{ any::TypeId, @@ -12,6 +12,7 @@ use std::{ sync::Arc, }; use thiserror::Error; +use uuid::Uuid; /// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_. /// This should _only_ be used for one specific asset type. @@ -122,7 +123,7 @@ impl std::fmt::Debug for StrongHandle { /// /// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists). #[derive(Component, Reflect)] -#[reflect(Component)] +#[reflect(Default, Component, Debug, Hash, PartialEq)] pub enum Handle { /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata. diff --git a/crates/bevy_asset/src/id.rs b/crates/bevy_asset/src/id.rs index 1efc61bacc1af..fc98902786315 100644 --- a/crates/bevy_asset/src/id.rs +++ b/crates/bevy_asset/src/id.rs @@ -1,6 +1,6 @@ use crate::{Asset, AssetIndex}; use bevy_reflect::Reflect; -use bevy_utils::Uuid; +use uuid::Uuid; use std::{ any::TypeId, diff --git a/crates/bevy_asset/src/io/android.rs b/crates/bevy_asset/src/io/android.rs index f792a72b00383..38791b35e0071 100644 --- a/crates/bevy_asset/src/io/android.rs +++ b/crates/bevy_asset/src/io/android.rs @@ -1,7 +1,7 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader, }; -use bevy_log::error; +use bevy_utils::tracing::error; use bevy_utils::BoxedFuture; use std::{ffi::CString, path::Path}; diff --git a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs index 1e7f3057bbfd2..485593599c489 100644 --- a/crates/bevy_asset/src/io/embedded/embedded_watcher.rs +++ b/crates/bevy_asset/src/io/embedded/embedded_watcher.rs @@ -3,7 +3,7 @@ use crate::io::{ memory::Dir, AssetSourceEvent, AssetWatcher, }; -use bevy_log::warn; +use bevy_utils::tracing::warn; use bevy_utils::{Duration, HashMap}; use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap}; use parking_lot::RwLock; diff --git a/crates/bevy_asset/src/io/file/file_watcher.rs b/crates/bevy_asset/src/io/file/file_watcher.rs index 720adb7ed965b..fbed3f8a0ecbe 100644 --- a/crates/bevy_asset/src/io/file/file_watcher.rs +++ b/crates/bevy_asset/src/io/file/file_watcher.rs @@ -1,6 +1,6 @@ use crate::io::{AssetSourceEvent, AssetWatcher}; use crate::path::normalize_path; -use bevy_log::error; +use bevy_utils::tracing::error; use bevy_utils::Duration; use crossbeam_channel::Sender; use notify_debouncer_full::{ diff --git a/crates/bevy_asset/src/io/file/mod.rs b/crates/bevy_asset/src/io/file/mod.rs index 25bd1e5175f22..19a9ffb0cdae8 100644 --- a/crates/bevy_asset/src/io/file/mod.rs +++ b/crates/bevy_asset/src/io/file/mod.rs @@ -6,7 +6,7 @@ mod file_asset; #[cfg(not(feature = "multi-threaded"))] mod sync_file_asset; -use bevy_log::error; +use bevy_utils::tracing::error; #[cfg(feature = "file_watcher")] pub use file_watcher::*; diff --git a/crates/bevy_asset/src/io/processor_gated.rs b/crates/bevy_asset/src/io/processor_gated.rs index ed5aacb0a911f..b86460f04b6fd 100644 --- a/crates/bevy_asset/src/io/processor_gated.rs +++ b/crates/bevy_asset/src/io/processor_gated.rs @@ -4,7 +4,7 @@ use crate::{ AssetPath, }; use async_lock::RwLockReadGuardArc; -use bevy_log::trace; +use bevy_utils::tracing::trace; use bevy_utils::BoxedFuture; use futures_io::AsyncRead; use std::{path::Path, pin::Pin, sync::Arc}; diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index 690eee2da1d2f..7293a73bea418 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -6,7 +6,7 @@ use crate::{ processor::AssetProcessorData, }; use bevy_ecs::system::Resource; -use bevy_log::{error, warn}; +use bevy_utils::tracing::{error, warn}; use bevy_utils::{CowArc, Duration, HashMap}; use std::{fmt::Display, hash::Hash, sync::Arc}; use thiserror::Error; diff --git a/crates/bevy_asset/src/io/wasm.rs b/crates/bevy_asset/src/io/wasm.rs index 7ee60ecfdb8c9..aab497ddfa7cc 100644 --- a/crates/bevy_asset/src/io/wasm.rs +++ b/crates/bevy_asset/src/io/wasm.rs @@ -1,7 +1,7 @@ use crate::io::{ get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader, }; -use bevy_log::error; +use bevy_utils::tracing::error; use bevy_utils::BoxedFuture; use js_sys::{Uint8Array, JSON}; use std::path::{Path, PathBuf}; diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 9fa056e3bced7..803f76c6e3590 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -55,9 +55,8 @@ use bevy_ecs::{ system::Resource, world::FromWorld, }; -use bevy_log::error; use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath}; -use bevy_utils::HashSet; +use bevy_utils::{tracing::error, HashSet}; use std::{any::TypeId, sync::Arc}; #[cfg(all(feature = "file_watcher", not(feature = "multi-threaded")))] @@ -379,7 +378,6 @@ impl AssetApp for App { .add_event::>() .add_event::>() .register_type::>() - .register_type::>() .add_systems( First, Assets::::asset_events diff --git a/crates/bevy_asset/src/meta.rs b/crates/bevy_asset/src/meta.rs index e8758999f7d7e..f20e0c2e32288 100644 --- a/crates/bevy_asset/src/meta.rs +++ b/crates/bevy_asset/src/meta.rs @@ -1,6 +1,6 @@ use crate::{self as bevy_asset, DeserializeMetaError, VisitAssetDependencies}; use crate::{loader::AssetLoader, processor::Process, Asset, AssetPath}; -use bevy_log::error; +use bevy_utils::tracing::error; use downcast_rs::{impl_downcast, Downcast}; use ron::ser::PrettyConfig; use serde::{Deserialize, Serialize}; diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index b9247cf04518f..e49db629362f0 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -381,7 +381,7 @@ impl<'a> AssetPath<'a> { /// Resolves an embedded asset path via concatenation. The result will be an `AssetPath` which /// is resolved relative to this path. This is similar in operation to `resolve`, except that - /// the the 'file' portion of the base path (that is, any characters after the last '/') + /// the 'file' portion of the base path (that is, any characters after the last '/') /// is removed before concatenation, in accordance with the behavior specified in /// IETF RFC 1808 "Relative URIs". /// diff --git a/crates/bevy_asset/src/processor/log.rs b/crates/bevy_asset/src/processor/log.rs index 642de9b127142..64207295e751b 100644 --- a/crates/bevy_asset/src/processor/log.rs +++ b/crates/bevy_asset/src/processor/log.rs @@ -1,6 +1,6 @@ use crate::AssetPath; use async_fs::File; -use bevy_log::error; +use bevy_utils::tracing::error; use bevy_utils::HashSet; use futures_lite::{AsyncReadExt, AsyncWriteExt}; use std::path::PathBuf; diff --git a/crates/bevy_asset/src/processor/mod.rs b/crates/bevy_asset/src/processor/mod.rs index 625a484330078..ace12c8f7301b 100644 --- a/crates/bevy_asset/src/processor/mod.rs +++ b/crates/bevy_asset/src/processor/mod.rs @@ -17,8 +17,8 @@ use crate::{ MissingAssetLoaderForExtensionError, }; use bevy_ecs::prelude::*; -use bevy_log::{debug, error, trace, warn}; use bevy_tasks::IoTaskPool; +use bevy_utils::tracing::{debug, error, trace, warn}; use bevy_utils::{BoxedFuture, HashMap, HashSet}; use futures_io::ErrorKind; use futures_lite::{AsyncReadExt, AsyncWriteExt, StreamExt}; diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 6e9b758fa77ca..dd967a9b92a03 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -5,7 +5,7 @@ use crate::{ UntypedAssetId, UntypedHandle, }; use bevy_ecs::world::World; -use bevy_log::warn; +use bevy_utils::tracing::warn; use bevy_utils::{Entry, HashMap, HashSet, TypeIdMap}; use crossbeam_channel::Sender; use std::{ @@ -205,7 +205,7 @@ impl AssetInfos { .ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?; match handles.entry(type_id) { - bevy_utils::hashbrown::hash_map::Entry::Occupied(entry) => { + Entry::Occupied(entry) => { let id = *entry.get(); // if there is a path_to_id entry, info always exists let info = self.infos.get_mut(&id).unwrap(); @@ -246,7 +246,7 @@ impl AssetInfos { } } // The entry does not exist, so this is a "fresh" asset load. We must create a new handle - bevy_utils::hashbrown::hash_map::Entry::Vacant(entry) => { + Entry::Vacant(entry) => { let should_load = match loading_mode { HandleLoadingMode::NotLoading => false, HandleLoadingMode::Request | HandleLoadingMode::Force => true, diff --git a/crates/bevy_asset/src/server/loaders.rs b/crates/bevy_asset/src/server/loaders.rs index 65f21d6b9b52f..98cc3bce9d28f 100644 --- a/crates/bevy_asset/src/server/loaders.rs +++ b/crates/bevy_asset/src/server/loaders.rs @@ -3,8 +3,8 @@ use crate::{ path::AssetPath, }; use async_broadcast::RecvError; -use bevy_log::{error, warn}; use bevy_tasks::IoTaskPool; +use bevy_utils::tracing::{error, warn}; use bevy_utils::{HashMap, TypeIdMap}; use std::{any::TypeId, sync::Arc}; use thiserror::Error; diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index cc0825d3aaa57..2be2848aece48 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -18,8 +18,8 @@ use crate::{ UntypedAssetLoadFailedEvent, UntypedHandle, }; use bevy_ecs::prelude::*; -use bevy_log::{error, info}; use bevy_tasks::IoTaskPool; +use bevy_utils::tracing::{error, info}; use bevy_utils::{CowArc, HashSet}; use crossbeam_channel::{Receiver, Sender}; use futures_lite::StreamExt; diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index eec6b282234b3..4b0470caa82e6 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -25,7 +25,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } rodio = { version = "0.17", default-features = false } [target.'cfg(target_os = "android")'.dependencies] -oboe = { version = "0.5", optional = true } +cpal = { version = "0.15", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] rodio = { version = "0.17", default-features = false, features = [ @@ -45,7 +45,7 @@ symphonia-isomp4 = ["rodio/symphonia-isomp4"] symphonia-vorbis = ["rodio/symphonia-vorbis"] symphonia-wav = ["rodio/symphonia-wav"] # Enable using a shared stdlib for cxx on Android. -android_shared_stdcxx = ["oboe/shared-stdcxx"] +android_shared_stdcxx = ["cpal/oboe-shared-stdcxx"] [lints] workspace = true diff --git a/crates/bevy_color/Cargo.toml b/crates/bevy_color/Cargo.toml index e23bdddc48817..29bd0218ad1f3 100644 --- a/crates/bevy_color/Cargo.toml +++ b/crates/bevy_color/Cargo.toml @@ -16,7 +16,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ bytemuck = "1" serde = "1.0" thiserror = "1.0" -wgpu = { version = "0.19.1", default-features = false } +wgpu = { version = "0.19.3", default-features = false } encase = { version = "0.7", default-features = false } [lints] diff --git a/crates/bevy_color/src/palettes/basic.rs b/crates/bevy_color/src/palettes/basic.rs index 80b9d28e8c11a..b877505340e33 100644 --- a/crates/bevy_color/src/palettes/basic.rs +++ b/crates/bevy_color/src/palettes/basic.rs @@ -1,39 +1,39 @@ //! Named colors from the CSS1 specification, also known as //! [basic colors](https://en.wikipedia.org/wiki/Web_colors#Basic_colors). //! This is the same set of colors used in the -//! [VGA graphcs standard](https://en.wikipedia.org/wiki/Video_Graphics_Array). +//! [VGA graphics standard](https://en.wikipedia.org/wiki/Video_Graphics_Array). use crate::Srgba; -///
-pub const BLACK: Srgba = Srgba::new(0.0, 0.0, 0.0, 1.0); -///
-pub const BLUE: Srgba = Srgba::new(0.0, 0.0, 1.0, 1.0); -///
-pub const CYAN: Srgba = Srgba::new(0.0, 1.0, 1.0, 1.0); -///
-pub const FUCHSIA: Srgba = Srgba::new(1.0, 0.0, 1.0, 1.0); -///
-pub const GRAY: Srgba = Srgba::new(0.5, 0.5, 0.5, 1.0); -///
-pub const GREEN: Srgba = Srgba::new(0.0, 0.5, 0.0, 1.0); -///
-pub const LIME: Srgba = Srgba::new(0.0, 1.0, 0.0, 1.0); -///
-pub const MAROON: Srgba = Srgba::new(0.5, 0.0, 0.0, 1.0); -///
-pub const NAVY: Srgba = Srgba::new(0.0, 0.0, 0.5, 1.0); -///
-pub const OLIVE: Srgba = Srgba::new(0.5, 0.5, 0.0, 1.0); -///
-pub const PURPLE: Srgba = Srgba::new(0.5, 0.0, 0.5, 1.0); -///
-pub const RED: Srgba = Srgba::new(1.0, 0.0, 0.0, 1.0); -///
-pub const SILVER: Srgba = Srgba::new(0.75, 0.75, 0.75, 1.0); -///
-pub const TEAL: Srgba = Srgba::new(0.0, 0.5, 0.5, 1.0); -///
-pub const WHITE: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0); -///
-pub const YELLOW: Srgba = Srgba::new(1.0, 1.0, 0.0, 1.0); +///
+pub const AQUA: Srgba = Srgba::rgb(0.0, 1.0, 1.0); +///
+pub const BLACK: Srgba = Srgba::rgb(0.0, 0.0, 0.0); +///
+pub const BLUE: Srgba = Srgba::rgb(0.0, 0.0, 1.0); +///
+pub const FUCHSIA: Srgba = Srgba::rgb(1.0, 0.0, 1.0); +///
+pub const GRAY: Srgba = Srgba::rgb(0.5019608, 0.5019608, 0.5019608); +///
+pub const GREEN: Srgba = Srgba::rgb(0.0, 0.5019608, 0.0); +///
+pub const LIME: Srgba = Srgba::rgb(0.0, 1.0, 0.0); +///
+pub const MAROON: Srgba = Srgba::rgb(0.5019608, 0.0, 0.0); +///
+pub const NAVY: Srgba = Srgba::rgb(0.0, 0.0, 0.5019608); +///
+pub const OLIVE: Srgba = Srgba::rgb(0.5019608, 0.5019608, 0.0); +///
+pub const PURPLE: Srgba = Srgba::rgb(0.5019608, 0.0, 0.5019608); +///
+pub const RED: Srgba = Srgba::rgb(1.0, 0.0, 0.0); +///
+pub const SILVER: Srgba = Srgba::rgb(0.7529412, 0.7529412, 0.7529412); +///
+pub const TEAL: Srgba = Srgba::rgb(0.0, 0.5019608, 0.5019608); +///
+pub const WHITE: Srgba = Srgba::rgb(1.0, 1.0, 1.0); +///
+pub const YELLOW: Srgba = Srgba::rgb(1.0, 1.0, 0.0); diff --git a/crates/bevy_color/src/palettes/css.rs b/crates/bevy_color/src/palettes/css.rs index 88391fac3db21..0c1e073bec4b6 100644 --- a/crates/bevy_color/src/palettes/css.rs +++ b/crates/bevy_color/src/palettes/css.rs @@ -106,8 +106,6 @@ pub const GOLD: Srgba = Srgba::new(1.0, 0.843, 0.0, 1.0); ///
pub const GOLDENROD: Srgba = Srgba::new(0.855, 0.647, 0.125, 1.0); ///
-pub const GREEN: Srgba = Srgba::new(0.0, 0.502, 0.0, 1.0); -///
pub const GREEN_YELLOW: Srgba = Srgba::new(0.678, 1.0, 0.184, 1.0); ///
pub const GREY: Srgba = Srgba::new(0.502, 0.502, 0.502, 1.0); diff --git a/crates/bevy_color/src/srgba.rs b/crates/bevy_color/src/srgba.rs index e23beea2181f3..8479af4781daa 100644 --- a/crates/bevy_color/src/srgba.rs +++ b/crates/bevy_color/src/srgba.rs @@ -1,3 +1,5 @@ +use std::ops::{Div, Mul}; + use crate::color_difference::EuclideanDistance; use crate::{Alpha, LinearRgba, Luminance, Mix, StandardColor, Xyza}; use bevy_math::Vec4; @@ -369,6 +371,48 @@ pub enum HexColorError { Char(char), } +/// All color channels are scaled directly, +/// but alpha is unchanged. +/// +/// Values are not clamped. +impl Mul for Srgba { + type Output = Self; + + fn mul(self, rhs: f32) -> Self { + Self { + red: self.red * rhs, + green: self.green * rhs, + blue: self.blue * rhs, + alpha: self.alpha, + } + } +} + +impl Mul for f32 { + type Output = Srgba; + + fn mul(self, rhs: Srgba) -> Srgba { + rhs * self + } +} + +/// All color channels are scaled directly, +/// but alpha is unchanged. +/// +/// Values are not clamped. +impl Div for Srgba { + type Output = Self; + + fn div(self, rhs: f32) -> Self { + Self { + red: self.red / rhs, + green: self.green / rhs, + blue: self.blue / rhs, + alpha: self.alpha, + } + } +} + #[cfg(test)] mod tests { use crate::testing::assert_approx_eq; diff --git a/crates/bevy_core/Cargo.toml b/crates/bevy_core/Cargo.toml index 5a9ab295991cf..0c0c804bf7a26 100644 --- a/crates/bevy_core/Cargo.toml +++ b/crates/bevy_core/Cargo.toml @@ -17,7 +17,6 @@ bevy_app = { path = "../bevy_app", version = "0.14.0-dev", features = [ bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev", features = [ "bevy_reflect", ] } -bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", ] } @@ -25,8 +24,8 @@ bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } # other -bytemuck = "1.5" serde = { version = "1.0", optional = true } +uuid = "1.0" [features] serialize = ["dep:serde"] diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 7cf8da08b6809..c88ec31be09bf 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -6,7 +6,6 @@ mod serde; mod task_pool_options; use bevy_ecs::system::Resource; -pub use bytemuck::{bytes_of, cast_slice, Pod, Zeroable}; pub use name::*; pub use task_pool_options::*; @@ -19,17 +18,9 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_ecs::component::{ComponentId, ComponentTicks, Tick}; use bevy_ecs::prelude::*; -use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -use bevy_utils::{Duration, HashSet, Instant, Uuid}; -use std::borrow::Cow; -use std::ffi::OsString; use std::marker::PhantomData; -use std::ops::Range; -use std::path::{Path, PathBuf}; -#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::tick_global_task_pools_on_main_thread; @@ -40,78 +31,9 @@ pub struct TypeRegistrationPlugin; impl Plugin for TypeRegistrationPlugin { fn build(&self, app: &mut App) { app.register_type::(); - - register_ecs_types(app); - register_rust_types(app); - register_math_types(app); } } -fn register_ecs_types(app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::(); -} - -fn register_rust_types(app: &mut App) { - app.register_type::>() - .register_type_data::, ReflectSerialize>() - .register_type_data::, ReflectDeserialize>() - .register_type::() - .register_type::() - .register_type::() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::>() - .register_type::() - .register_type::() - .register_type::(); -} - -fn register_math_types(app: &mut App) { - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::>() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::>() - .register_type::>(); -} - /// Setup of default task pools: [`AsyncComputeTaskPool`](bevy_tasks::AsyncComputeTaskPool), /// [`ComputeTaskPool`](bevy_tasks::ComputeTaskPool), [`IoTaskPool`](bevy_tasks::IoTaskPool). #[derive(Default)] diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 7b7aaee4aff32..1b28c00f649c5 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -27,7 +27,6 @@ bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } bevy_color = { path = "../bevy_color", version = "0.14.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" } bevy_render = { path = "../bevy_render", version = "0.14.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" } @@ -37,6 +36,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } serde = { version = "1", features = ["derive"] } bitflags = "2.3" radsort = "0.1" +nonmax = "0.5" [lints] workspace = true diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index 69c789933c686..d42cc412f262d 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -106,6 +106,8 @@ pub struct BloomSettings { impl BloomSettings { /// The default bloom preset. + /// + /// This uses the [`EnergyConserving`](BloomCompositeMode::EnergyConserving) composite mode. pub const NATURAL: Self = Self { intensity: 0.15, low_frequency_boost: 0.7, diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index e2684e1553f2c..5dc0a5558662b 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -42,7 +42,8 @@ use bevy_render::{ render_resource::CachedRenderPipelineId, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; +use bevy_utils::FloatOrd; +use nonmax::NonMaxU32; use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index ece71016219d0..1e6dafa4ccb7e 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -67,7 +67,8 @@ use bevy_render::{ view::{ExtractedView, ViewDepthTexture, ViewTarget}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; -use bevy_utils::{nonmax::NonMaxU32, tracing::warn, FloatOrd, HashMap}; +use bevy_utils::{tracing::warn, FloatOrd, HashMap}; +use nonmax::NonMaxU32; use crate::{ core_3d::main_transmissive_pass_3d_node::MainTransmissivePass3dNode, @@ -93,8 +94,6 @@ pub struct Core3dPlugin; impl Plugin for Core3dPlugin { fn build(&self, app: &mut App) { app.register_type::() - .register_type::() - .register_type::() .register_type::() .add_plugins((SkyboxPlugin, ExtractComponentPlugin::::default())) .add_systems(PostUpdate, check_msaa); diff --git a/crates/bevy_core_pipeline/src/deferred/mod.rs b/crates/bevy_core_pipeline/src/deferred/mod.rs index fd8ca0966d8ea..a6b659fdbe391 100644 --- a/crates/bevy_core_pipeline/src/deferred/mod.rs +++ b/crates/bevy_core_pipeline/src/deferred/mod.rs @@ -10,7 +10,7 @@ use bevy_render::{ render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, PhaseItem}, render_resource::{CachedRenderPipelineId, TextureFormat}, }; -use bevy_utils::nonmax::NonMaxU32; +use nonmax::NonMaxU32; pub const DEFERRED_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgba32Uint; pub const DEFERRED_LIGHTING_PASS_ID_FORMAT: TextureFormat = TextureFormat::R8Uint; diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index bae09b4467c15..f1442910850eb 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -86,7 +86,7 @@ impl Plugin for FxaaPlugin { fn build(&self, app: &mut App) { load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl); - app.register_type::().register_type::(); + app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { diff --git a/crates/bevy_core_pipeline/src/prepass/mod.rs b/crates/bevy_core_pipeline/src/prepass/mod.rs index c250985ffc2f8..348419336ff59 100644 --- a/crates/bevy_core_pipeline/src/prepass/mod.rs +++ b/crates/bevy_core_pipeline/src/prepass/mod.rs @@ -38,7 +38,7 @@ use bevy_render::{ render_resource::{CachedRenderPipelineId, Extent3d, TextureFormat, TextureView}, texture::ColorAttachment, }; -use bevy_utils::nonmax::NonMaxU32; +use nonmax::NonMaxU32; pub const NORMAL_PREPASS_FORMAT: TextureFormat = TextureFormat::Rgb10a2Unorm; pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float; diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 0045d9e05047e..7726ff1e36a15 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -14,6 +14,8 @@ use bevy_render::renderer::RenderDevice; use bevy_render::texture::{CompressedImageFormats, Image, ImageSampler, ImageType}; use bevy_render::view::{ViewTarget, ViewUniform}; use bevy_render::{render_resource::*, Render, RenderApp, RenderSet}; +#[cfg(not(feature = "tonemapping_luts"))] +use bevy_utils::tracing::error; mod node; @@ -199,7 +201,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { Tonemapping::AcesFitted => shader_defs.push("TONEMAP_METHOD_ACES_FITTED".into()), Tonemapping::AgX => { #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( + error!( "AgX tonemapping requires the `tonemapping_luts` feature. Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." @@ -211,7 +213,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { } Tonemapping::TonyMcMapface => { #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( + error!( "TonyMcMapFace tonemapping requires the `tonemapping_luts` feature. Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." @@ -220,7 +222,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline { } Tonemapping::BlenderFilmic => { #[cfg(not(feature = "tonemapping_luts"))] - bevy_log::error!( + error!( "BlenderFilmic tonemapping requires the `tonemapping_luts` feature. Either enable the `tonemapping_luts` feature for bevy in `Cargo.toml` (recommended), or use a different `Tonemapping` method in your `Camera2dBundle`/`Camera3dBundle`." diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml new file mode 100644 index 0000000000000..eb6597d8bd7ff --- /dev/null +++ b/crates/bevy_dev_tools/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bevy_dev_tools" +version = "0.14.0-dev" +edition = "2021" +description = "Collection of developer tools for the Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[features] +bevy_ci_testing = ["serde", "ron"] + +[dependencies] +# bevy +bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } +bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } + +# other +serde = { version = "1.0", features = ["derive"], optional = true } +ron = { version = "0.8.0", optional = true } + +[lints] +workspace = true diff --git a/crates/bevy_app/src/ci_testing.rs b/crates/bevy_dev_tools/src/ci_testing.rs similarity index 98% rename from crates/bevy_app/src/ci_testing.rs rename to crates/bevy_dev_tools/src/ci_testing.rs index 3ba0dde78e307..db2d4a14fc70e 100644 --- a/crates/bevy_app/src/ci_testing.rs +++ b/crates/bevy_dev_tools/src/ci_testing.rs @@ -1,6 +1,6 @@ //! Utilities for testing in CI environments. -use crate::{app::AppExit, App, Update}; +use bevy_app::{App, AppExit, Update}; use serde::Deserialize; use bevy_ecs::prelude::Resource; diff --git a/crates/bevy_dev_tools/src/lib.rs b/crates/bevy_dev_tools/src/lib.rs new file mode 100644 index 0000000000000..b025539ebbb57 --- /dev/null +++ b/crates/bevy_dev_tools/src/lib.rs @@ -0,0 +1,44 @@ +//! This crate provides additional utilities for the [Bevy game engine](https://bevyengine.org), +//! focused on improving developer experience. + +use bevy_app::prelude::*; +#[cfg(feature = "bevy_ci_testing")] +pub mod ci_testing; + +/// Enables developer tools in an [`App`]. This plugin is added automatically with `bevy_dev_tools` +/// feature. +/// +/// Warning: It is not recommended to enable this in final shipped games or applications. +/// Dev tools provide a high level of access to the internals of your application, +/// and may interfere with ordinary use and gameplay. +/// +/// To enable developer tools, you can either: +/// +/// - Create a custom crate feature (e.g "`dev_mode`"), which enables the `bevy_dev_tools` feature +/// along with any other development tools you might be using: +/// +/// ```toml +/// [feature] +/// dev_mode = ["bevy/bevy_dev_tools", "other_dev_tools"] +/// ``` +/// +/// - Use `--feature bevy/bevy_dev_tools` flag when using the `cargo run` command: +/// +/// `cargo run --features bevy/bevy_dev_tools` +/// +/// - Add the `bevy_dev_tools` feature to the bevy dependency in your `Cargo.toml` file: +/// +/// `features = ["bevy_dev_tools"]` +/// +/// Note: The third method is not recommended, as it requires you to remove the feature before +/// creating a build for release to the public. +pub struct DevToolsPlugin; + +impl Plugin for DevToolsPlugin { + fn build(&self, _app: &mut App) { + #[cfg(feature = "bevy_ci_testing")] + { + ci_testing::setup_app(_app); + } + } +} diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index 0ba362ab71d8d..f0a3ade496ce4 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -19,7 +19,6 @@ features = [] bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_time = { path = "../bevy_time", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index f3f57683086c1..2f007edcaaf50 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -1,8 +1,8 @@ use super::{Diagnostic, DiagnosticPath, DiagnosticsStore}; use bevy_app::prelude::*; use bevy_ecs::prelude::*; -use bevy_log::{debug, info}; use bevy_time::{Real, Time, Timer, TimerMode}; +use bevy_utils::tracing::{debug, info}; use bevy_utils::Duration; /// An App Plugin that logs diagnostics to the console. diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index 1fe0f62148508..0ffe3d479b2af 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -40,7 +40,7 @@ impl SystemInformationDiagnosticsPlugin { ))] pub mod internal { use bevy_ecs::{prelude::ResMut, system::Local}; - use bevy_log::info; + use bevy_utils::tracing::info; use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System}; use crate::{Diagnostic, Diagnostics, DiagnosticsStore}; @@ -136,7 +136,7 @@ pub mod internal { )))] pub mod internal { pub(crate) fn setup_system() { - bevy_log::warn!("This platform and/or configuration is not supported!"); + bevy_utils::tracing::warn!("This platform and/or configuration is not supported!"); } pub(crate) fn diagnostic_system() { diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index b3372469694d0..27edd72384157 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -21,6 +21,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", optional = tr bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } bevy_ecs_macros = { path = "macros", version = "0.14.0-dev" } +petgraph = "0.6" bitflags = "2.3" concurrent-queue = "2.4.0" @@ -28,6 +29,7 @@ fixedbitset = "0.4.2" rustc-hash = "1.1" serde = "1" thiserror = "1.0" +nonmax = "0.5" [dev-dependencies] rand = "0.8" diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index db10910e0c508..b7b7e1582c8b3 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -60,7 +60,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { - type Storage = #storage; + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage; } }) } @@ -110,10 +110,10 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } fn storage_path(bevy_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { - let typename = match ty { - StorageTy::Table => Ident::new("TableStorage", Span::call_site()), - StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()), + let storage_type = match ty { + StorageTy::Table => Ident::new("Table", Span::call_site()), + StorageTy::SparseSet => Ident::new("SparseSet", Span::call_site()), }; - quote! { #bevy_ecs_path::component::#typename } + quote! { #bevy_ecs_path::component::StorageType::#storage_type } } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index f6af2e9be024d..f3d42a66ef29b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -10,16 +10,17 @@ use crate::{ AddBundle, Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, - component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick}, + component::{Component, ComponentId, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld}, + world::unsafe_world_cell::UnsafeWorldCell, }; -use bevy_ptr::OwningPtr; +use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::all_tuples; use std::any::TypeId; +use std::ptr::NonNull; /// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity. /// @@ -203,7 +204,7 @@ unsafe impl Bundle for C { impl DynamicBundle for C { #[inline] fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) { - OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr)); + OwningPtr::make(self, |ptr| func(C::STORAGE_TYPE, ptr)); } } @@ -348,7 +349,7 @@ impl BundleInfo { &self.component_ids } - /// Returns an iterator over the the [ID](ComponentId) of each component stored in this bundle. + /// Returns an iterator over the [ID](ComponentId) of each component stored in this bundle. #[inline] pub fn iter_components(&self) -> impl Iterator + '_ { self.component_ids.iter().cloned() @@ -510,10 +511,10 @@ impl BundleInfo { // SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleInserter<'w> { world: UnsafeWorldCell<'w>, - bundle_info: *const BundleInfo, - add_bundle: *const AddBundle, - table: *mut Table, - archetype: *mut Archetype, + bundle_info: ConstNonNull, + add_bundle: ConstNonNull, + table: NonNull, + archetype: NonNull, result: InsertBundleResult, change_tick: Tick, } @@ -521,11 +522,11 @@ pub(crate) struct BundleInserter<'w> { pub(crate) enum InsertBundleResult { SameArchetype, NewArchetypeSameTable { - new_archetype: *mut Archetype, + new_archetype: NonNull, }, NewArchetypeNewTable { - new_archetype: *mut Archetype, - new_table: *mut Table, + new_archetype: NonNull, + new_table: NonNull
, }, } @@ -546,7 +547,7 @@ impl<'w> BundleInserter<'w> { /// Creates a new [`BundleInserter`]. /// /// # Safety - /// Caller must ensure that `bundle_id` exists in `world.bundles` + /// - Caller must ensure that `bundle_id` exists in `world.bundles`. #[inline] pub(crate) unsafe fn new_with_id( world: &'w mut World, @@ -575,10 +576,10 @@ impl<'w> BundleInserter<'w> { let table_id = archetype.table_id(); let table = &mut world.storages.tables[table_id]; Self { - add_bundle, - archetype, - bundle_info, - table, + add_bundle: add_bundle.into(), + archetype: archetype.into(), + bundle_info: bundle_info.into(), + table: table.into(), result: InsertBundleResult::SameArchetype, change_tick, world: world.as_unsafe_world_cell(), @@ -598,24 +599,26 @@ impl<'w> BundleInserter<'w> { if table_id == new_table_id { let table = &mut world.storages.tables[table_id]; Self { - add_bundle, - archetype, - bundle_info, - table, - result: InsertBundleResult::NewArchetypeSameTable { new_archetype }, + add_bundle: add_bundle.into(), + archetype: archetype.into(), + bundle_info: bundle_info.into(), + table: table.into(), + result: InsertBundleResult::NewArchetypeSameTable { + new_archetype: new_archetype.into(), + }, change_tick, world: world.as_unsafe_world_cell(), } } else { let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id); Self { - add_bundle, - archetype, - bundle_info, - table, + add_bundle: add_bundle.into(), + archetype: archetype.into(), + bundle_info: bundle_info.into(), + table: table.into(), result: InsertBundleResult::NewArchetypeNewTable { - new_archetype, - new_table, + new_archetype: new_archetype.into(), + new_table: new_table.into(), }, change_tick, world: world.as_unsafe_world_cell(), @@ -623,6 +626,7 @@ impl<'w> BundleInserter<'w> { } } } + /// # Safety /// `entity` must currently exist in the source archetype for this inserter. `location` /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type @@ -633,191 +637,174 @@ impl<'w> BundleInserter<'w> { location: EntityLocation, bundle: T, ) -> EntityLocation { - // SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid - let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| { - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; + let bundle_info = self.bundle_info.as_ref(); + let add_bundle = self.add_bundle.as_ref(); + let table = self.table.as_mut(); + let archetype = self.archetype.as_mut(); + + let (new_archetype, new_location) = match &mut self.result { + InsertBundleResult::SameArchetype => { + // SAFETY: Mutable references do not alias and will be dropped after this block + let sparse_sets = { + let world = self.world.world_mut(); + &mut world.storages.sparse_sets + }; - if archetype.has_on_add() { - world.trigger_on_add( + bundle_info.write_components( + table, + sparse_sets, + add_bundle, entity, - bundle_info - .iter_components() - .zip(add_bundle.bundle_status.iter()) - .filter(|(_, &status)| status == ComponentStatus::Added) - .map(|(id, _)| id), + location.table_row, + self.change_tick, + bundle, ); - } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } - }; - match &mut self.result { - InsertBundleResult::SameArchetype => { - { - // SAFETY: Mutable references do not alias and will be dropped after this block - let sparse_sets = { - let world = self.world.world_mut(); - &mut world.storages.sparse_sets - }; - let table = &mut *self.table; - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - - bundle_info.write_components( - table, - sparse_sets, - add_bundle, - entity, - location.table_row, - self.change_tick, - bundle, - ); - } - // SAFETY: We have no outstanding mutable references to world as they were dropped - unsafe { - let archetype = &*self.archetype; - trigger_hooks(archetype, self.world.into_deferred()); - } - location + + (archetype, location) } InsertBundleResult::NewArchetypeSameTable { new_archetype } => { - let new_location = { - // SAFETY: Mutable references do not alias and will be dropped after this block - let (sparse_sets, entities) = { - let world = self.world.world_mut(); - (&mut world.storages.sparse_sets, &mut world.entities) - }; - let table = &mut *self.table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; - - let result = archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; - entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); - } - let new_location = new_archetype.allocate(entity, result.table_row); - entities.set(entity.index(), new_location); - - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - bundle_info.write_components( - table, - sparse_sets, - add_bundle, - entity, - result.table_row, - self.change_tick, - bundle, - ); - new_location + let new_archetype = new_archetype.as_mut(); + + // SAFETY: Mutable references do not alias and will be dropped after this block + let (sparse_sets, entities) = { + let world = self.world.world_mut(); + (&mut world.storages.sparse_sets, &mut world.entities) }; - // SAFETY: We have no outstanding mutable references to world as they were dropped - unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + let new_location = new_archetype.allocate(entity, result.table_row); + entities.set(entity.index(), new_location); + bundle_info.write_components( + table, + sparse_sets, + add_bundle, + entity, + result.table_row, + self.change_tick, + bundle, + ); - new_location + (new_archetype, new_location) } InsertBundleResult::NewArchetypeNewTable { new_archetype, new_table, } => { - let new_location = { - // SAFETY: Mutable references do not alias and will be dropped after this block - let (archetypes_ptr, sparse_sets, entities) = { - let world = self.world.world_mut(); - let archetype_ptr: *mut Archetype = - world.archetypes.archetypes.as_mut_ptr(); - ( - archetype_ptr, - &mut world.storages.sparse_sets, - &mut world.entities, - ) - }; - let table = &mut *self.table; - let new_table = &mut **new_table; - let archetype = &mut *self.archetype; - let new_archetype = &mut **new_archetype; - let result = archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; - entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }, - ); - } - // PERF: store "non bundle" components in edge, then just move those to avoid - // redundant copies - let move_result = table.move_to_superset_unchecked(result.table_row, new_table); - let new_location = new_archetype.allocate(entity, move_result.new_row); - entities.set(entity.index(), new_location); - - // if an entity was moved into this entity's table spot, update its table row - if let Some(swapped_entity) = move_result.swapped_entity { - let swapped_location = - // SAFETY: If the swap was successful, swapped_entity must be valid. - unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; - let swapped_archetype = if archetype.id() == swapped_location.archetype_id { - archetype - } else if new_archetype.id() == swapped_location.archetype_id { - new_archetype - } else { - // SAFETY: the only two borrowed archetypes are above and we just did collision checks - &mut *archetypes_ptr.add(swapped_location.archetype_id.index()) - }; - - entities.set( - swapped_entity.index(), - EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: swapped_location.archetype_row, - table_id: swapped_location.table_id, - table_row: result.table_row, - }, - ); - swapped_archetype + let new_table = new_table.as_mut(); + let new_archetype = new_archetype.as_mut(); + + // SAFETY: Mutable references do not alias and will be dropped after this block + let (archetypes_ptr, sparse_sets, entities) = { + let world = self.world.world_mut(); + let archetype_ptr: *mut Archetype = world.archetypes.archetypes.as_mut_ptr(); + ( + archetype_ptr, + &mut world.storages.sparse_sets, + &mut world.entities, + ) + }; + let result = archetype.swap_remove(location.archetype_row); + if let Some(swapped_entity) = result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: location.archetype_row, + table_id: swapped_location.table_id, + table_row: swapped_location.table_row, + }, + ); + } + // PERF: store "non bundle" components in edge, then just move those to avoid + // redundant copies + let move_result = table.move_to_superset_unchecked(result.table_row, new_table); + let new_location = new_archetype.allocate(entity, move_result.new_row); + entities.set(entity.index(), new_location); + + // if an entity was moved into this entity's table spot, update its table row + if let Some(swapped_entity) = move_result.swapped_entity { + let swapped_location = + // SAFETY: If the swap was successful, swapped_entity must be valid. + unsafe { entities.get(swapped_entity).debug_checked_unwrap() }; + + entities.set( + swapped_entity.index(), + EntityLocation { + archetype_id: swapped_location.archetype_id, + archetype_row: swapped_location.archetype_row, + table_id: swapped_location.table_id, + table_row: result.table_row, + }, + ); + + if archetype.id() == swapped_location.archetype_id { + archetype + .set_entity_table_row(swapped_location.archetype_row, result.table_row); + } else if new_archetype.id() == swapped_location.archetype_id { + new_archetype + .set_entity_table_row(swapped_location.archetype_row, result.table_row); + } else { + // SAFETY: the only two borrowed archetypes are above and we just did collision checks + (*archetypes_ptr.add(swapped_location.archetype_id.index())) .set_entity_table_row(swapped_location.archetype_row, result.table_row); } + } - let bundle_info = &*self.bundle_info; - let add_bundle = &*self.add_bundle; - bundle_info.write_components( - new_table, - sparse_sets, - add_bundle, - entity, - move_result.new_row, - self.change_tick, - bundle, - ); + bundle_info.write_components( + new_table, + sparse_sets, + add_bundle, + entity, + move_result.new_row, + self.change_tick, + bundle, + ); - new_location - }; + (new_archetype, new_location) + } + }; - // SAFETY: We have no outstanding mutable references to world as they were dropped - unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) }; + // SAFETY: We have no outstanding mutable references to world as they were dropped + let mut deferred_world = unsafe { self.world.into_deferred() }; - new_location + if new_archetype.has_on_add() { + // SAFETY: All components in the bundle are guaranteed to exist in the World + // as they must be initialized before creating the BundleInfo. + unsafe { + deferred_world.trigger_on_add( + entity, + bundle_info + .iter_components() + .zip(add_bundle.bundle_status.iter()) + .filter(|(_, &status)| status == ComponentStatus::Added) + .map(|(id, _)| id), + ); } } + if new_archetype.has_on_insert() { + // SAFETY: All components in the bundle are guaranteed to exist in the World + // as they must be initialized before creating the BundleInfo. + unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) } + } + + new_location } #[inline] @@ -830,9 +817,9 @@ impl<'w> BundleInserter<'w> { // SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { world: UnsafeWorldCell<'w>, - bundle_info: *const BundleInfo, - table: *mut Table, - archetype: *mut Archetype, + bundle_info: ConstNonNull, + table: NonNull
, + archetype: NonNull, change_tick: Tick, } @@ -866,9 +853,9 @@ impl<'w> BundleSpawner<'w> { let archetype = &mut world.archetypes[new_archetype_id]; let table = &mut world.storages.tables[archetype.table_id()]; Self { - bundle_info, - table, - archetype, + bundle_info: bundle_info.into(), + table: table.into(), + archetype: archetype.into(), change_tick, world: world.as_unsafe_world_cell(), } @@ -877,7 +864,7 @@ impl<'w> BundleSpawner<'w> { #[inline] pub fn reserve_storage(&mut self, additional: usize) { // SAFETY: There are no outstanding world references - let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) }; + let (archetype, table) = unsafe { (self.archetype.as_mut(), self.table.as_mut()) }; archetype.reserve(additional); table.reserve(additional); } @@ -890,6 +877,10 @@ impl<'w> BundleSpawner<'w> { entity: Entity, bundle: T, ) -> EntityLocation { + let table = self.table.as_mut(); + let archetype = self.archetype.as_mut(); + let bundle_info = self.bundle_info.as_ref(); + // SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid let location = { // SAFETY: Mutable references do not alias and will be dropped after this block @@ -897,11 +888,8 @@ impl<'w> BundleSpawner<'w> { let world = self.world.world_mut(); (&mut world.storages.sparse_sets, &mut world.entities) }; - let table = &mut *self.table; - let archetype = &mut *self.archetype; let table_row = table.allocate(entity); let location = archetype.allocate(entity, table_row); - let bundle_info = &*self.bundle_info; bundle_info.write_components( table, sparse_sets, @@ -916,16 +904,16 @@ impl<'w> BundleSpawner<'w> { }; // SAFETY: We have no outstanding mutable references to world as they were dropped - unsafe { - let archetype = &*self.archetype; - let bundle_info = &*self.bundle_info; - let mut world = self.world.into_deferred(); - if archetype.has_on_add() { - world.trigger_on_add(entity, bundle_info.iter_components()); - } - if archetype.has_on_insert() { - world.trigger_on_insert(entity, bundle_info.iter_components()); - } + let mut deferred_world = unsafe { self.world.into_deferred() }; + if archetype.has_on_add() { + // SAFETY: All components in the bundle are guaranteed to exist in the World + // as they must be initialized before creating the BundleInfo. + unsafe { deferred_world.trigger_on_add(entity, bundle_info.iter_components()) }; + } + if archetype.has_on_insert() { + // SAFETY: All components in the bundle are guaranteed to exist in the World + // as they must be initialized before creating the BundleInfo. + unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) }; } location diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index a65599bf7a043..808fb148b5f73 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -151,41 +151,11 @@ use std::{ /// [`SyncCell`]: bevy_utils::synccell::SyncCell /// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html pub trait Component: Send + Sync + 'static { - /// A marker type indicating the storage type used for this component. - /// This must be either [`TableStorage`] or [`SparseStorage`]. - type Storage: ComponentStorage; - - /// Called when registering this component, allowing mutable access to it's [`ComponentInfo`]. - /// This is currently used for registering hooks. - fn init_component_info(_info: &mut ComponentInfo) {} -} - -/// Marker type for components stored in a [`Table`](crate::storage::Table). -pub struct TableStorage; - -/// Marker type for components stored in a [`ComponentSparseSet`](crate::storage::ComponentSparseSet). -pub struct SparseStorage; - -/// Types used to specify the storage strategy for a component. -/// -/// This trait is implemented for [`TableStorage`] and [`SparseStorage`]. -/// Custom implementations are forbidden. -pub trait ComponentStorage: sealed::Sealed { - /// A value indicating the storage strategy specified by this type. + /// A constant indicating the storage type used for this component. const STORAGE_TYPE: StorageType; -} - -impl ComponentStorage for TableStorage { - const STORAGE_TYPE: StorageType = StorageType::Table; -} -impl ComponentStorage for SparseStorage { - const STORAGE_TYPE: StorageType = StorageType::SparseSet; -} -mod sealed { - pub trait Sealed {} - impl Sealed for super::TableStorage {} - impl Sealed for super::SparseStorage {} + /// Called when registering this component, allowing mutable access to it's [`ComponentHooks`]. + fn register_component_hooks(_hooks: &mut ComponentHooks) {} } /// The storage used for a specific component type. @@ -222,7 +192,8 @@ pub struct ComponentHooks { impl ComponentHooks { /// Register a [`ComponentHook`] that will be run when this component is added to an entity. - /// An `on_add` hook will always be followed by `on_insert`. + /// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as + /// adding all of it's components. /// /// Will panic if the component already has an `on_add` hook pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self { @@ -230,9 +201,9 @@ impl ComponentHooks { .expect("Component id: {:?}, already has an on_add hook") } - /// Register a [`ComponentHook`] that will be run when this component is added or set by `.insert` - /// An `on_insert` hook will run even if the entity already has the component unlike `on_add`, - /// `on_insert` also always runs after any `on_add` hooks. + /// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`) + /// or replaced. The hook won't run if the component is already present and is only mutated. + /// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component). /// /// Will panic if the component already has an `on_insert` hook pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self { @@ -472,7 +443,7 @@ impl ComponentDescriptor { pub fn new() -> Self { Self { name: Cow::Borrowed(std::any::type_name::()), - storage_type: T::Storage::STORAGE_TYPE, + storage_type: T::STORAGE_TYPE, is_send_and_sync: true, type_id: Some(TypeId::of::()), layout: Layout::new::(), @@ -503,7 +474,7 @@ impl ComponentDescriptor { /// Create a new `ComponentDescriptor` for a resource. /// - /// The [`StorageType`] for resources is always [`TableStorage`]. + /// The [`StorageType`] for resources is always [`StorageType::Table`]. pub fn new_resource() -> Self { Self { name: Cow::Borrowed(std::any::type_name::()), @@ -580,7 +551,7 @@ impl Components { storages, ComponentDescriptor::new::(), ); - T::init_component_info(&mut components[index.index()]); + T::register_component_hooks(&mut components[index.index()].hooks); index }) } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index 3f397cf3dd5f5..9dbc26ac1a8cc 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -81,6 +81,17 @@ type IdCursor = isize; /// /// [generational index]: https://lucassardois.medium.com/generational-indices-guide-8e3c5f7fd594 /// +/// # Stability warning +/// For all intents and purposes, `Entity` should be treated as an opaque identifier. The internal bit +/// representation is liable to change from release to release as are the behaviors or performance +/// characteristics of any of its trait implementations (i.e. `Ord`, `Hash`, etc.). This means that changes in +/// `Entity`'s representation, though made readable through various functions on the type, are not considered +/// breaking changes under [SemVer]. +/// +/// In particular, directly serializing with `Serialize` and `Deserialize` make zero guarantee of long +/// term wire format compatibility. Changes in behavior will cause serialized `Entity` values persisted +/// to long term storage (i.e. disk, databases, etc.) will fail to deserialize upon being updated. +/// /// # Usage /// /// This data type is returned by iterating a `Query` that has `Entity` as part of its query fetch type parameter ([learn more]). @@ -127,6 +138,7 @@ type IdCursor = isize; /// [`EntityCommands`]: crate::system::EntityCommands /// [`Query::get`]: crate::system::Query::get /// [`World`]: crate::world::World +/// [SemVer]: https://semver.org/ #[derive(Clone, Copy)] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr( diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 1f3464997bb04..4f2e00f5f747b 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -52,8 +52,6 @@ pub mod prelude { }; } -pub use bevy_utils::all_tuples; - #[cfg(test)] mod tests { use crate as bevy_ecs; diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 160999e9e84b4..b19c5a6b516d8 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -204,7 +204,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { self } - /// Returns a reference to the the [`FilteredAccess`] that will be provided to the built [`Query`]. + /// Returns a reference to the [`FilteredAccess`] that will be provided to the built [`Query`]. pub fn access(&self) -> &FilteredAccess { &self.access } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 4fb9f8a111367..7f249f4a36660 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::Archetype, change_detection::{Ticks, TicksMut}, - component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, + component::{Component, ComponentId, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{ComponentSparseSet, Table, TableRow}, @@ -711,9 +711,9 @@ unsafe impl<'a> QueryData for FilteredEntityMut<'a> { #[doc(hidden)] pub struct ReadFetch<'w, T> { - // T::Storage = TableStorage + // T::STORAGE_TYPE = StorageType::Table table_components: Option>>, - // T::Storage = SparseStorage + // T::STORAGE_TYPE = StorageType::SparseSet sparse_set: Option<&'w ComponentSparseSet>, } @@ -747,7 +747,7 @@ unsafe impl WorldQuery for &T { ) -> ReadFetch<'w, T> { ReadFetch { table_components: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { + sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { // SAFETY: The underlying type associated with `component_id` is `T`, // which we are allowed to access since we registered it in `update_archetype_component_access`. // Note that we do not actually access any components in this function, we just get a shared @@ -764,7 +764,7 @@ unsafe impl WorldQuery for &T { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -806,7 +806,7 @@ unsafe impl WorldQuery for &T { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => { // SAFETY: STORAGE_TYPE = Table let table = unsafe { fetch.table_components.debug_checked_unwrap() }; @@ -862,13 +862,13 @@ unsafe impl ReadOnlyQueryData for &T {} #[doc(hidden)] pub struct RefFetch<'w, T> { - // T::Storage = TableStorage + // T::STORAGE_TYPE = StorageType::Table table_data: Option<( ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, )>, - // T::Storage = SparseStorage + // T::STORAGE_TYPE = StorageType::SparseSet sparse_set: Option<&'w ComponentSparseSet>, last_run: Tick, @@ -905,7 +905,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { ) -> RefFetch<'w, T> { RefFetch { table_data: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { + sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { // SAFETY: The underlying type associated with `component_id` is `T`, // which we are allowed to access since we registered it in `update_archetype_component_access`. // Note that we do not actually access any components in this function, we just get a shared @@ -924,7 +924,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -965,7 +965,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => { // SAFETY: STORAGE_TYPE = Table let (table_components, added_ticks, changed_ticks) = @@ -1045,13 +1045,13 @@ unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {} #[doc(hidden)] pub struct WriteFetch<'w, T> { - // T::Storage = TableStorage + // T::STORAGE_TYPE = StorageType::Table table_data: Option<( ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, )>, - // T::Storage = SparseStorage + // T::STORAGE_TYPE = StorageType::SparseSet sparse_set: Option<&'w ComponentSparseSet>, last_run: Tick, @@ -1088,7 +1088,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ) -> WriteFetch<'w, T> { WriteFetch { table_data: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { + sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { // SAFETY: The underlying type associated with `component_id` is `T`, // which we are allowed to access since we registered it in `update_archetype_component_access`. // Note that we do not actually access any components in this function, we just get a shared @@ -1107,7 +1107,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -1148,7 +1148,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => { // SAFETY: STORAGE_TYPE = Table let (table_components, added_ticks, changed_ticks) = @@ -1433,7 +1433,7 @@ unsafe impl WorldQuery for Has { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index e88293e227120..cb581b58b82d6 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,6 +1,6 @@ use crate::{ archetype::Archetype, - component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, + component::{Component, ComponentId, StorageType, Tick}, entity::Entity, query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery}, storage::{Column, ComponentSparseSet, Table, TableRow}, @@ -142,7 +142,7 @@ unsafe impl WorldQuery for With { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -250,7 +250,7 @@ unsafe impl WorldQuery for Without { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -604,7 +604,7 @@ unsafe impl WorldQuery for Added { ) -> Self::Fetch<'w> { Self::Fetch::<'w> { table_ticks: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) + sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet) .then(|| world.storages().sparse_sets.get(id).debug_checked_unwrap()), last_run, this_run, @@ -612,7 +612,7 @@ unsafe impl WorldQuery for Added { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -651,7 +651,7 @@ unsafe impl WorldQuery for Added { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => { // SAFETY: STORAGE_TYPE = Table let table = unsafe { fetch.table_ticks.debug_checked_unwrap() }; @@ -813,7 +813,7 @@ unsafe impl WorldQuery for Changed { ) -> Self::Fetch<'w> { Self::Fetch::<'w> { table_ticks: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet) + sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet) .then(|| world.storages().sparse_sets.get(id).debug_checked_unwrap()), last_run, this_run, @@ -821,7 +821,7 @@ unsafe impl WorldQuery for Changed { } const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => true, StorageType::SparseSet => false, } @@ -860,7 +860,7 @@ unsafe impl WorldQuery for Changed { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { + match T::STORAGE_TYPE { StorageType::Table => { // SAFETY: STORAGE_TYPE = Table let table = unsafe { fetch.table_ticks.debug_checked_unwrap() }; diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index 7f69130d801c3..8545b346bd15a 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -1,6 +1,7 @@ use crate::prelude::Mut; use crate::reflect::AppTypeRegistry; -use crate::system::{Command, EntityCommands, Resource}; +use crate::system::{EntityCommands, Resource}; +use crate::world::Command; use crate::{entity::Entity, reflect::ReflectComponent, world::World}; use bevy_reflect::{Reflect, TypeRegistry}; use std::borrow::Cow; diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index b293506342ccc..cc61c6969936c 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -93,9 +93,20 @@ impl SystemSchedule { } /// Instructs the executor to call [`System::apply_deferred`](crate::system::System::apply_deferred) -/// on the systems that have run but not applied their [`Deferred`](crate::system::Deferred) system parameters (like [`Commands`](crate::prelude::Commands)) or other system buffers. +/// on the systems that have run but not applied their [`Deferred`](crate::system::Deferred) system parameters +/// (like [`Commands`](crate::prelude::Commands)) or other system buffers. /// -/// **Notes** +/// ## Scheduling +/// +/// `apply_deferred` systems are scheduled *by default* +/// - later in the same schedule run (for example, if a system with `Commands` param +/// is scheduled in `Update`, all the changes will be visible in `PostUpdate`) +/// - between systems with dependencies if the dependency +/// [has deferred buffers](crate::system::System::has_deferred) +/// (if system `bar` directly or indirectly depends on `foo`, and `foo` uses `Commands` param, +/// changes to the world in `foo` will be visible in `bar`) +/// +/// ## Notes /// - This function (currently) does nothing if it's called manually or wrapped inside a [`PipeSystem`](crate::system::PipeSystem). /// - Modifying a [`Schedule`](super::Schedule) may change the order buffers are applied. #[doc(alias = "apply_system_buffers")] diff --git a/crates/bevy_ecs/src/schedule/graph_utils.rs b/crates/bevy_ecs/src/schedule/graph_utils.rs index 04fb9a9ddda92..b5d10b08e1e2a 100644 --- a/crates/bevy_ecs/src/schedule/graph_utils.rs +++ b/crates/bevy_ecs/src/schedule/graph_utils.rs @@ -1,10 +1,8 @@ use std::fmt::Debug; -use bevy_utils::{ - petgraph::{algo::TarjanScc, graphmap::NodeTrait, prelude::*}, - HashMap, HashSet, -}; +use bevy_utils::{HashMap, HashSet}; use fixedbitset::FixedBitSet; +use petgraph::{algo::TarjanScc, graphmap::NodeTrait, prelude::*}; use crate::schedule::set::*; diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 2244fffd09931..408fae9b1d615 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -7,13 +7,12 @@ use std::{ use bevy_utils::tracing::info_span; use bevy_utils::{default, tracing::info}; use bevy_utils::{ - petgraph::{algo::TarjanScc, prelude::*}, - thiserror::Error, tracing::{error, warn}, HashMap, HashSet, }; - use fixedbitset::FixedBitSet; +use petgraph::{algo::TarjanScc, prelude::*}; +use thiserror::Error; use crate::{ self as bevy_ecs, diff --git a/crates/bevy_ecs/src/schedule/stepping.rs b/crates/bevy_ecs/src/schedule/stepping.rs index b82fd6da76468..eb8b4699eb2b6 100644 --- a/crates/bevy_ecs/src/schedule/stepping.rs +++ b/crates/bevy_ecs/src/schedule/stepping.rs @@ -7,10 +7,10 @@ use crate::{ system::{IntoSystem, ResMut, Resource}, }; use bevy_utils::{ - thiserror::Error, tracing::{error, info, warn}, TypeIdMap, }; +use thiserror::Error; #[cfg(test)] use bevy_utils::tracing::debug; diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index dfb03f88681d2..dffdb71db961e 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -4,6 +4,7 @@ use crate::{ storage::{Column, TableRow}, }; use bevy_ptr::{OwningPtr, Ptr}; +use nonmax::NonMaxUsize; use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData}; type EntityIndex = u32; @@ -335,7 +336,7 @@ impl ComponentSparseSet { pub struct SparseSet { dense: Vec, indices: Vec, - sparse: SparseArray, + sparse: SparseArray, } /// A space-optimized version of [`SparseSet`] that cannot be changed @@ -344,7 +345,7 @@ pub struct SparseSet { pub(crate) struct ImmutableSparseSet { dense: Box<[V]>, indices: Box<[I]>, - sparse: ImmutableSparseArray, + sparse: ImmutableSparseArray, } macro_rules! impl_sparse_set { @@ -368,7 +369,7 @@ macro_rules! impl_sparse_set { pub fn get(&self, index: I) -> Option<&V> { self.sparse.get(index).map(|dense_index| { // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { self.dense.get_unchecked(*dense_index) } + unsafe { self.dense.get_unchecked(dense_index.get()) } }) } @@ -379,7 +380,7 @@ macro_rules! impl_sparse_set { let dense = &mut self.dense; self.sparse.get(index).map(move |dense_index| { // SAFETY: if the sparse index points to something in the dense vec, it exists - unsafe { dense.get_unchecked_mut(*dense_index) } + unsafe { dense.get_unchecked_mut(dense_index.get()) } }) } @@ -454,10 +455,11 @@ impl SparseSet { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFETY: dense indices stored in self.sparse always exist unsafe { - *self.dense.get_unchecked_mut(dense_index) = value; + *self.dense.get_unchecked_mut(dense_index.get()) = value; } } else { - self.sparse.insert(index.clone(), self.dense.len()); + self.sparse + .insert(index.clone(), NonMaxUsize::new(self.dense.len()).unwrap()); self.indices.push(index); self.dense.push(value); } @@ -468,11 +470,12 @@ impl SparseSet { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { // SAFETY: dense indices stored in self.sparse always exist - unsafe { self.dense.get_unchecked_mut(dense_index) } + unsafe { self.dense.get_unchecked_mut(dense_index.get()) } } else { let value = func(); let dense_index = self.dense.len(); - self.sparse.insert(index.clone(), dense_index); + self.sparse + .insert(index.clone(), NonMaxUsize::new(dense_index).unwrap()); self.indices.push(index); self.dense.push(value); // SAFETY: dense index was just populated above @@ -491,11 +494,12 @@ impl SparseSet { /// Returns `None` if `index` does not have a value in the sparse set. pub fn remove(&mut self, index: I) -> Option { self.sparse.remove(index).map(|dense_index| { - let is_last = dense_index == self.dense.len() - 1; - let value = self.dense.swap_remove(dense_index); - self.indices.swap_remove(dense_index); + let index = dense_index.get(); + let is_last = index == self.dense.len() - 1; + let value = self.dense.swap_remove(index); + self.indices.swap_remove(index); if !is_last { - let swapped_index = self.indices[dense_index].clone(); + let swapped_index = self.indices[index].clone(); *self.sparse.get_mut(swapped_index).unwrap() = dense_index; } value diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 65fa06ed7c6a7..a8d7fe9cc3116 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -1,66 +1,23 @@ -mod command_queue; mod parallel_scope; +use super::{Deferred, Resource}; use crate::{ self as bevy_ecs, bundle::Bundle, entity::{Entities, Entity}, system::{RunSystemWithInput, SystemId}, - world::{EntityWorldMut, FromWorld, World}, + world::{Command, CommandQueue, EntityWorldMut, FromWorld, World}, }; use bevy_ecs_macros::SystemParam; use bevy_utils::tracing::{error, info}; -pub use command_queue::CommandQueue; pub use parallel_scope::*; use std::marker::PhantomData; -use super::{Deferred, Resource, SystemBuffer, SystemMeta}; - -/// A [`World`] mutation. -/// -/// Should be used with [`Commands::add`]. -/// -/// # Usage -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # use bevy_ecs::system::Command; -/// // Our world resource -/// #[derive(Resource, Default)] -/// struct Counter(u64); -/// -/// // Our custom command -/// struct AddToCounter(u64); -/// -/// impl Command for AddToCounter { -/// fn apply(self, world: &mut World) { -/// let mut counter = world.get_resource_or_insert_with(Counter::default); -/// counter.0 += self.0; -/// } -/// } -/// -/// fn some_system(mut commands: Commands) { -/// commands.add(AddToCounter(42)); -/// } -/// ``` -pub trait Command: Send + 'static { - /// Applies this command, causing it to mutate the provided `world`. - /// - /// This method is used to define what a command "does" when it is ultimately applied. - /// Because this method takes `self`, you can store data or settings on the type that implements this trait. - /// This data is set by the system or other source of the command, and then ultimately read in this method. - fn apply(self, world: &mut World); -} - /// A [`Command`] queue to perform structural changes to the [`World`]. /// /// Since each command requires exclusive access to the `World`, /// all queued commands are automatically applied in sequence -/// when the [`apply_deferred`] system runs. -/// -/// The command queue of an individual system can also be manually applied -/// by calling [`System::apply_deferred`]. -/// Similarly, the command queue of a schedule can be manually applied via [`Schedule::apply_deferred`]. +/// when the `apply_deferred` system runs (see [`apply_deferred`] documentation for more details). /// /// Each command can be used to modify the [`World`] in arbitrary ways: /// * spawning or despawning entities @@ -106,24 +63,13 @@ pub trait Command: Send + 'static { /// # } /// ``` /// -/// [`System::apply_deferred`]: crate::system::System::apply_deferred /// [`apply_deferred`]: crate::schedule::apply_deferred -/// [`Schedule::apply_deferred`]: crate::schedule::Schedule::apply_deferred #[derive(SystemParam)] pub struct Commands<'w, 's> { queue: Deferred<'s, CommandQueue>, entities: &'w Entities, } -impl SystemBuffer for CommandQueue { - #[inline] - fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { - #[cfg(feature = "trace")] - let _span_guard = _system_meta.commands_span.enter(); - self.apply(world); - } -} - impl<'w, 's> Commands<'w, 's> { /// Returns a new `Commands` instance from a [`CommandQueue`] and a [`World`]. /// @@ -581,7 +527,7 @@ impl<'w, 's> Commands<'w, 's> { /// # Example /// /// ``` - /// # use bevy_ecs::{system::Command, prelude::*}; + /// # use bevy_ecs::{world::Command, prelude::*}; /// #[derive(Resource, Default)] /// struct Counter(u64); /// @@ -1138,8 +1084,8 @@ mod tests { use crate::{ self as bevy_ecs, component::Component, - system::{CommandQueue, Commands, Resource}, - world::World, + system::{Commands, Resource}, + world::{CommandQueue, World}, }; use std::sync::{ atomic::{AtomicUsize, Ordering}, diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index e5b3016f5c222..f9ecc9aaf62d6 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -442,7 +442,9 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { type Item<'w, 's> = Res<'w, T>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - let component_id = world.initialize_resource::(); + let component_id = world.components.init_resource::(); + world.initialize_resource_internal(component_id); + let combined_access = system_meta.component_access_set.combined_access(); assert!( !combined_access.has_write(component_id), @@ -532,7 +534,9 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { type Item<'w, 's> = ResMut<'w, T>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { - let component_id = world.initialize_resource::(); + let component_id = world.components.init_resource::(); + world.initialize_resource_internal(component_id); + let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_write(component_id) { panic!( @@ -1027,7 +1031,9 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { system_meta.set_non_send(); - let component_id = world.initialize_non_send_resource::(); + let component_id = world.components.init_non_send::(); + world.initialize_non_send_internal(component_id); + let combined_access = system_meta.component_access_set.combined_access(); assert!( !combined_access.has_write(component_id), @@ -1114,7 +1120,9 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { system_meta.set_non_send(); - let component_id = world.initialize_non_send_resource::(); + let component_id = world.components.init_non_send::(); + world.initialize_non_send_internal(component_id); + let combined_access = system_meta.component_access_set.combined_access(); if combined_access.has_write(component_id) { panic!( diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f4722f1511a4b..fa0dd6c4c821d 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -1,6 +1,6 @@ use crate::entity::Entity; -use crate::system::{BoxedSystem, Command, IntoSystem}; -use crate::world::World; +use crate::system::{BoxedSystem, IntoSystem}; +use crate::world::{Command, World}; use crate::{self as bevy_ecs}; use bevy_ecs_macros::Component; use thiserror::Error; @@ -164,12 +164,9 @@ impl World { /// /// ``` /// # use bevy_ecs::prelude::*; - /// #[derive(Resource, Default)] - /// struct Counter(u8); - /// - /// fn increment(mut counter: Local) { - /// counter.0 += 1; - /// println!("{}", counter.0); + /// fn increment(mut counter: Local) { + /// *counter += 1; + /// println!("{}", *counter); /// } /// /// let mut world = World::default(); @@ -255,20 +252,17 @@ impl World { /// /// ``` /// # use bevy_ecs::prelude::*; - /// #[derive(Resource, Default)] - /// struct Counter(u8); - /// - /// fn increment(In(increment_by): In, mut counter: Local) { - /// counter.0 += increment_by; - /// println!("{}", counter.0); + /// fn increment(In(increment_by): In, mut counter: Local) -> u8 { + /// *counter += increment_by; + /// *counter /// } /// /// let mut world = World::default(); /// let counter_one = world.register_system(increment); /// let counter_two = world.register_system(increment); - /// world.run_system_with_input(counter_one, 1); // -> 1 - /// world.run_system_with_input(counter_one, 20); // -> 21 - /// world.run_system_with_input(counter_two, 30); // -> 51 + /// assert_eq!(world.run_system_with_input(counter_one, 1).unwrap(), 1); + /// assert_eq!(world.run_system_with_input(counter_one, 20).unwrap(), 21); + /// assert_eq!(world.run_system_with_input(counter_two, 30).unwrap(), 30); /// ``` /// /// See [`World::run_system`] for more examples. diff --git a/crates/bevy_ecs/src/system/commands/command_queue.rs b/crates/bevy_ecs/src/world/command_queue.rs similarity index 97% rename from crates/bevy_ecs/src/system/commands/command_queue.rs rename to crates/bevy_ecs/src/world/command_queue.rs index a9dcd6f965de2..3103e3486ce2d 100644 --- a/crates/bevy_ecs/src/system/commands/command_queue.rs +++ b/crates/bevy_ecs/src/world/command_queue.rs @@ -1,10 +1,11 @@ +use crate::system::{SystemBuffer, SystemMeta}; + use std::{fmt::Debug, mem::MaybeUninit}; use bevy_ptr::{OwningPtr, Unaligned}; use bevy_utils::tracing::warn; -use super::Command; -use crate::world::World; +use crate::world::{Command, World}; struct CommandMeta { /// SAFETY: The `value` must point to a value of type `T: Command`, @@ -234,6 +235,15 @@ impl Drop for CommandQueue { } } +impl SystemBuffer for CommandQueue { + #[inline] + fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { + #[cfg(feature = "trace")] + let _span_guard = _system_meta.commands_span.enter(); + self.apply(world); + } +} + #[cfg(test)] mod test { use super::*; diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 81352445a9047..f9d5fc53a6b84 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -165,7 +165,7 @@ impl<'w> DeferredWorld<'w> { None => panic!( "Requested non-send resource {} does not exist in the `World`. Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", + Non-send resources can also be added by plugins.", std::any::type_name::() ), } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 81691f6a6a70c..74c7ffb7f5cdb 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,5 +1,6 @@ //! Defines the [`World`] and APIs for accessing it directly. +mod command_queue; mod deferred_world; mod entity_ref; pub mod error; @@ -8,6 +9,7 @@ pub mod unsafe_world_cell; mod world_cell; pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD}; +pub use crate::world::command_queue::CommandQueue; pub use deferred_world::DeferredWorld; pub use entity_ref::{ EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef, @@ -30,7 +32,7 @@ use crate::{ removal_detection::RemovedComponentEvents, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, - system::{CommandQueue, Commands, Res, Resource}, + system::{Commands, Res, Resource}, world::error::TryRunScheduleError, }; use bevy_ptr::{OwningPtr, Ptr}; @@ -43,9 +45,44 @@ use std::{ }; mod identifier; +use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; pub use identifier::WorldId; -use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; +/// A [`World`] mutation. +/// +/// Should be used with [`Commands::add`]. +/// +/// # Usage +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # use bevy_ecs::world::Command; +/// // Our world resource +/// #[derive(Resource, Default)] +/// struct Counter(u64); +/// +/// // Our custom command +/// struct AddToCounter(u64); +/// +/// impl Command for AddToCounter { +/// fn apply(self, world: &mut World) { +/// let mut counter = world.get_resource_or_insert_with(Counter::default); +/// counter.0 += self.0; +/// } +/// } +/// +/// fn some_system(mut commands: Commands) { +/// commands.add(AddToCounter(42)); +/// } +/// ``` +pub trait Command: Send + 'static { + /// Applies this command, causing it to mutate the provided `world`. + /// + /// This method is used to define what a command "does" when it is ultimately applied. + /// Because this method takes `self`, you can store data or settings on the type that implements this trait. + /// This data is set by the system or other source of the command, and then ultimately read in this method. + fn apply(self, world: &mut World); +} /// Stores and exposes operations on [entities](Entity), [components](Component), resources, /// and their associated metadata. @@ -1447,7 +1484,7 @@ impl World { None => panic!( "Requested non-send resource {} does not exist in the `World`. Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", + Non-send resources can also be added by plugins.", std::any::type_name::() ), } @@ -1469,7 +1506,7 @@ impl World { None => panic!( "Requested non-send resource {} does not exist in the `World`. Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", + Non-send resources can also be added by plugins.", std::any::type_name::() ), } @@ -1798,7 +1835,7 @@ impl World { /// # Panics /// Panics if `component_id` is not registered as a `Send` component type in this `World` #[inline] - fn initialize_resource_internal( + pub(crate) fn initialize_resource_internal( &mut self, component_id: ComponentId, ) -> &mut ResourceData { @@ -1813,7 +1850,7 @@ impl World { /// # Panics /// panics if `component_id` is not registered in this world #[inline] - fn initialize_non_send_internal( + pub(crate) fn initialize_non_send_internal( &mut self, component_id: ComponentId, ) -> &mut ResourceData { @@ -1825,18 +1862,6 @@ impl World { }) } - pub(crate) fn initialize_resource(&mut self) -> ComponentId { - let component_id = self.components.init_resource::(); - self.initialize_resource_internal(component_id); - component_id - } - - pub(crate) fn initialize_non_send_resource(&mut self) -> ComponentId { - let component_id = self.components.init_non_send::(); - self.initialize_non_send_internal(component_id); - component_id - } - /// Empties queued entities and adds them to the empty [`Archetype`](crate::archetype::Archetype). /// This should be called before doing operations that might operate on queued entities, /// such as inserting a [`Component`]. diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index b786a3cfb4425..9fbb6e52de668 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -2,19 +2,17 @@ #![warn(unsafe_op_in_unsafe_fn)] -use super::{Mut, Ref, World, WorldId}; +use super::{command_queue::CommandQueue, Mut, Ref, World, WorldId}; use crate::{ archetype::{Archetype, ArchetypeComponentId, Archetypes}, bundle::Bundles, change_detection::{MutUntyped, Ticks, TicksMut}, - component::{ - ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, Tick, TickCells, - }, + component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells}, entity::{Entities, Entity, EntityLocation}, prelude::Component, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, - system::{CommandQueue, Res, Resource}, + system::{Res, Resource}, }; use bevy_ptr::Ptr; use std::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr, ptr::addr_of_mut}; @@ -713,7 +711,7 @@ impl<'w> UnsafeEntityCell<'w> { get_component( self.world, component_id, - T::Storage::STORAGE_TYPE, + T::STORAGE_TYPE, self.entity, self.location, ) @@ -740,7 +738,7 @@ impl<'w> UnsafeEntityCell<'w> { get_component_and_ticks( self.world, component_id, - T::Storage::STORAGE_TYPE, + T::STORAGE_TYPE, self.entity, self.location, ) @@ -770,7 +768,7 @@ impl<'w> UnsafeEntityCell<'w> { get_ticks( self.world, component_id, - T::Storage::STORAGE_TYPE, + T::STORAGE_TYPE, self.entity, self.location, ) @@ -839,7 +837,7 @@ impl<'w> UnsafeEntityCell<'w> { get_component_and_ticks( self.world, component_id, - T::Storage::STORAGE_TYPE, + T::STORAGE_TYPE, self.entity, self.location, ) diff --git a/crates/bevy_ecs/src/world/world_cell.rs b/crates/bevy_ecs/src/world/world_cell.rs index 852858ba607b3..2ae79b32306e6 100644 --- a/crates/bevy_ecs/src/world/world_cell.rs +++ b/crates/bevy_ecs/src/world/world_cell.rs @@ -296,7 +296,7 @@ impl<'w> WorldCell<'w> { None => panic!( "Requested non-send resource {} does not exist in the `World`. Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", + Non-send resources can also be added by plugins.", std::any::type_name::() ), } @@ -330,7 +330,7 @@ impl<'w> WorldCell<'w> { None => panic!( "Requested non-send resource {} does not exist in the `World`. Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be be added by plugins.", + Non-send resources can also be added by plugins.", std::any::type_name::() ), } diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index 000d8692265c2..bf0bd644abd26 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -13,7 +13,6 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_input = { path = "../bevy_input", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } bevy_time = { path = "../bevy_time", version = "0.14.0-dev" } diff --git a/crates/bevy_gilrs/src/rumble.rs b/crates/bevy_gilrs/src/rumble.rs index c982a52ce2fa5..b59abef9a066f 100644 --- a/crates/bevy_gilrs/src/rumble.rs +++ b/crates/bevy_gilrs/src/rumble.rs @@ -4,8 +4,8 @@ use bevy_ecs::prelude::{EventReader, Res, ResMut, Resource}; #[cfg(target_arch = "wasm32")] use bevy_ecs::system::NonSendMut; use bevy_input::gamepad::{GamepadRumbleIntensity, GamepadRumbleRequest}; -use bevy_log::{debug, warn}; use bevy_time::{Real, Time}; +use bevy_utils::tracing::{debug, warn}; use bevy_utils::{synccell::SyncCell, Duration, HashMap}; use gilrs::{ ff::{self, BaseEffect, BaseEffectType, Repeat, Replay}, diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index b573c5d6d921d..a7ecaddaf0b8b 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -23,12 +23,12 @@ bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" } bevy_render = { path = "../bevy_render", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } -bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_gizmos_macros = { path = "macros", version = "0.14.0-dev" } +bytemuck = "1.0" + [lints] workspace = true diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index dc0db0cbfa96a..a27b91192a2a3 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -14,7 +14,7 @@ //! //! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples. -/// Label for the the render systems handling the +/// System set label for the systems handling the rendering of gizmos. #[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)] pub enum GizmoRenderSystem { /// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase @@ -32,6 +32,7 @@ pub mod circles; pub mod config; pub mod gizmos; pub mod grid; +pub mod light; pub mod primitives; #[cfg(feature = "bevy_sprite")] @@ -46,6 +47,7 @@ pub mod prelude { aabb::{AabbGizmoConfigGroup, ShowAabbGizmo}, config::{DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore}, gizmos::Gizmos, + light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo}, primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d}, AppGizmoBuilder, }; @@ -55,7 +57,6 @@ use aabb::AabbGizmoPlugin; use bevy_app::{App, Last, Plugin}; use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; use bevy_color::LinearRgba; -use bevy_core::cast_slice; use bevy_ecs::{ component::Component, query::ROQueryItem, @@ -81,10 +82,12 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; +use bytemuck::cast_slice; use config::{ DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig, }; use gizmos::GizmoStorage; +use light::LightGizmoPlugin; use std::{any::TypeId, mem}; const LINE_SHADER_HANDLE: Handle = Handle::weak_from_u128(7414812689238026784); @@ -96,7 +99,9 @@ impl Plugin for GizmoPlugin { fn build(&self, app: &mut bevy_app::App) { // Gizmos cannot work without either a 3D or 2D renderer. #[cfg(all(not(feature = "bevy_pbr"), not(feature = "bevy_sprite")))] - bevy_log::error!("bevy_gizmos requires either bevy_pbr or bevy_sprite. Please enable one."); + bevy_utils::tracing::error!( + "bevy_gizmos requires either bevy_pbr or bevy_sprite. Please enable one." + ); load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl); @@ -108,7 +113,8 @@ impl Plugin for GizmoPlugin { .init_resource::() // We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist. .init_gizmo_group::() - .add_plugins(AabbGizmoPlugin); + .add_plugins(AabbGizmoPlugin) + .add_plugins(LightGizmoPlugin); let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs new file mode 100644 index 0000000000000..52d69a400cdbb --- /dev/null +++ b/crates/bevy_gizmos/src/light.rs @@ -0,0 +1,324 @@ +//! A module adding debug visualization of [`PointLight`]s, [`SpotLight`]s and [`DirectionalLight`]s. + +use std::f32::consts::PI; + +use crate::{self as bevy_gizmos, primitives::dim3::GizmoPrimitive3d}; + +use bevy_app::{Plugin, PostUpdate}; +use bevy_color::{ + palettes::basic::{BLUE, GREEN, RED}, + Color, Oklcha, +}; +use bevy_ecs::{ + component::Component, + entity::Entity, + query::Without, + reflect::ReflectComponent, + schedule::IntoSystemConfigs, + system::{Query, Res}, +}; +use bevy_math::{ + primitives::{Cone, Sphere}, + Quat, Vec3, +}; +use bevy_pbr::{DirectionalLight, PointLight, SpotLight}; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; +use bevy_transform::{components::GlobalTransform, TransformSystem}; + +use crate::{ + config::{GizmoConfigGroup, GizmoConfigStore}, + gizmos::Gizmos, + AppGizmoBuilder, +}; + +/// Draws a standard sphere for the radius and an axis sphere for the range. +fn point_light_gizmo( + transform: &GlobalTransform, + point_light: &PointLight, + color: Color, + gizmos: &mut Gizmos, +) { + let position = transform.translation(); + gizmos + .primitive_3d( + Sphere { + radius: point_light.radius, + }, + position, + Quat::IDENTITY, + color, + ) + .segments(16); + gizmos + .sphere(position, Quat::IDENTITY, point_light.range, color) + .circle_segments(32); +} + +/// Draws a sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the +/// farthest point of effect of the spot light along its direction. +fn spot_light_gizmo( + transform: &GlobalTransform, + spot_light: &SpotLight, + color: Color, + gizmos: &mut Gizmos, +) { + let (_, rotation, translation) = transform.to_scale_rotation_translation(); + gizmos + .primitive_3d( + Sphere { + radius: spot_light.radius, + }, + translation, + Quat::IDENTITY, + color, + ) + .segments(16); + + // Offset the tip of the cone to the light position. + for angle in [spot_light.inner_angle, spot_light.outer_angle] { + let height = spot_light.range * angle.cos(); + let position = translation + rotation * Vec3::NEG_Z * height / 2.0; + gizmos + .primitive_3d( + Cone { + radius: spot_light.range * angle.sin(), + height, + }, + position, + rotation * Quat::from_rotation_x(PI / 2.0), + color, + ) + .height_segments(4) + .base_segments(32); + } + + for arc_rotation in [ + Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle), + Quat::from_euler( + bevy_math::EulerRot::XZY, + 0.0, + PI / 2.0, + PI / 2.0 - spot_light.outer_angle, + ), + ] { + gizmos + .arc_3d( + 2.0 * spot_light.outer_angle, + spot_light.range, + translation, + rotation * arc_rotation, + color, + ) + .segments(16); + } +} + +/// Draws an arrow alongside the directional light direction. +fn directional_light_gizmo( + transform: &GlobalTransform, + color: Color, + gizmos: &mut Gizmos, +) { + let (_, rotation, translation) = transform.to_scale_rotation_translation(); + gizmos + .arrow(translation, translation + rotation * Vec3::NEG_Z, color) + .with_tip_length(0.3); +} + +/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s +/// and [`DirectionalLight`]s for debugging. +pub struct LightGizmoPlugin; + +impl Plugin for LightGizmoPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_type::() + .init_gizmo_group::() + .add_systems( + PostUpdate, + ( + draw_lights, + draw_all_lights.run_if(|config: Res| { + config.config::().1.draw_all + }), + ) + .after(TransformSystem::TransformPropagate), + ); + } +} + +/// Configures how a color is attributed to a light gizmo. +#[derive(Debug, Clone, Copy, Default, Reflect)] +pub enum LightGizmoColor { + /// User-specified color. + Manual(Color), + /// Random color derived from the light's [`Entity`]. + Varied, + /// Take the color of the represented light. + #[default] + MatchLightColor, + /// Take the color provided by [`LightGizmoConfigGroup`] depending on the light kind. + ByLightType, +} + +/// The [`GizmoConfigGroup`] used to configure the visualization of lights. +#[derive(Clone, Reflect, GizmoConfigGroup)] +pub struct LightGizmoConfigGroup { + /// Draw a gizmo for all lights if true. + /// + /// Defaults to `false`. + pub draw_all: bool, + /// Default color strategy for all light gizmos. + /// + /// Defaults to [`LightGizmoColor::MatchLightColor`]. + pub color: LightGizmoColor, + /// [`Color`] to use for drawing a [`PointLight`] gizmo when [`LightGizmoColor::ByLightType`] is used. + /// + /// Defaults to [`RED`]. + pub point_light_color: Color, + /// [`Color`] to use for drawing a [`SpotLight`] gizmo when [`LightGizmoColor::ByLightType`] is used. + /// + /// Defaults to [`GREEN`]. + pub spot_light_color: Color, + /// [`Color`] to use for drawing a [`DirectionalLight`] gizmo when [`LightGizmoColor::ByLightType`] is used. + /// + /// Defaults to [`BLUE`]. + pub directional_light_color: Color, +} + +impl Default for LightGizmoConfigGroup { + fn default() -> Self { + Self { + draw_all: false, + color: LightGizmoColor::MatchLightColor, + point_light_color: RED.into(), + spot_light_color: GREEN.into(), + directional_light_color: BLUE.into(), + } + } +} + +/// Add this [`Component`] to an entity to draw any of its lights components +/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]). +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component, Default)] +pub struct ShowLightGizmo { + /// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`]. + /// + /// Defaults to [`None`]. + pub color: Option, +} + +fn draw_lights( + point_query: Query<(Entity, &PointLight, &GlobalTransform, &ShowLightGizmo)>, + spot_query: Query<(Entity, &SpotLight, &GlobalTransform, &ShowLightGizmo)>, + directional_query: Query<(Entity, &DirectionalLight, &GlobalTransform, &ShowLightGizmo)>, + mut gizmos: Gizmos, +) { + let color = |entity: Entity, gizmo_color: Option, light_color, type_color| { + match gizmo_color.unwrap_or(gizmos.config_ext.color) { + LightGizmoColor::Manual(color) => color, + LightGizmoColor::Varied => Oklcha::sequential_dispersed(entity.index()).into(), + LightGizmoColor::MatchLightColor => light_color, + LightGizmoColor::ByLightType => type_color, + } + }; + for (entity, light, transform, light_gizmo) in &point_query { + let color = color( + entity, + light_gizmo.color, + light.color, + gizmos.config_ext.point_light_color, + ); + point_light_gizmo(transform, light, color, &mut gizmos); + } + for (entity, light, transform, light_gizmo) in &spot_query { + let color = color( + entity, + light_gizmo.color, + light.color, + gizmos.config_ext.spot_light_color, + ); + spot_light_gizmo(transform, light, color, &mut gizmos); + } + for (entity, light, transform, light_gizmo) in &directional_query { + let color = color( + entity, + light_gizmo.color, + light.color, + gizmos.config_ext.directional_light_color, + ); + directional_light_gizmo(transform, color, &mut gizmos); + } +} + +fn draw_all_lights( + point_query: Query<(Entity, &PointLight, &GlobalTransform), Without>, + spot_query: Query<(Entity, &SpotLight, &GlobalTransform), Without>, + directional_query: Query< + (Entity, &DirectionalLight, &GlobalTransform), + Without, + >, + mut gizmos: Gizmos, +) { + match gizmos.config_ext.color { + LightGizmoColor::Manual(color) => { + for (_, light, transform) in &point_query { + point_light_gizmo(transform, light, color, &mut gizmos); + } + for (_, light, transform) in &spot_query { + spot_light_gizmo(transform, light, color, &mut gizmos); + } + for (_, _, transform) in &directional_query { + directional_light_gizmo(transform, color, &mut gizmos); + } + } + LightGizmoColor::Varied => { + let color = |entity: Entity| Oklcha::sequential_dispersed(entity.index()).into(); + for (entity, light, transform) in &point_query { + point_light_gizmo(transform, light, color(entity), &mut gizmos); + } + for (entity, light, transform) in &spot_query { + spot_light_gizmo(transform, light, color(entity), &mut gizmos); + } + for (entity, _, transform) in &directional_query { + directional_light_gizmo(transform, color(entity), &mut gizmos); + } + } + LightGizmoColor::MatchLightColor => { + for (_, light, transform) in &point_query { + point_light_gizmo(transform, light, light.color, &mut gizmos); + } + for (_, light, transform) in &spot_query { + spot_light_gizmo(transform, light, light.color, &mut gizmos); + } + for (_, light, transform) in &directional_query { + directional_light_gizmo(transform, light.color, &mut gizmos); + } + } + LightGizmoColor::ByLightType => { + for (_, light, transform) in &point_query { + point_light_gizmo( + transform, + light, + gizmos.config_ext.point_light_color, + &mut gizmos, + ); + } + for (_, light, transform) in &spot_query { + spot_light_gizmo( + transform, + light, + gizmos.config_ext.spot_light_color, + &mut gizmos, + ); + } + for (_, _, transform) in &directional_query { + directional_light_gizmo( + transform, + gizmos.config_ext.directional_light_color, + &mut gizmos, + ); + } + } + } +} diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 6359517a68f66..eae53b1fcf3b4 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -609,14 +609,36 @@ pub struct Cone3dBuilder<'a, 'w, 's, T: GizmoConfigGroup> { // Color of the cone color: Color, - // Number of segments used to approximate the cone geometry - segments: usize, + // Number of segments used to approximate the cone base geometry + base_segments: usize, + + // Number of segments used to approximate the cone height geometry + height_segments: usize, } impl Cone3dBuilder<'_, '_, '_, T> { - /// Set the number of segments used to approximate the cone geometry. + /// Set the number of segments used to approximate the cone geometry for its base and height. pub fn segments(mut self, segments: usize) -> Self { - self.segments = segments; + self.base_segments = segments; + self.height_segments = segments; + self + } + + /// Set the number of segments to approximate the height of the cone geometry. + /// + /// `segments` should be a multiple of the value passed to [`Self::height_segments`] + /// for the height to connect properly with the base. + pub fn base_segments(mut self, segments: usize) -> Self { + self.base_segments = segments; + self + } + + /// Set the number of segments to approximate the height of the cone geometry. + /// + /// `segments` should be a divisor of the value passed to [`Self::base_segments`] + /// for the height to connect properly with the base. + pub fn height_segments(mut self, segments: usize) -> Self { + self.height_segments = segments; self } } @@ -638,7 +660,8 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { position, rotation, color, - segments: DEFAULT_NUMBER_SEGMENTS, + base_segments: DEFAULT_NUMBER_SEGMENTS, + height_segments: DEFAULT_NUMBER_SEGMENTS, } } } @@ -656,7 +679,8 @@ impl Drop for Cone3dBuilder<'_, '_, '_, T> { position, rotation, color, - segments, + base_segments, + height_segments, } = self; let half_height = *height * 0.5; @@ -665,7 +689,7 @@ impl Drop for Cone3dBuilder<'_, '_, '_, T> { draw_circle_3d( gizmos, *radius, - *segments, + *base_segments, *rotation, *position - *rotation * Vec3::Y * half_height, *color, @@ -673,7 +697,7 @@ impl Drop for Cone3dBuilder<'_, '_, '_, T> { // connect the base circle with the tip of the cone let end = Vec3::Y * half_height; - circle_coordinates(*radius, *segments) + circle_coordinates(*radius, *height_segments) .map(|p| Vec3::new(p.x, -half_height, p.y)) .map(move |p| [p, end]) .map(|ps| ps.map(rotate_then_translate_3d(*rotation, *position))) diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 2d7817f6f4572..22c5f49450f76 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -22,7 +22,6 @@ bevy_core = { path = "../bevy_core", version = "0.14.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_pbr = { path = "../bevy_pbr", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ @@ -55,6 +54,7 @@ base64 = "0.21.5" percent-encoding = "2.1" serde = { version = "1.0", features = ["derive"] } serde_json = "1" +smallvec = "1.11" [lints] workspace = true diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index ca6a309684e95..b59d6eaf1fff5 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -9,7 +9,6 @@ use bevy_core_pipeline::prelude::Camera3dBundle; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::{entity::Entity, world::World}; use bevy_hierarchy::{BuildWorldChildren, WorldChildBuilder}; -use bevy_log::{error, info_span, warn}; use bevy_math::{Affine2, Mat4, Vec3}; use bevy_pbr::{ DirectionalLight, DirectionalLightBundle, PbrBundle, PointLight, PointLightBundle, SpotLight, @@ -36,10 +35,8 @@ use bevy_scene::Scene; #[cfg(not(target_arch = "wasm32"))] use bevy_tasks::IoTaskPool; use bevy_transform::components::Transform; -use bevy_utils::{ - smallvec::{smallvec, SmallVec}, - HashMap, HashSet, -}; +use bevy_utils::tracing::{error, info_span, warn}; +use bevy_utils::{HashMap, HashSet}; use gltf::{ accessor::Iter, mesh::{util::ReadIndices, Mode}, @@ -47,6 +44,7 @@ use gltf::{ Material, Node, Primitive, Semantic, }; use serde::{Deserialize, Serialize}; +use smallvec::{smallvec, SmallVec}; use std::io::Error; use std::{ collections::VecDeque, @@ -474,9 +472,9 @@ async fn load_gltf<'a, 'b, 'c>( let vertex_count_after = mesh.count_vertices(); if vertex_count_before != vertex_count_after { - bevy_log::debug!("Missing vertex normals in indexed geometry, computing them as flat. Vertex count increased from {} to {}", vertex_count_before, vertex_count_after); + bevy_utils::tracing::debug!("Missing vertex normals in indexed geometry, computing them as flat. Vertex count increased from {} to {}", vertex_count_before, vertex_count_after); } else { - bevy_log::debug!( + bevy_utils::tracing::debug!( "Missing vertex normals in indexed geometry, computing them as flat." ); } @@ -490,7 +488,7 @@ async fn load_gltf<'a, 'b, 'c>( } else if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_some() && primitive.material().normal_texture().is_some() { - bevy_log::debug!( + bevy_utils::tracing::debug!( "Missing vertex tangents for {}, computing them using the mikktspace algorithm. Consider using a tool such as Blender to pre-compute the tangents.", file_name ); diff --git a/crates/bevy_hierarchy/Cargo.toml b/crates/bevy_hierarchy/Cargo.toml index 96ffbbf08ac05..342126e3f1976 100644 --- a/crates/bevy_hierarchy/Cargo.toml +++ b/crates/bevy_hierarchy/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["bevy"] [features] default = ["bevy_app"] trace = [] -bevy_app = ["reflect", "dep:bevy_app", "bevy_core", "bevy_log"] +bevy_app = ["reflect", "dep:bevy_app", "bevy_core"] reflect = ["bevy_ecs/bevy_reflect", "bevy_reflect"] [dependencies] @@ -19,11 +19,13 @@ reflect = ["bevy_ecs/bevy_reflect", "bevy_reflect"] bevy_app = { path = "../bevy_app", version = "0.14.0-dev", optional = true } bevy_core = { path = "../bevy_core", version = "0.14.0-dev", optional = true } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev", default-features = false } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev", optional = true } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", + "smallvec", ], optional = true } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } +smallvec = { version = "1.11", features = ["union", "const_generics"] } + [lints] workspace = true diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index d119afe95ee70..f8d206dee5580 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -3,10 +3,10 @@ use bevy_ecs::{ bundle::Bundle, entity::Entity, prelude::Events, - system::{Command, Commands, EntityCommands}, - world::{EntityWorldMut, World}, + system::{Commands, EntityCommands}, + world::{Command, EntityWorldMut, World}, }; -use bevy_utils::smallvec::{smallvec, SmallVec}; +use smallvec::{smallvec, SmallVec}; // Do not use `world.send_event_batch` as it prints error message when the Events are not available in the world, // even though it's a valid use case to execute commands on a world without events. Loading a GLTF file for example @@ -696,14 +696,14 @@ mod tests { components::{Children, Parent}, HierarchyEvent::{self, ChildAdded, ChildMoved, ChildRemoved}, }; - use bevy_utils::smallvec::{smallvec, SmallVec}; + use smallvec::{smallvec, SmallVec}; use bevy_ecs::{ component::Component, entity::Entity, event::Events, - system::{CommandQueue, Commands}, - world::World, + system::Commands, + world::{CommandQueue, World}, }; /// Assert the (non)existence and state of the child's [`Parent`] component. diff --git a/crates/bevy_hierarchy/src/components/children.rs b/crates/bevy_hierarchy/src/components/children.rs index d980289abca79..5a53462d82c58 100644 --- a/crates/bevy_hierarchy/src/components/children.rs +++ b/crates/bevy_hierarchy/src/components/children.rs @@ -6,8 +6,8 @@ use bevy_ecs::{ prelude::FromWorld, world::World, }; -use bevy_utils::smallvec::SmallVec; use core::slice; +use smallvec::SmallVec; use std::ops::Deref; /// Contains references to the child entities of this entity. diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index d707be839c76f..26f26233a0fe5 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -1,8 +1,8 @@ use crate::components::{Children, Parent}; use bevy_ecs::{ entity::Entity, - system::{Command, EntityCommands}, - world::{EntityWorldMut, World}, + system::EntityCommands, + world::{Command, EntityWorldMut, World}, }; use bevy_utils::tracing::debug; @@ -139,8 +139,8 @@ impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { mod tests { use bevy_ecs::{ component::Component, - system::{CommandQueue, Commands}, - world::World, + system::Commands, + world::{CommandQueue, World}, }; use super::DespawnRecursiveExt; diff --git a/crates/bevy_hierarchy/src/lib.rs b/crates/bevy_hierarchy/src/lib.rs index 83c0a10569ea3..5e1ad9e9ad4ef 100644 --- a/crates/bevy_hierarchy/src/lib.rs +++ b/crates/bevy_hierarchy/src/lib.rs @@ -84,13 +84,10 @@ use bevy_app::prelude::*; #[derive(Default)] pub struct HierarchyPlugin; -#[cfg(feature = "bevy_app")] -use bevy_utils::smallvec::SmallVec; impl Plugin for HierarchyPlugin { fn build(&self, app: &mut App) { app.register_type::() .register_type::() - .register_type::>() .add_event::(); } } diff --git a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs index d5d0783c1e328..c3d766bcde7d6 100644 --- a/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs +++ b/crates/bevy_hierarchy/src/valid_parent_check_plugin.rs @@ -64,7 +64,7 @@ pub fn check_hierarchy_component_has_valid_parent( let parent = parent.get(); if !component_query.contains(parent) && !already_diagnosed.contains(&entity) { already_diagnosed.insert(entity); - bevy_log::warn!( + bevy_utils::tracing::warn!( "warning[B0004]: {name} with the {ty_name} component has a parent without {ty_name}.\n\ This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/#b0004", ty_name = get_short_name(std::any::type_name::()), diff --git a/crates/bevy_input/src/keyboard.rs b/crates/bevy_input/src/keyboard.rs index a7999b47c4b1e..72fc2cfd430a7 100644 --- a/crates/bevy_input/src/keyboard.rs +++ b/crates/bevy_input/src/keyboard.rs @@ -86,7 +86,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// ## Usage /// /// The event is consumed inside of the [`keyboard_input_system`] -/// to update the [`Input`](ButtonInput) resource. +/// to update the [`ButtonInput`](ButtonInput) resource. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] #[reflect(Debug, PartialEq)] #[cfg_attr( @@ -162,7 +162,7 @@ pub enum NativeKeyCode { /// /// ## Usage /// -/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res>`. +/// It is used as the generic `T` value of an [`ButtonInput`] to create a `Res>`. /// /// Code representing the location of a physical key /// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index e6841ace81636..6513f174c1d29 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -34,19 +34,15 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; -use keyboard::{keyboard_input_system, Key, KeyCode, KeyboardInput, NativeKey, NativeKeyCode}; -use mouse::{ - mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseScrollUnit, - MouseWheel, -}; -use touch::{touch_screen_input_system, ForceTouch, TouchInput, TouchPhase, Touches}; +use keyboard::{keyboard_input_system, KeyCode, KeyboardInput}; +use mouse::{mouse_button_input_system, MouseButton, MouseButtonInput, MouseMotion, MouseWheel}; +use touch::{touch_screen_input_system, TouchInput, Touches}; use touchpad::{TouchpadMagnify, TouchpadRotate}; use gamepad::{ gamepad_axis_event_system, gamepad_button_event_system, gamepad_connection_system, - gamepad_event_system, AxisSettings, ButtonAxisSettings, ButtonSettings, Gamepad, GamepadAxis, - GamepadAxisChangedEvent, GamepadAxisType, GamepadButton, GamepadButtonChangedEvent, - GamepadButtonInput, GamepadButtonType, GamepadConnection, GamepadConnectionEvent, GamepadEvent, + gamepad_event_system, GamepadAxis, GamepadAxisChangedEvent, GamepadButton, + GamepadButtonChangedEvent, GamepadButtonInput, GamepadConnectionEvent, GamepadEvent, GamepadRumbleRequest, GamepadSettings, Gamepads, }; @@ -108,43 +104,15 @@ impl Plugin for InputPlugin { .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystem)); // Register common types - app.register_type::(); - - // Register keyboard types - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); - - // Register mouse types - app.register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); - - // Register touchpad types - app.register_type::() - .register_type::(); - - // Register touch types - app.register_type::() - .register_type::() - .register_type::(); - - // Register gamepad types - app.register_type::() - .register_type::() - .register_type::() - .register_type::() + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::(); + .register_type::(); } } diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index ee0469e651e22..0ea6362ee3fd5 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -68,6 +68,7 @@ serialize = [ "bevy_input/serialize", "bevy_time/serialize", "bevy_window/serialize", + "bevy_winit?/serialize", "bevy_transform/serialize", "bevy_math/serialize", "bevy_scene?/serialize", @@ -113,7 +114,7 @@ webgpu = [ # enable systems that allow for automated testing on CI bevy_ci_testing = [ - "bevy_app/bevy_ci_testing", + "bevy_dev_tools/bevy_ci_testing", "bevy_time/bevy_ci_testing", "bevy_render?/bevy_ci_testing", "bevy_render?/ci_limits", @@ -162,6 +163,12 @@ bevy_debug_stepping = [ "bevy_app/bevy_debug_stepping", ] +# Provides a collection of developer tools +bevy_dev_tools = ["dep:bevy_dev_tools"] + +# Enable support for the ios_simulator by downgrading some rendering capabilities +ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"] + [dependencies] # bevy bevy_a11y = { path = "../bevy_a11y", version = "0.14.0-dev" } @@ -200,6 +207,7 @@ bevy_ui = { path = "../bevy_ui", optional = true, version = "0.14.0-dev" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.14.0-dev" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.14.0-dev" } bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.14.0-dev", default-features = false } +bevy_dev_tools = { path = "../bevy_dev_tools/", optional = true, version = "0.14.0-dev" } [lints] workspace = true diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index c0869a24909f2..08d68b0fdcff1 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -27,6 +27,7 @@ use bevy_app::{Plugin, PluginGroup, PluginGroupBuilder}; /// * [`AudioPlugin`](crate::audio::AudioPlugin) - with feature `bevy_audio` /// * [`GilrsPlugin`](crate::gilrs::GilrsPlugin) - with feature `bevy_gilrs` /// * [`AnimationPlugin`](crate::animation::AnimationPlugin) - with feature `bevy_animation` +/// * [`DevToolsPlugin`](crate::dev_tools::DevToolsPlugin) - with feature `bevy_dev_tools` /// /// [`DefaultPlugins`] obeys *Cargo* *feature* flags. Users may exert control over this plugin group /// by disabling `default-features` in their `Cargo.toml` and enabling only those features @@ -134,6 +135,11 @@ impl PluginGroup for DefaultPlugins { group = group.add(bevy_gizmos::GizmoPlugin); } + #[cfg(feature = "bevy_dev_tools")] + { + group = group.add(bevy_dev_tools::DevToolsPlugin); + } + group = group.add(IgnoreAmbiguitiesPlugin); group diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 25a2584c7d83c..896091c404c98 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -199,3 +199,9 @@ pub mod dynamic_plugin { //! Dynamic linking of plugins pub use bevy_dynamic_plugin::*; } + +#[cfg(feature = "bevy_dev_tools")] +pub mod dev_tools { + //! Collection of developer tools + pub use bevy_dev_tools::*; +} diff --git a/crates/bevy_log/src/lib.rs b/crates/bevy_log/src/lib.rs index 4815418640ca1..601b10198f919 100644 --- a/crates/bevy_log/src/lib.rs +++ b/crates/bevy_log/src/lib.rs @@ -106,9 +106,12 @@ pub struct LogPlugin { /// This can be further filtered using the `filter` setting. pub level: Level, - /// Optionally apply extra transformations to the tracing subscriber. - /// For example add [`Layers`](tracing_subscriber::layer::Layer) - pub update_subscriber: Option BoxedSubscriber>, + /// Optionally apply extra transformations to the tracing subscriber, + /// such as adding [`Layer`](tracing_subscriber::layer::Layer)s. + /// + /// Access to [`App`] is also provided to allow for communication between the [`Subscriber`] + /// and the [`App`]. + pub update_subscriber: Option BoxedSubscriber>, } /// Alias for a boxed [`Subscriber`]. @@ -193,7 +196,7 @@ impl Plugin for LogPlugin { let subscriber = subscriber.with(tracy_layer); if let Some(update_subscriber) = self.update_subscriber { - finished_subscriber = update_subscriber(Box::new(subscriber)); + finished_subscriber = update_subscriber(app, Box::new(subscriber)); } else { finished_subscriber = Box::new(subscriber); } diff --git a/crates/bevy_math/src/bounding/bounded2d/mod.rs b/crates/bevy_math/src/bounding/bounded2d/mod.rs index 237dda02c20da..26ccd39b94652 100644 --- a/crates/bevy_math/src/bounding/bounded2d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded2d/mod.rs @@ -93,11 +93,12 @@ impl Aabb2d { } impl BoundingVolume for Aabb2d { - type Position = Vec2; + type Translation = Vec2; + type Rotation = f32; type HalfSize = Vec2; #[inline(always)] - fn center(&self) -> Self::Position { + fn center(&self) -> Self::Translation { (self.min + self.max) / 2. } @@ -147,6 +148,66 @@ impl BoundingVolume for Aabb2d { debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y); b } + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self { + self.transform_by(translation, rotation); + self + } + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) { + self.rotate_by(rotation); + self.translate_by(translation); + } + + #[inline(always)] + fn translate_by(&mut self, translation: Self::Translation) { + self.min += translation; + self.max += translation; + } + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn rotated_by(mut self, rotation: Self::Rotation) -> Self { + self.rotate_by(rotation); + self + } + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn rotate_by(&mut self, rotation: Self::Rotation) { + let rot_mat = Mat2::from_angle(rotation); + let abs_rot_mat = Mat2::from_cols(rot_mat.x_axis.abs(), rot_mat.y_axis.abs()); + let half_size = abs_rot_mat * self.half_size(); + *self = Self::new(rot_mat * self.center(), half_size); + } } impl IntersectsVolume for Aabb2d { @@ -277,6 +338,24 @@ mod aabb2d_tests { assert!(!shrunk.contains(&a)); } + #[test] + fn transform() { + let a = Aabb2d { + min: Vec2::new(-2.0, -2.0), + max: Vec2::new(2.0, 2.0), + }; + let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4); + let half_length = 2_f32.hypot(2.0); + assert_eq!( + transformed.min, + Vec2::new(2.0 - half_length, -half_length - 2.0) + ); + assert_eq!( + transformed.max, + Vec2::new(2.0 + half_length, half_length - 2.0) + ); + } + #[test] fn closest_point() { let aabb = Aabb2d { @@ -396,11 +475,12 @@ impl BoundingCircle { } impl BoundingVolume for BoundingCircle { - type Position = Vec2; + type Translation = Vec2; + type Rotation = f32; type HalfSize = f32; #[inline(always)] - fn center(&self) -> Self::Position { + fn center(&self) -> Self::Translation { self.center } @@ -449,6 +529,16 @@ impl BoundingVolume for BoundingCircle { debug_assert!(self.radius() >= amount); Self::new(self.center, self.radius() - amount) } + + #[inline(always)] + fn translate_by(&mut self, translation: Vec2) { + self.center += translation; + } + + #[inline(always)] + fn rotate_by(&mut self, rotation: f32) { + self.center = Mat2::from_angle(rotation) * self.center; + } } impl IntersectsVolume for BoundingCircle { @@ -551,6 +641,17 @@ mod bounding_circle_tests { assert!(!shrunk.contains(&a)); } + #[test] + fn transform() { + let a = BoundingCircle::new(Vec2::ONE, 5.0); + let transformed = a.transformed_by(Vec2::new(2.0, -2.0), std::f32::consts::FRAC_PI_4); + assert_eq!( + transformed.center, + Vec2::new(2.0, std::f32::consts::SQRT_2 - 2.0) + ); + assert_eq!(transformed.radius(), 5.0); + } + #[test] fn closest_point() { let circle = BoundingCircle::new(Vec2::ZERO, 1.0); diff --git a/crates/bevy_math/src/bounding/bounded3d/mod.rs b/crates/bevy_math/src/bounding/bounded3d/mod.rs index fff2b900dea04..53597d2c4bb64 100644 --- a/crates/bevy_math/src/bounding/bounded3d/mod.rs +++ b/crates/bevy_math/src/bounding/bounded3d/mod.rs @@ -1,5 +1,7 @@ mod primitive_impls; +use glam::Mat3; + use super::{BoundingVolume, IntersectsVolume}; use crate::prelude::{Quat, Vec3}; @@ -87,11 +89,12 @@ impl Aabb3d { } impl BoundingVolume for Aabb3d { - type Position = Vec3; + type Translation = Vec3; + type Rotation = Quat; type HalfSize = Vec3; #[inline(always)] - fn center(&self) -> Self::Position { + fn center(&self) -> Self::Translation { (self.min + self.max) / 2. } @@ -143,6 +146,70 @@ impl BoundingVolume for Aabb3d { debug_assert!(b.min.x <= b.max.x && b.min.y <= b.max.y && b.min.z <= b.max.z); b } + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self { + self.transform_by(translation, rotation); + self + } + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) { + self.rotate_by(rotation); + self.translate_by(translation); + } + + #[inline(always)] + fn translate_by(&mut self, translation: Self::Translation) { + self.min += translation; + self.max += translation; + } + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn rotated_by(mut self, rotation: Self::Rotation) -> Self { + self.rotate_by(rotation); + self + } + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is an Axis-Aligned Bounding Box that encompasses the rotated shape. + /// + /// Note that the result may not be as tightly fitting as the original, and repeated rotations + /// can cause the AABB to grow indefinitely. Avoid applying multiple rotations to the same AABB, + /// and consider storing the original AABB and rotating that every time instead. + #[inline(always)] + fn rotate_by(&mut self, rotation: Self::Rotation) { + let rot_mat = Mat3::from_quat(rotation); + let abs_rot_mat = Mat3::from_cols( + rot_mat.x_axis.abs(), + rot_mat.y_axis.abs(), + rot_mat.z_axis.abs(), + ); + let half_size = abs_rot_mat * self.half_size(); + *self = Self::new(rot_mat * self.center(), half_size); + } } impl IntersectsVolume for Aabb3d { @@ -170,7 +237,7 @@ mod aabb3d_tests { use super::Aabb3d; use crate::{ bounding::{BoundingSphere, BoundingVolume, IntersectsVolume}, - Vec3, + Quat, Vec3, }; #[test] @@ -273,6 +340,27 @@ mod aabb3d_tests { assert!(!shrunk.contains(&a)); } + #[test] + fn transform() { + let a = Aabb3d { + min: Vec3::new(-2.0, -2.0, -2.0), + max: Vec3::new(2.0, 2.0, 2.0), + }; + let transformed = a.transformed_by( + Vec3::new(2.0, -2.0, 4.0), + Quat::from_rotation_z(std::f32::consts::FRAC_PI_4), + ); + let half_length = 2_f32.hypot(2.0); + assert_eq!( + transformed.min, + Vec3::new(2.0 - half_length, -half_length - 2.0, 2.0) + ); + assert_eq!( + transformed.max, + Vec3::new(2.0 + half_length, half_length - 2.0, 6.0) + ); + } + #[test] fn closest_point() { let aabb = Aabb3d { @@ -388,11 +476,12 @@ impl BoundingSphere { } impl BoundingVolume for BoundingSphere { - type Position = Vec3; + type Translation = Vec3; + type Rotation = Quat; type HalfSize = f32; #[inline(always)] - fn center(&self) -> Self::Position { + fn center(&self) -> Self::Translation { self.center } @@ -451,6 +540,16 @@ impl BoundingVolume for BoundingSphere { }, } } + + #[inline(always)] + fn translate_by(&mut self, translation: Vec3) { + self.center += translation; + } + + #[inline(always)] + fn rotate_by(&mut self, rotation: Quat) { + self.center = rotation * self.center; + } } impl IntersectsVolume for BoundingSphere { @@ -471,10 +570,12 @@ impl IntersectsVolume for BoundingSphere { #[cfg(test)] mod bounding_sphere_tests { + use approx::assert_relative_eq; + use super::BoundingSphere; use crate::{ bounding::{BoundingVolume, IntersectsVolume}, - Vec3, + Quat, Vec3, }; #[test] @@ -553,6 +654,20 @@ mod bounding_sphere_tests { assert!(!shrunk.contains(&a)); } + #[test] + fn transform() { + let a = BoundingSphere::new(Vec3::ONE, 5.0); + let transformed = a.transformed_by( + Vec3::new(2.0, -2.0, 4.0), + Quat::from_rotation_z(std::f32::consts::FRAC_PI_4), + ); + assert_relative_eq!( + transformed.center, + Vec3::new(2.0, std::f32::consts::SQRT_2 - 2.0, 5.0) + ); + assert_eq!(transformed.radius(), 5.0); + } + #[test] fn closest_point() { let sphere = BoundingSphere::new(Vec3::ZERO, 1.0); diff --git a/crates/bevy_math/src/bounding/mod.rs b/crates/bevy_math/src/bounding/mod.rs index caadf7eb520ce..3fcda1d7069ee 100644 --- a/crates/bevy_math/src/bounding/mod.rs +++ b/crates/bevy_math/src/bounding/mod.rs @@ -10,16 +10,20 @@ /// overlapping elements or finding intersections. /// /// This trait supports both 2D and 3D bounding shapes. -pub trait BoundingVolume { +pub trait BoundingVolume: Sized { /// The position type used for the volume. This should be `Vec2` for 2D and `Vec3` for 3D. - type Position: Clone + Copy + PartialEq; + type Translation: Clone + Copy + PartialEq; + + /// The rotation type used for the volume. This should be `f32` for 2D and `Quat` for 3D. + type Rotation: Clone + Copy + PartialEq; + /// The type used for the size of the bounding volume. Usually a half size. For example an /// `f32` radius for a circle, or a `Vec3` with half sizes for x, y and z for a 3D axis-aligned /// bounding box type HalfSize; /// Returns the center of the bounding volume. - fn center(&self) -> Self::Position; + fn center(&self) -> Self::Translation; /// Returns the half size of the bounding volume. fn half_size(&self) -> Self::HalfSize; @@ -38,11 +42,47 @@ pub trait BoundingVolume { /// Computes the smallest bounding volume that contains both `self` and `other`. fn merge(&self, other: &Self) -> Self; - /// Increase the size of the bounding volume in each direction by the given amount + /// Increases the size of the bounding volume in each direction by the given amount. fn grow(&self, amount: Self::HalfSize) -> Self; - /// Decrease the size of the bounding volume in each direction by the given amount + /// Decreases the size of the bounding volume in each direction by the given amount. fn shrink(&self, amount: Self::HalfSize) -> Self; + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + fn transformed_by(mut self, translation: Self::Translation, rotation: Self::Rotation) -> Self { + self.transform_by(translation, rotation); + self + } + + /// Transforms the bounding volume by first rotating it around the origin and then applying a translation. + fn transform_by(&mut self, translation: Self::Translation, rotation: Self::Rotation) { + self.rotate_by(rotation); + self.translate_by(translation); + } + + /// Translates the bounding volume by the given translation. + fn translated_by(mut self, translation: Self::Translation) -> Self { + self.translate_by(translation); + self + } + + /// Translates the bounding volume by the given translation. + fn translate_by(&mut self, translation: Self::Translation); + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is a combination of the original volume and the rotated volume, + /// so it is guaranteed to be either the same size or larger than the original. + fn rotated_by(mut self, rotation: Self::Rotation) -> Self { + self.rotate_by(rotation); + self + } + + /// Rotates the bounding volume around the origin by the given rotation. + /// + /// The result is a combination of the original volume and the rotated volume, + /// so it is guaranteed to be either the same size or larger than the original. + fn rotate_by(&mut self, rotation: Self::Rotation); } /// A trait that generalizes intersection tests against a volume. diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index 8ac785878f6af..05a600a4f876f 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -38,6 +38,30 @@ impl std::fmt::Display for InvalidDirectionError { } } +/// Checks that a vector with the given squared length is normalized. +/// +/// Warns for small error with a length threshold of approximately `1e-4`, +/// and panics for large error with a length threshold of approximately `1e-2`. +/// +/// The format used for the logged warning is `"Warning: {warning} The length is {length}`, +/// and similarly for the error. +#[cfg(debug_assertions)] +fn assert_is_normalized(message: &str, length_squared: f32) { + let length_error_squared = (length_squared - 1.0).abs(); + + // Panic for large error and warn for slight error. + if length_error_squared > 2e-2 || length_error_squared.is_nan() { + // Length error is approximately 1e-2 or more. + panic!("Error: {message} The length is {}.", length_squared.sqrt()); + } else if length_error_squared > 2e-4 { + // Length error is approximately 1e-4 or more. + eprintln!( + "Warning: {message} The length is {}.", + length_squared.sqrt() + ); + } +} + /// A normalized vector pointing in a direction in 2D space #[deprecated( since = "0.14.0", @@ -81,9 +105,13 @@ impl Dir2 { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec2) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir2::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -132,6 +160,20 @@ impl std::ops::Neg for Dir2 { } } +impl std::ops::Mul for Dir2 { + type Output = Vec2; + fn mul(self, rhs: f32) -> Self::Output { + self.0 * rhs + } +} + +impl std::ops::Mul for f32 { + type Output = Vec2; + fn mul(self, rhs: Dir2) -> Self::Output { + self * rhs.0 + } +} + #[cfg(feature = "approx")] impl approx::AbsDiffEq for Dir2 { type Epsilon = f32; @@ -197,9 +239,13 @@ impl Dir3 { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir3::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -261,6 +307,13 @@ impl std::ops::Mul for Dir3 { } } +impl std::ops::Mul for f32 { + type Output = Vec3; + fn mul(self, rhs: Dir3) -> Self::Output { + self * rhs.0 + } +} + impl std::ops::Mul for Quat { type Output = Dir3; @@ -268,11 +321,13 @@ impl std::ops::Mul for Quat { fn mul(self, direction: Dir3) -> Self::Output { let rotated = self * *direction; - // Make sure the result is normalized. - // This can fail for non-unit quaternions. - debug_assert!(rotated.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "`Dir3` is denormalized after rotation.", + rotated.length_squared(), + ); - Dir3::new_unchecked(rotated) + Dir3(rotated) } } @@ -344,9 +399,13 @@ impl Dir3A { /// /// # Warning /// - /// `value` must be normalized, i.e it's length must be `1.0`. + /// `value` must be normalized, i.e its length must be `1.0`. pub fn new_unchecked(value: Vec3A) -> Self { - debug_assert!(value.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "The vector given to `Dir3A::new_unchecked` is not normalized.", + value.length_squared(), + ); Self(value) } @@ -408,6 +467,13 @@ impl std::ops::Mul for Dir3A { } } +impl std::ops::Mul for f32 { + type Output = Vec3A; + fn mul(self, rhs: Dir3A) -> Self::Output { + self * rhs.0 + } +} + impl std::ops::Mul for Quat { type Output = Dir3A; @@ -415,11 +481,13 @@ impl std::ops::Mul for Quat { fn mul(self, direction: Dir3A) -> Self::Output { let rotated = self * *direction; - // Make sure the result is normalized. - // This can fail for non-unit quaternions. - debug_assert!(rotated.is_normalized()); + #[cfg(debug_assertions)] + assert_is_normalized( + "`Dir3A` is denormalized after rotation.", + rotated.length_squared(), + ); - Dir3A::new_unchecked(rotated) + Dir3A(rotated) } } diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index 1e05323c85ca3..28e24aab94c70 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -40,6 +40,7 @@ fixedbitset = "0.4" bytemuck = { version = "1", features = ["derive"] } radsort = "0.1" smallvec = "1.6" +nonmax = "0.5" [lints] workspace = true diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index bc06f4d34d775..9258cf9f91ab8 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -234,13 +234,9 @@ impl Plugin for PbrPlugin { app.register_asset_reflect::() .register_type::() - .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() @@ -250,10 +246,7 @@ impl Plugin for PbrPlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() - .register_type::() - .register_type::() .init_resource::() .init_resource::() .init_resource::() diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index ac94689bc3089..e131d92d2c3ca 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -589,7 +589,7 @@ fn calculate_cascade( // It is critical for `world_to_cascade` to be stable. So rather than forming `cascade_to_world` // and inverting it, which risks instability due to numerical precision, we directly form - // `world_to_cascde` as the reference material suggests. + // `world_to_cascade` as the reference material suggests. let light_to_world_transpose = light_to_world.transpose(); let world_to_cascade = Mat4::from_cols( light_to_world_transpose.x_axis, diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index a83509267d2ec..3231cb8df5d6d 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -59,7 +59,16 @@ pub struct StandardMaterial { /// /// The default emissive color is [`Color::BLACK`], which doesn't add anything to the material color. /// - /// Note that **an emissive material won't light up surrounding areas like a light source**, + /// To increase emissive strength, channel values for `emissive` + /// colors can exceed `1.0`. For instance, a `base_color` of + /// `Color::linear_rgb(1.0, 0.0, 0.0)` represents the brightest + /// red for objects that reflect light, but an emissive color + /// like `Color::linear_rgb(1000.0, 0.0, 0.0)` can be used to create + /// intensely bright red emissive effects. + /// + /// Increasing the emissive strength of the color will impact visual effects + /// like bloom, but it's important to note that **an emissive material won't + /// light up surrounding areas like a light source**, /// it just adds a value to the color seen on screen. pub emissive: Color, @@ -727,7 +736,9 @@ impl AsBindGroupShaderType for StandardMaterial { thickness: self.thickness, ior: self.ior, attenuation_distance: self.attenuation_distance, - attenuation_color: LinearRgba::from(self.base_color).to_f32_array().into(), + attenuation_color: LinearRgba::from(self.attenuation_color) + .to_f32_array() + .into(), flags: flags.bits(), alpha_cutoff, parallax_depth_scale: self.parallax_depth_scale, diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 54eec9411159a..061c34a8b2f3b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -5,7 +5,7 @@ use bevy_math::{Mat4, UVec3, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles use bevy_render::{ camera::Camera, mesh::Mesh, - primitives::{CascadesFrusta, CubemapFrusta, Frustum}, + primitives::{CascadesFrusta, CubemapFrusta, Frustum, HalfSpace}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::*, @@ -18,10 +18,8 @@ use bevy_render::{ use bevy_transform::{components::GlobalTransform, prelude::Transform}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use bevy_utils::{ - nonmax::NonMaxU32, - tracing::{error, warn}, -}; +use bevy_utils::tracing::{error, warn}; +use nonmax::NonMaxU32; use std::{hash::Hash, num::NonZeroU64, ops::Range}; use crate::*; @@ -1144,7 +1142,7 @@ pub fn prepare_lights( .unwrap() .iter() .take(MAX_CASCADES_PER_LIGHT); - for (cascade_index, ((cascade, frusta), bound)) in cascades + for (cascade_index, ((cascade, frustum), bound)) in cascades .zip(frusta) .zip(&light.cascade_shadow_config.bounds) .enumerate() @@ -1171,6 +1169,11 @@ pub fn prepare_lights( }); directional_depth_texture_array_index += 1; + let mut frustum = *frustum; + // Push the near clip plane out to infinity for directional lights + frustum.half_spaces[4] = + HalfSpace::new(frustum.half_spaces[4].normal().extend(f32::INFINITY)); + let view_light_entity = commands .spawn(( ShadowView { @@ -1191,7 +1194,7 @@ pub fn prepare_lights( hdr: false, color_grading: Default::default(), }, - *frusta, + frustum, RenderPhase::::default(), LightEntity::Directional { light_entity, diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index d26cc1fcb2a5e..d6c0322461528 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -26,6 +26,119 @@ mod sealed { impl Sealed for super::Unaligned {} } +/// A newtype around [`NonNull`] that only allows conversion to read-only borrows or pointers. +/// +/// This type can be thought of as the `*const T` to [`NonNull`]'s `*mut T`. +#[repr(transparent)] +pub struct ConstNonNull(NonNull); + +impl ConstNonNull { + /// Creates a new `ConstNonNull` if `ptr` is non-null. + /// + /// # Examples + /// + /// ``` + /// use bevy_ptr::ConstNonNull; + /// + /// let x = 0u32; + /// let ptr = ConstNonNull::::new(&x as *const _).expect("ptr is null!"); + /// + /// if let Some(ptr) = ConstNonNull::::new(std::ptr::null()) { + /// unreachable!(); + /// } + /// ``` + pub fn new(ptr: *const T) -> Option { + NonNull::new(ptr.cast_mut()).map(Self) + } + + /// Creates a new `ConstNonNull`. + /// + /// # Safety + /// + /// `ptr` must be non-null. + /// + /// # Examples + /// + /// ``` + /// use bevy_ptr::ConstNonNull; + /// + /// let x = 0u32; + /// let ptr = unsafe { ConstNonNull::new_unchecked(&x as *const _) }; + /// ``` + /// + /// *Incorrect* usage of this function: + /// + /// ```rust,no_run + /// use bevy_ptr::ConstNonNull; + /// + /// // NEVER DO THAT!!! This is undefined behavior. ⚠️ + /// let ptr = unsafe { ConstNonNull::::new_unchecked(std::ptr::null()) }; + /// ``` + pub const unsafe fn new_unchecked(ptr: *const T) -> Self { + // SAFETY: This function's safety invariants are identical to `NonNull::new_unchecked` + // The caller must satisfy all of them. + unsafe { Self(NonNull::new_unchecked(ptr.cast_mut())) } + } + + /// Returns a shared reference to the value. + /// + /// # Safety + /// + /// When calling this method, you have to ensure that all of the following is true: + /// + /// * The pointer must be properly aligned. + /// + /// * It must be "dereferenceable" in the sense defined in [the module documentation]. + /// + /// * The pointer must point to an initialized instance of `T`. + /// + /// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is + /// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data. + /// In particular, while this reference exists, the memory the pointer points to must + /// not get mutated (except inside `UnsafeCell`). + /// + /// This applies even if the result of this method is unused! + /// (The part about being initialized is not yet fully decided, but until + /// it is, the only safe approach is to ensure that they are indeed initialized.) + /// + /// # Examples + /// + /// ``` + /// use bevy_ptr::ConstNonNull; + /// + /// let mut x = 0u32; + /// let ptr = ConstNonNull::new(&mut x as *mut _).expect("ptr is null!"); + /// + /// let ref_x = unsafe { ptr.as_ref() }; + /// println!("{ref_x}"); + /// ``` + /// + /// [the module documentation]: core::ptr#safety + #[inline] + pub unsafe fn as_ref<'a>(&self) -> &'a T { + // SAFETY: This function's safety invariants are identical to `NonNull::as_ref` + // The caller must satisfy all of them. + unsafe { self.0.as_ref() } + } +} + +impl From> for ConstNonNull { + fn from(value: NonNull) -> ConstNonNull { + ConstNonNull(value) + } +} + +impl<'a, T: ?Sized> From<&'a T> for ConstNonNull { + fn from(value: &'a T) -> ConstNonNull { + ConstNonNull(NonNull::from(value)) + } +} + +impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull { + fn from(value: &'a mut T) -> ConstNonNull { + ConstNonNull(NonNull::from(value)) + } +} /// Type-erased borrow of some unknown type chosen when constructing this type. /// /// This type tries to act "borrow-like" which means that: @@ -280,6 +393,7 @@ impl<'a> OwningPtr<'a> { f(unsafe { PtrMut::from(&mut *temp).promote() }) } } + impl<'a, A: IsAligned> OwningPtr<'a, A> { /// Creates a new instance from a raw pointer. /// @@ -346,6 +460,7 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> { unsafe { PtrMut::new(self.0) } } } + impl<'a> OwningPtr<'a, Unaligned> { /// Consumes the [`OwningPtr`] to obtain ownership of the underlying data of type `T`. /// diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index fccab4f5cca03..3a197c692c4d9 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -10,12 +10,13 @@ keywords = ["bevy"] readme = "README.md" [features] -default = [] +default = ["smallvec"] # When enabled, provides Bevy-related reflection implementations bevy = ["smallvec", "bevy_math", "smol_str"] glam = ["dep:glam"] bevy_math = ["glam", "dep:bevy_math"] -smallvec = [] +smallvec = ["dep:smallvec"] +uuid = ["dep:uuid"] # When enabled, allows documentation comments to be accessed via reflection documentation = ["bevy_reflect_derive/documentation"] @@ -33,9 +34,11 @@ erased-serde = "0.4" downcast-rs = "1.2" thiserror = "1.0" serde = "1" +smallvec = { version = "1.11", optional = true } glam = { version = "0.25", features = ["serde"], optional = true } smol_str = { version = "0.2.0", optional = true } +uuid = { version = "1.0", optional = true, features = ["v4", "serde"] } [dev-dependencies] ron = "0.8.0" diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index 869ca861328c3..48625ec3ca283 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -470,7 +470,12 @@ impl<'a> ReflectMeta<'a> { &self, where_clause_options: &WhereClauseOptions, ) -> proc_macro2::TokenStream { - crate::registration::impl_get_type_registration(self, where_clause_options, None) + crate::registration::impl_get_type_registration( + self, + where_clause_options, + None, + Option::>::None, + ) } /// The collection of docstrings for this type, if any. @@ -502,6 +507,7 @@ impl<'a> ReflectStruct<'a> { self.meta(), where_clause_options, self.serialization_data(), + Some(self.active_types().iter()), ) } @@ -512,22 +518,21 @@ impl<'a> ReflectStruct<'a> { .collect() } - /// Get an iterator of fields which are exposed to the reflection API + /// Get an iterator of fields which are exposed to the reflection API. pub fn active_fields(&self) -> impl Iterator> { - self.fields + self.fields() .iter() .filter(|field| field.attrs.ignore.is_active()) } /// Get an iterator of fields which are ignored by the reflection API pub fn ignored_fields(&self) -> impl Iterator> { - self.fields + self.fields() .iter() .filter(|field| field.attrs.ignore.is_ignored()) } /// The complete set of fields in this struct. - #[allow(dead_code)] pub fn fields(&self) -> &[StructField<'a>] { &self.fields } @@ -573,6 +578,21 @@ impl<'a> ReflectEnum<'a> { pub fn where_clause_options(&self) -> WhereClauseOptions { WhereClauseOptions::new_with_fields(self.meta(), self.active_types().into_boxed_slice()) } + + /// Returns the `GetTypeRegistration` impl as a `TokenStream`. + /// + /// Returns a specific implementation for enums and this method should be preferred over the generic [`get_type_registration`](crate::ReflectMeta) method + pub fn get_type_registration( + &self, + where_clause_options: &WhereClauseOptions, + ) -> proc_macro2::TokenStream { + crate::registration::impl_get_type_registration( + self.meta(), + where_clause_options, + None, + Some(self.active_fields().map(|field| &field.data.ty)), + ) + } } impl<'a> EnumVariant<'a> { diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 65aea76b9627d..056a293ec91be 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -84,9 +84,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream let type_path_impl = impl_type_path(reflect_enum.meta()); - let get_type_registration_impl = reflect_enum - .meta() - .get_type_registration(&where_clause_options); + let get_type_registration_impl = reflect_enum.get_type_registration(&where_clause_options); let (impl_generics, ty_generics, where_clause) = reflect_enum.meta().type_path().generics().split_for_impl(); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs index 04189073a3886..d277d823c59f9 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/registration.rs @@ -4,17 +4,29 @@ use crate::derive_data::ReflectMeta; use crate::serialization::SerializationDataDef; use crate::utility::WhereClauseOptions; use quote::quote; +use syn::Type; /// Creates the `GetTypeRegistration` impl for the given type data. #[allow(clippy::too_many_arguments)] -pub(crate) fn impl_get_type_registration( +pub(crate) fn impl_get_type_registration<'a>( meta: &ReflectMeta, where_clause_options: &WhereClauseOptions, serialization_data: Option<&SerializationDataDef>, + type_dependencies: Option>, ) -> proc_macro2::TokenStream { let type_path = meta.type_path(); let bevy_reflect_path = meta.bevy_reflect_path(); let registration_data = meta.attrs().idents(); + + let type_deps_fn = type_dependencies.map(|deps| { + quote! { + #[inline(never)] + fn register_type_dependencies(registry: &mut #bevy_reflect_path::TypeRegistry) { + #(<#deps as #bevy_reflect_path::__macro_exports::RegisterForReflection>::__register(registry);)* + } + } + }); + let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl(); let where_reflect_clause = where_clause_options.extend_where_clause(where_clause); @@ -44,6 +56,8 @@ pub(crate) fn impl_get_type_registration( #(registration.insert::<#registration_data>(#bevy_reflect_path::FromType::::from_type());)* registration } + + #type_deps_fn } } } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs index 5e619cf1ade45..a32fcabeea640 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/utility.rs @@ -217,11 +217,15 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> { // `TypePath` is always required for active fields since they are used to // construct `NamedField` and `UnnamedField` instances for the `Typed` impl. - Some( - self.active_fields - .iter() - .map(move |ty| quote!(#ty : #reflect_bound + #bevy_reflect_path::TypePath)), - ) + // Likewise, `GetTypeRegistration` is always required for active fields since + // they are used to register the type's dependencies. + Some(self.active_fields.iter().map(move |ty| { + quote!( + #ty : #reflect_bound + + #bevy_reflect_path::TypePath + + #bevy_reflect_path::__macro_exports::RegisterForReflection + ) + })) } } diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index a911b3b2fdc0e..b27c7a1e1ae9f 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -1,5 +1,4 @@ use bevy_reflect_derive::impl_type_path; -use bevy_utils::smallvec; use smallvec::SmallVec; use std::any::Any; @@ -154,7 +153,7 @@ where } } -impl_type_path!(::bevy_utils::smallvec::SmallVec); +impl_type_path!(::smallvec::SmallVec); impl FromReflect for SmallVec where diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 07500814d0263..578879091bfcc 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,5 +1,5 @@ use crate::std_traits::ReflectDefault; -use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned}; +use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned, TypeRegistry}; use crate::{ impl_type_path, map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicEnum, DynamicMap, Enum, EnumInfo, FromReflect, FromType, GetTypeRegistration, List, ListInfo, @@ -221,7 +221,7 @@ impl_reflect_value!(::std::ffi::OsString(Debug, Hash, PartialEq)); macro_rules! impl_reflect_for_veclike { ($ty:path, $insert:expr, $remove:expr, $push:expr, $pop:expr, $sub:ty) => { - impl List for $ty { + impl List for $ty { #[inline] fn get(&self, index: usize) -> Option<&dyn Reflect> { <$sub>::get(self, index).map(|value| value as &dyn Reflect) @@ -280,7 +280,7 @@ macro_rules! impl_reflect_for_veclike { } } - impl Reflect for $ty { + impl Reflect for $ty { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -347,7 +347,7 @@ macro_rules! impl_reflect_for_veclike { } } - impl Typed for $ty { + impl Typed for $ty { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) @@ -356,15 +356,19 @@ macro_rules! impl_reflect_for_veclike { impl_type_path!($ty); - impl GetTypeRegistration for $ty { + impl GetTypeRegistration for $ty { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::<$ty>(); registration.insert::(FromType::<$ty>::from_type()); registration } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } } - impl FromReflect for $ty { + impl FromReflect for $ty { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { let mut new_list = Self::with_capacity(ref_list.len()); @@ -401,8 +405,8 @@ macro_rules! impl_reflect_for_hashmap { ($ty:path) => { impl Map for $ty where - K: FromReflect + TypePath + Eq + Hash, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { @@ -498,8 +502,8 @@ macro_rules! impl_reflect_for_hashmap { impl Reflect for $ty where - K: FromReflect + TypePath + Eq + Hash, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { @@ -567,8 +571,8 @@ macro_rules! impl_reflect_for_hashmap { impl Typed for $ty where - K: FromReflect + TypePath + Eq + Hash, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn type_info() -> &'static TypeInfo { @@ -579,8 +583,8 @@ macro_rules! impl_reflect_for_hashmap { impl GetTypeRegistration for $ty where - K: FromReflect + TypePath + Eq + Hash, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Send + Sync, { fn get_type_registration() -> TypeRegistration { @@ -588,12 +592,17 @@ macro_rules! impl_reflect_for_hashmap { registration.insert::(FromType::::from_type()); registration } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + registry.register::(); + } } impl FromReflect for $ty where - K: FromReflect + TypePath + Eq + Hash, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Hash, + V: FromReflect + TypePath + GetTypeRegistration, S: TypePath + BuildHasher + Default + Send + Sync, { fn from_reflect(reflect: &dyn Reflect) -> Option { @@ -624,8 +633,8 @@ impl_type_path!(::bevy_utils::hashbrown::HashMap); impl Map for ::std::collections::BTreeMap where - K: FromReflect + TypePath + Eq + Ord, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration, { fn get(&self, key: &dyn Reflect) -> Option<&dyn Reflect> { key.downcast_ref::() @@ -720,8 +729,8 @@ where impl Reflect for ::std::collections::BTreeMap where - K: FromReflect + TypePath + Eq + Ord, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration, { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -788,8 +797,8 @@ where impl Typed for ::std::collections::BTreeMap where - K: FromReflect + TypePath + Eq + Ord, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration, { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); @@ -799,8 +808,8 @@ where impl GetTypeRegistration for ::std::collections::BTreeMap where - K: FromReflect + TypePath + Eq + Ord, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration, { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::(); @@ -811,8 +820,8 @@ where impl FromReflect for ::std::collections::BTreeMap where - K: FromReflect + TypePath + Eq + Ord, - V: FromReflect + TypePath, + K: FromReflect + TypePath + GetTypeRegistration + Eq + Ord, + V: FromReflect + TypePath + GetTypeRegistration, { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Map(ref_map) = reflect.reflect_ref() { @@ -831,7 +840,7 @@ where impl_type_path!(::std::collections::BTreeMap); -impl Array for [T; N] { +impl Array for [T; N] { #[inline] fn get(&self, index: usize) -> Option<&dyn Reflect> { <[T]>::get(self, index).map(|value| value as &dyn Reflect) @@ -860,7 +869,7 @@ impl Array for [T; N] { } } -impl Reflect for [T; N] { +impl Reflect for [T; N] { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -942,7 +951,7 @@ impl Reflect for [T; N] { } } -impl FromReflect for [T; N] { +impl FromReflect for [T; N] { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Array(ref_array) = reflect.reflect_ref() { let mut temp_vec = Vec::with_capacity(ref_array.len()); @@ -956,7 +965,7 @@ impl FromReflect for [T; N] { } } -impl Typed for [T; N] { +impl Typed for [T; N] { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::Array(ArrayInfo::new::(N))) @@ -975,37 +984,27 @@ impl TypePath for [T; N] { } } -// TODO: -// `FromType::from_type` requires `Deserialize<'de>` to be implemented for `T`. -// Currently serde only supports `Deserialize<'de>` for arrays up to size 32. -// This can be changed to use const generics once serde utilizes const generics for arrays. -// Tracking issue: https://github.com/serde-rs/serde/issues/1937 -macro_rules! impl_array_get_type_registration { - ($($N:expr)+) => { - $( - impl GetTypeRegistration for [T; $N] { - fn get_type_registration() -> TypeRegistration { - TypeRegistration::of::<[T; $N]>() - } - } - )+ - }; -} +impl GetTypeRegistration for [T; N] { + fn get_type_registration() -> TypeRegistration { + TypeRegistration::of::<[T; N]>() + } -impl_array_get_type_registration! { - 0 1 2 3 4 5 6 7 8 9 - 10 11 12 13 14 15 16 17 18 19 - 20 21 22 23 24 25 26 27 28 29 - 30 31 32 + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } } -impl GetTypeRegistration for Option { +impl GetTypeRegistration for Option { fn get_type_registration() -> TypeRegistration { TypeRegistration::of::>() } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } } -impl Enum for Option { +impl Enum for Option { fn field(&self, _name: &str) -> Option<&dyn Reflect> { None } @@ -1076,7 +1075,7 @@ impl Enum for Option { } } -impl Reflect for Option { +impl Reflect for Option { #[inline] fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) @@ -1189,7 +1188,7 @@ impl Reflect for Option { } } -impl FromReflect for Option { +impl FromReflect for Option { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Enum(dyn_enum) = reflect.reflect_ref() { match dyn_enum.variant_name() { @@ -1227,7 +1226,7 @@ impl FromReflect for Option { } } -impl Typed for Option { +impl Typed for Option { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| { @@ -1392,7 +1391,7 @@ where } } -impl List for Cow<'static, [T]> { +impl List for Cow<'static, [T]> { fn get(&self, index: usize) -> Option<&dyn Reflect> { self.as_ref().get(index).map(|x| x as &dyn Reflect) } @@ -1451,7 +1450,7 @@ impl List for Cow<'static, [T]> { } } -impl Reflect for Cow<'static, [T]> { +impl Reflect for Cow<'static, [T]> { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -1518,20 +1517,26 @@ impl Reflect for Cow<'static, [T]> { } } -impl Typed for Cow<'static, [T]> { +impl Typed for Cow<'static, [T]> { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); CELL.get_or_insert::(|| TypeInfo::List(ListInfo::new::())) } } -impl GetTypeRegistration for Cow<'static, [T]> { +impl GetTypeRegistration + for Cow<'static, [T]> +{ fn get_type_registration() -> TypeRegistration { TypeRegistration::of::>() } + + fn register_type_dependencies(registry: &mut TypeRegistry) { + registry.register::(); + } } -impl FromReflect for Cow<'static, [T]> { +impl FromReflect for Cow<'static, [T]> { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { let mut temp_vec = Vec::with_capacity(ref_list.len()); diff --git a/crates/bevy_reflect/src/impls/uuid.rs b/crates/bevy_reflect/src/impls/uuid.rs index 27a1af41a4fc3..f845dda798c15 100644 --- a/crates/bevy_reflect/src/impls/uuid.rs +++ b/crates/bevy_reflect/src/impls/uuid.rs @@ -3,7 +3,7 @@ use crate as bevy_reflect; use crate::{std_traits::ReflectDefault, ReflectDeserialize, ReflectSerialize}; use bevy_reflect_derive::impl_reflect_value; -impl_reflect_value!(::bevy_utils::Uuid( +impl_reflect_value!(::uuid::Uuid( Serialize, Deserialize, Default, diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index ab7b2d0a0adb1..2add0144f0d04 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -481,9 +481,11 @@ mod tuple_struct; mod type_info; mod type_path; mod type_registry; + mod impls { #[cfg(feature = "glam")] mod glam; + #[cfg(feature = "bevy_math")] mod math { mod direction; @@ -491,12 +493,14 @@ mod impls { mod primitives3d; mod rect; } + #[cfg(feature = "smallvec")] mod smallvec; #[cfg(feature = "smol_str")] mod smol_str; mod std; + #[cfg(feature = "uuid")] mod uuid; } @@ -535,9 +539,48 @@ pub use erased_serde; extern crate alloc; +/// Exports used by the reflection macros. +/// +/// These are not meant to be used directly and are subject to breaking changes. #[doc(hidden)] pub mod __macro_exports { - pub use bevy_utils::uuid::generate_composite_uuid; + use crate::{ + DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicStruct, DynamicTuple, + DynamicTupleStruct, GetTypeRegistration, TypeRegistry, + }; + + /// A wrapper trait around [`GetTypeRegistration`]. + /// + /// This trait is used by the derive macro to recursively register all type dependencies. + /// It's used instead of `GetTypeRegistration` directly to avoid making dynamic types also + /// implement `GetTypeRegistration` in order to be used as active fields. + /// + /// This trait has a blanket implementation for all types that implement `GetTypeRegistration` + /// and manual implementations for all dynamic types (which simply do nothing). + pub trait RegisterForReflection { + #[allow(unused_variables)] + fn __register(registry: &mut TypeRegistry) {} + } + + impl RegisterForReflection for T { + fn __register(registry: &mut TypeRegistry) { + registry.register::(); + } + } + + impl RegisterForReflection for DynamicEnum {} + + impl RegisterForReflection for DynamicTupleStruct {} + + impl RegisterForReflection for DynamicStruct {} + + impl RegisterForReflection for DynamicMap {} + + impl RegisterForReflection for DynamicList {} + + impl RegisterForReflection for DynamicArray {} + + impl RegisterForReflection for DynamicTuple {} } #[cfg(test)] @@ -1002,6 +1045,124 @@ mod tests { assert_eq!(new_foo, expected_new_foo); } + #[test] + fn should_auto_register_fields() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect)] + enum Bar { + Variant(Baz), + } + + #[derive(Reflect)] + struct Baz(usize); + + // === Basic === // + let mut registry = TypeRegistry::empty(); + registry.register::(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `Foo`" + ); + + // === Option === // + let mut registry = TypeRegistry::empty(); + registry.register::>(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `Option`" + ); + + // === Tuple === // + let mut registry = TypeRegistry::empty(); + registry.register::<(Foo, Foo)>(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `(Foo, Foo)`" + ); + + // === Array === // + let mut registry = TypeRegistry::empty(); + registry.register::<[Foo; 3]>(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `[Foo; 3]`" + ); + + // === Vec === // + let mut registry = TypeRegistry::empty(); + registry.register::>(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `Vec`" + ); + + // === HashMap === // + let mut registry = TypeRegistry::empty(); + registry.register::>(); + + assert!( + registry.contains(TypeId::of::()), + "registry should contain auto-registered `Bar` from `HashMap`" + ); + } + + #[test] + fn should_allow_dynamic_fields() { + #[derive(Reflect)] + #[reflect(from_reflect = false)] + struct MyStruct( + DynamicEnum, + DynamicTupleStruct, + DynamicStruct, + DynamicMap, + DynamicList, + DynamicArray, + DynamicTuple, + i32, + ); + + assert_impl_all!(MyStruct: Reflect, GetTypeRegistration); + + let mut registry = TypeRegistry::empty(); + registry.register::(); + + assert_eq!(2, registry.iter().count()); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + } + + #[test] + fn should_not_auto_register_existing_types() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect, Default)] + struct Bar(usize); + + let mut registry = TypeRegistry::empty(); + registry.register::(); + registry.register_type_data::(); + registry.register::(); + + assert!( + registry + .get_type_data::(TypeId::of::()) + .is_some(), + "registry should contain existing registration for `Bar`" + ); + } + #[test] fn reflect_serialize() { #[derive(Reflect)] @@ -1387,7 +1548,6 @@ mod tests { // List (SmallVec) #[cfg(feature = "smallvec")] { - use bevy_utils::smallvec; type MySmallVec = smallvec::SmallVec<[String; 2]>; let info = MySmallVec::type_info(); @@ -1981,6 +2141,39 @@ bevy_reflect::tests::Test { let _ = ::type_path(); } + #[test] + fn recursive_registration_does_not_hang() { + #[derive(Reflect)] + struct Recurse(T); + + let mut registry = TypeRegistry::empty(); + + registry.register::>>(); + + #[derive(Reflect)] + #[reflect(no_field_bounds)] + struct SelfRecurse { + recurse: Vec, + } + + registry.register::(); + + #[derive(Reflect)] + #[reflect(no_field_bounds)] + enum RecurseA { + Recurse(RecurseB), + } + + #[derive(Reflect)] + struct RecurseB { + vector: Vec, + } + + registry.register::(); + assert!(registry.contains(TypeId::of::())); + assert!(registry.contains(TypeId::of::())); + } + #[test] fn can_opt_out_type_path() { #[derive(Reflect)] diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 78d78e1a42ea2..de6af437df4bb 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -3,8 +3,8 @@ use bevy_utils::all_tuples; use crate::{ self as bevy_reflect, utility::GenericTypePathCell, FromReflect, GetTypeRegistration, Reflect, - ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed, - UnnamedField, + ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, TypeRegistry, + Typed, UnnamedField, }; use crate::{ReflectKind, TypePathTable}; use std::any::{Any, TypeId}; @@ -461,7 +461,7 @@ pub fn tuple_debug(dyn_tuple: &dyn Tuple, f: &mut Formatter<'_>) -> std::fmt::Re macro_rules! impl_reflect_tuple { {$($index:tt : $name:tt),*} => { - impl<$($name: Reflect + TypePath),*> Tuple for ($($name,)*) { + impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Tuple for ($($name,)*) { #[inline] fn field(&self, index: usize) -> Option<&dyn Reflect> { match index { @@ -512,7 +512,7 @@ macro_rules! impl_reflect_tuple { } } - impl<$($name: Reflect + TypePath),*> Reflect for ($($name,)*) { + impl<$($name: Reflect + TypePath + GetTypeRegistration),*> Reflect for ($($name,)*) { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { Some(::type_info()) } @@ -575,7 +575,7 @@ macro_rules! impl_reflect_tuple { } } - impl <$($name: Reflect + TypePath),*> Typed for ($($name,)*) { + impl <$($name: Reflect + TypePath + GetTypeRegistration),*> Typed for ($($name,)*) { fn type_info() -> &'static TypeInfo { static CELL: $crate::utility::GenericTypeInfoCell = $crate::utility::GenericTypeInfoCell::new(); CELL.get_or_insert::(|| { @@ -588,14 +588,17 @@ macro_rules! impl_reflect_tuple { } } - - impl<$($name: Reflect + TypePath),*> GetTypeRegistration for ($($name,)*) { + impl<$($name: Reflect + TypePath + GetTypeRegistration),*> GetTypeRegistration for ($($name,)*) { fn get_type_registration() -> TypeRegistration { TypeRegistration::of::<($($name,)*)>() } + + fn register_type_dependencies(_registry: &mut TypeRegistry) { + $(_registry.register::<$name>();)* + } } - impl<$($name: FromReflect + TypePath),*> FromReflect for ($($name,)*) + impl<$($name: FromReflect + TypePath + GetTypeRegistration),*> FromReflect for ($($name,)*) { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::Tuple(_ref_tuple) = reflect.reflect_ref() { diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 8fc39297f3d5a..2846781f47792 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -56,8 +56,15 @@ impl Debug for TypeRegistryArc { /// See the [crate-level documentation] for more information on type registration. /// /// [crate-level documentation]: crate -pub trait GetTypeRegistration { +pub trait GetTypeRegistration: 'static { + /// Returns the default [`TypeRegistration`] for this type. fn get_type_registration() -> TypeRegistration; + /// Registers other types needed by this type. + /// + /// This method is called by [`TypeRegistry::register`] to register any other required types. + /// Often, this is done for fields of structs and enum variants to ensure all types are properly registered. + #[allow(unused_variables)] + fn register_type_dependencies(registry: &mut TypeRegistry) {} } impl Default for TypeRegistry { @@ -100,39 +107,132 @@ impl TypeRegistry { registry } - /// Registers the type `T`, adding reflect data as specified in the [`Reflect`] derive: - /// ```ignore (Neither bevy_ecs nor serde "derive" are available.) - /// #[derive(Component, serde::Serialize, serde::Deserialize, Reflect)] - /// #[reflect(Component, Serialize, Deserialize)] // will register ReflectComponent, ReflectSerialize, ReflectDeserialize + /// Attempts to register the type `T` if it has not yet been registered already. + /// + /// This will also recursively register any type dependencies as specified by [`GetTypeRegistration::register_type_dependencies`]. + /// When deriving `Reflect`, this will generally be all the fields of the struct or enum variant. + /// As with any type registration, these type dependencies will not be registered more than once. + /// + /// If the registration for type `T` already exists, it will not be registered again and neither will its type dependencies. + /// To register the type, overwriting any existing registration, use [register](Self::overwrite_registration) instead. + /// + /// Additionally, this will add any reflect [type data](TypeData) as specified in the [`Reflect`] derive. + /// + /// # Example + /// + /// ``` + /// # use std::any::TypeId; + /// # use bevy_reflect::{Reflect, TypeRegistry, std_traits::ReflectDefault}; + /// #[derive(Reflect, Default)] + /// #[reflect(Default)] + /// struct Foo { + /// name: Option, + /// value: i32 + /// } + /// + /// let mut type_registry = TypeRegistry::default(); + /// + /// type_registry.register::(); + /// + /// // The main type + /// assert!(type_registry.contains(TypeId::of::())); + /// + /// // Its type dependencies + /// assert!(type_registry.contains(TypeId::of::>())); + /// assert!(type_registry.contains(TypeId::of::())); + /// + /// // Its type data + /// assert!(type_registry.get_type_data::(TypeId::of::()).is_some()); /// ``` pub fn register(&mut self) where T: GetTypeRegistration, { - self.add_registration(T::get_type_registration()); + if self.register_internal(TypeId::of::(), T::get_type_registration) { + T::register_type_dependencies(self); + } + } + + /// Attempts to register the type described by `registration`. + /// + /// If the registration for the type already exists, it will not be registered again. + /// + /// To forcibly register the type, overwriting any existing registration, use the + /// [`overwrite_registration`](Self::overwrite_registration) method instead. + /// + /// This method will _not_ register type dependencies. + /// Use [`register`](Self::register) to register a type with its dependencies. + /// + /// Returns `true` if the registration was added and `false` if it already exists. + pub fn add_registration(&mut self, registration: TypeRegistration) -> bool { + let type_id = registration.type_id(); + self.register_internal(type_id, || registration) } /// Registers the type described by `registration`. - pub fn add_registration(&mut self, registration: TypeRegistration) { - if self.registrations.contains_key(®istration.type_id()) { - return; + /// + /// If the registration for the type already exists, it will be overwritten. + /// + /// To avoid overwriting existing registrations, it's recommended to use the + /// [`register`](Self::register) or [`add_registration`](Self::add_registration) methods instead. + /// + /// This method will _not_ register type dependencies. + /// Use [`register`](Self::register) to register a type with its dependencies. + pub fn overwrite_registration(&mut self, registration: TypeRegistration) { + Self::update_registration_indices( + ®istration, + &mut self.short_path_to_id, + &mut self.type_path_to_id, + &mut self.ambiguous_names, + ); + self.registrations + .insert(registration.type_id(), registration); + } + + /// Internal method to register a type with a given [`TypeId`] and [`TypeRegistration`]. + /// + /// By using this method, we are able to reduce the number of `TypeId` hashes and lookups needed + /// to register a type. + /// + /// This method is internal to prevent users from accidentally registering a type with a `TypeId` + /// that does not match the type in the `TypeRegistration`. + fn register_internal( + &mut self, + type_id: TypeId, + get_registration: impl FnOnce() -> TypeRegistration, + ) -> bool { + match self.registrations.entry(type_id) { + bevy_utils::Entry::Occupied(_) => false, + bevy_utils::Entry::Vacant(entry) => { + let registration = get_registration(); + Self::update_registration_indices( + ®istration, + &mut self.short_path_to_id, + &mut self.type_path_to_id, + &mut self.ambiguous_names, + ); + entry.insert(registration); + true + } } + } + /// Internal method to register additional lookups for a given [`TypeRegistration`]. + fn update_registration_indices( + registration: &TypeRegistration, + short_path_to_id: &mut HashMap<&'static str, TypeId>, + type_path_to_id: &mut HashMap<&'static str, TypeId>, + ambiguous_names: &mut HashSet<&'static str>, + ) { let short_name = registration.type_info().type_path_table().short_path(); - if self.short_path_to_id.contains_key(short_name) - || self.ambiguous_names.contains(short_name) - { + if short_path_to_id.contains_key(short_name) || ambiguous_names.contains(short_name) { // name is ambiguous. fall back to long names for all ambiguous types - self.short_path_to_id.remove(short_name); - self.ambiguous_names.insert(short_name); + short_path_to_id.remove(short_name); + ambiguous_names.insert(short_name); } else { - self.short_path_to_id - .insert(short_name, registration.type_id()); + short_path_to_id.insert(short_name, registration.type_id()); } - self.type_path_to_id - .insert(registration.type_info().type_path(), registration.type_id()); - self.registrations - .insert(registration.type_id(), registration); + type_path_to_id.insert(registration.type_info().type_path(), registration.type_id()); } /// Registers the type data `D` for type `T`. @@ -162,6 +262,10 @@ impl TypeRegistry { data.insert(D::from_type()); } + pub fn contains(&self, type_id: TypeId) -> bool { + self.registrations.contains_key(&type_id) + } + /// Returns a reference to the [`TypeRegistration`] of the type with the /// given [`TypeId`]. /// diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.rs index c3693d06310db..5788063757ce3 100644 --- a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.rs +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.rs @@ -1,4 +1,4 @@ -use bevy_reflect::{Reflect, TypePath}; +use bevy_reflect::{GetField, Reflect, Struct, TypePath}; #[derive(Reflect)] #[reflect(from_reflect = false)] @@ -11,7 +11,7 @@ struct Foo { struct NoReflect(f32); fn main() { - let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); + let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); // foo doesn't implement Reflect because NoReflect doesn't implement Reflect foo.get_field::("a").unwrap(); } diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr index 40d36dcf977ae..22a6cc8d53ccf 100644 --- a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/generics.fail.stderr @@ -1,16 +1,34 @@ -error[E0599]: no method named `get_field` found for struct `Box<(dyn Reflect + 'static)>` in the current scope - --> tests/reflect_derive/generics.fail.rs:16:9 - | -16 | foo.get_field::("a").unwrap(); - | ^^^^^^^^^ method not found in `Box` - error[E0277]: the trait bound `NoReflect: Reflect` is not satisfied - --> tests/reflect_derive/generics.fail.rs:14:37 + --> tests/reflect_derive/generics.fail.rs:16:21 + | +16 | foo.get_field::("a").unwrap(); + | --------- ^^^^^^^^^ the trait `Reflect` is not implemented for `NoReflect` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `Reflect`: + bool + char + isize + i8 + i16 + i32 + i64 + i128 + and $N others +note: required by a bound in `bevy_reflect::GetField::get_field` + --> /home/runner/work/bevy/bevy/crates/bevy_reflect/src/struct_trait.rs:242:21 + | +242 | fn get_field(&self, name: &str) -> Option<&T>; + | ^^^^^^^ required by this bound in `GetField::get_field` + +error[E0277]: the trait bound `NoReflect: GetTypeRegistration` is not satisfied + --> tests/reflect_derive/generics.fail.rs:14:36 | -14 | let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Reflect` is not implemented for `NoReflect` +14 | let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GetTypeRegistration` is not implemented for `NoReflect` | - = help: the following other types implement trait `Reflect`: + = help: the following other types implement trait `GetTypeRegistration`: bool char isize @@ -20,7 +38,8 @@ error[E0277]: the trait bound `NoReflect: Reflect` is not satisfied i64 i128 and $N others -note: required for `Foo` to implement `Reflect` + = note: required for `NoReflect` to implement `RegisterForReflection` +note: required for `Foo` to implement `bevy_reflect::Struct` --> tests/reflect_derive/generics.fail.rs:3:10 | 3 | #[derive(Reflect)] @@ -28,5 +47,5 @@ note: required for `Foo` to implement `Reflect` 4 | #[reflect(from_reflect = false)] 5 | struct Foo { | ^^^^^^ - = note: required for the cast from `Box>` to `Box<(dyn Reflect + 'static)>` + = note: required for the cast from `Box>` to `Box<(dyn bevy_reflect::Struct + 'static)>` = note: this error originates in the derive macro `Reflect` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 7903dd8d08bea..ca51b826c46c1 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -19,7 +19,7 @@ webp = ["image/webp"] dds = ["ddsfile"] pnm = ["image/pnm"] multi-threaded = ["bevy_tasks/multi-threaded"] -bevy_ci_testing = ["bevy_app/bevy_ci_testing"] +bevy_ci_testing = ["bevy_dev_tools/bevy_ci_testing"] shader_format_glsl = ["naga/glsl-in", "naga/wgsl-out", "naga_oil/glsl"] shader_format_spirv = ["wgpu/spirv", "naga/spv-in", "naga/spv-out"] @@ -46,7 +46,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.14.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ @@ -58,6 +57,7 @@ bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" } bevy_window = { path = "../bevy_window", version = "0.14.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" } +bevy_dev_tools = { path = "../bevy_dev_tools", version = "0.14.0-dev", optional = true } # rendering image = { version = "0.24", default-features = false } @@ -66,7 +66,7 @@ image = { version = "0.24", default-features = false } codespan-reporting = "0.11.0" # `fragile-send-sync-non-atomic-wasm` feature means we can't use WASM threads for rendering # It is enabled for now to avoid having to do a significant overhaul of the renderer just for wasm -wgpu = { version = "0.19.1", default-features = false, features = [ +wgpu = { version = "0.19.3", default-features = false, features = [ "wgsl", "dx12", "metal", @@ -96,7 +96,8 @@ profiling = { version = "1", features = [ "profile-with-tracing", ], optional = true } async-channel = "2.2.0" - +nonmax = "0.5" +smallvec = "1.11" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # Omit the `glsl` feature in non-WebAssembly by default. @@ -107,9 +108,7 @@ naga_oil = { version = "0.13", default-features = false, features = [ [target.'cfg(target_arch = "wasm32")'.dependencies] naga_oil = "0.13" js-sys = "0.3" -# web-sys doesn't follow semver for the WebGPU APIs as they are unstable -# Make sure that WebGPU builds work when changing this! -web-sys = { version = "=0.3.67", features = [ +web-sys = { version = "0.3.67", features = [ 'Blob', 'Document', 'Element', diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 859d56e98deb5..54b0573081f67 100644 --- a/crates/bevy_render/src/batching/mod.rs +++ b/crates/bevy_render/src/batching/mod.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ prelude::Res, system::{Query, ResMut, StaticSystemParam, SystemParam, SystemParamItem}, }; -use bevy_utils::nonmax::NonMaxU32; +use nonmax::NonMaxU32; use crate::{ render_phase::{CachedRenderPipelinePhaseItem, DrawFunctionId, RenderPhase}, @@ -88,7 +88,7 @@ pub fn batch_and_prepare_render_phase { diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index a1e02be219857..579129bb0f014 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -24,12 +24,7 @@ pub struct CameraPlugin; impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { app.register_type::() - .register_type::() - .register_type::>() - .register_type::() - .register_type::() .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_render/src/extract_resource.rs b/crates/bevy_render/src/extract_resource.rs index 7a928bd291570..038a612fe21f6 100644 --- a/crates/bevy_render/src/extract_resource.rs +++ b/crates/bevy_render/src/extract_resource.rs @@ -51,7 +51,7 @@ pub fn extract_resource( } else { #[cfg(debug_assertions)] if !main_resource.is_added() { - bevy_log::warn_once!( + bevy_utils::warn_once!( "Removing resource {} from render world not expected, adding using `Commands`. This may decrease performance", std::any::type_name::() diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index d972356b02a7e..3227bd00137bd 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -329,17 +329,7 @@ impl Plugin for RenderPlugin { app.register_type::() // These types cannot be registered in bevy_color, as it does not depend on the rest of Bevy - // BLOCKED: once https://github.com/bevyengine/bevy/pull/5781 lands, we can remove all but the Color registration .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 33a21e62d6b58..dc4549cd040b5 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -11,16 +11,15 @@ use crate::{ renderer::RenderDevice, }; use bevy_asset::{Asset, Handle}; -use bevy_core::cast_slice; use bevy_derive::EnumVariantMeta; use bevy_ecs::system::{ lifetimeless::{SRes, SResMut}, SystemParamItem, }; -use bevy_log::warn; use bevy_math::*; use bevy_reflect::Reflect; -use bevy_utils::tracing::error; +use bevy_utils::tracing::{error, warn}; +use bytemuck::cast_slice; use std::{collections::BTreeMap, hash::Hash, iter::FusedIterator}; use thiserror::Error; use wgpu::{ diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index a6d67e960fe4d..7748a5a1eaeb5 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -13,7 +13,7 @@ use std::{ use crate::{prelude::Image, render_asset::RenderAssetPlugin, RenderApp}; use bevy_app::{App, Plugin}; -use bevy_asset::{AssetApp, Handle}; +use bevy_asset::AssetApp; use bevy_ecs::{entity::Entity, system::Resource}; /// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU. @@ -24,10 +24,6 @@ impl Plugin for MeshPlugin { app.init_asset::() .init_asset::() .register_asset_reflect::() - .register_type::>>() - .register_type::>>() - .register_type::>() - .register_type::() .register_type::() .register_type::>() // 'Mesh' must be prepared after 'Image' as meshes rely on the morph target image being ready diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index ebb3e717e1654..0b1686fbce641 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -7,14 +7,17 @@ use bevy_ecs::{ system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState}, world::{FromWorld, Mut}, }; +use bevy_reflect::std_traits::ReflectDefault; use bevy_reflect::{ utility::{reflect_hasher, NonGenericTypeInfoCell}, - FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, - Typed, ValueInfo, + FromReflect, FromType, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectFromPtr, + ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, + TypeInfo, TypePath, TypeRegistration, Typed, ValueInfo, }; -use bevy_utils::{thiserror::Error, HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; +use thiserror::Error; #[derive(Debug, Error)] pub enum PrepareAssetError { @@ -162,6 +165,18 @@ impl Reflect for RenderAssetUsages { } } +impl GetTypeRegistration for RenderAssetUsages { + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::(); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration.insert::(FromType::::from_type()); + registration + } +} + impl FromReflect for RenderAssetUsages { fn from_reflect(reflect: &dyn Reflect) -> Option { let raw_value = *reflect.as_any().downcast_ref::()?; diff --git a/crates/bevy_render/src/render_graph/app.rs b/crates/bevy_render/src/render_graph/app.rs index 97ababa312b73..94082590aa86b 100644 --- a/crates/bevy_render/src/render_graph/app.rs +++ b/crates/bevy_render/src/render_graph/app.rs @@ -1,6 +1,6 @@ use bevy_app::App; use bevy_ecs::world::FromWorld; -use bevy_log::warn; +use bevy_utils::tracing::warn; use super::{IntoRenderNodeArray, Node, RenderGraph, RenderLabel, RenderSubGraph}; diff --git a/crates/bevy_render/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs index e66698fc371d9..1a3a544b464f4 100644 --- a/crates/bevy_render/src/render_phase/mod.rs +++ b/crates/bevy_render/src/render_phase/mod.rs @@ -29,9 +29,9 @@ mod draw; mod draw_state; mod rangefinder; -use bevy_utils::nonmax::NonMaxU32; pub use draw::*; pub use draw_state::*; +use nonmax::NonMaxU32; pub use rangefinder::*; use crate::render_resource::{CachedRenderPipelineId, PipelineCache}; diff --git a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs index 59d47849a3abb..d92c7a3897e06 100644 --- a/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs +++ b/crates/bevy_render/src/render_resource/batched_uniform_buffer.rs @@ -3,11 +3,11 @@ use crate::{ render_resource::DynamicUniformBuffer, renderer::{RenderDevice, RenderQueue}, }; -use bevy_utils::nonmax::NonMaxU32; use encase::{ private::{ArrayMetadata, BufferMut, Metadata, RuntimeSizedArray, WriteInto, Writer}, ShaderType, }; +use nonmax::NonMaxU32; use std::{marker::PhantomData, num::NonZeroU64}; use wgpu::{BindingResource, Limits}; @@ -81,7 +81,7 @@ impl BatchedUniformBuffer { pub fn push(&mut self, component: T) -> GpuArrayBufferIndex { let result = GpuArrayBufferIndex { - index: NonMaxU32::new(self.temp.0.len() as u32).unwrap(), + index: self.temp.0.len() as u32, dynamic_offset: NonMaxU32::new(self.current_offset), element_type: PhantomData, }; diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 4e6898b7cb1c4..96f885f8e9c1d 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -7,9 +7,9 @@ use crate::{ texture::FallbackImage, }; pub use bevy_render_macros::AsBindGroup; -use bevy_utils::thiserror::Error; use encase::ShaderType; use std::ops::Deref; +use thiserror::Error; use wgpu::{BindGroupEntry, BindGroupLayoutEntry, BindingResource}; define_atomic_id!(BindGroupId); diff --git a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs b/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs index b877d38c027e1..7bb9ff1cafd21 100644 --- a/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs +++ b/crates/bevy_render/src/render_resource/bind_group_layout_entries.rs @@ -222,7 +222,7 @@ impl IntoBindGroupLayoutEntryBuilder for BindingType { impl IntoBindGroupLayoutEntryBuilder for BindGroupLayoutEntry { fn into_bind_group_layout_entry_builder(self) -> BindGroupLayoutEntryBuilder { if self.binding != u32::MAX { - bevy_log::warn!("The BindGroupLayoutEntries api ignores the binding index when converting a raw wgpu::BindGroupLayoutEntry. You can ignore this warning by setting it to u32::MAX."); + bevy_utils::tracing::warn!("The BindGroupLayoutEntries api ignores the binding index when converting a raw wgpu::BindGroupLayoutEntry. You can ignore this warning by setting it to u32::MAX."); } BindGroupLayoutEntryBuilder { ty: self.ty, diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index cae1ef4f05381..26656c58ed983 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -2,7 +2,7 @@ use crate::{ render_resource::Buffer, renderer::{RenderDevice, RenderQueue}, }; -use bevy_core::{cast_slice, Pod}; +use bytemuck::{cast_slice, Pod}; use wgpu::BufferUsages; /// A structure for storing raw bytes that have already been properly formatted diff --git a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs index dbfe6a5962231..60d8cc68e400c 100644 --- a/crates/bevy_render/src/render_resource/gpu_array_buffer.rs +++ b/crates/bevy_render/src/render_resource/gpu_array_buffer.rs @@ -7,8 +7,8 @@ use crate::{ renderer::{RenderDevice, RenderQueue}, }; use bevy_ecs::{prelude::Component, system::Resource}; -use bevy_utils::nonmax::NonMaxU32; use encase::{private::WriteInto, ShaderSize, ShaderType}; +use nonmax::NonMaxU32; use std::marker::PhantomData; use wgpu::BindingResource; @@ -57,7 +57,7 @@ impl GpuArrayBuffer { GpuArrayBuffer::Uniform(buffer) => buffer.push(value), GpuArrayBuffer::Storage(buffer) => { let buffer = buffer.get_mut(); - let index = NonMaxU32::new(buffer.len() as u32).unwrap(); + let index = buffer.len() as u32; buffer.push(value); GpuArrayBufferIndex { index, @@ -109,7 +109,7 @@ impl GpuArrayBuffer { #[derive(Component, Clone)] pub struct GpuArrayBufferIndex { /// The index to use in a shader into the array. - pub index: NonMaxU32, + pub index: u32, /// The dynamic offset to use when setting the bind group in a pass. /// Only used on platforms that don't support storage buffers. pub dynamic_offset: Option, diff --git a/crates/bevy_render/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs index 3db56919b1247..c155f4027da46 100644 --- a/crates/bevy_render/src/renderer/graph_runner.rs +++ b/crates/bevy_render/src/renderer/graph_runner.rs @@ -1,11 +1,9 @@ use bevy_ecs::{prelude::Entity, world::World}; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; -use bevy_utils::{ - smallvec::{smallvec, SmallVec}, - HashMap, -}; +use bevy_utils::HashMap; +use smallvec::{smallvec, SmallVec}; use std::{borrow::Cow, collections::VecDeque}; use thiserror::Error; diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index 41090adfc0d0d..d5bbed5ee6c87 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -13,7 +13,7 @@ impl Image { is_srgb: bool, asset_usage: RenderAssetUsages, ) -> Image { - use bevy_core::cast_slice; + use bytemuck::cast_slice; let width; let height; diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 866cbc928c68e..986ae7d104ea4 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -118,6 +118,8 @@ impl Plugin for ImagePlugin { feature = "bmp", feature = "basis-universal", feature = "ktx2", + feature = "webp", + feature = "pnm" ))] app.preregister_asset_loader::(IMG_FILE_EXTENSIONS); } @@ -131,6 +133,8 @@ impl Plugin for ImagePlugin { feature = "bmp", feature = "basis-universal", feature = "ktx2", + feature = "webp", + feature = "pnm" ))] { app.init_asset_loader::(); diff --git a/crates/bevy_render/src/view/window/mod.rs b/crates/bevy_render/src/view/window/mod.rs index 6bca35efdea2d..64d089cee0f7c 100644 --- a/crates/bevy_render/src/view/window/mod.rs +++ b/crates/bevy_render/src/view/window/mod.rs @@ -276,7 +276,7 @@ pub fn prepare_windows( format!("MSAA {}x", fallback.samples()) }; - bevy_log::warn!( + bevy_utils::tracing::warn!( "MSAA {}x is not supported on this device. Falling back to {}.", msaa.samples(), fallback_str, diff --git a/crates/bevy_render/src/view/window/screenshot.rs b/crates/bevy_render/src/view/window/screenshot.rs index c13a60f88524c..b51c4f479770a 100644 --- a/crates/bevy_render/src/view/window/screenshot.rs +++ b/crates/bevy_render/src/view/window/screenshot.rs @@ -3,8 +3,8 @@ use std::{borrow::Cow, path::Path, sync::PoisonError}; use bevy_app::Plugin; use bevy_asset::{load_internal_asset, Handle}; use bevy_ecs::{entity::EntityHashMap, prelude::*}; -use bevy_log::{error, info, info_span}; use bevy_tasks::AsyncComputeTaskPool; +use bevy_utils::tracing::{error, info, info_span}; use std::sync::Mutex; use thiserror::Error; use wgpu::{ @@ -144,7 +144,7 @@ impl Plugin for ScreenshotPlugin { #[cfg(feature = "bevy_ci_testing")] if app .world - .contains_resource::() + .contains_resource::() { app.add_systems(bevy_app::Update, ci_testing_screenshot_at); } @@ -154,7 +154,7 @@ impl Plugin for ScreenshotPlugin { #[cfg(feature = "bevy_ci_testing")] fn ci_testing_screenshot_at( mut current_frame: Local, - ci_testing_config: Res, + ci_testing_config: Res, mut screenshot_manager: ResMut, main_window: Query>, ) { diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 2cf9a190e1fd4..ac2f98ffae8bb 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -193,7 +193,7 @@ where #[cfg(test)] mod tests { use bevy_ecs::entity::EntityHashMap; - use bevy_ecs::{reflect::AppTypeRegistry, system::Command, world::World}; + use bevy_ecs::{reflect::AppTypeRegistry, world::Command, world::World}; use bevy_hierarchy::{Parent, PushChild}; use crate::dynamic_scene_builder::DynamicSceneBuilder; diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index d8e0ea63acb5a..86c0c30d9a3d9 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -5,8 +5,8 @@ use bevy_ecs::{ entity::Entity, event::{Event, Events, ManualEventReader}, reflect::AppTypeRegistry, - system::{Command, Resource}, - world::{Mut, World}, + system::Resource, + world::{Command, Mut, World}, }; use bevy_hierarchy::{Parent, PushChild}; use bevy_utils::{tracing::error, HashMap, HashSet}; diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index ce8c402de2e08..01fea40918265 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -19,7 +19,6 @@ bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" } bevy_color = { path = "../bevy_color", version = "0.14.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index cf0c1e968328f..0ad06f6afc696 100644 --- a/crates/bevy_sprite/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use crate::{Sprite, TextureAtlas}; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; @@ -11,9 +13,9 @@ use bevy_transform::components::{GlobalTransform, Transform}; /// /// # Extra behaviours /// -/// You may add the following components to enable additional behaviours +/// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw specific sections of a sprite sheet, (See [`SpriteSheetBundle`]) +/// - [`TextureAtlas`] to draw a specific section of the texture #[derive(Bundle, Clone, Debug, Default)] pub struct SpriteBundle { /// Specifies the rendering properties of the sprite, such as color tint and flip. @@ -41,6 +43,10 @@ pub struct SpriteBundle { /// Check the following examples for usage: /// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) /// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) +#[deprecated( + since = "0.14.0", + note = "Use `TextureAtlas` alongside a `SpriteBundle` instead" +)] #[derive(Bundle, Clone, Debug, Default)] pub struct SpriteSheetBundle { /// Specifies the rendering properties of the sprite, such as color tint and flip. diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2b7ed84a41a83..685ecb0d4b54a 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -12,9 +12,13 @@ mod texture_atlas_builder; mod texture_slice; pub mod prelude { + #[allow(deprecated)] + #[doc(hidden)] + pub use crate::bundle::SpriteSheetBundle; + #[doc(hidden)] pub use crate::{ - bundle::{SpriteBundle, SpriteSheetBundle}, + bundle::SpriteBundle, sprite::{ImageScaleMode, Sprite}, texture_atlas::{TextureAtlas, TextureAtlasLayout}, texture_slice::{BorderRect, SliceScaleMode, TextureSlice, TextureSlicer}, diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index a8588c73f80e3..2b5772c686559 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -10,7 +10,6 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::SRes, SystemParamItem}, }; -use bevy_log::error; use bevy_render::{ mesh::{Mesh, MeshVertexBufferLayoutRef}, prelude::Image, @@ -30,6 +29,7 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::{GlobalTransform, Transform}; +use bevy_utils::tracing::error; use bevy_utils::{FloatOrd, HashMap, HashSet}; use std::hash::Hash; use std::marker::PhantomData; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 045c1e2ca2594..5cd5fcb894374 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ system::{lifetimeless::*, SystemParamItem, SystemState}, }; use bevy_math::{Affine3, Vec4}; -use bevy_reflect::Reflect; +use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::mesh::MeshVertexBufferLayoutRef; use bevy_render::{ batching::{ @@ -39,7 +39,7 @@ use crate::Material2dBindGroupId; /// /// It wraps a [`Handle`] to differentiate from the 3d pipelines which use the handles directly as components #[derive(Default, Clone, Component, Debug, Reflect, PartialEq, Eq)] -#[reflect(Component)] +#[reflect(Default, Component)] pub struct Mesh2dHandle(pub Handle); impl From> for Mesh2dHandle { diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 40d0dd5f3ca68..ae57eb760c091 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -32,8 +32,6 @@ pub struct Sprite { } /// Controls how the image is altered when scaled. -/// -/// Note: This is not yet compatible with texture atlases #[derive(Component, Debug, Clone, Reflect)] #[reflect(Component)] pub enum ImageScaleMode { diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 23327db5e9947..32d923b681c11 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,11 +1,11 @@ use bevy_asset::AssetId; -use bevy_log::{debug, error, warn}; use bevy_math::{URect, UVec2}; use bevy_render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, texture::{Image, TextureFormatPixelInfo}, }; +use bevy_utils::tracing::{debug, error, warn}; use bevy_utils::HashMap; use rectangle_pack::{ contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation, @@ -173,14 +173,10 @@ impl<'a> TextureAtlasBuilder<'a> { /// let texture = textures.add(texture); /// let layout = layouts.add(atlas_layout); /// // Spawn your sprite - /// commands.spawn(SpriteSheetBundle { - /// texture, - /// atlas: TextureAtlas { - /// layout, - /// index: 0 - /// }, - /// ..Default::default() - /// }); + /// commands.spawn(( + /// SpriteBundle { texture, ..Default::default() }, + /// TextureAtlas::from(layout), + /// )); /// } /// ``` /// diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index cbc5a370b43f9..1f08e9a817d49 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -1,4 +1,4 @@ -use crate::{ExtractedSprite, ImageScaleMode, Sprite}; +use crate::{ExtractedSprite, ImageScaleMode, Sprite, TextureAtlas, TextureAtlasLayout}; use super::TextureSlice; use bevy_asset::{AssetEvent, Assets, Handle}; @@ -63,37 +63,55 @@ impl ComputedTextureSlices { /// will be computed according to the `image_handle` dimensions or the sprite rect. /// /// Returns `None` if the image asset is not loaded +/// +/// # Arguments +/// +/// * `sprite` - The sprite component, will be used to find the draw area size +/// * `scale_mode` - The image scaling component +/// * `image_handle` - The texture to slice or tile +/// * `images` - The image assets, use to retrieve the image dimensions +/// * `atlas` - Optional texture atlas, if set the slicing will happen on the matching sub section +/// of the texture +/// * `atlas_layouts` - The atlas layout assets, used to retrieve the texture atlas section rect #[must_use] fn compute_sprite_slices( sprite: &Sprite, scale_mode: &ImageScaleMode, image_handle: &Handle, images: &Assets, + atlas: Option<&TextureAtlas>, + atlas_layouts: &Assets, ) -> Option { - let image_size = images.get(image_handle).map(|i| { - Vec2::new( - i.texture_descriptor.size.width as f32, - i.texture_descriptor.size.height as f32, - ) - })?; - let slices = match scale_mode { - ImageScaleMode::Sliced(slicer) => slicer.compute_slices( - sprite.rect.unwrap_or(Rect { + let (image_size, texture_rect) = match atlas { + Some(a) => { + let layout = atlas_layouts.get(&a.layout)?; + ( + layout.size.as_vec2(), + layout.textures.get(a.index)?.as_rect(), + ) + } + None => { + let image = images.get(image_handle)?; + let size = Vec2::new( + image.texture_descriptor.size.width as f32, + image.texture_descriptor.size.height as f32, + ); + let rect = sprite.rect.unwrap_or(Rect { min: Vec2::ZERO, - max: image_size, - }), - sprite.custom_size, - ), + max: size, + }); + (size, rect) + } + }; + let slices = match scale_mode { + ImageScaleMode::Sliced(slicer) => slicer.compute_slices(texture_rect, sprite.custom_size), ImageScaleMode::Tiled { tile_x, tile_y, stretch_value, } => { let slice = TextureSlice { - texture_rect: sprite.rect.unwrap_or(Rect { - min: Vec2::ZERO, - max: image_size, - }), + texture_rect, draw_size: sprite.custom_size.unwrap_or(image_size), offset: Vec2::ZERO, }; @@ -109,7 +127,14 @@ pub(crate) fn compute_slices_on_asset_event( mut commands: Commands, mut events: EventReader>, images: Res>, - sprites: Query<(Entity, &ImageScaleMode, &Sprite, &Handle)>, + atlas_layouts: Res>, + sprites: Query<( + Entity, + &ImageScaleMode, + &Sprite, + &Handle, + Option<&TextureAtlas>, + )>, ) { // We store the asset ids of added/modified image assets let added_handles: HashSet<_> = events @@ -123,11 +148,18 @@ pub(crate) fn compute_slices_on_asset_event( return; } // We recompute the sprite slices for sprite entities with a matching asset handle id - for (entity, scale_mode, sprite, image_handle) in &sprites { + for (entity, scale_mode, sprite, image_handle, atlas) in &sprites { if !added_handles.contains(&image_handle.id()) { continue; } - if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) { + if let Some(slices) = compute_sprite_slices( + sprite, + scale_mode, + image_handle, + &images, + atlas, + &atlas_layouts, + ) { commands.entity(entity).insert(slices); } } @@ -138,17 +170,32 @@ pub(crate) fn compute_slices_on_asset_event( pub(crate) fn compute_slices_on_sprite_change( mut commands: Commands, images: Res>, + atlas_layouts: Res>, changed_sprites: Query< - (Entity, &ImageScaleMode, &Sprite, &Handle), + ( + Entity, + &ImageScaleMode, + &Sprite, + &Handle, + Option<&TextureAtlas>, + ), Or<( Changed, Changed>, Changed, + Changed, )>, >, ) { - for (entity, scale_mode, sprite, image_handle) in &changed_sprites { - if let Some(slices) = compute_sprite_slices(sprite, scale_mode, image_handle, &images) { + for (entity, scale_mode, sprite, image_handle, atlas) in &changed_sprites { + if let Some(slices) = compute_sprite_slices( + sprite, + scale_mode, + image_handle, + &images, + atlas, + &atlas_layouts, + ) { commands.entity(entity).insert(slices); } } diff --git a/crates/bevy_sprite/src/texture_slice/mod.rs b/crates/bevy_sprite/src/texture_slice/mod.rs index d617634acd3bd..516f025ca6676 100644 --- a/crates/bevy_sprite/src/texture_slice/mod.rs +++ b/crates/bevy_sprite/src/texture_slice/mod.rs @@ -86,7 +86,7 @@ impl TextureSlice { remaining_columns -= size_y; } if slices.len() > 1_000 { - bevy_log::warn!("One of your tiled textures has generated {} slices. You might want to use higher stretch values to avoid a great performance cost", slices.len()); + bevy_utils::tracing::warn!("One of your tiled textures has generated {} slices. You might want to use higher stretch values to avoid a great performance cost", slices.len()); } slices } diff --git a/crates/bevy_sprite/src/texture_slice/slicer.rs b/crates/bevy_sprite/src/texture_slice/slicer.rs index f048d6d880a53..d930aab705d12 100644 --- a/crates/bevy_sprite/src/texture_slice/slicer.rs +++ b/crates/bevy_sprite/src/texture_slice/slicer.rs @@ -72,7 +72,7 @@ impl TextureSlicer { TextureSlice { texture_rect: Rect { min: vec2(base_rect.max.x - right, base_rect.min.y), - max: vec2(base_rect.max.x, top), + max: vec2(base_rect.max.x, base_rect.min.y + top), }, draw_size: vec2(right, top) * min_coef, offset: vec2( @@ -198,6 +198,9 @@ impl TextureSlicer { /// /// * `rect` - The section of the texture to slice in 9 parts /// * `render_size` - The optional draw size of the texture. If not set the `rect` size will be used. + // + // TODO: Support `URect` and `UVec2` instead (See `https://github.com/bevyengine/bevy/pull/11698`) + // #[must_use] pub fn compute_slices(&self, rect: Rect, render_size: Option) -> Vec { let render_size = render_size.unwrap_or_else(|| rect.size()); @@ -207,7 +210,7 @@ impl TextureSlicer { || self.border.top >= rect_size.y || self.border.bottom >= rect_size.y { - bevy_log::error!( + bevy_utils::tracing::error!( "TextureSlicer::border has out of bounds values. No slicing will be applied" ); return vec![TextureSlice { diff --git a/crates/bevy_tasks/src/thread_executor.rs b/crates/bevy_tasks/src/thread_executor.rs index dc989f902c12d..cc6b272efb4ff 100644 --- a/crates/bevy_tasks/src/thread_executor.rs +++ b/crates/bevy_tasks/src/thread_executor.rs @@ -106,7 +106,7 @@ impl<'task, 'ticker> ThreadExecutorTicker<'task, 'ticker> { } /// Synchronously try to tick a task on the executor. - /// Returns false if if does not find a task to tick. + /// Returns false if does not find a task to tick. pub fn try_tick(&self) -> bool { self.executor.executor.try_tick() } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 3ad31358ccf44..e0824cf9e3671 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -77,11 +77,6 @@ impl Plugin for TextPlugin { app.init_asset::() .register_type::() .register_type::() - .register_type::() - .register_type::>() - .register_type::() - .register_type::() - .register_type::() .init_asset_loader::() .init_resource::() .init_resource::() diff --git a/crates/bevy_time/Cargo.toml b/crates/bevy_time/Cargo.toml index beb9d0db56108..5b586ebab3d03 100644 --- a/crates/bevy_time/Cargo.toml +++ b/crates/bevy_time/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["bevy"] [features] default = [] serialize = ["serde"] -bevy_ci_testing = ["bevy_app/bevy_ci_testing"] +bevy_ci_testing = ["bevy_dev_tools/bevy_ci_testing"] [dependencies] # bevy @@ -23,6 +23,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", ] } bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" } +bevy_dev_tools = { path = "../bevy_dev_tools", version = "0.14.0-dev", optional = true } # other crossbeam-channel = "0.5.0" diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 98c075e9985ff..c94e6f46db0ba 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -51,7 +51,6 @@ impl Plugin for TimePlugin { .register_type::>() .register_type::>() .register_type::() - .register_type::() .add_systems( First, (time_system, virtual_time_system.after(time_system)).in_set(TimeSystem), @@ -69,7 +68,7 @@ impl Plugin for TimePlugin { #[cfg(feature = "bevy_ci_testing")] if let Some(ci_testing_config) = app .world - .get_resource::() + .get_resource::() { if let Some(frame_time) = ci_testing_config.frame_time { app.world diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index 1a2f0d4c8abfb..8cbad487ba5e9 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -1,7 +1,7 @@ //! Extension to [`EntityCommands`] to modify `bevy_hierarchy` hierarchies //! while preserving [`GlobalTransform`]. -use bevy_ecs::{prelude::Entity, system::Command, system::EntityCommands, world::World}; +use bevy_ecs::{prelude::Entity, system::EntityCommands, world::Command, world::World}; use bevy_hierarchy::{PushChild, RemoveParent}; use crate::{GlobalTransform, Transform}; diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 8f6bac916a739..bdbce91e941bb 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -182,7 +182,7 @@ unsafe fn propagate_recursive( mod test { use bevy_app::prelude::*; use bevy_ecs::prelude::*; - use bevy_ecs::system::CommandQueue; + use bevy_ecs::world::CommandQueue; use bevy_math::{vec3, Vec3}; use bevy_tasks::{ComputeTaskPool, TaskPool}; diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 3f2649257755f..b83a227e99d8d 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -19,7 +19,6 @@ bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } bevy_input = { path = "../bevy_input", version = "0.14.0-dev" } -bevy_log = { path = "../bevy_log", version = "0.14.0-dev" } bevy_math = { path = "../bevy_math", version = "0.14.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [ "bevy", @@ -36,9 +35,11 @@ taffy = { version = "0.3.10" } serde = { version = "1", features = ["derive"], optional = true } bytemuck = { version = "1.5", features = ["derive"] } thiserror = "1.0.0" +nonmax = "0.5" +smallvec = "1.11" [features] -serialize = ["serde"] +serialize = ["serde", "smallvec/serde"] [lints] workspace = true diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 6becec74be2cc..08d488de34bb9 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -12,10 +12,11 @@ use bevy_math::{Rect, Vec2}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::ViewVisibility}; use bevy_transform::components::GlobalTransform; - -use bevy_utils::{smallvec::SmallVec, HashMap}; +use bevy_utils::HashMap; use bevy_window::{PrimaryWindow, Window}; +use smallvec::SmallVec; + #[cfg(feature = "serialize")] use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; diff --git a/crates/bevy_ui/src/layout/debug.rs b/crates/bevy_ui/src/layout/debug.rs index 37fb5c2b88845..8d5f27e169eca 100644 --- a/crates/bevy_ui/src/layout/debug.rs +++ b/crates/bevy_ui/src/layout/debug.rs @@ -25,7 +25,7 @@ pub fn print_ui_layout_tree(ui_surface: &UiSurface) { &mut out, ); } - bevy_log::info!("Layout tree for camera entity: {entity:?}\n{out}"); + bevy_utils::tracing::info!("Layout tree for camera entity: {entity:?}\n{out}"); } } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index df3ac93372bb6..6b7940e1484ec 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -2,21 +2,20 @@ mod convert; pub mod debug; use crate::{ContentSize, DefaultUiCamera, Node, Outline, Style, TargetCamera, UiScale}; -use bevy_ecs::entity::EntityHashMap; use bevy_ecs::{ change_detection::{DetectChanges, DetectChangesMut}, - entity::Entity, + entity::{Entity, EntityHashMap}, event::EventReader, query::{With, Without}, removal_detection::RemovedComponents, - system::{Query, Res, ResMut, Resource}, + system::{Query, Res, ResMut, Resource, SystemParam}, world::Ref, }; use bevy_hierarchy::{Children, Parent}; -use bevy_log::warn; use bevy_math::{UVec2, Vec2}; use bevy_render::camera::{Camera, NormalizedRenderTarget}; use bevy_transform::components::Transform; +use bevy_utils::tracing::warn; use bevy_utils::{default, HashMap, HashSet}; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; use std::fmt; @@ -53,6 +52,7 @@ struct RootNodePair { #[derive(Resource)] pub struct UiSurface { entity_to_taffy: EntityHashMap, + camera_entity_to_taffy: EntityHashMap, camera_roots: EntityHashMap>, taffy: Taffy, } @@ -79,6 +79,7 @@ impl Default for UiSurface { taffy.disable_rounding(); Self { entity_to_taffy: Default::default(), + camera_entity_to_taffy: Default::default(), camera_roots: Default::default(), taffy, } @@ -167,6 +168,10 @@ without UI components as a child of an entity with UI components, results may be ..default() }; + let camera_node = *self + .camera_entity_to_taffy + .entry(camera_id) + .or_insert_with(|| self.taffy.new_leaf(viewport_style.clone()).unwrap()); let existing_roots = self.camera_roots.entry(camera_id).or_default(); let mut new_roots = Vec::new(); for entity in children { @@ -181,24 +186,16 @@ without UI components as a child of an entity with UI components, results may be self.taffy.remove_child(previous_parent, node).unwrap(); } + self.taffy.add_child(camera_node, node).unwrap(); + RootNodePair { - implicit_viewport_node: self - .taffy - .new_with_children(viewport_style.clone(), &[node]) - .unwrap(), + implicit_viewport_node: camera_node, user_root_node: node, } }); new_roots.push(root_node); } - // Cleanup the implicit root nodes of any user root nodes that have been removed - for old_root in existing_roots { - if !new_roots.contains(old_root) { - self.taffy.remove(old_root.implicit_viewport_node).unwrap(); - } - } - self.camera_roots.insert(camera_id, new_roots); } @@ -219,6 +216,15 @@ without UI components as a child of an entity with UI components, results may be } } + /// Removes each camera entity from the internal map and then removes their associated node from taffy + pub fn remove_camera_entities(&mut self, entities: impl IntoIterator) { + for entity in entities { + if let Some(node) = self.camera_entity_to_taffy.remove(&entity) { + self.taffy.remove(node).unwrap(); + } + } + } + /// Removes each entity from the internal map and then removes their associated node from taffy pub fn remove_entities(&mut self, entities: impl IntoIterator) { for entity in entities { @@ -253,6 +259,14 @@ pub enum LayoutError { TaffyError(#[from] taffy::error::TaffyError), } +#[derive(SystemParam)] +pub struct UiLayoutSystemRemovedComponentParam<'w, 's> { + removed_cameras: RemovedComponents<'w, 's, Camera>, + removed_children: RemovedComponents<'w, 's, Children>, + removed_content_sizes: RemovedComponents<'w, 's, ContentSize>, + removed_nodes: RemovedComponents<'w, 's, Node>, +} + /// Updates the UI's layout tree, computes the new layout geometry and then updates the sizes and transforms of all the UI nodes. #[allow(clippy::too_many_arguments)] pub fn ui_layout_system( @@ -268,9 +282,7 @@ pub fn ui_layout_system( mut measure_query: Query<(Entity, &mut ContentSize)>, children_query: Query<(Entity, Ref), With>, just_children_query: Query<&Children>, - mut removed_children: RemovedComponents, - mut removed_content_sizes: RemovedComponents, - mut removed_nodes: RemovedComponents, + mut removed_components: UiLayoutSystemRemovedComponentParam, mut node_transform_query: Query<(&mut Node, &mut Transform)>, ) { struct CameraLayoutInfo { @@ -357,7 +369,7 @@ pub fn ui_layout_system( scale_factor_events.clear(); // When a `ContentSize` component is removed from an entity, we need to remove the measure from the corresponding taffy node. - for entity in removed_content_sizes.read() { + for entity in removed_components.removed_content_sizes.read() { ui_surface.try_remove_measure(entity); } for (entity, mut content_size) in &mut measure_query { @@ -367,15 +379,24 @@ pub fn ui_layout_system( } // clean up removed nodes - ui_surface.remove_entities(removed_nodes.read()); + ui_surface.remove_entities(removed_components.removed_nodes.read()); + + // clean up removed cameras + ui_surface.remove_camera_entities(removed_components.removed_cameras.read()); // update camera children - for (camera_id, CameraLayoutInfo { root_nodes, .. }) in &camera_layout_info { - ui_surface.set_camera_children(*camera_id, root_nodes.iter().cloned()); + for (camera_id, _) in cameras.iter() { + let root_nodes = + if let Some(CameraLayoutInfo { root_nodes, .. }) = camera_layout_info.get(&camera_id) { + root_nodes.iter().cloned() + } else { + [].iter().cloned() + }; + ui_surface.set_camera_children(camera_id, root_nodes); } // update and remove children - for entity in removed_children.read() { + for entity in removed_components.removed_children.read() { ui_surface.try_remove_children(entity); } for (entity, children) in &children_query { @@ -460,7 +481,7 @@ pub fn resolve_outlines_system( ) { let viewport_size = primary_window .get_single() - .map(|window| Vec2::new(window.resolution.width(), window.resolution.height())) + .map(|window| window.size()) .unwrap_or(Vec2::ZERO) / ui_scale.0; @@ -521,17 +542,20 @@ mod tests { use bevy_core_pipeline::core_2d::Camera2dBundle; use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; + use bevy_ecs::prelude::{Commands, Component, In, Query, With}; use bevy_ecs::schedule::apply_deferred; use bevy_ecs::schedule::IntoSystemConfigs; use bevy_ecs::schedule::Schedule; + use bevy_ecs::system::RunSystemOnce; use bevy_ecs::world::World; use bevy_hierarchy::despawn_with_children_recursive; use bevy_hierarchy::BuildWorldChildren; use bevy_hierarchy::Children; - use bevy_math::vec2; use bevy_math::Vec2; + use bevy_math::{vec2, UVec2}; use bevy_render::camera::ManualTextureViews; use bevy_render::camera::OrthographicProjection; + use bevy_render::prelude::Camera; use bevy_render::texture::Image; use bevy_utils::prelude::default; use bevy_utils::HashMap; @@ -657,6 +681,54 @@ mod tests { assert!(ui_surface.entity_to_taffy.is_empty()); } + #[test] + fn ui_surface_tracks_camera_entities() { + let (mut world, mut ui_schedule) = setup_ui_test_world(); + + // despawn all cameras so we can reset ui_surface back to a fresh state + let camera_entities = world + .query_filtered::>() + .iter(&world) + .collect::>(); + for camera_entity in camera_entities { + world.despawn(camera_entity); + } + + ui_schedule.run(&mut world); + + // no UI entities in world, none in UiSurface + let ui_surface = world.resource::(); + assert!(ui_surface.camera_entity_to_taffy.is_empty()); + + // respawn camera + let camera_entity = world.spawn(Camera2dBundle::default()).id(); + + let ui_entity = world + .spawn((NodeBundle::default(), TargetCamera(camera_entity))) + .id(); + + // `ui_layout_system` should map `camera_entity` to a ui node in `UiSurface::camera_entity_to_taffy` + ui_schedule.run(&mut world); + + let ui_surface = world.resource::(); + assert!(ui_surface + .camera_entity_to_taffy + .contains_key(&camera_entity)); + assert_eq!(ui_surface.camera_entity_to_taffy.len(), 1); + + world.despawn(ui_entity); + world.despawn(camera_entity); + + // `ui_layout_system` should remove `camera_entity` from `UiSurface::camera_entity_to_taffy` + ui_schedule.run(&mut world); + + let ui_surface = world.resource::(); + assert!(!ui_surface + .camera_entity_to_taffy + .contains_key(&camera_entity)); + assert!(ui_surface.camera_entity_to_taffy.is_empty()); + } + #[test] #[should_panic] fn despawning_a_ui_entity_should_remove_its_corresponding_ui_node() { @@ -785,6 +857,149 @@ mod tests { assert!(ui_surface.entity_to_taffy.is_empty()); } + #[test] + fn ui_node_should_properly_update_when_changing_target_camera() { + #[derive(Component)] + struct MovingUiNode; + + fn update_camera_viewports( + primary_window_query: Query<&Window, With>, + mut cameras: Query<&mut Camera>, + ) { + let primary_window = primary_window_query + .get_single() + .expect("missing primary window"); + let camera_count = cameras.iter().len(); + for (camera_index, mut camera) in cameras.iter_mut().enumerate() { + let viewport_width = + primary_window.resolution.physical_width() / camera_count as u32; + let viewport_height = primary_window.resolution.physical_height(); + let physical_position = UVec2::new(viewport_width * camera_index as u32, 0); + let physical_size = UVec2::new(viewport_width, viewport_height); + camera.viewport = Some(bevy_render::camera::Viewport { + physical_position, + physical_size, + ..default() + }); + } + } + + fn move_ui_node( + In(pos): In, + mut commands: Commands, + cameras: Query<(Entity, &Camera)>, + moving_ui_query: Query>, + ) { + let (target_camera_entity, _) = cameras + .iter() + .find(|(_, camera)| { + let Some(logical_viewport_rect) = camera.logical_viewport_rect() else { + panic!("missing logical viewport") + }; + // make sure cursor is in viewport and that viewport has at least 1px of size + logical_viewport_rect.contains(pos) + && logical_viewport_rect.max.cmpge(Vec2::splat(0.)).any() + }) + .expect("cursor position outside of camera viewport"); + for moving_ui_entity in moving_ui_query.iter() { + commands + .entity(moving_ui_entity) + .insert(TargetCamera(target_camera_entity)) + .insert(Style { + position_type: PositionType::Absolute, + top: Val::Px(pos.y), + left: Val::Px(pos.x), + ..default() + }); + } + } + + fn do_move_and_test( + world: &mut World, + ui_schedule: &mut Schedule, + new_pos: Vec2, + expected_camera_entity: &Entity, + ) { + world.run_system_once_with(new_pos, move_ui_node); + ui_schedule.run(world); + let (ui_node_entity, TargetCamera(target_camera_entity)) = world + .query_filtered::<(Entity, &TargetCamera), With>() + .get_single(world) + .expect("missing MovingUiNode"); + assert_eq!(expected_camera_entity, target_camera_entity); + let ui_surface = world.resource::(); + + let layout = ui_surface + .get_layout(ui_node_entity) + .expect("failed to get layout"); + + // negative test for #12255 + assert_eq!(Vec2::new(layout.location.x, layout.location.y), new_pos); + } + + fn get_taffy_node_count(world: &World) -> usize { + world.resource::().taffy.total_node_count() + } + + let (mut world, mut ui_schedule) = setup_ui_test_world(); + + world.spawn(Camera2dBundle { + camera: Camera { + order: 1, + ..default() + }, + ..default() + }); + + world.spawn(( + NodeBundle { + style: Style { + position_type: PositionType::Absolute, + top: Val::Px(0.), + left: Val::Px(0.), + ..default() + }, + ..default() + }, + MovingUiNode, + )); + + ui_schedule.run(&mut world); + + let pos_inc = Vec2::splat(1.); + let total_cameras = world.query::<&Camera>().iter(&world).len(); + // add total cameras - 1 (the assumed default) to get an idea for how many nodes we should expect + let expected_max_taffy_node_count = get_taffy_node_count(&world) + total_cameras - 1; + + world.run_system_once(update_camera_viewports); + + ui_schedule.run(&mut world); + + let viewport_rects = world + .query::<(Entity, &Camera)>() + .iter(&world) + .map(|(e, c)| (e, c.logical_viewport_rect().expect("missing viewport"))) + .collect::>(); + + for (camera_entity, viewport) in viewport_rects.iter() { + let target_pos = viewport.min + pos_inc; + do_move_and_test(&mut world, &mut ui_schedule, target_pos, camera_entity); + } + + // reverse direction + let mut viewport_rects = viewport_rects.clone(); + viewport_rects.reverse(); + for (camera_entity, viewport) in viewport_rects.iter() { + let target_pos = viewport.max - pos_inc; + do_move_and_test(&mut world, &mut ui_schedule, target_pos, camera_entity); + } + + let current_taffy_node_count = get_taffy_node_count(&world); + if current_taffy_node_count > expected_max_taffy_node_count { + panic!("extra taffy nodes detected: current: {current_taffy_node_count} max expected: {expected_max_taffy_node_count}"); + } + } + #[test] fn ui_node_should_be_set_to_its_content_size() { let (mut world, mut ui_schedule) = setup_ui_test_world(); diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 2e4306ee598c0..17daf47fb1c74 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -97,37 +97,19 @@ impl Plugin for UiPlugin { app.init_resource::() .init_resource::() .init_resource::() - .register_type::() - .register_type::() - .register_type::() .register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() .register_type::() - .register_type::() - .register_type::() - .register_type::() .register_type::() - .register_type::() - .register_type::() - .register_type::() .register_type::() - .register_type::() - .register_type::() - .register_type::() .register_type::() - .register_type::() .register_type:: diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index ace3e2fe5a1bc..f175167130399 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -93,7 +93,7 @@ fn update_winit( pub(crate) mod test_setup { use crate::ExampleMode; use bevy::{ - color::palettes::basic::{GREEN, YELLOW}, + color::palettes::basic::{LIME, YELLOW}, prelude::*, window::RequestRedraw, }; @@ -181,7 +181,7 @@ pub(crate) mod test_setup { ), TextSection::from_style(TextStyle { font_size: 50.0, - color: GREEN.into(), + color: LIME.into(), ..default() }), TextSection::new( diff --git a/examples/window/window_settings.rs b/examples/window/window_settings.rs index 31ea347e64db6..2e509e2fba7eb 100644 --- a/examples/window/window_settings.rs +++ b/examples/window/window_settings.rs @@ -17,6 +17,8 @@ fn main() { name: Some("bevy.app".into()), resolution: (500., 300.).into(), present_mode: PresentMode::AutoVsync, + // Tells wasm to resize the window according to the available canvas + fit_canvas_to_parent: true, // Tells wasm not to override default event handling, like F5, Ctrl+R etc. prevent_default_event_handling: false, window_theme: Some(WindowTheme::Dark), diff --git a/tools/build-wasm-example/src/main.rs b/tools/build-wasm-example/src/main.rs index f121bd63c6d48..5ae1827db0c72 100644 --- a/tools/build-wasm-example/src/main.rs +++ b/tools/build-wasm-example/src/main.rs @@ -73,13 +73,10 @@ fn main() { parameters.push("--features"); parameters.push(&features_string); } - let mut cmd = cmd!( + let cmd = cmd!( sh, "cargo build {parameters...} --profile release --target wasm32-unknown-unknown --example {example}" ); - if matches!(cli.api, WebApi::Webgpu) { - cmd = cmd.env("RUSTFLAGS", "--cfg=web_sys_unstable_apis"); - } cmd.run().expect("Error building example"); cmd!( diff --git a/tools/ci/src/main.rs b/tools/ci/src/main.rs index 4f6886ad61cb0..617f96f7f7da8 100644 --- a/tools/ci/src/main.rs +++ b/tools/ci/src/main.rs @@ -1,11 +1,12 @@ //! CI script used for Bevy. -use xshell::{cmd, Shell}; - use bitflags::bitflags; +use core::panic; +use std::collections::BTreeMap; +use xshell::{cmd, Cmd, Shell}; bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] struct Check: u32 { const FORMAT = 0b00000001; const CLIPPY = 0b00000010; @@ -20,6 +21,29 @@ bitflags! { } } +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + struct Flag: u32 { + const KEEP_GOING = 0b00000001; + } +} + +// None of the CI tests require any information at runtime other than the options that have been set, +// which is why all of these are 'static; we could easily update this to use more flexible types. +struct CITest<'a> { + /// The command to execute + command: Cmd<'a>, + + /// The message to display if the test command fails + failure_message: &'static str, + + /// The subdirectory path to run the test command within + subdir: Option<&'static str>, + + /// Environment variables that need to be set before the test runs + env_vars: Vec<(&'static str, &'static str)>, +} + fn main() { // When run locally, results may differ from actual CI runs triggered by // .github/workflows/ci.yml @@ -44,125 +68,300 @@ fn main() { ("doc-test", Check::DOC_TEST), ]; - let what_to_run = if let Some(arg) = std::env::args().nth(1).as_deref() { - if let Some((_, check)) = arguments.iter().find(|(str, _)| *str == arg) { - *check - } else { - println!( - "Invalid argument: {arg:?}.\nEnter one of: {}.", - arguments[1..] - .iter() - .map(|(s, _)| s) - .fold(arguments[0].0.to_owned(), |c, v| c + ", " + v) - ); - return; + let flag_arguments = [("--keep-going", Flag::KEEP_GOING)]; + + // Parameters are parsed disregarding their order. Note that the first arg is generally the name of + // the executable, so it is ignored. Any parameter may either be a flag or the name of a battery of tests + // to include. + let (mut checks, mut flags) = (Check::empty(), Flag::empty()); + for arg in std::env::args().skip(1) { + if let Some((_, flag)) = flag_arguments.iter().find(|(flag_arg, _)| *flag_arg == arg) { + flags.insert(*flag); + continue; } - } else { - Check::all() - }; + if let Some((_, check)) = arguments.iter().find(|(check_arg, _)| *check_arg == arg) { + // Note that this actually adds all of the constituent checks to the test suite. + checks.insert(*check); + continue; + } + // We encountered an invalid parameter: + println!( + "Invalid argument: {arg:?}.\n\ + Valid parameters: {}.", + arguments[1..] + .iter() + .map(|(s, _)| s) + .chain(flag_arguments.iter().map(|(s, _)| s)) + .fold(arguments[0].0.to_owned(), |c, v| c + ", " + v) + ); + return; + } + + // If no checks are specified, we run every check + if checks.is_empty() { + checks = Check::all(); + } let sh = Shell::new().unwrap(); - if what_to_run.contains(Check::FORMAT) { + // Each check contains a 'battery' (vector) that can include more than one command, but almost all of them + // just contain a single command. + let mut test_suite: BTreeMap> = BTreeMap::new(); + + if checks.contains(Check::FORMAT) { // See if any code needs to be formatted - cmd!(sh, "cargo fmt --all -- --check") - .run() - .expect("Please run 'cargo fmt --all' to format your code."); + test_suite.insert( + Check::FORMAT, + vec![CITest { + command: cmd!(sh, "cargo fmt --all -- --check"), + failure_message: "Please run 'cargo fmt --all' to format your code.", + subdir: None, + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::CLIPPY) { + if checks.contains(Check::CLIPPY) { // See if clippy has any complaints. // - Type complexity must be ignored because we use huge templates for queries - cmd!( - sh, - "cargo clippy --workspace --all-targets --all-features -- -Dwarnings" - ) - .run() - .expect("Please fix clippy errors in output above."); - } - - if what_to_run.contains(Check::COMPILE_FAIL) { - { - // ECS Compile Fail Tests - // Run UI tests (they do not get executed with the workspace tests) - // - See crates/bevy_ecs_compile_fail_tests/README.md - let _subdir = sh.push_dir("crates/bevy_ecs_compile_fail_tests"); - cmd!(sh, "cargo test --target-dir ../../target") - .run() - .expect("Compiler errors of the ECS compile fail tests seem to be different than expected! Check locally and compare rust versions."); - } - { - // Reflect Compile Fail Tests - // Run tests (they do not get executed with the workspace tests) - // - See crates/bevy_reflect_compile_fail_tests/README.md - let _subdir = sh.push_dir("crates/bevy_reflect_compile_fail_tests"); - cmd!(sh, "cargo test --target-dir ../../target") - .run() - .expect("Compiler errors of the Reflect compile fail tests seem to be different than expected! Check locally and compare rust versions."); - } - { - // Macro Compile Fail Tests - // Run tests (they do not get executed with the workspace tests) - // - See crates/bevy_macros_compile_fail_tests/README.md - let _subdir = sh.push_dir("crates/bevy_macros_compile_fail_tests"); - cmd!(sh, "cargo test --target-dir ../../target") - .run() - .expect("Compiler errors of the macros compile fail tests seem to be different than expected! Check locally and compare rust versions."); + test_suite.insert( + Check::CLIPPY, + vec![CITest { + command: cmd!( + sh, + "cargo clippy --workspace --all-targets --all-features -- -Dwarnings" + ), + failure_message: "Please fix clippy errors in output above.", + subdir: None, + env_vars: vec![], + }], + ); + } + + if checks.contains(Check::COMPILE_FAIL) { + let mut args = vec!["--target-dir", "../../target"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--no-fail-fast"); } + + // ECS Compile Fail Tests + // Run UI tests (they do not get executed with the workspace tests) + // - See crates/bevy_ecs_compile_fail_tests/README.md + + // (These must be cloned because of move semantics in `cmd!`) + let args_clone = args.clone(); + + test_suite.insert(Check::COMPILE_FAIL, vec![CITest { + command: cmd!(sh, "cargo test {args_clone...}"), + failure_message: "Compiler errors of the ECS compile fail tests seem to be different than expected! Check locally and compare rust versions.", + subdir: Some("crates/bevy_ecs_compile_fail_tests"), + env_vars: vec![], + }]); + + // Reflect Compile Fail Tests + // Run tests (they do not get executed with the workspace tests) + // - See crates/bevy_reflect_compile_fail_tests/README.md + let args_clone = args.clone(); + + test_suite.entry(Check::COMPILE_FAIL).and_modify(|tests| tests.push( CITest { + command: cmd!(sh, "cargo test {args_clone...}"), + failure_message: "Compiler errors of the Reflect compile fail tests seem to be different than expected! Check locally and compare rust versions.", + subdir: Some("crates/bevy_reflect_compile_fail_tests"), + env_vars: vec![], + })); + + // Macro Compile Fail Tests + // Run tests (they do not get executed with the workspace tests) + // - See crates/bevy_macros_compile_fail_tests/README.md + let args_clone = args.clone(); + + test_suite.entry(Check::COMPILE_FAIL).and_modify(|tests| tests.push( CITest { + command: cmd!(sh, "cargo test {args_clone...}"), + failure_message: "Compiler errors of the macros compile fail tests seem to be different than expected! Check locally and compare rust versions.", + subdir: Some("crates/bevy_macros_compile_fail_tests"), + env_vars: vec![], + })); } - if what_to_run.contains(Check::TEST) { + if checks.contains(Check::TEST) { // Run tests (except doc tests and without building examples) - cmd!(sh, "cargo test --workspace --lib --bins --tests --benches") - .run() - .expect("Please fix failing tests in output above."); + let mut args = vec!["--workspace", "--lib", "--bins", "--tests", "--benches"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--no-fail-fast"); + } + + test_suite.insert( + Check::TEST, + vec![CITest { + command: cmd!(sh, "cargo test {args...}"), + failure_message: "Please fix failing tests in output above.", + subdir: None, + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::DOC_TEST) { + if checks.contains(Check::DOC_TEST) { // Run doc tests - cmd!(sh, "cargo test --workspace --doc") - .run() - .expect("Please fix failing doc-tests in output above."); + let mut args = vec!["--workspace", "--doc"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--no-fail-fast"); + } + + test_suite.insert( + Check::DOC_TEST, + vec![CITest { + command: cmd!(sh, "cargo test {args...}"), + failure_message: "Please fix failing doc-tests in output above.", + subdir: None, + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::DOC_CHECK) { + if checks.contains(Check::DOC_CHECK) { // Check that building docs work and does not emit warnings - std::env::set_var("RUSTDOCFLAGS", "-D warnings"); - cmd!( - sh, - "cargo doc --workspace --all-features --no-deps --document-private-items" - ) - .run() - .expect("Please fix doc warnings in output above."); + let mut args = vec![ + "--workspace", + "--all-features", + "--no-deps", + "--document-private-items", + ]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::DOC_CHECK, + vec![CITest { + command: cmd!(sh, "cargo doc {args...}"), + failure_message: "Please fix doc warnings in output above.", + subdir: None, + env_vars: vec![("RUSTDOCFLAGS", "-D warnings")], + }], + ); } - if what_to_run.contains(Check::BENCH_CHECK) { - let _subdir = sh.push_dir("benches"); + if checks.contains(Check::BENCH_CHECK) { // Check that benches are building - cmd!(sh, "cargo check --benches --target-dir ../target") - .run() - .expect("Failed to check the benches."); + let mut args = vec!["--benches", "--target-dir", "../target"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::BENCH_CHECK, + vec![CITest { + command: cmd!(sh, "cargo check {args...}"), + failure_message: "Failed to check the benches.", + subdir: Some("benches"), + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::EXAMPLE_CHECK) { + if checks.contains(Check::EXAMPLE_CHECK) { // Build examples and check they compile - cmd!(sh, "cargo check --workspace --examples") - .run() - .expect("Please fix compiler errors for examples in output above."); + let mut args = vec!["--workspace", "--examples"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::EXAMPLE_CHECK, + vec![CITest { + command: cmd!(sh, "cargo check {args...}"), + failure_message: "Please fix compiler errors for examples in output above.", + subdir: None, + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::COMPILE_CHECK) { + if checks.contains(Check::COMPILE_CHECK) { // Build bevy and check that it compiles - cmd!(sh, "cargo check --workspace") - .run() - .expect("Please fix compiler errors in output above."); + let mut args = vec!["--workspace"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::COMPILE_CHECK, + vec![CITest { + command: cmd!(sh, "cargo check {args...}"), + failure_message: "Please fix compiler errors in output above.", + subdir: None, + env_vars: vec![], + }], + ); } - if what_to_run.contains(Check::CFG_CHECK) { + if checks.contains(Check::CFG_CHECK) { // Check cfg and imports - std::env::set_var("RUSTFLAGS", "-D warnings"); - cmd!(sh, "cargo +nightly check -Zcheck-cfg --workspace") - .run() - .expect("Please fix failing cfg checks in output above."); + let mut args = vec!["-Zcheck-cfg", "--workspace"]; + if flags.contains(Flag::KEEP_GOING) { + args.push("--keep-going"); + } + + test_suite.insert( + Check::CFG_CHECK, + vec![CITest { + command: cmd!(sh, "cargo +nightly check {args...}"), + failure_message: "Please fix failing cfg checks in output above.", + subdir: None, + env_vars: vec![("RUSTFLAGS", "-D warnings")], + }], + ); + } + + // Actually run the tests: + + let mut failed_checks: Check = Check::empty(); + let mut failure_message: String = String::new(); + + // In KEEP_GOING-mode, we save all errors until the end; otherwise, we just + // panic with the given message for test failure. + fn fail( + current_check: Check, + failure_message: &'static str, + failed_checks: &mut Check, + existing_fail_message: &mut String, + flags: &Flag, + ) { + if flags.contains(Flag::KEEP_GOING) { + failed_checks.insert(current_check); + if !existing_fail_message.is_empty() { + existing_fail_message.push('\n'); + } + existing_fail_message.push_str(failure_message); + } else { + panic!("{failure_message}"); + } + } + + for (check, battery) in test_suite { + for ci_test in battery { + // If the CI test is to be executed in a subdirectory, we move there before running the command + let _subdir_hook = ci_test.subdir.map(|path| sh.push_dir(path)); + + // Actually run the test, setting environment variables temporarily + if ci_test.command.envs(ci_test.env_vars).run().is_err() { + fail( + check, + ci_test.failure_message, + &mut failed_checks, + &mut failure_message, + &flags, + ); + } + // ^ This must run while `_subdir_hook` is in scope; it is dropped on loop iteration. + } + } + + if !failed_checks.is_empty() { + panic!( + "One or more CI checks failed.\n\ + {failure_message}" + ); } } diff --git a/tools/example-showcase/window-settings-wasm.patch b/tools/example-showcase/window-settings-wasm.patch index 02e8965901869..5fcb1fba9556c 100644 --- a/tools/example-showcase/window-settings-wasm.patch +++ b/tools/example-showcase/window-settings-wasm.patch @@ -1,11 +1,13 @@ diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs -index 7b5c75d38..8e9404b93 100644 +index 87cdfb050..1d87a0bf5 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs -@@ -245,8 +245,8 @@ impl Default for Window { +@@ -266,9 +266,9 @@ impl Default for Window { transparent: false, focused: true, window_level: Default::default(), +- fit_canvas_to_parent: false, ++ fit_canvas_to_parent: true, prevent_default_event_handling: true, - canvas: None, + canvas: Some("#bevy".to_string()), diff --git a/tools/publish.sh b/tools/publish.sh index 3bb2f9b09cf24..d9b63ddde835b 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -41,6 +41,7 @@ crates=( bevy_a11y bevy_ui bevy_winit + bevy_dev_tools bevy_internal bevy_dylib bevy_color diff --git a/typos.toml b/typos.toml index 4e77918adcdfd..066195a7cf714 100644 --- a/typos.toml +++ b/typos.toml @@ -8,7 +8,6 @@ extend-exclude = [ # and the value is the correct word. If the key and value are the same, the # word is treated as always correct. If the value is an empty string, the word # is treated as always incorrect. -# the toi of the mountain # Match Whole Word - Case Sensitive [default.extend-identifiers]