From 512b7463a326d0048f16c2956d502395c5cb3cba Mon Sep 17 00:00:00 2001 From: James Liu Date: Wed, 6 Mar 2024 18:30:15 -0800 Subject: [PATCH 01/13] Disentangle bevy_utils/bevy_core's reexported dependencies (#12313) # Objective Make bevy_utils less of a compilation bottleneck. Tackle #11478. ## Solution * Move all of the directly reexported dependencies and move them to where they're actually used. * Remove the UUID utilities that have gone unused since `TypePath` took over for `TypeUuid`. * There was also a extraneous bytemuck dependency on `bevy_core` that has not been used for a long time (since `encase` became the primary way to prepare GPU buffers). * Remove the `all_tuples` macro reexport from bevy_ecs since it's accessible from `bevy_utils`. --- ## Changelog Removed: Many of the reexports from bevy_utils (petgraph, uuid, nonmax, smallvec, and thiserror). Removed: bevy_core's reexports of bytemuck. ## Migration Guide bevy_utils' reexports of petgraph, uuid, nonmax, smallvec, and thiserror have been removed. bevy_core' reexports of bytemuck's types has been removed. Add them as dependencies in your own crate instead. --- Cargo.toml | 1 + crates/bevy_animation/Cargo.toml | 2 +- crates/bevy_animation/src/lib.rs | 3 +- crates/bevy_app/Cargo.toml | 2 +- crates/bevy_app/src/app.rs | 3 +- crates/bevy_app/src/plugin.rs | 3 +- crates/bevy_asset/Cargo.toml | 5 ++- crates/bevy_asset/src/assets.rs | 3 +- crates/bevy_asset/src/handle.rs | 3 +- crates/bevy_asset/src/id.rs | 2 +- crates/bevy_core/Cargo.toml | 2 +- crates/bevy_core/src/lib.rs | 1 - crates/bevy_core_pipeline/Cargo.toml | 1 + crates/bevy_core_pipeline/src/core_2d/mod.rs | 3 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 3 +- crates/bevy_core_pipeline/src/deferred/mod.rs | 2 +- crates/bevy_core_pipeline/src/prepass/mod.rs | 2 +- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/lib.rs | 2 -- crates/bevy_ecs/src/schedule/graph_utils.rs | 6 ++-- crates/bevy_ecs/src/schedule/schedule.rs | 5 ++- crates/bevy_ecs/src/schedule/stepping.rs | 2 +- crates/bevy_gizmos/Cargo.toml | 3 +- crates/bevy_gizmos/src/lib.rs | 2 +- crates/bevy_gltf/Cargo.toml | 1 + crates/bevy_gltf/src/loader.rs | 6 ++-- crates/bevy_hierarchy/Cargo.toml | 3 ++ crates/bevy_hierarchy/src/child_builder.rs | 4 +-- .../bevy_hierarchy/src/components/children.rs | 2 +- crates/bevy_pbr/Cargo.toml | 1 + crates/bevy_pbr/src/render/light.rs | 6 ++-- crates/bevy_reflect/Cargo.toml | 7 ++-- crates/bevy_reflect/src/impls/smallvec.rs | 3 +- crates/bevy_reflect/src/impls/uuid.rs | 2 +- crates/bevy_reflect/src/lib.rs | 2 +- crates/bevy_render/Cargo.toml | 3 +- crates/bevy_render/src/batching/mod.rs | 2 +- crates/bevy_render/src/mesh/mesh/mod.rs | 5 ++- crates/bevy_render/src/render_asset.rs | 3 +- crates/bevy_render/src/render_phase/mod.rs | 2 +- .../render_resource/batched_uniform_buffer.rs | 2 +- .../src/render_resource/bind_group.rs | 2 +- .../src/render_resource/buffer_vec.rs | 2 +- .../src/render_resource/gpu_array_buffer.rs | 2 +- .../bevy_render/src/renderer/graph_runner.rs | 6 ++-- .../src/texture/image_texture_conversion.rs | 2 +- crates/bevy_ui/Cargo.toml | 4 ++- crates/bevy_ui/src/focus.rs | 5 +-- crates/bevy_ui/src/render/render_pass.rs | 3 +- crates/bevy_ui/src/ui_node.rs | 3 +- crates/bevy_utils/Cargo.toml | 5 --- crates/bevy_utils/src/lib.rs | 11 ------ crates/bevy_utils/src/uuid.rs | 35 ------------------- examples/asset/asset_decompression.rs | 1 - examples/asset/custom_asset.rs | 1 - examples/asset/processing/asset_processing.rs | 2 +- examples/games/contributors.rs | 6 +--- 57 files changed, 81 insertions(+), 125 deletions(-) delete mode 100644 crates/bevy_utils/src/uuid.rs diff --git a/Cargo.toml b/Cargo.toml index 6b7994eae3aa2..6de2dccefc702 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -338,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" diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index d5892658270c1..e47fefb987848 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -26,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 c420e8d98abc5..08f10a167dfd2 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -20,8 +20,9 @@ use bevy_render::mesh::morph::MorphWeights; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_utils::hashbrown::HashMap; -use bevy_utils::{tracing::error, NoOpHash, Uuid}; +use bevy_utils::{tracing::error, NoOpHash}; use sha1_smol::Sha1; +use uuid::Uuid; #[allow(missing_docs)] pub mod prelude { diff --git a/crates/bevy_app/Cargo.toml b/crates/bevy_app/Cargo.toml index e42d986f1a669..e4f7649325776 100644 --- a/crates/bevy_app/Cargo.toml +++ b/crates/bevy_app/Cargo.toml @@ -26,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 f339b5d8002f5..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; 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 4dfdb2bb02cc9..ddec0a6b6a59b 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -21,7 +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_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" } @@ -37,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" } 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 1107cb424ad82..d14c325973c77 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -4,7 +4,7 @@ use crate::{ }; use bevy_ecs::prelude::*; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; -use bevy_utils::{get_short_name, Uuid}; +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. 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_core/Cargo.toml b/crates/bevy_core/Cargo.toml index cd769fcb0f36f..0c0c804bf7a26 100644 --- a/crates/bevy_core/Cargo.toml +++ b/crates/bevy_core/Cargo.toml @@ -24,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 3dc709a15e05d..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::*; diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 824e78ee89923..1b28c00f649c5 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -36,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/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 c796d32825b8e..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, 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/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_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index db804278b4b2f..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" 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/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_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index 205759c974ee2..a7ecaddaf0b8b 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -23,11 +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_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 134bfd15514fa..a27b91192a2a3 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -57,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, @@ -83,6 +82,7 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_utils::TypeIdMap; +use bytemuck::cast_slice; use config::{ DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig, }; diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index e1a29899db8de..22c5f49450f76 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -54,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 276eb9f8f677f..b59d6eaf1fff5 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -36,10 +36,7 @@ use bevy_scene::Scene; use bevy_tasks::IoTaskPool; use bevy_transform::components::Transform; use bevy_utils::tracing::{error, info_span, warn}; -use bevy_utils::{ - smallvec::{smallvec, SmallVec}, - HashMap, HashSet, -}; +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, diff --git a/crates/bevy_hierarchy/Cargo.toml b/crates/bevy_hierarchy/Cargo.toml index ede2cabe6054c..342126e3f1976 100644 --- a/crates/bevy_hierarchy/Cargo.toml +++ b/crates/bevy_hierarchy/Cargo.toml @@ -21,8 +21,11 @@ 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_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 b6ec13fe73372..f8d206dee5580 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -6,7 +6,7 @@ use bevy_ecs::{ 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,7 +696,7 @@ 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, 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_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/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 0b3c65ddfaf6c..061c34a8b2f3b 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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::*; 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/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/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 52eae900d6d67..2add0144f0d04 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -500,6 +500,7 @@ mod impls { mod smol_str; mod std; + #[cfg(feature = "uuid")] mod uuid; } @@ -1547,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(); diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 7e9012d99df3e..ca51b826c46c1 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -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. diff --git a/crates/bevy_render/src/batching/mod.rs b/crates/bevy_render/src/batching/mod.rs index 6f16617fe718a..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}, diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index ae57a9a297c4e..dc4549cd040b5 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -11,7 +11,6 @@ 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}, @@ -19,8 +18,8 @@ use bevy_ecs::system::{ }; use bevy_math::*; use bevy_reflect::Reflect; -use bevy_utils::tracing::error; -use bevy_utils::tracing::warn; +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/render_asset.rs b/crates/bevy_render/src/render_asset.rs index f4d6234aec5c3..0b1686fbce641 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -14,9 +14,10 @@ use bevy_reflect::{ 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 { 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 cb81152ee5147..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}; 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/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 1d314f0a1da7c..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; 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_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index a417a5bfac8e0..b83a227e99d8d 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -35,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/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index e0119509d7b27..1f3ffb0d20dea 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -14,7 +14,8 @@ use bevy_render::{ renderer::*, view::*, }; -use bevy_utils::{nonmax::NonMaxU32, FloatOrd}; +use bevy_utils::FloatOrd; +use nonmax::NonMaxU32; pub struct UiPassNode { ui_view_query: QueryState< diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index e8051aff651d6..36db16ca60614 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -9,8 +9,9 @@ use bevy_render::{ texture::Image, }; use bevy_transform::prelude::GlobalTransform; -use bevy_utils::{smallvec::SmallVec, warn_once}; +use bevy_utils::warn_once; use bevy_window::{PrimaryWindow, WindowRef}; +use smallvec::SmallVec; use std::num::{NonZeroI16, NonZeroU16}; use thiserror::Error; diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index d9165cb5bb927..88fe6dc0a574c 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -15,14 +15,9 @@ detailed_trace = [] ahash = "0.8.7" tracing = { version = "0.1", default-features = false, features = ["std"] } web-time = { version = "0.2" } -uuid = { version = "1.1", features = ["v4", "serde"] } hashbrown = { version = "0.14", features = ["serde"] } bevy_utils_proc_macros = { version = "0.14.0-dev", path = "macros" } -petgraph = "0.6" -thiserror = "1.0" thread_local = "1.0" -nonmax = "0.5" -smallvec = { version = "1.11", features = ["serde", "union", "const_generics"] } [dev-dependencies] static_assertions = "1.1.0" diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 66e7336a4236b..33f5312171041 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -15,8 +15,6 @@ pub use short_names::get_short_name; pub mod synccell; pub mod syncunsafecell; -pub mod uuid; - mod cow_arc; mod default; mod float_ord; @@ -24,7 +22,6 @@ pub mod intern; mod once; mod parallel_queue; -pub use crate::uuid::Uuid; pub use ahash::{AHasher, RandomState}; pub use bevy_utils_proc_macros::*; pub use cow_arc::*; @@ -32,17 +29,9 @@ pub use default::default; pub use float_ord::*; pub use hashbrown; pub use parallel_queue::*; -pub use petgraph; -pub use smallvec; -pub use thiserror; pub use tracing; pub use web_time::{Duration, Instant, SystemTime, SystemTimeError, TryFromFloatSecsError}; -#[allow(missing_docs)] -pub mod nonmax { - pub use nonmax::*; -} - use hashbrown::hash_map::RawEntryMut; use std::{ any::TypeId, diff --git a/crates/bevy_utils/src/uuid.rs b/crates/bevy_utils/src/uuid.rs deleted file mode 100644 index 3542c9411eee0..0000000000000 --- a/crates/bevy_utils/src/uuid.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! UUID utilities. -//! -//! This module re-exports the [`Uuid`] type from the [`uuid`] crate, -//! and provides additional utility functions. -//! -//! [`uuid`]: uuid - -pub use ::uuid::Uuid; - -/// Generates a new UUID from the given UUIDs `a` and `b`, -/// where the bytes are generated by a bitwise `a ^ b.rotate_right(1)`. -/// -/// The generated UUID will be a `UUIDv4` -/// (meaning that the bytes should be random, not e.g. derived from the system time). -#[allow(clippy::unusual_byte_groupings)] // unusual byte grouping is meant to signal the relevant bits -pub const fn generate_composite_uuid(a: Uuid, b: Uuid) -> Uuid { - let mut new = [0; 16]; - let mut i = 0; - while i < new.len() { - // rotating ensures different uuids for A> and B> because: A ^ (B ^ C) = B ^ (A ^ C) - // notice that you have to rotate the second parameter: A.rr ^ (B.rr ^ C) = B.rr ^ (A.rr ^ C) - // Solution: A ^ (B ^ C.rr).rr != B ^ (A ^ C.rr).rr - new[i] = a.as_bytes()[i] ^ b.as_bytes()[i].rotate_right(1); - - i += 1; - } - - // Version: the most significant 4 bits in the 6th byte: 11110000 - new[6] = new[6] & 0b0000_1111 | 0b0100_0000; // set version to v4 - - // Variant: the most significant 3 bits in the 8th byte: 11100000 - new[8] = new[8] & 0b000_11111 | 0b100_00000; // set variant to rfc4122 - - Uuid::from_bytes(new) -} diff --git a/examples/asset/asset_decompression.rs b/examples/asset/asset_decompression.rs index 1c72521b0eb7a..cd17a38a89a9e 100644 --- a/examples/asset/asset_decompression.rs +++ b/examples/asset/asset_decompression.rs @@ -1,6 +1,5 @@ //! Implements loader for a Gzip compressed asset. -use bevy::utils::thiserror; use bevy::{ asset::{ io::{Reader, VecReader}, diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs index f54a1d3a19e55..c10e297c380bd 100644 --- a/examples/asset/custom_asset.rs +++ b/examples/asset/custom_asset.rs @@ -1,6 +1,5 @@ //! Implements loader for a custom asset type. -use bevy::utils::thiserror; use bevy::{ asset::{io::Reader, ron, AssetLoader, AsyncReadExt, LoadContext}, prelude::*, diff --git a/examples/asset/processing/asset_processing.rs b/examples/asset/processing/asset_processing.rs index 640d0cdabf369..3f660ef85f500 100644 --- a/examples/asset/processing/asset_processing.rs +++ b/examples/asset/processing/asset_processing.rs @@ -12,7 +12,7 @@ use bevy::{ }, prelude::*, reflect::TypePath, - utils::{thiserror, BoxedFuture}, + utils::BoxedFuture, }; use serde::{Deserialize, Serialize}; use std::convert::Infallible; diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index 52dadf9766652..067f0bdf58b20 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -1,10 +1,6 @@ //! This example displays each contributor to the bevy source code as a bouncing bevy-ball. -use bevy::{ - math::bounding::Aabb2d, - prelude::*, - utils::{thiserror, HashMap}, -}; +use bevy::{math::bounding::Aabb2d, prelude::*, utils::HashMap}; use rand::{prelude::SliceRandom, Rng}; use std::{ env::VarError, From f67b17d80d410045f0fb0af668521e2f91569159 Mon Sep 17 00:00:00 2001 From: Antony Date: Thu, 7 Mar 2024 05:44:52 +0000 Subject: [PATCH 02/13] Use `.register_asset_source()` in `extra_asset_source` example (#12350) # Objective Fixes #12347. ## Solution Use `.register_asset_source()` in `extra_asset_source` example instead of writing part of the app state. --- examples/asset/extra_source.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/asset/extra_source.rs b/examples/asset/extra_source.rs index fca5d92796e90..04d860d7af115 100644 --- a/examples/asset/extra_source.rs +++ b/examples/asset/extra_source.rs @@ -2,29 +2,25 @@ //! This asset source exists in addition to the default asset source. use bevy::asset::{ - io::{AssetSourceBuilder, AssetSourceBuilders, AssetSourceId}, + io::{AssetSourceBuilder, AssetSourceId}, AssetPath, }; use bevy::prelude::*; use std::path::Path; fn main() { - let mut app = App::new(); - - // We add an extra asset source with the name "example_files" to the - // AssetSourceBuilders. - // This needs to be done before AssetPlugin finalizes building them - let mut sources = app - .world - .get_resource_or_insert_with::(default); - sources.insert( - "example_files", - AssetSourceBuilder::platform_default("examples/asset/files", None), - ); - - // DefaultPlugins contains AssetPlugin so it needs to be added to our App - // after inserting our new asset source - app.add_plugins(DefaultPlugins) + App::new() + // Add an extra asset source with the name "example_files" to + // AssetSourceBuilders. + // + // This must be done before AssetPlugin finalizes building assets. + .register_asset_source( + "example_files", + AssetSourceBuilder::platform_default("examples/asset/files", None), + ) + // DefaultPlugins contains AssetPlugin so it must be added to our App + // after inserting our new asset source. + .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .run(); } @@ -32,7 +28,7 @@ fn main() { fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2dBundle::default()); - // Now we can load the asset using our new asset source + // Now we can load the asset using our new asset source. // // The actual file path relative to workspace root is // "examples/asset/files/bevy_pixel_light.png". From 713d91b7218b2ec77cac6f35e6eda6942f41b4c5 Mon Sep 17 00:00:00 2001 From: BD103 <59022059+BD103@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:20:38 -0500 Subject: [PATCH 03/13] Improve Bloom 3D lighting (#11981) # Objective - With the recent lighting changes, the default configuration in the `bloom_3d` example is less clear what bloom actually does - See [this screenshot](https://github.com/bevyengine/bevy-website/pull/1023/files/4fdb1455d5a3371d69db32b036e38731342b48de#r1494648414) for a comparison. - `bloom_3d` additionally uses a for-loop to spawn the spheres, which can be turned into `commands::spawn_batch` call. - The text is black, which is difficult to see on the gray background. ## Solution - Increase emmisive values of materials. - Set text to white. ## Showcase Before: before After: image --------- Co-authored-by: Alice Cecile --- crates/bevy_core_pipeline/src/bloom/settings.rs | 2 ++ examples/3d/bloom_3d.rs | 15 +++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) 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/examples/3d/bloom_3d.rs b/examples/3d/bloom_3d.rs index 34101d35ffd55..b2fd467942997 100644 --- a/examples/3d/bloom_3d.rs +++ b/examples/3d/bloom_3d.rs @@ -36,19 +36,20 @@ fn setup_scene( transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }, - BloomSettings::default(), // 3. Enable bloom for the camera + // 3. Enable bloom for the camera + BloomSettings::NATURAL, )); let material_emissive1 = materials.add(StandardMaterial { - emissive: Color::linear_rgb(2300.0, 900.0, 300.0), // 4. Put something bright in a dark environment to see the effect + emissive: Color::linear_rgb(23000.0, 9000.0, 3000.0), // 4. Put something bright in a dark environment to see the effect ..default() }); let material_emissive2 = materials.add(StandardMaterial { - emissive: Color::linear_rgb(300.0, 2300.0, 900.0), + emissive: Color::linear_rgb(3000.0, 23000.0, 9000.0), ..default() }); let material_emissive3 = materials.add(StandardMaterial { - emissive: Color::linear_rgb(900.0, 300.0, 2300.0), + emissive: Color::linear_rgb(9000.0, 3000.0, 23000.0), ..default() }); let material_non_emissive = materials.add(StandardMaterial { @@ -60,6 +61,8 @@ fn setup_scene( for x in -5..5 { for z in -5..5 { + // This generates a pseudo-random integer between `[0, 6)`, but deterministically so + // the same spheres are always the same colors. let mut hasher = DefaultHasher::new(); (x, z).hash(&mut hasher); let rand = (hasher.finish() - 2) % 6; @@ -90,7 +93,7 @@ fn setup_scene( "", TextStyle { font_size: 20.0, - color: Color::BLACK, + color: Color::WHITE, ..default() }, ) @@ -219,7 +222,7 @@ fn update_bloom_settings( *text = "Bloom: Off (Toggle: Space)".to_string(); if keycode.just_pressed(KeyCode::Space) { - commands.entity(entity).insert(BloomSettings::default()); + commands.entity(entity).insert(BloomSettings::NATURAL); } } } From dfdf2b9ea4d26daff697b6851806cf342fadd8e3 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 7 Mar 2024 12:22:42 -0800 Subject: [PATCH 04/13] Implement the `AnimationGraph`, allowing for multiple animations to be blended together. (#11989) This is an implementation of RFC #51: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md Note that the implementation strategy is different from the one outlined in that RFC, because two-phase animation has now landed. # Objective Bevy needs animation blending. The RFC for this is [RFC 51]. ## Solution This is an implementation of the RFC. Note that the implementation strategy is different from the one outlined there, because two-phase animation has now landed. This is just a draft to get the conversation started. Currently we're missing a few things: - [x] A fully-fleshed-out mechanism for transitions - [x] A serialization format for `AnimationGraph`s - [x] Examples are broken, other than `animated_fox` - [x] Documentation --- ## Changelog ### Added * The `AnimationPlayer` has been reworked to support blending multiple animations together through an `AnimationGraph`, and as such will no longer function unless a `Handle` has been added to the entity containing the player. See [RFC 51] for more details. * Transition functionality has moved from the `AnimationPlayer` to a new component, `AnimationTransitions`, which works in tandem with the `AnimationGraph`. ## Migration Guide * `AnimationPlayer`s can no longer play animations by themselves and need to be paired with a `Handle`. Code that was using `AnimationPlayer` to play animations will need to create an `AnimationGraph` asset first, add a node for the clip (or clips) you want to play, and then supply the index of that node to the `AnimationPlayer`'s `play` method. * The `AnimationPlayer::play_with_transition()` method has been removed and replaced with the `AnimationTransitions` component. If you were previously using `AnimationPlayer::play_with_transition()`, add all animations that you were playing to the `AnimationGraph`, and create an `AnimationTransitions` component to manage the blending between them. [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md --------- Co-authored-by: Rob Parrett --- Cargo.toml | 11 + assets/animation_graphs/Fox.animgraph.ron | 35 + crates/bevy_animation/Cargo.toml | 9 + crates/bevy_animation/src/graph.rs | 400 +++++++++ crates/bevy_animation/src/lib.rs | 774 +++++++++++------- crates/bevy_animation/src/transition.rs | 132 +++ crates/bevy_asset/src/id.rs | 3 +- crates/bevy_reflect/Cargo.toml | 2 + crates/bevy_reflect/src/impls/petgraph.rs | 15 + crates/bevy_reflect/src/lib.rs | 3 +- examples/3d/irradiance_volumes.rs | 28 +- examples/README.md | 1 + examples/animation/animated_fox.rs | 131 ++- examples/animation/animated_transform.rs | 9 +- examples/animation/animation_graph.rs | 576 +++++++++++++ examples/animation/morph_targets.rs | 12 +- examples/stress_tests/many_foxes.rs | 61 +- .../tools/scene_viewer/animation_plugin.rs | 51 +- 18 files changed, 1865 insertions(+), 388 deletions(-) create mode 100644 assets/animation_graphs/Fox.animgraph.ron create mode 100644 crates/bevy_animation/src/graph.rs create mode 100644 crates/bevy_animation/src/transition.rs create mode 100644 crates/bevy_reflect/src/impls/petgraph.rs create mode 100644 examples/animation/animation_graph.rs diff --git a/Cargo.toml b/Cargo.toml index 6de2dccefc702..4f365f59f2ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -980,6 +980,17 @@ description = "Plays an animation from a skinned glTF" category = "Animation" wasm = true +[[example]] +name = "animation_graph" +path = "examples/animation/animation_graph.rs" +doc-scrape-examples = true + +[package.metadata.example.animation_graph] +name = "Animation Graph" +description = "Blends multiple animations together with a graph" +category = "Animation" +wasm = true + [[example]] name = "morph_targets" path = "examples/animation/morph_targets.rs" diff --git a/assets/animation_graphs/Fox.animgraph.ron b/assets/animation_graphs/Fox.animgraph.ron new file mode 100644 index 0000000000000..a1b21f1254172 --- /dev/null +++ b/assets/animation_graphs/Fox.animgraph.ron @@ -0,0 +1,35 @@ +( + graph: ( + nodes: [ + ( + clip: None, + weight: 1.0, + ), + ( + clip: None, + weight: 0.5, + ), + ( + clip: Some(AssetPath("models/animated/Fox.glb#Animation0")), + weight: 1.0, + ), + ( + clip: Some(AssetPath("models/animated/Fox.glb#Animation1")), + weight: 1.0, + ), + ( + clip: Some(AssetPath("models/animated/Fox.glb#Animation2")), + weight: 1.0, + ), + ], + node_holes: [], + edge_property: directed, + edges: [ + Some((0, 1, ())), + Some((0, 2, ())), + Some((1, 3, ())), + Some((1, 4, ())), + ], + ), + root: 0, +) \ No newline at end of file diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index e47fefb987848..c6cc5275f5255 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -13,9 +13,12 @@ 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_derive = { path = "../bevy_derive", 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", + "petgraph", ] } bevy_render = { path = "../bevy_render", version = "0.14.0-dev" } bevy_time = { path = "../bevy_time", version = "0.14.0-dev" } @@ -25,7 +28,13 @@ bevy_transform = { path = "../bevy_transform", version = "0.14.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.14.0-dev" } # other +fixedbitset = "0.4" +petgraph = { version = "0.6", features = ["serde-1"] } +ron = "0.8" +serde = "1" sha1_smol = { version = "1.0" } +thiserror = "1" +thread_local = "1" uuid = { version = "1.7", features = ["v4"] } [lints] diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs new file mode 100644 index 0000000000000..aba53f1d7adfa --- /dev/null +++ b/crates/bevy_animation/src/graph.rs @@ -0,0 +1,400 @@ +//! The animation graph, which allows animations to be blended together. + +use std::io::{self, Write}; +use std::ops::{Index, IndexMut}; + +use bevy_asset::io::Reader; +use bevy_asset::{Asset, AssetId, AssetLoader, AssetPath, AsyncReadExt as _, Handle, LoadContext}; +use bevy_reflect::{Reflect, ReflectSerialize}; +use bevy_utils::BoxedFuture; +use petgraph::graph::{DiGraph, NodeIndex}; +use ron::de::SpannedError; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::AnimationClip; + +/// A graph structure that describes how animation clips are to be blended +/// together. +/// +/// Applications frequently want to be able to play multiple animations at once +/// and to fine-tune the influence that animations have on a skinned mesh. Bevy +/// uses an *animation graph* to store this information. Animation graphs are a +/// directed acyclic graph (DAG) that describes how animations are to be +/// weighted and combined together. Every frame, Bevy evaluates the graph from +/// the root and blends the animations together in a bottom-up fashion to +/// produce the final pose. +/// +/// There are two types of nodes: *blend nodes* and *clip nodes*, both of which +/// can have an associated weight. Blend nodes have no associated animation clip +/// and simply affect the weights of all their descendant nodes. Clip nodes +/// specify an animation clip to play. When a graph is created, it starts with +/// only a single blend node, the root node. +/// +/// For example, consider the following graph: +/// +/// ```text +/// ┌────────────┐ +/// │ │ +/// │ Idle ├─────────────────────┐ +/// │ │ │ +/// └────────────┘ │ +/// │ +/// ┌────────────┐ │ ┌────────────┐ +/// │ │ │ │ │ +/// │ Run ├──┐ ├──┤ Root │ +/// │ │ │ ┌────────────┐ │ │ │ +/// └────────────┘ │ │ Blend │ │ └────────────┘ +/// ├──┤ ├──┘ +/// ┌────────────┐ │ │ 0.5 │ +/// │ │ │ └────────────┘ +/// │ Walk ├──┘ +/// │ │ +/// └────────────┘ +/// ``` +/// +/// In this case, assuming that Idle, Run, and Walk are all playing with weight +/// 1.0, the Run and Walk animations will be equally blended together, then +/// their weights will be halved and finally blended with the Idle animation. +/// Thus the weight of Run and Walk are effectively half of the weight of Idle. +/// +/// Animation graphs are assets and can be serialized to and loaded from [RON] +/// files. Canonically, such files have an `.animgraph.ron` extension. +/// +/// The animation graph implements [RFC 51]. See that document for more +/// information. +/// +/// [RON]: https://github.com/ron-rs/ron +/// +/// [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md +#[derive(Asset, Reflect, Clone, Debug, Serialize)] +#[reflect(Serialize, Debug)] +#[serde(into = "SerializedAnimationGraph")] +pub struct AnimationGraph { + /// The `petgraph` data structure that defines the animation graph. + pub graph: AnimationDiGraph, + /// The index of the root node in the animation graph. + pub root: NodeIndex, +} + +/// A type alias for the `petgraph` data structure that defines the animation +/// graph. +pub type AnimationDiGraph = DiGraph; + +/// The index of either an animation or blend node in the animation graph. +/// +/// These indices are the way that [`crate::AnimationPlayer`]s identify +/// particular animations. +pub type AnimationNodeIndex = NodeIndex; + +/// An individual node within an animation graph. +/// +/// If `clip` is present, this is a *clip node*. Otherwise, it's a *blend node*. +/// Both clip and blend nodes can have weights, and those weights are propagated +/// down to descendants. +#[derive(Clone, Reflect, Debug)] +pub struct AnimationGraphNode { + /// The animation clip associated with this node, if any. + /// + /// If the clip is present, this node is an *animation clip node*. + /// Otherwise, this node is a *blend node*. + pub clip: Option>, + + /// The weight of this node. + /// + /// Weights are propagated down to descendants. Thus if an animation clip + /// has weight 0.3 and its parent blend node has weight 0.6, the computed + /// weight of the animation clip is 0.18. + pub weight: f32, +} + +/// An [`AssetLoader`] that can load [`AnimationGraph`]s as assets. +/// +/// The canonical extension for [`AnimationGraph`]s is `.animgraph.ron`. Plain +/// `.animgraph` is supported as well. +#[derive(Default)] +pub struct AnimationGraphAssetLoader; + +/// Various errors that can occur when serializing or deserializing animation +/// graphs to and from RON, respectively. +#[derive(Error, Debug)] +pub enum AnimationGraphLoadError { + /// An I/O error occurred. + #[error("I/O")] + Io(#[from] io::Error), + /// An error occurred in RON serialization or deserialization. + #[error("RON serialization")] + Ron(#[from] ron::Error), + /// An error occurred in RON deserialization, and the location of the error + /// is supplied. + #[error("RON serialization")] + SpannedRon(#[from] SpannedError), +} + +/// A version of [`AnimationGraph`] suitable for serializing as an asset. +/// +/// Animation nodes can refer to external animation clips, and the [`AssetId`] +/// is typically not sufficient to identify the clips, since the +/// [`bevy_asset::AssetServer`] assigns IDs in unpredictable ways. That fact +/// motivates this type, which replaces the `Handle` with an +/// asset path. Loading an animation graph via the [`bevy_asset::AssetServer`] +/// actually loads a serialized instance of this type, as does serializing an +/// [`AnimationGraph`] through `serde`. +#[derive(Serialize, Deserialize)] +pub struct SerializedAnimationGraph { + /// Corresponds to the `graph` field on [`AnimationGraph`]. + pub graph: DiGraph, + /// Corresponds to the `root` field on [`AnimationGraph`]. + pub root: NodeIndex, +} + +/// A version of [`AnimationGraphNode`] suitable for serializing as an asset. +/// +/// See the comments in [`SerializedAnimationGraph`] for more information. +#[derive(Serialize, Deserialize)] +pub struct SerializedAnimationGraphNode { + /// Corresponds to the `clip` field on [`AnimationGraphNode`]. + pub clip: Option, + /// Corresponds to the `weight` field on [`AnimationGraphNode`]. + pub weight: f32, +} + +/// A version of `Handle` suitable for serializing as an asset. +/// +/// This replaces any handle that has a path with an [`AssetPath`]. Failing +/// that, the asset ID is serialized directly. +#[derive(Serialize, Deserialize)] +pub enum SerializedAnimationClip { + /// Records an asset path. + AssetPath(AssetPath<'static>), + /// The fallback that records an asset ID. + /// + /// Because asset IDs can change, this should not be relied upon. Prefer to + /// use asset paths where possible. + AssetId(AssetId), +} + +impl AnimationGraph { + /// Creates a new animation graph with a root node and no other nodes. + pub fn new() -> Self { + let mut graph = DiGraph::default(); + let root = graph.add_node(AnimationGraphNode::default()); + Self { graph, root } + } + + /// A convenience function for creating an [`AnimationGraph`] from a single + /// [`AnimationClip`]. + /// + /// The clip will be a direct child of the root with weight 1.0. Both the + /// graph and the index of the added node are returned as a tuple. + pub fn from_clip(clip: Handle) -> (Self, AnimationNodeIndex) { + let mut graph = Self::new(); + let node_index = graph.add_clip(clip, 1.0, graph.root); + (graph, node_index) + } + + /// Adds an [`AnimationClip`] to the animation graph with the given weight + /// and returns its index. + /// + /// The animation clip will be the child of the given parent. + pub fn add_clip( + &mut self, + clip: Handle, + weight: f32, + parent: AnimationNodeIndex, + ) -> AnimationNodeIndex { + let node_index = self.graph.add_node(AnimationGraphNode { + clip: Some(clip), + weight, + }); + self.graph.add_edge(parent, node_index, ()); + node_index + } + + /// A convenience method to add multiple [`AnimationClip`]s to the animation + /// graph. + /// + /// All of the animation clips will have the same weight and will be + /// parented to the same node. + /// + /// Returns the indices of the new nodes. + pub fn add_clips<'a, I>( + &'a mut self, + clips: I, + weight: f32, + parent: AnimationNodeIndex, + ) -> impl Iterator + 'a + where + I: IntoIterator>, + ::IntoIter: 'a, + { + clips + .into_iter() + .map(move |clip| self.add_clip(clip, weight, parent)) + } + + /// Adds a blend node to the animation graph with the given weight and + /// returns its index. + /// + /// The blend node will be placed under the supplied `parent` node. During + /// animation evaluation, the descendants of this blend node will have their + /// weights multiplied by the weight of the blend. + pub fn add_blend(&mut self, weight: f32, parent: AnimationNodeIndex) -> AnimationNodeIndex { + let node_index = self + .graph + .add_node(AnimationGraphNode { clip: None, weight }); + self.graph.add_edge(parent, node_index, ()); + node_index + } + + /// Adds an edge from the edge `from` to `to`, making `to` a child of + /// `from`. + /// + /// The behavior is unspecified if adding this produces a cycle in the + /// graph. + pub fn add_edge(&mut self, from: NodeIndex, to: NodeIndex) { + self.graph.add_edge(from, to, ()); + } + + /// Removes an edge between `from` and `to` if it exists. + /// + /// Returns true if the edge was successfully removed or false if no such + /// edge existed. + pub fn remove_edge(&mut self, from: NodeIndex, to: NodeIndex) -> bool { + self.graph + .find_edge(from, to) + .map(|edge| self.graph.remove_edge(edge)) + .is_some() + } + + /// Returns the [`AnimationGraphNode`] associated with the given index. + /// + /// If no node with the given index exists, returns `None`. + pub fn get(&self, animation: AnimationNodeIndex) -> Option<&AnimationGraphNode> { + self.graph.node_weight(animation) + } + + /// Returns a mutable reference to the [`AnimationGraphNode`] associated + /// with the given index. + /// + /// If no node with the given index exists, returns `None`. + pub fn get_mut(&mut self, animation: AnimationNodeIndex) -> Option<&mut AnimationGraphNode> { + self.graph.node_weight_mut(animation) + } + + /// Returns an iterator over the [`AnimationGraphNode`]s in this graph. + pub fn nodes(&self) -> impl Iterator { + self.graph.node_indices() + } + + /// Serializes the animation graph to the given [`Write`]r in RON format. + /// + /// If writing to a file, it can later be loaded with the + /// [`AnimationGraphAssetLoader`] to reconstruct the graph. + pub fn save(&self, writer: &mut W) -> Result<(), AnimationGraphLoadError> + where + W: Write, + { + let mut ron_serializer = ron::ser::Serializer::new(writer, None)?; + Ok(self.serialize(&mut ron_serializer)?) + } +} + +impl Index for AnimationGraph { + type Output = AnimationGraphNode; + + fn index(&self, index: AnimationNodeIndex) -> &Self::Output { + &self.graph[index] + } +} + +impl IndexMut for AnimationGraph { + fn index_mut(&mut self, index: AnimationNodeIndex) -> &mut Self::Output { + &mut self.graph[index] + } +} + +impl Default for AnimationGraphNode { + fn default() -> Self { + Self { + clip: None, + weight: 1.0, + } + } +} + +impl Default for AnimationGraph { + fn default() -> Self { + Self::new() + } +} + +impl AssetLoader for AnimationGraphAssetLoader { + type Asset = AnimationGraph; + + type Settings = (); + + type Error = AnimationGraphLoadError; + + fn load<'a>( + &'a self, + reader: &'a mut Reader, + _: &'a Self::Settings, + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, Result> { + Box::pin(async move { + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes).await?; + + // Deserialize a `SerializedAnimationGraph` directly, so that we can + // get the list of the animation clips it refers to and load them. + let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; + let serialized_animation_graph = + SerializedAnimationGraph::deserialize(&mut deserializer) + .map_err(|err| deserializer.span_error(err))?; + + // Load all `AssetPath`s to convert from a + // `SerializedAnimationGraph` to a real `AnimationGraph`. + Ok(AnimationGraph { + graph: serialized_animation_graph.graph.map( + |_, serialized_node| AnimationGraphNode { + clip: serialized_node.clip.as_ref().map(|clip| match clip { + SerializedAnimationClip::AssetId(asset_id) => Handle::Weak(*asset_id), + SerializedAnimationClip::AssetPath(asset_path) => { + load_context.load(asset_path) + } + }), + weight: serialized_node.weight, + }, + |_, _| (), + ), + root: serialized_animation_graph.root, + }) + }) + } + + fn extensions(&self) -> &[&str] { + &["animgraph", "animgraph.ron"] + } +} + +impl From for SerializedAnimationGraph { + fn from(animation_graph: AnimationGraph) -> Self { + // If any of the animation clips have paths, then serialize them as + // `SerializedAnimationClip::AssetPath` so that the + // `AnimationGraphAssetLoader` can load them. + Self { + graph: animation_graph.graph.map( + |_, node| SerializedAnimationGraphNode { + weight: node.weight, + clip: node.clip.as_ref().map(|clip| match clip.path() { + Some(path) => SerializedAnimationClip::AssetPath(path.clone()), + None => SerializedAnimationClip::AssetId(clip.id()), + }), + }, + |_, _| (), + ), + root: animation_graph.root, + } + } +} diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 08f10a167dfd2..d3dbed8406193 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -1,12 +1,15 @@ //! Animation for the game engine Bevy mod animatable; +mod graph; +mod transition; mod util; +use std::cell::RefCell; +use std::collections::BTreeMap; use std::hash::{Hash, Hasher}; use std::iter; use std::ops::{Add, Mul}; -use std::time::Duration; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, Assets, Handle}; @@ -20,19 +23,30 @@ use bevy_render::mesh::morph::MorphWeights; use bevy_time::Time; use bevy_transform::{prelude::Transform, TransformSystem}; use bevy_utils::hashbrown::HashMap; -use bevy_utils::{tracing::error, NoOpHash}; +use bevy_utils::{ + tracing::{error, trace}, + NoOpHash, +}; +use fixedbitset::FixedBitSet; +use graph::{AnimationGraph, AnimationNodeIndex}; +use petgraph::graph::NodeIndex; +use petgraph::Direction; +use prelude::{AnimationGraphAssetLoader, AnimationTransitions}; use sha1_smol::Sha1; +use thread_local::ThreadLocal; use uuid::Uuid; #[allow(missing_docs)] pub mod prelude { #[doc(hidden)] pub use crate::{ - animatable::*, AnimationClip, AnimationPlayer, AnimationPlugin, Interpolation, Keyframes, - VariableCurve, + animatable::*, graph::*, transition::*, AnimationClip, AnimationPlayer, AnimationPlugin, + Interpolation, Keyframes, VariableCurve, }; } +use crate::transition::{advance_transitions, expire_completed_transitions}; + /// The [UUID namespace] of animation targets (e.g. bones). /// /// [UUID namespace]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) @@ -279,8 +293,17 @@ pub enum RepeatAnimation { Forever, } +/// An animation that an [`AnimationPlayer`] is currently either playing or was +/// playing, but is presently paused. +/// +/// An stopped animation is considered no longer active. #[derive(Debug, Reflect)] -struct PlayingAnimation { +pub struct ActiveAnimation { + /// The factor by which the weight from the [`AnimationGraph`] is multiplied. + weight: f32, + /// The actual weight of this animation this frame, taking the + /// [`AnimationGraph`] into account. + computed_weight: f32, repeat: RepeatAnimation, speed: f32, /// Total time the animation has been played. @@ -291,26 +314,28 @@ struct PlayingAnimation { /// /// Note: This will always be in the range [0.0, animation clip duration] seek_time: f32, - animation_clip: Handle, /// Number of times the animation has completed. /// If the animation is playing in reverse, this increments when the animation passes the start. completions: u32, + paused: bool, } -impl Default for PlayingAnimation { +impl Default for ActiveAnimation { fn default() -> Self { Self { + weight: 1.0, + computed_weight: 1.0, repeat: RepeatAnimation::default(), speed: 1.0, elapsed: 0.0, seek_time: 0.0, - animation_clip: Default::default(), completions: 0, + paused: false, } } } -impl PlayingAnimation { +impl ActiveAnimation { /// Check if the animation has finished, based on its repetition behavior and the number of times it has repeated. /// /// Note: An animation with `RepeatAnimation::Forever` will never finish. @@ -353,38 +378,112 @@ impl PlayingAnimation { } /// Reset back to the initial state as if no time has elapsed. - fn replay(&mut self) { + pub fn replay(&mut self) { self.completions = 0; self.elapsed = 0.0; self.seek_time = 0.0; } -} -/// An animation that is being faded out as part of a transition -struct AnimationTransition { - /// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out. - current_weight: f32, - /// How much to decrease `current_weight` per second - weight_decline_per_sec: f32, - /// The animation that is being faded out - animation: PlayingAnimation, + /// Returns the current weight of this animation. + pub fn weight(&self) -> f32 { + self.weight + } + + /// Sets the weight of this animation. + pub fn set_weight(&mut self, weight: f32) { + self.weight = weight; + } + + /// Pause the animation. + pub fn pause(&mut self) -> &mut Self { + self.paused = true; + self + } + + /// Unpause the animation. + pub fn resume(&mut self) -> &mut Self { + self.paused = false; + self + } + + /// Returns true if this animation is currently paused. + /// + /// Note that paused animations are still [`ActiveAnimation`]s. + #[inline] + pub fn is_paused(&self) -> bool { + self.paused + } + + /// Sets the repeat mode for this playing animation. + pub fn set_repeat(&mut self, repeat: RepeatAnimation) -> &mut Self { + self.repeat = repeat; + self + } + + /// Marks this animation as repeating forever. + pub fn repeat(&mut self) -> &mut Self { + self.set_repeat(RepeatAnimation::Forever) + } + + /// Returns the repeat mode assigned to this active animation. + pub fn repeat_mode(&self) -> RepeatAnimation { + self.repeat + } + + /// Returns the number of times this animation has completed. + pub fn completions(&self) -> u32 { + self.completions + } + + /// Returns true if the animation is playing in reverse. + pub fn is_playback_reversed(&self) -> bool { + self.speed < 0.0 + } + + /// Returns the speed of the animation playback. + pub fn speed(&self) -> f32 { + self.speed + } + + /// Sets the speed of the animation playback. + pub fn set_speed(&mut self, speed: f32) -> &mut Self { + self.speed = speed; + self + } + + /// Returns the amount of time the animation has been playing. + pub fn elapsed(&self) -> f32 { + self.elapsed + } + + /// Returns the seek time of the animation. + /// + /// This is nonnegative and no more than the clip duration. + pub fn seek_time(&self) -> f32 { + self.seek_time + } + + /// Seeks to a specific time in the animation. + pub fn seek_to(&mut self, seek_time: f32) -> &mut Self { + self.seek_time = seek_time; + self + } + + /// Seeks to the beginning of the animation. + pub fn rewind(&mut self) -> &mut Self { + self.seek_time = 0.0; + self + } } /// Animation controls #[derive(Component, Default, Reflect)] #[reflect(Component)] pub struct AnimationPlayer { - paused: bool, - - animation: PlayingAnimation, - - // List of previous animations we're currently transitioning away from. - // Usually this is empty, when transitioning between animations, there is - // one entry. When another animation transition happens while a transition - // is still ongoing, then there can be more than one entry. - // Once a transition is finished, it will be automatically removed from the list - #[reflect(ignore)] - transitions: Vec, + /// We use a `BTreeMap` instead of a `HashMap` here to ensure a consistent + /// ordering when applying the animations. + active_animations: BTreeMap, + blend_weights: HashMap, } /// The components that we might need to read or write during animation of each @@ -397,157 +496,160 @@ struct AnimationTargetContext<'a> { morph_weights: Option>, } -impl AnimationPlayer { - /// Start playing an animation, resetting state of the player. - /// This will use a linear blending between the previous and the new animation to make a smooth transition. - pub fn start(&mut self, handle: Handle) -> &mut Self { - self.animation = PlayingAnimation { - animation_clip: handle, - ..Default::default() - }; - - // We want a hard transition. - // In case any previous transitions are still playing, stop them - self.transitions.clear(); - - self - } +/// Information needed during the traversal of the animation graph in +/// [`advance_animations`]. +#[derive(Default)] +pub struct AnimationGraphEvaluator { + /// The stack used for the depth-first search of the graph. + dfs_stack: Vec, + /// The list of visited nodes during the depth-first traversal. + dfs_visited: FixedBitSet, + /// Accumulated weights for each node. + weights: Vec, +} - /// Start playing an animation, resetting state of the player. - /// This will use a linear blending between the previous and the new animation to make a smooth transition. - pub fn start_with_transition( - &mut self, - handle: Handle, - transition_duration: Duration, - ) -> &mut Self { - let mut animation = PlayingAnimation { - animation_clip: handle, - ..Default::default() - }; - std::mem::swap(&mut animation, &mut self.animation); - - // Add the current transition. If other transitions are still ongoing, - // this will keep those transitions running and cause a transition between - // the output of that previous transition to the new animation. - self.transitions.push(AnimationTransition { - current_weight: 1.0, - weight_decline_per_sec: 1.0 / transition_duration.as_secs_f32(), - animation, - }); +thread_local! { + /// A cached per-thread copy of the graph evaluator. + /// + /// Caching the evaluator lets us save allocation traffic from frame to + /// frame. + static ANIMATION_GRAPH_EVALUATOR: RefCell = + RefCell::new(AnimationGraphEvaluator::default()); +} - self +impl AnimationPlayer { + /// Start playing an animation, restarting it if necessary. + pub fn start(&mut self, animation: AnimationNodeIndex) -> &mut ActiveAnimation { + self.active_animations.entry(animation).or_default() } - /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. - pub fn play(&mut self, handle: Handle) -> &mut Self { - if !self.is_playing_clip(&handle) || self.is_paused() { - self.start(handle); - } - self + /// Start playing an animation, unless the requested animation is already playing. + pub fn play(&mut self, animation: AnimationNodeIndex) -> &mut ActiveAnimation { + let playing_animation = self.active_animations.entry(animation).or_default(); + playing_animation.weight = 1.0; + playing_animation } - /// Start playing an animation, resetting state of the player, unless the requested animation is already playing. - /// This will use a linear blending between the previous and the new animation to make a smooth transition - pub fn play_with_transition( - &mut self, - handle: Handle, - transition_duration: Duration, - ) -> &mut Self { - if !self.is_playing_clip(&handle) || self.is_paused() { - self.start_with_transition(handle, transition_duration); - } + /// Stops playing the given animation, removing it from the list of playing + /// animations. + pub fn stop(&mut self, animation: AnimationNodeIndex) -> &mut Self { + self.active_animations.remove(&animation); self } - /// Handle to the animation clip being played. - pub fn animation_clip(&self) -> &Handle { - &self.animation.animation_clip - } - - /// Check if the given animation clip is being played. - pub fn is_playing_clip(&self, handle: &Handle) -> bool { - self.animation_clip() == handle - } - - /// Check if the playing animation has finished, according to the repetition behavior. - pub fn is_finished(&self) -> bool { - self.animation.is_finished() - } - - /// Sets repeat to [`RepeatAnimation::Forever`]. - /// - /// See also [`Self::set_repeat`]. - pub fn repeat(&mut self) -> &mut Self { - self.animation.repeat = RepeatAnimation::Forever; + /// Stops all currently-playing animations. + pub fn stop_all(&mut self) -> &mut Self { + self.active_animations.clear(); self } - /// Set the repetition behaviour of the animation. - pub fn set_repeat(&mut self, repeat: RepeatAnimation) -> &mut Self { - self.animation.repeat = repeat; - self + /// Iterates through all animations that this [`AnimationPlayer`] is + /// currently playing. + pub fn playing_animations( + &self, + ) -> impl Iterator { + self.active_animations.iter() } - /// Repetition behavior of the animation. - pub fn repeat_mode(&self) -> RepeatAnimation { - self.animation.repeat + /// Iterates through all animations that this [`AnimationPlayer`] is + /// currently playing, mutably. + pub fn playing_animations_mut( + &mut self, + ) -> impl Iterator { + self.active_animations.iter_mut() } - /// Number of times the animation has completed. - pub fn completions(&self) -> u32 { - self.animation.completions + /// Check if the given animation node is being played. + pub fn is_playing_animation(&self, animation: AnimationNodeIndex) -> bool { + self.active_animations.contains_key(&animation) } - /// Check if the animation is playing in reverse. - pub fn is_playback_reversed(&self) -> bool { - self.animation.speed < 0.0 + /// Check if all playing animations have finished, according to the repetition behavior. + pub fn all_finished(&self) -> bool { + self.active_animations + .values() + .all(|playing_animation| playing_animation.is_finished()) } - /// Pause the animation - pub fn pause(&mut self) { - self.paused = true; + /// Check if all playing animations are paused. + #[doc(alias = "is_paused")] + pub fn all_paused(&self) -> bool { + self.active_animations + .values() + .all(|playing_animation| playing_animation.is_paused()) } - /// Unpause the animation - pub fn resume(&mut self) { - self.paused = false; + /// Resume all playing animations. + #[doc(alias = "pause")] + pub fn pause_all(&mut self) -> &mut Self { + for (_, playing_animation) in self.playing_animations_mut() { + playing_animation.pause(); + } + self } - /// Is the animation paused - pub fn is_paused(&self) -> bool { - self.paused + /// Resume all active animations. + #[doc(alias = "resume")] + pub fn resume_all(&mut self) -> &mut Self { + for (_, playing_animation) in self.playing_animations_mut() { + playing_animation.resume(); + } + self } - /// Speed of the animation playback - pub fn speed(&self) -> f32 { - self.animation.speed + /// Rewinds all active animations. + #[doc(alias = "rewind")] + pub fn rewind_all(&mut self) -> &mut Self { + for (_, playing_animation) in self.playing_animations_mut() { + playing_animation.rewind(); + } + self } - /// Set the speed of the animation playback - pub fn set_speed(&mut self, speed: f32) -> &mut Self { - self.animation.speed = speed; + /// Multiplies the speed of all active animations by the given factor. + #[doc(alias = "set_speed")] + pub fn adjust_speeds(&mut self, factor: f32) -> &mut Self { + for (_, playing_animation) in self.playing_animations_mut() { + let new_speed = playing_animation.speed() * factor; + playing_animation.set_speed(new_speed); + } self } - /// Time elapsed playing the animation - pub fn elapsed(&self) -> f32 { - self.animation.elapsed + /// Seeks all active animations forward or backward by the same amount. + /// + /// To seek forward, pass a positive value; to seek negative, pass a + /// negative value. Values below 0.0 or beyond the end of the animation clip + /// are clamped appropriately. + #[doc(alias = "seek_to")] + pub fn seek_all_by(&mut self, amount: f32) -> &mut Self { + for (_, playing_animation) in self.playing_animations_mut() { + let new_time = playing_animation.seek_time(); + playing_animation.seek_to(new_time + amount); + } + self } - /// Seek time inside of the animation. Always within the range [0.0, clip duration]. - pub fn seek_time(&self) -> f32 { - self.animation.seek_time + /// Returns the [`ActiveAnimation`] associated with the given animation + /// node if it's currently playing. + /// + /// If the animation isn't currently active, returns `None`. + pub fn animation(&self, animation: AnimationNodeIndex) -> Option<&ActiveAnimation> { + self.active_animations.get(&animation) } - /// Seek to a specific time in the animation. - pub fn seek_to(&mut self, seek_time: f32) -> &mut Self { - self.animation.seek_time = seek_time; - self + /// Returns a mutable reference to the [`ActiveAnimation`] associated with + /// the given animation node if it's currently active. + /// + /// If the animation isn't currently active, returns `None`. + pub fn animation_mut(&mut self, animation: AnimationNodeIndex) -> Option<&mut ActiveAnimation> { + self.active_animations.get_mut(&animation) } - /// Reset the animation to its initial state, as if no time has elapsed. - pub fn replay(&mut self) { - self.animation.replay(); + /// Returns true if the animation is currently playing or paused, or false + /// if the animation is stopped. + pub fn animation_is_playing(&self, animation: AnimationNodeIndex) -> bool { + self.active_animations.contains_key(&animation) } } @@ -555,46 +657,87 @@ impl AnimationPlayer { pub fn advance_animations( time: Res