From 17b3c272c199c2884675487389929e785bb9e60d Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Mon, 15 Aug 2022 18:47:37 -0700 Subject: [PATCH 01/11] Limit FontAtlasSets --- crates/bevy_text/src/error.rs | 2 ++ crates/bevy_text/src/font_atlas_set.rs | 6 +++++- crates/bevy_text/src/glyph_brush.rs | 5 +++-- crates/bevy_text/src/lib.rs | 19 ++++++++++++++++++- crates/bevy_text/src/pipeline.rs | 4 +++- crates/bevy_text/src/text2d.rs | 7 ++++++- crates/bevy_ui/src/widget/text.rs | 7 ++++++- 7 files changed, 43 insertions(+), 7 deletions(-) diff --git a/crates/bevy_text/src/error.rs b/crates/bevy_text/src/error.rs index 1bb7cf1253581..075f3199f9981 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -7,4 +7,6 @@ pub enum TextError { NoSuchFont, #[error("failed to add glyph to newly-created atlas {0:?}")] FailedToAddGlyph(GlyphId), + #[error("exceeded {0:?} availble TextAltases for font. This can be caused by using an excessive number of font sizes. Consider using Transform::scale to modify font size dynamically. If you need more font sizes modify TextSettings.max_font_atlases." )] + ExceedMaxTextAtlases(usize), } diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 655ad7ccf3755..fcb121c0a7ca1 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -1,4 +1,4 @@ -use crate::{error::TextError, Font, FontAtlas}; +use crate::{error::TextError, Font, FontAtlas, TextSettings}; use ab_glyph::{GlyphId, OutlinedGlyph, Point}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; @@ -50,7 +50,11 @@ impl FontAtlasSet { texture_atlases: &mut Assets, textures: &mut Assets, outlined_glyph: OutlinedGlyph, + text_settings: &TextSettings, ) -> Result { + if self.font_atlases.len() >= text_settings.max_font_atlases { + return Err(TextError::ExceedMaxTextAtlases(text_settings.max_font_atlases)); + } let glyph = outlined_glyph.glyph(); let glyph_id = glyph.id; let glyph_position = glyph.position; diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index ca7331901775d..ef077e26e7418 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -7,7 +7,7 @@ use glyph_brush_layout::{ FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText, }; -use crate::{error::TextError, Font, FontAtlasSet, GlyphAtlasInfo, TextAlignment}; +use crate::{error::TextError, Font, FontAtlasSet, GlyphAtlasInfo, TextAlignment, TextSettings}; pub struct GlyphBrush { fonts: Vec, @@ -51,6 +51,7 @@ impl GlyphBrush { fonts: &Assets, texture_atlases: &mut Assets, textures: &mut Assets, + text_settings: &TextSettings, ) -> Result, TextError> { if glyphs.is_empty() { return Ok(Vec::new()); @@ -104,7 +105,7 @@ impl GlyphBrush { .get_glyph_atlas_info(section_data.2, glyph_id, glyph_position) .map(Ok) .unwrap_or_else(|| { - font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph) + font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph, text_settings) })?; let texture_atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 4dc1fc3b34070..a6cd183ce6736 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -28,7 +28,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion}; +use bevy_ecs::{entity::Entity, prelude::Resource, schedule::ParallelSystemDescriptorCoercion}; use bevy_render::{RenderApp, RenderStage}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; @@ -38,6 +38,21 @@ pub type DefaultTextPipeline = TextPipeline; #[derive(Default)] pub struct TextPlugin; +/// `TextPlugin` settings +#[derive(Resource, Clone)] +pub struct TextSettings { + /// Number of font atlases supported in a FontAtlasSet + pub max_font_atlases: usize, +} + +impl Default for TextSettings { + fn default() -> Self { + Self { + max_font_atlases: 100, + } + } +} + impl Plugin for TextPlugin { fn build(&self, app: &mut App) { app.add_asset::() @@ -52,6 +67,8 @@ impl Plugin for TextPlugin { update_text2d_layout.after(ModifiesWindows), ); + let _ = app.world.get_resource_or_insert_with(TextSettings::default); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_system_to_stage( RenderStage::Extract, diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index ea72b965e6bab..3c03c71055ad2 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -12,7 +12,7 @@ use glyph_brush_layout::{FontId, SectionText}; use crate::{ error::TextError, glyph_brush::GlyphBrush, scale_value, Font, FontAtlasSet, PositionedGlyph, - TextAlignment, TextSection, + TextAlignment, TextSection, TextSettings, }; #[derive(Resource)] @@ -62,6 +62,7 @@ impl TextPipeline { font_atlas_set_storage: &mut Assets, texture_atlases: &mut Assets, textures: &mut Assets, + text_settings: &TextSettings, ) -> Result<(), TextError> { let mut scaled_fonts = Vec::new(); let sections = sections @@ -123,6 +124,7 @@ impl TextPipeline { fonts, texture_atlases, textures, + text_settings, )?; self.glyph_map.insert(id, TextLayoutInfo { glyphs, size }); diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index dd4a5b63f5a23..3e972d005ed42 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -22,7 +22,7 @@ use bevy_utils::HashSet; use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; use crate::{ - DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, VerticalAlign, + DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextSettings, VerticalAlign, }; /// The calculated size of text drawn in 2D scene. @@ -152,6 +152,7 @@ pub fn update_text2d_layout( mut textures: ResMut>, fonts: Res>, windows: Res, + text_settings: Res, mut scale_factor_changed: EventReader, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, @@ -187,6 +188,7 @@ pub fn update_text2d_layout( &mut *font_atlas_set_storage, &mut *texture_atlases, &mut *textures, + text_settings.as_ref(), ) { Err(TextError::NoSuchFont) => { // There was an error processing the text layout, let's add this entity to the @@ -196,6 +198,9 @@ pub fn update_text2d_layout( Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {}.", e); } + Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + panic!("Fatal error when processing text: {}.", e); + } Ok(()) => { let text_layout_info = text_pipeline.get_glyphs(&entity).expect( "Failed to get glyphs from the pipeline that have just been computed", diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index ff004f8ea0a0c..95594b1ea8db6 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ use bevy_math::Vec2; use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; -use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError}; +use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError, TextSettings}; use bevy_window::{WindowId, Windows}; #[derive(Debug, Default)] @@ -43,6 +43,7 @@ pub fn text_system( mut textures: ResMut>, fonts: Res>, windows: Res, + text_settings: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, mut text_pipeline: ResMut, @@ -104,6 +105,7 @@ pub fn text_system( &mut *font_atlas_set_storage, &mut *texture_atlases, &mut *textures, + text_settings.as_ref(), ) { Err(TextError::NoSuchFont) => { // There was an error processing the text layout, let's add this entity to the @@ -113,6 +115,9 @@ pub fn text_system( Err(e @ TextError::FailedToAddGlyph(_)) => { panic!("Fatal error when processing text: {}.", e); } + Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + panic!("Fatal error when processing text: {}.", e); + } Ok(()) => { let text_layout_info = text_pipeline.get_glyphs(&entity).expect( "Failed to get glyphs from the pipeline that have just been computed", From 6da90e124a1f0de1392a7d4c674a195dd2a72041 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Mon, 15 Aug 2022 19:20:28 -0700 Subject: [PATCH 02/11] format fixes --- crates/bevy_text/src/font_atlas_set.rs | 4 +++- crates/bevy_text/src/glyph_brush.rs | 7 ++++++- crates/bevy_text/src/text2d.rs | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index fcb121c0a7ca1..e797b2fd076d7 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -53,7 +53,9 @@ impl FontAtlasSet { text_settings: &TextSettings, ) -> Result { if self.font_atlases.len() >= text_settings.max_font_atlases { - return Err(TextError::ExceedMaxTextAtlases(text_settings.max_font_atlases)); + return Err(TextError::ExceedMaxTextAtlases( + text_settings.max_font_atlases, + )); } let glyph = outlined_glyph.glyph(); let glyph_id = glyph.id; diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index ef077e26e7418..b4ae95f848831 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -105,7 +105,12 @@ impl GlyphBrush { .get_glyph_atlas_info(section_data.2, glyph_id, glyph_position) .map(Ok) .unwrap_or_else(|| { - font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph, text_settings) + font_atlas_set.add_glyph_to_atlas( + texture_atlases, + textures, + outlined_glyph, + text_settings, + ) })?; let texture_atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 3e972d005ed42..1f5cc949098f1 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -22,7 +22,8 @@ use bevy_utils::HashSet; use bevy_window::{WindowId, WindowScaleFactorChanged, Windows}; use crate::{ - DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextSettings, VerticalAlign, + DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, TextError, TextSettings, + VerticalAlign, }; /// The calculated size of text drawn in 2D scene. From fa65bb56a5dbb33a12794d5caeb8f0d56893dbd4 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Mon, 15 Aug 2022 19:34:16 -0700 Subject: [PATCH 03/11] Resolve clippy errors --- crates/bevy_text/src/glyph_brush.rs | 1 + crates/bevy_text/src/lib.rs | 2 +- crates/bevy_text/src/text2d.rs | 5 +---- crates/bevy_ui/src/widget/text.rs | 5 +---- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index b4ae95f848831..592cd0c26dc61 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -43,6 +43,7 @@ impl GlyphBrush { Ok(section_glyphs) } + #[allow(clippy::too_many_arguments)] pub fn process_glyphs( &self, glyphs: Vec, diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index a6cd183ce6736..f47f7b3f2ad88 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -39,7 +39,7 @@ pub type DefaultTextPipeline = TextPipeline; pub struct TextPlugin; /// `TextPlugin` settings -#[derive(Resource, Clone)] +#[derive(Resource)] pub struct TextSettings { /// Number of font atlases supported in a FontAtlasSet pub max_font_atlases: usize, diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 1f5cc949098f1..acd5b896355a9 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -196,10 +196,7 @@ pub fn update_text2d_layout( // queue for further processing queue.insert(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) => { - panic!("Fatal error when processing text: {}.", e); - } - Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { panic!("Fatal error when processing text: {}.", e); } Ok(()) => { diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 95594b1ea8db6..48abf2a307e24 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -112,10 +112,7 @@ pub fn text_system( // queue for further processing new_queue.push(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) => { - panic!("Fatal error when processing text: {}.", e); - } - Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { panic!("Fatal error when processing text: {}.", e); } Ok(()) => { From ec9ca46af010c84c6081243a828811029b1498a8 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Mon, 15 Aug 2022 19:36:48 -0700 Subject: [PATCH 04/11] format fixes again --- crates/bevy_text/src/text2d.rs | 3 ++- crates/bevy_ui/src/widget/text.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index acd5b896355a9..4d47a39a7edef 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -196,7 +196,8 @@ pub fn update_text2d_layout( // queue for further processing queue.insert(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) + | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { panic!("Fatal error when processing text: {}.", e); } Ok(()) => { diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 48abf2a307e24..9a23507936745 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -112,7 +112,8 @@ pub fn text_system( // queue for further processing new_queue.push(entity); } - Err(e @ TextError::FailedToAddGlyph(_)) | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { + Err(e @ TextError::FailedToAddGlyph(_)) + | Err(e @ TextError::ExceedMaxTextAtlases(_)) => { panic!("Fatal error when processing text: {}.", e); } Ok(()) => { From c87df9e3ffed8c616eb000029676d9bf07b8e790 Mon Sep 17 00:00:00 2001 From: xtr3m3nerd Date: Tue, 16 Aug 2022 12:42:33 -0700 Subject: [PATCH 05/11] Update crates/bevy_text/src/error.rs Co-authored-by: Jerome Humbert --- crates/bevy_text/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_text/src/error.rs b/crates/bevy_text/src/error.rs index 075f3199f9981..4d4091bd37380 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -7,6 +7,6 @@ pub enum TextError { NoSuchFont, #[error("failed to add glyph to newly-created atlas {0:?}")] FailedToAddGlyph(GlyphId), - #[error("exceeded {0:?} availble TextAltases for font. This can be caused by using an excessive number of font sizes. Consider using Transform::scale to modify font size dynamically. If you need more font sizes modify TextSettings.max_font_atlases." )] + #[error("exceeded {0:?} available TextAltases for font. This can be caused by using an excessive number of font sizes. Consider using Transform::scale to modify font size dynamically. If you need more font sizes modify TextSettings.max_font_atlases." )] ExceedMaxTextAtlases(usize), } From 744f7471b80177b33a51451a95e72e07e60b01a7 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Tue, 16 Aug 2022 12:53:35 -0700 Subject: [PATCH 06/11] import from resolved module --- crates/bevy_text/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index f47f7b3f2ad88..bbd2389417ddc 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -28,7 +28,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::{entity::Entity, prelude::Resource, schedule::ParallelSystemDescriptorCoercion}; +use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion, system::Resource}; use bevy_render::{RenderApp, RenderStage}; use bevy_sprite::SpriteSystem; use bevy_window::ModifiesWindows; From d6f7476795e53eae3d6b21a68943bfa9136e9ca4 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Tue, 23 Aug 2022 11:36:32 -0700 Subject: [PATCH 07/11] Add LRU Cache for managing font_atlas_set --- crates/bevy_text/src/font_atlas_set.rs | 28 +++++++++++++++++++++----- crates/bevy_text/src/lib.rs | 4 +++- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index e797b2fd076d7..78f32e6f2eada 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -14,6 +14,7 @@ type FontSizeKey = FloatOrd; #[uuid = "73ba778b-b6b5-4f45-982d-d21b6b86ace2"] pub struct FontAtlasSet { font_atlases: HashMap>, + queue: Vec, } #[derive(Debug, Clone)] @@ -26,6 +27,7 @@ impl Default for FontAtlasSet { fn default() -> Self { FontAtlasSet { font_atlases: HashMap::with_capacity_and_hasher(1, Default::default()), + queue: Vec::new(), } } } @@ -52,10 +54,19 @@ impl FontAtlasSet { outlined_glyph: OutlinedGlyph, text_settings: &TextSettings, ) -> Result { - if self.font_atlases.len() >= text_settings.max_font_atlases { - return Err(TextError::ExceedMaxTextAtlases( - text_settings.max_font_atlases, - )); + if !text_settings.allow_dynamic_font_size { + if self.font_atlases.len() >= text_settings.max_font_atlases { + return Err(TextError::ExceedMaxTextAtlases( + text_settings.max_font_atlases, + )); + } + } else { + // Clear last space in queue to make room for new font size + while self.queue.len() >= text_settings.max_font_atlases - 1 { + if let Some(font_size_key) = self.queue.pop() { + self.font_atlases.remove(&font_size_key); + } + } } let glyph = outlined_glyph.glyph(); let glyph_id = glyph.id; @@ -71,6 +82,7 @@ impl FontAtlasSet { Vec2::splat(512.0), )] }); + self.queue.insert(0, FloatOrd(font_size)); let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph); let add_char_to_font_atlas = |atlas: &mut FontAtlas| -> bool { atlas.add_glyph( @@ -112,11 +124,17 @@ impl FontAtlasSet { } pub fn get_glyph_atlas_info( - &self, + &mut self, font_size: f32, glyph_id: GlyphId, position: Point, ) -> Option { + // Move to front of used queue. + let some_index = self.queue.iter().position(|x| *x == FloatOrd(font_size)); + if let Some(index) = some_index { + let key = self.queue.remove(index); + self.queue.insert(0, key); + } self.font_atlases .get(&FloatOrd(font_size)) .and_then(|font_atlases| { diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index bbd2389417ddc..7cd7d402251bb 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -43,12 +43,14 @@ pub struct TextPlugin; pub struct TextSettings { /// Number of font atlases supported in a FontAtlasSet pub max_font_atlases: usize, + pub allow_dynamic_font_size: bool, } impl Default for TextSettings { fn default() -> Self { Self { - max_font_atlases: 100, + max_font_atlases: 16, + allow_dynamic_font_size: false, } } } From 1b0fbea33498a136544edab20b710699ee6bc88f Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Tue, 23 Aug 2022 13:11:06 -0700 Subject: [PATCH 08/11] Add documentation to allow_dynamic_font_size --- crates/bevy_text/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 7cd7d402251bb..f18b225481c1b 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -43,6 +43,8 @@ pub struct TextPlugin; pub struct TextSettings { /// Number of font atlases supported in a FontAtlasSet pub max_font_atlases: usize, + /// Allows font size to be set dynamically exceeding the amount set in max_font_atlases. + /// Note each font size has to be generated which can have a strong performance impact. pub allow_dynamic_font_size: bool, } From 303a2858d26ff40ede241045c3bde4ed699effc9 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Fri, 26 Aug 2022 15:08:33 -0700 Subject: [PATCH 09/11] improve error message and fix documentation --- crates/bevy_text/src/error.rs | 2 +- crates/bevy_text/src/lib.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/bevy_text/src/error.rs b/crates/bevy_text/src/error.rs index 4d4091bd37380..bf6e4e774cc47 100644 --- a/crates/bevy_text/src/error.rs +++ b/crates/bevy_text/src/error.rs @@ -7,6 +7,6 @@ pub enum TextError { NoSuchFont, #[error("failed to add glyph to newly-created atlas {0:?}")] FailedToAddGlyph(GlyphId), - #[error("exceeded {0:?} available TextAltases for font. This can be caused by using an excessive number of font sizes. Consider using Transform::scale to modify font size dynamically. If you need more font sizes modify TextSettings.max_font_atlases." )] + #[error("exceeded {0:?} available TextAltases for font. This can be caused by using an excessive number of font sizes. If you are changing font sizes dynamically consider using Transform::scale to modify the size. If you need more font sizes modify TextSettings.max_font_atlases." )] ExceedMaxTextAtlases(usize), } diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index f18b225481c1b..07dddd7c7f18a 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -41,7 +41,7 @@ pub struct TextPlugin; /// `TextPlugin` settings #[derive(Resource)] pub struct TextSettings { - /// Number of font atlases supported in a FontAtlasSet + /// Maximum number of font atlases supported in a FontAtlasSet pub max_font_atlases: usize, /// Allows font size to be set dynamically exceeding the amount set in max_font_atlases. /// Note each font size has to be generated which can have a strong performance impact. @@ -65,14 +65,13 @@ impl Plugin for TextPlugin { .register_type::() .register_type::() .init_asset_loader::() + .init_resource::() .insert_resource(DefaultTextPipeline::default()) .add_system_to_stage( CoreStage::PostUpdate, update_text2d_layout.after(ModifiesWindows), ); - let _ = app.world.get_resource_or_insert_with(TextSettings::default); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_system_to_stage( RenderStage::Extract, From b2fc494279074a8fb7ad1483f0ef5a0a89ec4fb0 Mon Sep 17 00:00:00 2001 From: Xavier Vargas Date: Tue, 6 Sep 2022 15:44:28 -0700 Subject: [PATCH 10/11] Merge branch 'main' of https://github.com/bevyengine/bevy into font_fix --- .github/workflows/ci.yml | 4 + Cargo.toml | 20 ++ assets/scenes/load_scene_example.scn.ron | 6 +- crates/bevy_asset/src/io/wasm_asset_io.rs | 1 + crates/bevy_asset/src/lib.rs | 4 +- crates/bevy_asset/src/path.rs | 2 +- crates/bevy_audio/src/audio_source.rs | 2 +- crates/bevy_core/src/lib.rs | 26 ++- .../src/core_2d/camera_2d.rs | 5 +- crates/bevy_core_pipeline/src/core_3d/mod.rs | 1 + crates/bevy_core_pipeline/src/lib.rs | 7 +- crates/bevy_ecs/macros/src/lib.rs | 4 +- crates/bevy_ecs/src/entity/mod.rs | 76 +++++--- crates/bevy_ecs/src/event.rs | 25 ++- crates/bevy_ecs/src/lib.rs | 6 +- crates/bevy_ecs/src/query/fetch.rs | 2 +- crates/bevy_ecs/src/query/iter.rs | 53 ++++++ crates/bevy_ecs/src/schedule/mod.rs | 29 +-- crates/bevy_ecs/src/storage/table.rs | 8 - crates/bevy_ecs/src/system/mod.rs | 1 + crates/bevy_ecs/src/system/query.rs | 30 +-- crates/bevy_ecs/src/system/system_chaining.rs | 128 +++++++++++++ crates/bevy_ecs/src/system/system_param.rs | 90 ++++++++- crates/bevy_ecs/src/world/mod.rs | 106 ++--------- crates/bevy_hierarchy/src/child_builder.rs | 7 - crates/bevy_input/Cargo.toml | 1 + crates/bevy_pbr/src/render/mesh.rs | 37 ++-- .../bevy_pbr/src/render/mesh_functions.wgsl | 49 +++-- crates/bevy_pbr/src/render/mesh_types.wgsl | 2 + crates/bevy_pbr/src/render/pbr_functions.wgsl | 10 +- crates/bevy_reflect/src/array.rs | 7 + crates/bevy_reflect/src/impls/smallvec.rs | 34 +++- crates/bevy_reflect/src/impls/std.rs | 171 +++++++++++++++--- crates/bevy_reflect/src/lib.rs | 25 ++- crates/bevy_reflect/src/list.rs | 11 ++ crates/bevy_reflect/src/map.rs | 7 + crates/bevy_reflect/src/tuple.rs | 15 ++ crates/bevy_render/src/camera/camera.rs | 22 +-- crates/bevy_render/src/camera/mod.rs | 2 +- crates/bevy_render/src/camera/projection.rs | 19 -- crates/bevy_render/src/color/mod.rs | 4 +- crates/bevy_render/src/lib.rs | 12 +- crates/bevy_render/src/mesh/mesh/mod.rs | 8 +- crates/bevy_render/src/render_graph/graph.rs | 4 +- crates/bevy_render/src/view/visibility/mod.rs | 8 +- crates/bevy_sprite/src/lib.rs | 1 + .../src/mesh2d/color_material.wgsl | 7 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 19 +- crates/bevy_sprite/src/render/mod.rs | 15 +- crates/bevy_time/src/lib.rs | 2 + crates/bevy_time/src/time.rs | 4 +- crates/bevy_ui/src/flex/mod.rs | 16 +- crates/bevy_ui/src/geometry.rs | 12 ++ crates/bevy_ui/src/lib.rs | 24 ++- crates/bevy_ui/src/render/mod.rs | 3 +- crates/bevy_ui/src/ui_node.rs | 6 +- crates/bevy_ui/src/widget/text.rs | 13 +- crates/bevy_winit/src/winit_windows.rs | 2 +- deny.toml | 3 +- docs/linux_dependencies.md | 29 +++ examples/2d/mesh2d_manual.rs | 14 +- examples/2d/mesh2d_vertex_color_texture.rs | 26 ++- examples/3d/lighting.rs | 12 +- examples/3d/load_gltf.rs | 6 +- examples/3d/render_to_texture.rs | 14 +- examples/3d/shadow_biases.rs | 25 ++- examples/3d/shadow_caster_receiver.rs | 11 +- examples/3d/shapes.rs | 17 +- examples/3d/skybox.rs | 25 +-- examples/3d/split_screen.rs | 11 +- examples/3d/spotlight.rs | 14 +- examples/3d/texture.rs | 22 +-- examples/README.md | 2 + examples/android/android.rs | 2 +- examples/animation/animated_fox.rs | 9 +- examples/animation/animated_transform.rs | 22 +-- examples/animation/custom_skinned_mesh.rs | 6 +- examples/animation/gltf_skinned_mesh.rs | 6 +- examples/ecs/generic_system.rs | 8 +- examples/ecs/hierarchy.rs | 32 ++-- examples/games/alien_cake_addict.rs | 10 +- examples/games/breakout.rs | 22 +-- examples/shader/post_processing.rs | 2 +- examples/stress_tests/many_cubes.rs | 4 +- examples/stress_tests/many_foxes.rs | 13 +- examples/stress_tests/many_lights.rs | 4 +- examples/tools/scene_viewer.rs | 20 +- examples/transforms/scale.rs | 5 +- examples/transforms/transform.rs | 7 +- examples/ui/scaling.rs | 144 +++++++++++++++ examples/ui/ui.rs | 2 +- examples/window/window_resizing.rs | 93 ++++++++++ 92 files changed, 1327 insertions(+), 530 deletions(-) create mode 100644 examples/ui/scaling.rs create mode 100644 examples/window/window_resizing.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 624e3215057ec..41184438f9616 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,10 @@ jobs: ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-build-stable-${{ hashFiles('**/Cargo.toml') }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true - name: Install alsa and udev run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev if: runner.os == 'linux' diff --git a/Cargo.toml b/Cargo.toml index b0920d9ecd0fa..f2157f120d846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1452,6 +1452,16 @@ description = "Illustrates various features of Bevy UI" category = "UI (User Interface)" wasm = true +[[example]] +name = "ui_scaling" +path = "examples/ui/scaling.rs" + +[package.metadata.example.ui_scaling] +name = "UI Scaling" +description = "Illustrates how to scale the UI" +category = "UI (User Interface)" +wasm = true + # Window [[example]] name = "clear_color" @@ -1527,6 +1537,16 @@ path = "tests/window/minimising.rs" [package.metadata.example.minimising] hidden = true +[[example]] +name = "window_resizing" +path = "examples/window/window_resizing.rs" + +[package.metadata.example.window_resizing] +name = "Window Resizing" +description = "Demonstrates resizing and responding to resizing a window" +category = "Window" +wasm = true + # Android [[example]] crate-type = ["cdylib"] diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index 1f9de9dae7da8..338084e9671b6 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -6,15 +6,15 @@ "type": "bevy_transform::components::transform::Transform", "struct": { "translation": { - "type": "glam::vec3::Vec3", + "type": "glam::f32::vec3::Vec3", "value": (0.0, 0.0, 0.0), }, "rotation": { - "type": "glam::quat::Quat", + "type": "glam::f32::sse2::quat::Quat", "value": (0.0, 0.0, 0.0, 1.0), }, "scale": { - "type": "glam::vec3::Vec3", + "type": "glam::f32::vec3::Vec3", "value": (1.0, 1.0, 1.0), }, }, diff --git a/crates/bevy_asset/src/io/wasm_asset_io.rs b/crates/bevy_asset/src/io/wasm_asset_io.rs index c4ecae76cf71a..1cf8d3b2715ea 100644 --- a/crates/bevy_asset/src/io/wasm_asset_io.rs +++ b/crates/bevy_asset/src/io/wasm_asset_io.rs @@ -52,6 +52,7 @@ impl AssetIo for WasmAssetIo { &self, _path: &Path, ) -> Result>, AssetIoError> { + bevy_log::warn!("Loading folders is not supported in WASM"); Ok(Box::new(std::iter::empty::())) } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 4a8d37c27a24a..f57e2d0123409 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -64,7 +64,9 @@ pub enum AssetStage { #[derive(Default)] pub struct AssetPlugin; -/// [`AssetServer`] settings. +/// Settings for the [`AssetServer`]. +/// +/// This resource must be added before the [`AssetPlugin`] or `DefaultPlugins` to take effect. #[derive(Resource)] pub struct AssetServerSettings { /// The base folder where assets are loaded from, relative to the executable. diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index d47bca18e7617..1cfbc87dcc33d 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -154,7 +154,7 @@ impl<'a, 'b> From<&'a AssetPath<'b>> for AssetPathId { impl<'a> From<&'a str> for AssetPath<'a> { fn from(asset_path: &'a str) -> Self { - let mut parts = asset_path.split('#'); + let mut parts = asset_path.splitn(2, '#'); let path = Path::new(parts.next().expect("Path must be set.")); let label = parts.next(); AssetPath { diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 4fcf2a4c36a81..217c9aa868350 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -58,7 +58,7 @@ impl AssetLoader for AudioLoader { /// A type implementing this trait can be decoded as a rodio source pub trait Decodable: Send + Sync + 'static { /// The decoder that can decode the implementing type - type Decoder: rodio::Source + Send + Sync + Iterator; + type Decoder: rodio::Source + Send + Iterator; /// A single value given by the decoder type DecoderItem: rodio::Sample + Send + Sync; diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 41b8e02d4935e..7cc0735c98503 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -16,7 +16,8 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::entity::Entity; -use bevy_utils::HashSet; +use bevy_utils::{Duration, HashSet, Instant}; +use std::borrow::Cow; use std::ops::Range; /// Adds core functionality to Apps. @@ -43,7 +44,10 @@ fn register_rust_types(app: &mut App) { app.register_type::>() .register_type::() .register_type::>() - .register_type::>(); + .register_type::>() + .register_type::>() + .register_type::() + .register_type::(); } fn register_math_types(app: &mut App) { @@ -53,11 +57,29 @@ fn register_math_types(app: &mut 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::(); } diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index e389ca68331d7..b94b59a0b6525 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -2,9 +2,7 @@ use crate::clear_color::ClearColorConfig; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_reflect::Reflect; use bevy_render::{ - camera::{ - Camera, CameraProjection, CameraRenderGraph, DepthCalculation, OrthographicProjection, - }, + camera::{Camera, CameraProjection, CameraRenderGraph, OrthographicProjection}, extract_component::ExtractComponent, primitives::Frustum, view::VisibleEntities, @@ -56,7 +54,6 @@ impl Camera2dBundle { // the camera's translation by far and use a right handed coordinate system let projection = OrthographicProjection { far, - depth_calculation: DepthCalculation::ZDifference, ..Default::default() }; let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index c69e8104dc15f..a78528b46f0f5 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -43,6 +43,7 @@ pub struct Core3dPlugin; impl Plugin for Core3dPlugin { fn build(&self, app: &mut App) { app.register_type::() + .register_type::() .add_plugin(ExtractComponentPlugin::::default()); let render_app = match app.get_sub_app_mut(RenderApp) { diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index 28304608e7661..0cc46286148d8 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -11,7 +11,11 @@ pub mod prelude { }; } -use crate::{clear_color::ClearColor, core_2d::Core2dPlugin, core_3d::Core3dPlugin}; +use crate::{ + clear_color::{ClearColor, ClearColorConfig}, + core_2d::Core2dPlugin, + core_3d::Core3dPlugin, +}; use bevy_app::{App, Plugin}; use bevy_render::extract_resource::ExtractResourcePlugin; @@ -21,6 +25,7 @@ pub struct CorePipelinePlugin; impl Plugin for CorePipelinePlugin { fn build(&self, app: &mut App) { app.register_type::() + .register_type::() .init_resource::() .add_plugin(ExtractResourcePlugin::::default()) .add_plugin(Core2dPlugin) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 98fbe27200b2e..f518e0a0e73f7 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -149,8 +149,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { fn component_ids( components: &mut #ecs_path::component::Components, storages: &mut #ecs_path::storage::Storages, - ) -> Vec<#ecs_path::component::ComponentId> { - let mut component_ids = Vec::with_capacity(#field_len); + ) -> ::std::vec::Vec<#ecs_path::component::ComponentId> { + let mut component_ids = ::std::vec::Vec::with_capacity(#field_len); #(#field_component_ids)* component_ids } diff --git a/crates/bevy_ecs/src/entity/mod.rs b/crates/bevy_ecs/src/entity/mod.rs index e022483fa836c..80de2bb9fd008 100644 --- a/crates/bevy_ecs/src/entity/mod.rs +++ b/crates/bevy_ecs/src/entity/mod.rs @@ -40,11 +40,20 @@ pub use self::serde::*; pub use map_entities::*; use crate::{archetype::ArchetypeId, storage::SparseSetIndex}; -use std::{ - convert::TryFrom, - fmt, mem, - sync::atomic::{AtomicI64, Ordering}, -}; +use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering}; + +#[cfg(target_has_atomic = "64")] +use std::sync::atomic::AtomicI64 as AtomicIdCursor; +#[cfg(target_has_atomic = "64")] +type IdCursor = i64; + +/// Most modern platforms support 64-bit atomics, but some less-common platforms +/// do not. This fallback allows compilation using a 32-bit cursor instead, with +/// the caveat that some conversions may fail (and panic) at runtime. +#[cfg(not(target_has_atomic = "64"))] +use std::sync::atomic::AtomicIsize as AtomicIdCursor; +#[cfg(not(target_has_atomic = "64"))] +type IdCursor = isize; /// Lightweight identifier of an [entity](crate::entity). /// @@ -157,7 +166,7 @@ impl Entity { /// } /// } /// ``` - pub fn from_raw(id: u32) -> Entity { + pub const fn from_raw(id: u32) -> Entity { Entity { id, generation: 0 } } @@ -174,7 +183,7 @@ impl Entity { /// Reconstruct an `Entity` previously destructured with [`Entity::to_bits`]. /// /// Only useful when applied to results from `to_bits` in the same instance of an application. - pub fn from_bits(bits: u64) -> Self { + pub const fn from_bits(bits: u64) -> Self { Self { generation: (bits >> 32) as u32, id: bits as u32, @@ -187,7 +196,7 @@ impl Entity { /// with both live and dead entities. Useful for compactly representing entities within a /// specific snapshot of the world, such as when serializing. #[inline] - pub fn id(self) -> u32 { + pub const fn id(self) -> u32 { self.id } @@ -195,7 +204,7 @@ impl Entity { /// entity with a given id is despawned. This serves as a "count" of the number of times a /// given id has been reused (id, generation) pairs uniquely identify a given Entity. #[inline] - pub fn generation(self) -> u32 { + pub const fn generation(self) -> u32 { self.generation } } @@ -291,7 +300,7 @@ pub struct Entities { /// /// Once `flush()` is done, `free_cursor` will equal `pending.len()`. pending: Vec, - free_cursor: AtomicI64, + free_cursor: AtomicIdCursor, /// Stores the number of free entities for [`len`](Entities::len) len: u32, } @@ -304,8 +313,12 @@ impl Entities { // Use one atomic subtract to grab a range of new IDs. The range might be // entirely nonnegative, meaning all IDs come from the freelist, or entirely // negative, meaning they are all new IDs to allocate, or a mix of both. - let range_end = self.free_cursor.fetch_sub(count as i64, Ordering::Relaxed); - let range_start = range_end - count as i64; + let range_end = self + .free_cursor + // Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics + // and use AtomicIsize instead (see note on `IdCursor`). + .fetch_sub(IdCursor::try_from(count).unwrap(), Ordering::Relaxed); + let range_start = range_end - IdCursor::try_from(count).unwrap(); let freelist_range = range_start.max(0) as usize..range_end.max(0) as usize; @@ -322,7 +335,7 @@ impl Entities { // In this example, we truncate the end to 0, leaving us with `-3..0`. // Then we negate these values to indicate how far beyond the end of `meta.end()` // to go, yielding `meta.len()+0 .. meta.len()+3`. - let base = self.meta.len() as i64; + let base = self.meta.len() as IdCursor; let new_id_end = u32::try_from(base - range_start).expect("too many entities"); @@ -359,7 +372,7 @@ impl Entities { // and farther beyond `meta.len()`. Entity { generation: 0, - id: u32::try_from(self.meta.len() as i64 - n).expect("too many entities"), + id: u32::try_from(self.meta.len() as IdCursor - n).expect("too many entities"), } } } @@ -377,7 +390,7 @@ impl Entities { self.verify_flushed(); self.len += 1; if let Some(id) = self.pending.pop() { - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; Entity { generation: self.meta[id as usize].generation, @@ -399,14 +412,14 @@ impl Entities { let loc = if entity.id as usize >= self.meta.len() { self.pending.extend((self.meta.len() as u32)..entity.id); - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY); self.len += 1; None } else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) { self.pending.swap_remove(index); - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; self.len += 1; None @@ -430,14 +443,14 @@ impl Entities { let result = if entity.id as usize >= self.meta.len() { self.pending.extend((self.meta.len() as u32)..entity.id); - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; self.meta.resize(entity.id as usize + 1, EntityMeta::EMPTY); self.len += 1; AllocAtWithoutReplacement::DidNotExist } else if let Some(index) = self.pending.iter().position(|item| *item == entity.id) { self.pending.swap_remove(index); - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; self.len += 1; AllocAtWithoutReplacement::DidNotExist @@ -472,7 +485,7 @@ impl Entities { self.pending.push(entity.id); - let new_free_cursor = self.pending.len() as i64; + let new_free_cursor = self.pending.len() as IdCursor; *self.free_cursor.get_mut() = new_free_cursor; self.len -= 1; Some(loc) @@ -483,7 +496,9 @@ impl Entities { self.verify_flushed(); let freelist_size = *self.free_cursor.get_mut(); - let shortfall = additional as i64 - freelist_size; + // Unwrap: these conversions can only fail on platforms that don't support 64-bit atomics + // and use AtomicIsize instead (see note on `IdCursor`). + let shortfall = IdCursor::try_from(additional).unwrap() - freelist_size; if shortfall > 0 { self.meta.reserve(shortfall as usize); } @@ -540,7 +555,7 @@ impl Entities { } fn needs_flush(&mut self) -> bool { - *self.free_cursor.get_mut() != self.pending.len() as i64 + *self.free_cursor.get_mut() != self.pending.len() as IdCursor } /// Allocates space for entities previously reserved with `reserve_entity` or @@ -682,4 +697,21 @@ mod tests { assert!(entities.contains(e)); assert!(entities.get(e).is_none()); } + + #[test] + fn entity_const() { + const C1: Entity = Entity::from_raw(42); + assert_eq!(42, C1.id); + assert_eq!(0, C1.generation); + + const C2: Entity = Entity::from_bits(0x0000_00ff_0000_00cc); + assert_eq!(0x0000_00cc, C2.id); + assert_eq!(0x0000_00ff, C2.generation); + + const C3: u32 = Entity::from_raw(33).id(); + assert_eq!(33, C3); + + const C4: u32 = Entity::from_bits(0x00dd_00ff_0000_0000).generation(); + assert_eq!(0x00dd_00ff, C4); + } } diff --git a/crates/bevy_ecs/src/event.rs b/crates/bevy_ecs/src/event.rs index 04a7e6d74906e..47972a4fa46ee 100644 --- a/crates/bevy_ecs/src/event.rs +++ b/crates/bevy_ecs/src/event.rs @@ -2,7 +2,7 @@ use crate as bevy_ecs; use crate::system::{Local, Res, ResMut, Resource, SystemParam}; -use bevy_utils::tracing::trace; +use bevy_utils::tracing::{trace, warn}; use std::ops::{Deref, DerefMut}; use std::{ fmt::{self}, @@ -149,6 +149,14 @@ impl Default for Events { } } +impl Events { + pub fn oldest_event_count(&self) -> usize { + self.events_a + .start_event_count + .min(self.events_b.start_event_count) + } +} + #[derive(Debug)] struct EventSequence { events: Vec>, @@ -351,10 +359,18 @@ impl ManualEventReader { + ExactSizeIterator)> { // if the reader has seen some of the events in a buffer, find the proper index offset. // otherwise read all events in the buffer + let missed = self.missed_events(events); + if missed > 0 { + let plural = if missed == 1 { "event" } else { "events" }; + let type_name = std::any::type_name::(); + warn!("Missed {missed} `{type_name}` {plural}. Consider reading from the `EventReader` more often (generally the best solution) or calling Events::update() less frequently (normally this is called once per frame). This problem is most likely due to run criteria/fixed timesteps or consuming events conditionally. See the Events documentation for more information."); + } + let a_index = (self.last_event_count).saturating_sub(events.events_a.start_event_count); let b_index = (self.last_event_count).saturating_sub(events.events_b.start_event_count); let a = events.events_a.get(a_index..).unwrap_or_default(); let b = events.events_b.get(b_index..).unwrap_or_default(); + let unread_count = a.len() + b.len(); // Ensure `len` is implemented correctly debug_assert_eq!(unread_count, self.len(events)); @@ -379,6 +395,13 @@ impl ManualEventReader { .min(events.len()) } + /// Amount of events we missed. + pub fn missed_events(&self, events: &Events) -> usize { + events + .oldest_event_count() + .saturating_sub(self.last_event_count) + } + /// See [`EventReader::is_empty`] pub fn is_empty(&self, events: &Events) -> bool { self.len(events) == 0 diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3b3e3e2b18d37..d129f4c833d68 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -39,9 +39,9 @@ pub mod prelude { StageLabel, State, SystemLabel, SystemSet, SystemStage, }, system::{ - Commands, In, IntoChainSystem, IntoExclusiveSystem, IntoSystem, Local, NonSend, - NonSendMut, ParallelCommands, ParamSet, Query, RemovedComponents, Res, ResMut, - Resource, System, SystemParamFunction, + adapter as system_adapter, Commands, In, IntoChainSystem, IntoExclusiveSystem, + IntoSystem, Local, NonSend, NonSendMut, ParallelCommands, ParamSet, Query, + RemovedComponents, Res, ResMut, Resource, System, SystemParamFunction, }, world::{FromWorld, Mut, World}, }; diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 21fab0db52367..ddcee9905b75c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1413,7 +1413,7 @@ macro_rules! impl_tuple_fetch { /// The `AnyOf` query parameter fetches entities with any of the component types included in T. /// -/// `Query>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), (Or(With, With, With)>`. +/// `Query>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), Or<(With, With, With)>>`. /// Each of the components in `T` is returned as an `Option`, as with `Option` queries. /// Entities are guaranteed to have at least one of the components in `T`. #[derive(Clone)] diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 1c8f7e92562d0..b75b13aa54e92 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -214,6 +214,59 @@ where { } +/// An iterator over `K`-sized combinations of query items without repetition. +/// +/// In this context, a combination is an unordered subset of `K` elements. +/// The number of combinations depend on how `K` relates to the number of entities matching the [`Query`] (called `N`): +/// - if `K = N`, only one combination exists, +/// - if `K < N`, there are NCK combinations (see the [performance section] of `Query`), +/// - if `K > N`, there are no combinations. +/// +/// # Usage +/// +/// This type is returned by calling [`Query::iter_combinations`] or [`Query::iter_combinations_mut`]. +/// +/// It implements [`Iterator`] only if it iterates over read-only query items ([learn more]). +/// +/// In the case of mutable query items, it can be iterated by calling [`fetch_next`] in a `while let` loop. +/// +/// # Examples +/// +/// The following example shows how to traverse the iterator when the query items are read-only. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Component)] +/// # struct ComponentA; +/// # +/// fn some_system(query: Query<&ComponentA>) { +/// for [a1, a2] in query.iter_combinations() { +/// // ... +/// } +/// } +/// ``` +/// +/// The following example shows how `fetch_next` should be called with a `while let` loop to traverse the iterator when the query items are mutable. +/// +/// ``` +/// # use bevy_ecs::prelude::*; +/// # #[derive(Component)] +/// # struct ComponentA; +/// # +/// fn some_system(mut query: Query<&mut ComponentA>) { +/// let mut combinations = query.iter_combinations_mut(); +/// while let Some([a1, a2]) = combinations.fetch_next() { +/// // ... +/// } +/// } +/// ``` +/// +/// [`fetch_next`]: Self::fetch_next +/// [learn more]: Self#impl-Iterator +/// [performance section]: crate::system::Query#performance +/// [`Query`]: crate::system::Query +/// [`Query::iter_combinations`]: crate::system::Query::iter_combinations +/// [`Query::iter_combinations_mut`]: crate::system::Query::iter_combinations_mut pub struct QueryCombinationIter<'w, 's, Q: WorldQuery, F: WorldQuery, const K: usize> { tables: &'w Tables, archetypes: &'w Archetypes, diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 2e1bd167caedf..0ac1e8702d685 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -211,9 +211,10 @@ impl Schedule { ) } + let label = stage_label.as_label(); let stage = self - .get_stage_mut::(&stage_label) - .unwrap_or_else(move || stage_not_found(&stage_label.as_label())); + .get_stage_mut::(label) + .unwrap_or_else(move || stage_not_found(&label)); stage.add_system(system); self } @@ -278,14 +279,12 @@ impl Schedule { /// Panics if `label` refers to a non-existing stage, or if it's not of type `T`. pub fn stage &mut T>( &mut self, - label: impl StageLabel, + stage_label: impl StageLabel, func: F, ) -> &mut Self { - let stage = self.get_stage_mut::(&label).unwrap_or_else(move || { - panic!( - "stage '{:?}' does not exist or is the wrong type", - label.as_label() - ) + let label = stage_label.as_label(); + let stage = self.get_stage_mut::(label).unwrap_or_else(move || { + panic!("stage '{label:?}' does not exist or is the wrong type",) }); func(stage); self @@ -304,11 +303,12 @@ impl Schedule { /// # let mut schedule = Schedule::default(); /// # schedule.add_stage("my_stage", SystemStage::parallel()); /// # - /// let stage = schedule.get_stage::(&"my_stage").unwrap(); + /// let stage = schedule.get_stage::("my_stage").unwrap(); /// ``` - pub fn get_stage(&self, label: &dyn StageLabel) -> Option<&T> { + pub fn get_stage(&self, stage_label: impl StageLabel) -> Option<&T> { + let label = stage_label.as_label(); self.stages - .get(&label.as_label()) + .get(&label) .and_then(|stage| stage.downcast_ref::()) } @@ -325,11 +325,12 @@ impl Schedule { /// # let mut schedule = Schedule::default(); /// # schedule.add_stage("my_stage", SystemStage::parallel()); /// # - /// let stage = schedule.get_stage_mut::(&"my_stage").unwrap(); + /// let stage = schedule.get_stage_mut::("my_stage").unwrap(); /// ``` - pub fn get_stage_mut(&mut self, label: &dyn StageLabel) -> Option<&mut T> { + pub fn get_stage_mut(&mut self, stage_label: impl StageLabel) -> Option<&mut T> { + let label = stage_label.as_label(); self.stages - .get_mut(&label.as_label()) + .get_mut(&label) .and_then(|stage| stage.downcast_mut::()) } diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index d1c83e4d6bc45..bdfbf51e0f9a5 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -96,14 +96,6 @@ impl Column { self.data.is_empty() } - /// # Safety - /// index must be in-bounds - #[inline] - pub(crate) unsafe fn get_ticks_unchecked_mut(&mut self, row: usize) -> &mut ComponentTicks { - debug_assert!(row < self.len()); - self.ticks.get_unchecked_mut(row).get_mut() - } - /// # Safety /// index must be in-bounds #[inline] diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index ec3fd6ea78553..aef76c277a5ec 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -59,6 +59,7 @@ //! - [`NonSendMut`] and `Option` //! - [`&World`](crate::world::World) //! - [`RemovedComponents`] +//! - [`SystemName`] //! - [`SystemChangeTick`] //! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata) //! - [`Bundles`](crate::bundle::Bundles) (Provides Bundles metadata) diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index d1bf9b2b747c6..ad6dcc02f1b45 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -283,7 +283,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// For example, `Query<(&mut A, &B, &mut C), With>` will become `Query<(&A, &B, &C), With>`. /// This can be useful when working around the borrow checker, /// or reusing functionality between systems via functions that accept query types. - pub fn to_readonly(&self) -> Query<'_, '_, Q::ReadOnly, F::ReadOnly> { + pub fn to_readonly(&self) -> Query<'_, 's, Q::ReadOnly, F::ReadOnly> { let new_state = self.state.as_readonly(); // SAFETY: This is memory safe because it turns the query immutable. unsafe { @@ -372,7 +372,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { #[inline] pub fn iter_combinations( &self, - ) -> QueryCombinationIter<'_, '_, Q::ReadOnly, F::ReadOnly, K> { + ) -> QueryCombinationIter<'_, 's, Q::ReadOnly, F::ReadOnly, K> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { @@ -409,7 +409,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { #[inline] pub fn iter_combinations_mut( &mut self, - ) -> QueryCombinationIter<'_, '_, Q, F, K> { + ) -> QueryCombinationIter<'_, 's, Q, F, K> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { @@ -455,7 +455,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { pub fn iter_many( &self, entities: EntityList, - ) -> QueryManyIter<'_, '_, Q::ReadOnly, F::ReadOnly, EntityList::IntoIter> + ) -> QueryManyIter<'_, 's, Q::ReadOnly, F::ReadOnly, EntityList::IntoIter> where EntityList::Item: Borrow, { @@ -504,7 +504,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { pub fn iter_many_mut( &mut self, entities: EntityList, - ) -> QueryManyIter<'_, '_, Q, F, EntityList::IntoIter> + ) -> QueryManyIter<'_, 's, Q, F, EntityList::IntoIter> where EntityList::Item: Borrow, { @@ -527,7 +527,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// This function makes it possible to violate Rust's aliasing guarantees. You must make sure /// this call does not result in multiple mutable references to the same component #[inline] - pub unsafe fn iter_unsafe(&'s self) -> QueryIter<'w, 's, Q, F> { + pub unsafe fn iter_unsafe(&self) -> QueryIter<'_, 's, Q, F> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict self.state @@ -543,7 +543,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { #[inline] pub unsafe fn iter_combinations_unsafe( &self, - ) -> QueryCombinationIter<'_, '_, Q, F, K> { + ) -> QueryCombinationIter<'_, 's, Q, F, K> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict self.state.iter_combinations_unchecked_manual( @@ -564,7 +564,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { pub unsafe fn iter_many_unsafe( &self, entities: EntityList, - ) -> QueryManyIter<'_, '_, Q, F, EntityList::IntoIter> + ) -> QueryManyIter<'_, 's, Q, F, EntityList::IntoIter> where EntityList::Item: Borrow, { @@ -635,7 +635,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// # bevy_ecs::system::assert_is_system(gravity_system); /// ``` #[inline] - pub fn for_each_mut<'a, FN: FnMut(QueryItem<'a, Q>)>(&'a mut self, f: FN) { + pub fn for_each_mut<'a>(&'a mut self, f: impl FnMut(QueryItem<'a, Q>)) { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict unsafe { @@ -701,10 +701,10 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// /// [`ComputeTaskPool`]: bevy_tasks::prelude::ComputeTaskPool #[inline] - pub fn par_for_each_mut<'a, FN: Fn(QueryItem<'a, Q>) + Send + Sync + Clone>( + pub fn par_for_each_mut<'a>( &'a mut self, batch_size: usize, - f: FN, + f: impl Fn(QueryItem<'a, Q>) + Send + Sync + Clone, ) { // SAFETY: system runs without conflicts with other systems. same-system queries have runtime // borrow checks when they conflict @@ -947,9 +947,9 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// this call does not result in multiple mutable references to the same component #[inline] pub unsafe fn get_unchecked( - &'s self, + &self, entity: Entity, - ) -> Result, QueryEntityError> { + ) -> Result, QueryEntityError> { // SEMI-SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict self.state @@ -1374,7 +1374,7 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// # bevy_ecs::system::assert_is_system(print_selected_character_name_system); /// ``` #[inline] - pub fn get_inner(&'s self, entity: Entity) -> Result, QueryEntityError> { + pub fn get_inner(&self, entity: Entity) -> Result, QueryEntityError> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { @@ -1411,7 +1411,7 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: WorldQuery> Query<'w, 's, Q, F> { /// # bevy_ecs::system::assert_is_system(report_names_system); /// ``` #[inline] - pub fn iter_inner(&'s self) -> QueryIter<'w, 's, Q::ReadOnly, F::ReadOnly> { + pub fn iter_inner(&self) -> QueryIter<'w, 's, Q::ReadOnly, F::ReadOnly> { // SAFETY: system runs without conflicts with other systems. // same-system queries have runtime borrow checks when they conflict unsafe { diff --git a/crates/bevy_ecs/src/system/system_chaining.rs b/crates/bevy_ecs/src/system/system_chaining.rs index 0fac40e231389..0a338cf0f4863 100644 --- a/crates/bevy_ecs/src/system/system_chaining.rs +++ b/crates/bevy_ecs/src/system/system_chaining.rs @@ -142,3 +142,131 @@ where } } } + +/// A collection of common adapters for [chaining](super::ChainSystem) the result of a system. +pub mod adapter { + use crate::system::In; + use std::fmt::Debug; + + /// Converts a regular function into a system adapter. + /// + /// # Examples + /// ``` + /// use bevy_ecs::prelude::*; + /// + /// return1 + /// .chain(system_adapter::new(u32::try_from)) + /// .chain(system_adapter::unwrap) + /// .chain(print); + /// + /// fn return1() -> u64 { 1 } + /// fn print(In(x): In) { + /// println!("{x:?}"); + /// } + /// ``` + pub fn new(mut f: impl FnMut(T) -> U) -> impl FnMut(In) -> U { + move |In(x)| f(x) + } + + /// System adapter that unwraps the `Ok` variant of a [`Result`]. + /// This is useful for fallible systems that should panic in the case of an error. + /// + /// There is no equivalent adapter for [`Option`]. Instead, it's best to provide + /// an error message and convert to a `Result` using `ok_or{_else}`. + /// + /// # Examples + /// + /// Panicking on error + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::single_threaded()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // Panic if the load system returns an error. + /// load_save_system.chain(system_adapter::unwrap) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system which may fail irreparably. + /// fn load_save_system() -> Result<(), std::io::Error> { + /// let save_file = open_file("my_save.json")?; + /// dbg!(save_file); + /// Ok(()) + /// } + /// # fn open_file(name: &str) -> Result<&'static str, std::io::Error> + /// # { Ok("hello world") } + /// ``` + pub fn unwrap(In(res): In>) -> T { + res.unwrap() + } + + /// System adapter that ignores the output of the previous system in a chain. + /// This is useful for fallible systems that should simply return early in case of an `Err`/`None`. + /// + /// # Examples + /// + /// Returning early + /// + /// ``` + /// use bevy_ecs::prelude::*; + /// + /// // Marker component for an enemy entity. + /// #[derive(Component)] + /// struct Monster; + /// # + /// # #[derive(StageLabel)] + /// # enum CoreStage { Update }; + /// + /// // Building a new schedule/app... + /// # use bevy_ecs::schedule::SystemStage; + /// # let mut sched = Schedule::default(); sched + /// # .add_stage(CoreStage::Update, SystemStage::single_threaded()) + /// .add_system_to_stage( + /// CoreStage::Update, + /// // If the system fails, just move on and try again next frame. + /// fallible_system.chain(system_adapter::ignore) + /// ) + /// // ... + /// # ; + /// # let mut world = World::new(); + /// # sched.run(&mut world); + /// + /// // A system which may return early. It's more convenient to use the `?` operator for this. + /// fn fallible_system( + /// q: Query> + /// ) -> Option<()> { + /// let monster_id = q.iter().next()?; + /// println!("Monster entity is {monster_id:?}"); + /// Some(()) + /// } + /// ``` + pub fn ignore(In(_): In) {} + + #[cfg(test)] + #[test] + fn assert_systems() { + use std::str::FromStr; + + use crate::{prelude::*, system::assert_is_system}; + + /// Mocks a system that returns a value of type `T`. + fn returning() -> T { + unimplemented!() + } + + assert_is_system(returning::>.chain(unwrap)); + assert_is_system(returning::>.chain(ignore)); + assert_is_system(returning::<&str>.chain(new(u64::from_str)).chain(unwrap)); + } +} diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7523fe1ac9fbe..8762bd64c4255 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -16,6 +16,7 @@ pub use bevy_ecs_macros::SystemParam; use bevy_ecs_macros::{all_tuples, impl_param_set}; use bevy_ptr::UnsafeCellDeref; use std::{ + borrow::Cow, fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, @@ -675,7 +676,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { /// ``` pub struct Local<'a, T: FromWorld + Send + Sync + 'static>(&'a mut T); -// SAFE: Local only accesses internal state +// SAFETY: Local only accesses internal state unsafe impl ReadOnlySystemParamFetch for LocalState {} impl<'a, T: FromWorld + Send + Sync + 'static> Debug for Local<'a, T> @@ -711,7 +712,7 @@ impl<'a, T: Send + Sync + 'static + FromWorld> SystemParam for Local<'a, T> { type Fetch = LocalState; } -// SAFE: only local state is accessed +// SAFETY: only local state is accessed unsafe impl SystemParamState for LocalState { fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { Self(T::from_world(world)) @@ -1304,6 +1305,91 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemChangeTickState { } } +/// Name of the system that corresponds to this [`crate::system::SystemState`]. +/// +/// This is not a reliable identifier, it is more so useful for debugging +/// purposes of finding where a system parameter is being used incorrectly. +pub struct SystemName<'s> { + name: &'s str, +} + +impl<'s> SystemName<'s> { + pub fn name(&self) -> &str { + self.name + } +} + +impl<'s> Deref for SystemName<'s> { + type Target = str; + fn deref(&self) -> &Self::Target { + self.name() + } +} + +impl<'s> AsRef for SystemName<'s> { + fn as_ref(&self) -> &str { + self.name() + } +} + +impl<'s> From> for &'s str { + fn from(name: SystemName<'s>) -> &'s str { + name.name + } +} + +impl<'s> std::fmt::Debug for SystemName<'s> { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_tuple("SystemName").field(&self.name()).finish() + } +} + +impl<'s> std::fmt::Display for SystemName<'s> { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.name(), f) + } +} + +impl<'s> SystemParam for SystemName<'s> { + type Fetch = SystemNameState; +} + +// SAFETY: Only reads internal system state +unsafe impl ReadOnlySystemParamFetch for SystemNameState {} + +/// The [`SystemParamState`] of [`SystemName`]. +#[doc(hidden)] +pub struct SystemNameState { + name: Cow<'static, str>, +} + +// SAFETY: no component value access +unsafe impl SystemParamState for SystemNameState { + fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { + Self { + name: system_meta.name.clone(), + } + } +} + +impl<'w, 's> SystemParamFetch<'w, 's> for SystemNameState { + type Item = SystemName<'s>; + + #[inline] + unsafe fn get_param( + state: &'s mut Self, + _system_meta: &SystemMeta, + _world: &'w World, + _change_tick: u32, + ) -> Self::Item { + SystemName { + name: state.name.as_ref(), + } + } +} + macro_rules! impl_system_param_tuple { ($($param: ident),*) => { impl<$($param: SystemParam),*> SystemParam for ($($param,)*) { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 314bea0d4d82b..82863ef36cae1 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -660,8 +660,12 @@ impl World { #[inline] pub fn insert_resource(&mut self, value: R) { let component_id = self.components.init_resource::(); - // SAFETY: component_id just initialized and corresponds to resource of type T - unsafe { self.insert_resource_with_id(component_id, value) }; + OwningPtr::make(value, |ptr| { + // SAFETY: component_id was just initialized and corresponds to resource of type R + unsafe { + self.insert_resource_by_id(component_id, ptr); + } + }); } /// Inserts a new non-send resource with standard starting values. @@ -688,8 +692,12 @@ impl World { pub fn insert_non_send_resource(&mut self, value: R) { self.validate_non_send_access::(); let component_id = self.components.init_non_send::(); - // SAFETY: component_id just initialized and corresponds to resource of type R - unsafe { self.insert_resource_with_id(component_id, value) }; + OwningPtr::make(value, |ptr| { + // SAFETY: component_id was just initialized and corresponds to resource of type R + unsafe { + self.insert_resource_by_id(component_id, ptr); + } + }); } /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. @@ -1111,7 +1119,10 @@ impl World { }, }; let result = f(self, value_mut); - assert!(!self.contains_resource::()); + assert!(!self.contains_resource::(), + "Resource `{}` was inserted during a call to World::resource_scope.\n\ + This is not allowed as the original resource is reinserted to the world after the FnOnce param is invoked.", + std::any::type_name::()); let resource_archetype = self.archetypes.resource_mut(); let unique_components = resource_archetype.unique_components_mut(); @@ -1205,24 +1216,6 @@ impl World { self.get_resource_unchecked_mut_with_id(component_id) } - /// # Safety - /// `component_id` must be valid and correspond to a resource component of type `R` - #[inline] - unsafe fn insert_resource_with_id(&mut self, component_id: ComponentId, value: R) { - let change_tick = self.change_tick(); - let column = self.initialize_resource_internal(component_id); - if column.is_empty() { - // SAFETY: column is of type R and has been allocated above - OwningPtr::make(value, |ptr| { - column.push(ptr, ComponentTicks::new(change_tick)); - }); - } else { - // SAFETY: column is of type R and has already been allocated - *column.get_data_unchecked_mut(0).deref_mut::() = value; - column.get_ticks_unchecked_mut(0).set_changed(change_tick); - } - } - /// Inserts a new resource with the given `value`. Will replace the value if it already existed. /// /// **You should prefer to use the typed API [`World::insert_resource`] where possible and only @@ -1230,6 +1223,8 @@ impl World { /// /// # Safety /// The value referenced by `value` must be valid for the given [`ComponentId`] of this world + /// `component_id` must exist in this [`World`] + #[inline] pub unsafe fn insert_resource_by_id( &mut self, component_id: ComponentId, @@ -1237,12 +1232,7 @@ impl World { ) { let change_tick = self.change_tick(); - self.components().get_info(component_id).unwrap_or_else(|| { - panic!( - "insert_resource_by_id called with component id which doesn't exist in this world" - ) - }); - // SAFETY: component_id is valid, checked by the lines above + // SAFETY: component_id is valid, ensured by caller let column = self.initialize_resource_internal(component_id); if column.is_empty() { // SAFETY: column is of type R and has been allocated above @@ -1561,7 +1551,7 @@ mod tests { use super::World; use crate::{ change_detection::DetectChanges, - component::{ComponentDescriptor, ComponentId, ComponentInfo, StorageType}, + component::{ComponentDescriptor, ComponentInfo, StorageType}, ptr::OwningPtr, system::Resource, }; @@ -1780,62 +1770,6 @@ mod tests { assert_eq!(DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst), 1); } - #[test] - fn insert_resource_by_id_drop_old() { - let drop_test_helper = DropTestHelper::new(); - let res = std::panic::catch_unwind(|| { - let mut world = World::new(); - - world.insert_resource(drop_test_helper.make_component(false, 0)); - let component_id = world - .components() - .get_resource_id(std::any::TypeId::of::()) - .unwrap(); - - OwningPtr::make(drop_test_helper.make_component(true, 1), |ptr| { - // SAFETY: value is valid for the component layout - unsafe { - world.insert_resource_by_id(component_id, ptr); - } - }); - - OwningPtr::make(drop_test_helper.make_component(false, 2), |ptr| { - // SAFETY: value is valid for the component layout - unsafe { - world.insert_resource_by_id(component_id, ptr); - } - }); - }); - - let drop_log = drop_test_helper.finish(res); - - assert_eq!( - drop_log, - [ - DropLogItem::Create(0), - DropLogItem::Create(1), - DropLogItem::Drop(0), // inserting 1 drops 0 - DropLogItem::Create(2), - DropLogItem::Drop(1), // inserting 2 drops 1 - DropLogItem::Drop(2), // 2 could not be inserted because dropping 1 panicked, so it is dropped as well - ] - ); - } - - #[test] - #[should_panic = "insert_resource_by_id called with component id which doesn't exist in this world"] - fn insert_resource_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); - - let mut world = World::new(); - OwningPtr::make((), |ptr| { - // SAFETY: ptr must be valid for the component_id `invalid_component_id` which is invalid, but checked by `insert_resource_by_id` - unsafe { - world.insert_resource_by_id(invalid_component_id, ptr); - } - }); - } - #[derive(Component)] struct Foo; diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 2fc9fa2d0d40c..c7334a3d9bac0 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -599,8 +599,6 @@ mod tests { ); assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); let remove_children = [child1, child4]; { @@ -641,9 +639,6 @@ mod tests { assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child1).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child2).unwrap(), Parent(parent)); - world.entity_mut(parent).insert_children(1, &entities[3..]); let expected_children: SmallVec<[Entity; 8]> = smallvec![child1, child3, child4, child2]; assert_eq!( @@ -652,8 +647,6 @@ mod tests { ); assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child3).unwrap(), Parent(parent)); - assert_eq!(*world.get::(child4).unwrap(), Parent(parent)); let remove_children = [child1, child4]; world.entity_mut(parent).remove_children(&remove_children); diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 3fc8b049fb241..e3f500db46e09 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -18,6 +18,7 @@ bevy_app = { path = "../bevy_app", version = "0.9.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.9.0-dev" } bevy_math = { path = "../bevy_math", version = "0.9.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.9.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.9.0-dev" } # other serde = { version = "1", features = ["derive"], optional = true } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 082e20b733585..f5e9a9ae8519b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -9,7 +9,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem, SystemState}, }; -use bevy_math::{Mat4, Vec2}; +use bevy_math::{Mat3A, Mat4, Vec2}; use bevy_reflect::TypeUuid; use bevy_render::{ extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, @@ -117,6 +117,9 @@ bitflags::bitflags! { #[repr(transparent)] struct MeshFlags: u32 { const SHADOW_RECEIVER = (1 << 0); + // Indicates the sign of the determinant of the 3x3 model matrix. If the sign is positive, + // then the flag should be set, else it should not be set. + const SIGN_DETERMINANT_MODEL_3X3 = (1 << 31); const NONE = 0; const UNINITIALIZED = 0xFFFF; } @@ -143,13 +146,16 @@ pub fn extract_meshes( for (entity, _, transform, handle, not_receiver, not_caster) in visible_meshes { let transform = transform.compute_matrix(); - let shadow_receiver_flags = if not_receiver.is_some() { - MeshFlags::empty().bits + let mut flags = if not_receiver.is_some() { + MeshFlags::empty() } else { - MeshFlags::SHADOW_RECEIVER.bits + MeshFlags::SHADOW_RECEIVER }; + if Mat3A::from_mat4(transform).determinant().is_sign_positive() { + flags |= MeshFlags::SIGN_DETERMINANT_MODEL_3X3; + } let uniform = MeshUniform { - flags: shadow_receiver_flags, + flags: flags.bits, transform, inverse_transpose_model: transform.inverse().transpose(), }; @@ -494,35 +500,36 @@ impl MeshPipeline { bitflags::bitflags! { #[repr(transparent)] // NOTE: Apparently quadro drivers support up to 64x MSAA. - /// MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. + /// MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. pub struct MeshPipelineKey: u32 { const NONE = 0; const TRANSPARENT_MAIN_PASS = (1 << 0); - const MSAA_RESERVED_BITS = MeshPipelineKey::MSAA_MASK_BITS << MeshPipelineKey::MSAA_SHIFT_BITS; - const PRIMITIVE_TOPOLOGY_RESERVED_BITS = MeshPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << MeshPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; + const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } } impl MeshPipelineKey { - const MSAA_MASK_BITS: u32 = 0b111111; - const MSAA_SHIFT_BITS: u32 = 32 - 6; + const MSAA_MASK_BITS: u32 = 0b111; + const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3; pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - MeshPipelineKey::from_bits(msaa_bits).unwrap() + let msaa_bits = + (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; + Self::from_bits(msaa_bits).unwrap() } pub fn msaa_samples(&self) -> u32 { - ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 + 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { let primitive_topology_bits = ((primitive_topology as u32) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - MeshPipelineKey::from_bits(primitive_topology_bits).unwrap() + Self::from_bits(primitive_topology_bits).unwrap() } pub fn primitive_topology(&self) -> PrimitiveTopology { @@ -917,7 +924,7 @@ mod tests { use super::MeshPipelineKey; #[test] fn mesh_key_msaa_samples() { - for i in 1..=64 { + for i in [1, 2, 4, 8, 16, 32, 64, 128] { assert_eq!(MeshPipelineKey::from_msaa_samples(i).msaa_samples(), i); } } diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 20c763bd22d79..40779b28d5c6e 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -17,20 +17,47 @@ fn mesh_position_local_to_clip(model: mat4x4, vertex_position: vec4) - } fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { - return mat3x3( - mesh.inverse_transpose_model[0].xyz, - mesh.inverse_transpose_model[1].xyz, - mesh.inverse_transpose_model[2].xyz - ) * vertex_normal; + // NOTE: The mikktspace method of normal mapping requires that the world normal is + // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents + // and normal maps so that the exact inverse process is applied when shading. Blender, Unity, + // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code + // unless you really know what you are doing. + // http://www.mikktspace.com/ + return normalize( + mat3x3( + mesh.inverse_transpose_model[0].xyz, + mesh.inverse_transpose_model[1].xyz, + mesh.inverse_transpose_model[2].xyz + ) * vertex_normal + ); +} + +// Calculates the sign of the determinant of the 3x3 model matrix based on a +// mesh flag +fn sign_determinant_model_3x3() -> f32 { + // bool(u32) is false if 0u else true + // f32(bool) is 1.0 if true else 0.0 + // * 2.0 - 1.0 remaps 0.0 or 1.0 to -1.0 or 1.0 respectively + return f32(bool(mesh.flags & MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT)) * 2.0 - 1.0; } fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { + // NOTE: The mikktspace method of normal mapping requires that the world tangent is + // re-normalized in the vertex shader to match the way mikktspace bakes vertex tangents + // and normal maps so that the exact inverse process is applied when shading. Blender, Unity, + // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code + // unless you really know what you are doing. + // http://www.mikktspace.com/ return vec4( - mat3x3( - model[0].xyz, - model[1].xyz, - model[2].xyz - ) * vertex_tangent.xyz, - vertex_tangent.w + normalize( + mat3x3( + model[0].xyz, + model[1].xyz, + model[2].xyz + ) * vertex_tangent.xyz + ), + // NOTE: Multiplying by the sign of the determinant of the 3x3 model matrix accounts for + // situations such as negative scaling. + vertex_tangent.w * sign_determinant_model_3x3() ); } diff --git a/crates/bevy_pbr/src/render/mesh_types.wgsl b/crates/bevy_pbr/src/render/mesh_types.wgsl index e0bfd2c2899cd..d44adbc2bb13f 100644 --- a/crates/bevy_pbr/src/render/mesh_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_types.wgsl @@ -14,3 +14,5 @@ struct SkinnedMesh { #endif let MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u; +// 2^31 - if the flag is set, the sign is positive, else it is negative +let MESH_FLAGS_SIGN_DETERMINANT_MODEL_3X3_BIT: u32 = 2147483648u; diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index cd922bf74412c..a77c065fca221 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -15,7 +15,13 @@ fn prepare_normal( #endif is_front: bool, ) -> vec3 { - var N: vec3 = normalize(world_normal); + // NOTE: The mikktspace method of normal mapping explicitly requires that the world normal NOT + // be re-normalized in the fragment shader. This is primarily to match the way mikktspace + // bakes vertex tangents and normal maps so that this is the exact inverse. Blender, Unity, + // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code + // unless you really know what you are doing. + // http://www.mikktspace.com/ + var N: vec3 = world_normal; #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP @@ -236,7 +242,7 @@ fn pbr( fn tone_mapping(in: vec4) -> vec4 { // tone_mapping return vec4(reinhard_luminance(in.rgb), in.a); - + // Gamma correction. // Not needed with sRGB buffer // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index a7c1da4185b29..495f83d8cb5eb 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -30,6 +30,8 @@ pub trait Array: Reflect { } /// Returns an iterator over the collection. fn iter(&self) -> ArrayIter; + /// Drain the elements of this array to get a vector of owned values. + fn drain(self: Box) -> Vec>; fn clone_dynamic(&self) -> DynamicArray { DynamicArray { @@ -246,6 +248,11 @@ impl Array for DynamicArray { } } + #[inline] + fn drain(self: Box) -> Vec> { + self.values.into_vec() + } + #[inline] fn clone_dynamic(&self) -> DynamicArray { DynamicArray { diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 61d507986b15a..ef855e9426379 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -3,12 +3,13 @@ use std::any::Any; use crate::utility::GenericTypeInfoCell; use crate::{ - Array, ArrayIter, FromReflect, List, ListInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed, + Array, ArrayIter, FromReflect, FromType, GetTypeRegistration, List, ListInfo, Reflect, + ReflectFromPtr, ReflectMut, ReflectRef, TypeInfo, TypeRegistration, Typed, }; impl Array for SmallVec where - T::Item: FromReflect + Clone, + T::Item: FromReflect, { fn get(&self, index: usize) -> Option<&dyn Reflect> { if index < SmallVec::len(self) { @@ -36,11 +37,17 @@ where index: 0, } } + + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } } impl List for SmallVec where - T::Item: FromReflect + Clone, + T::Item: FromReflect, { fn push(&mut self, value: Box) { let value = value.take::().unwrap_or_else(|value| { @@ -53,11 +60,15 @@ where }); SmallVec::push(self, value); } + + fn pop(&mut self) -> Option> { + self.pop().map(|value| Box::new(value) as Box) + } } impl Reflect for SmallVec where - T::Item: FromReflect + Clone, + T::Item: FromReflect, { fn type_name(&self) -> &str { std::any::type_name::() @@ -115,7 +126,7 @@ where impl Typed for SmallVec where - T::Item: FromReflect + Clone, + T::Item: FromReflect, { fn type_info() -> &'static TypeInfo { static CELL: GenericTypeInfoCell = GenericTypeInfoCell::new(); @@ -125,7 +136,7 @@ where impl FromReflect for SmallVec where - T::Item: FromReflect + Clone, + T::Item: FromReflect, { fn from_reflect(reflect: &dyn Reflect) -> Option { if let ReflectRef::List(ref_list) = reflect.reflect_ref() { @@ -139,3 +150,14 @@ where } } } + +impl GetTypeRegistration for SmallVec +where + T::Item: FromReflect, +{ + fn get_type_registration() -> TypeRegistration { + let mut registration = TypeRegistration::of::>(); + registration.insert::(FromType::>::from_type()); + registration + } +} diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index 7b66aba360249..462791d32bc95 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -18,7 +18,7 @@ use std::{ NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, }, - ops::Range, + ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, }; impl_reflect_value!(bool(Debug, Hash, PartialEq, Serialize, Deserialize)); @@ -40,7 +40,12 @@ impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize)); impl_reflect_value!(String(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_reflect_value!(Result()); impl_reflect_value!(HashSet()); -impl_reflect_value!(Range()); +impl_reflect_value!(Range()); +impl_reflect_value!(RangeInclusive()); +impl_reflect_value!(RangeFrom()); +impl_reflect_value!(RangeTo()); +impl_reflect_value!(RangeToInclusive()); +impl_reflect_value!(RangeFull()); impl_reflect_value!(Duration(Debug, Hash, PartialEq, Serialize, Deserialize)); impl_reflect_value!(Instant(Debug, Hash, PartialEq)); impl_reflect_value!(NonZeroI128(Debug, Hash, PartialEq, Serialize, Deserialize)); @@ -75,7 +80,13 @@ impl_from_reflect_value!(f64); impl_from_reflect_value!(String); impl_from_reflect_value!(HashSet); impl_from_reflect_value!(Range); +impl_from_reflect_value!(RangeInclusive); +impl_from_reflect_value!(RangeFrom); +impl_from_reflect_value!(RangeTo); +impl_from_reflect_value!(RangeToInclusive); +impl_from_reflect_value!(RangeFull); impl_from_reflect_value!(Duration); +impl_from_reflect_value!(Instant); impl_from_reflect_value!(NonZeroI128); impl_from_reflect_value!(NonZeroU128); impl_from_reflect_value!(NonZeroIsize); @@ -112,6 +123,13 @@ impl Array for Vec { index: 0, } } + + #[inline] + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } } impl List for Vec { @@ -126,6 +144,10 @@ impl List for Vec { }); Vec::push(self, value); } + + fn pop(&mut self) -> Option> { + self.pop().map(|value| Box::new(value) as Box) + } } impl Reflect for Vec { @@ -246,6 +268,17 @@ impl Map for HashMap { } } + fn drain(self: Box) -> Vec<(Box, Box)> { + self.into_iter() + .map(|(key, value)| { + ( + Box::new(key) as Box, + Box::new(value) as Box, + ) + }) + .collect() + } + fn clone_dynamic(&self) -> DynamicMap { let mut dynamic_map = DynamicMap::default(); dynamic_map.set_name(self.type_name().to_string()); @@ -345,8 +378,8 @@ impl Typed for HashMap { impl GetTypeRegistration for HashMap where - K: FromReflect + Clone + Eq + Hash, - V: FromReflect + Clone, + K: FromReflect + Eq + Hash, + V: FromReflect, { fn get_type_registration() -> TypeRegistration { let mut registration = TypeRegistration::of::>(); @@ -394,6 +427,13 @@ impl Array for [T; N] { index: 0, } } + + #[inline] + fn drain(self: Box) -> Vec> { + self.into_iter() + .map(|value| Box::new(value) as Box) + .collect() + } } impl Reflect for [T; N] { @@ -585,13 +625,13 @@ impl Reflect for Cow<'static, str> { } } -impl GetTypeRegistration for Option { +impl GetTypeRegistration for Option { fn get_type_registration() -> TypeRegistration { TypeRegistration::of::>() } } -impl Enum for Option { +impl Enum for Option { fn field(&self, _name: &str) -> Option<&dyn Reflect> { None } @@ -655,7 +695,7 @@ impl Enum for Option { } } -impl Reflect for Option { +impl Reflect for Option { #[inline] fn type_name(&self) -> &str { std::any::type_name::() @@ -695,8 +735,7 @@ impl Reflect for Option { if self.variant_name() == value.variant_name() { // Same variant -> just update fields for (index, field) in value.iter_fields().enumerate() { - let name = value.name_at(index).unwrap(); - if let Some(v) = self.field_mut(name) { + if let Some(v) = self.field_at_mut(index) { v.apply(field.value()); } } @@ -706,14 +745,23 @@ impl Reflect for Option { "Some" => { let field = value .field_at(0) - .expect("Field at index 0 should exist") - .clone_value(); - let field = field.take::().unwrap_or_else(|_| { - panic!( - "Field at index 0 should be of type {}", - std::any::type_name::() - ) - }); + .unwrap_or_else(|| { + panic!( + "Field in `Some` variant of {} should exist", + std::any::type_name::>() + ) + }) + .clone_value() + .take::() + .unwrap_or_else(|value| { + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Field in `Some` variant of {} should be of type {}", + std::any::type_name::>(), + std::any::type_name::() + ) + }) + }); *self = Some(field); } "None" => { @@ -741,7 +789,7 @@ impl Reflect for Option { #[inline] fn clone_value(&self) -> Box { - Box::new(self.clone()) + Box::new(Enum::clone_dynamic(self)) } fn reflect_hash(&self) -> Option { @@ -753,21 +801,30 @@ 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() { "Some" => { let field = dyn_enum .field_at(0) - .expect("Field at index 0 should exist") - .clone_value(); - let field = field.take::().unwrap_or_else(|_| { - panic!( - "Field at index 0 should be of type {}", - std::any::type_name::() - ) - }); + .unwrap_or_else(|| { + panic!( + "Field in `Some` variant of {} should exist", + std::any::type_name::>() + ) + }) + .clone_value() + .take::() + .unwrap_or_else(|value| { + T::from_reflect(&*value).unwrap_or_else(|| { + panic!( + "Field in `Some` variant of {} should be of type {}", + std::any::type_name::>(), + std::any::type_name::() + ) + }) + }); Some(Some(field)) } "None" => Some(None), @@ -783,7 +840,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::(|| { @@ -827,10 +884,12 @@ impl FromReflect for Cow<'static, str> { #[cfg(test)] mod tests { + use crate as bevy_reflect; use crate::{ - Enum, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, Typed, VariantInfo, VariantType, + Enum, FromReflect, Reflect, ReflectSerialize, TypeInfo, TypeRegistry, Typed, VariantInfo, + VariantType, }; - use bevy_utils::HashMap; + use bevy_utils::{HashMap, Instant}; use std::f32::consts::{PI, TAU}; #[test] @@ -939,6 +998,51 @@ mod tests { assert_eq!(Some(321), value); } + #[test] + fn option_should_from_reflect() { + #[derive(Reflect, FromReflect, PartialEq, Debug)] + struct Foo(usize); + + let expected = Some(Foo(123)); + let output = as FromReflect>::from_reflect(&expected).unwrap(); + + assert_eq!(expected, output); + } + + #[test] + fn option_should_apply() { + #[derive(Reflect, FromReflect, PartialEq, Debug)] + struct Foo(usize); + + // === None on None === // + let patch = None::; + let mut value = None; + Reflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "None apply onto None"); + + // === Some on None === // + let patch = Some(Foo(123)); + let mut value = None; + Reflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "Some apply onto None"); + + // === None on Some === // + let patch = None::; + let mut value = Some(Foo(321)); + Reflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "None apply onto Some"); + + // === Some on Some === // + let patch = Some(Foo(123)); + let mut value = Some(Foo(321)); + Reflect::apply(&mut value, &patch); + + assert_eq!(patch, value, "Some apply onto Some"); + } + #[test] fn option_should_impl_typed() { type MyOption = Option; @@ -979,4 +1083,11 @@ mod tests { let forty_two: std::num::NonZeroUsize = crate::FromReflect::from_reflect(a).unwrap(); assert_eq!(forty_two, std::num::NonZeroUsize::new(42).unwrap()); } + + #[test] + fn instant_should_from_reflect() { + let expected = Instant::now(); + let output = ::from_reflect(&expected).unwrap(); + assert_eq!(expected, output); + } } diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index f6dbc37ce67f3..5f35d27e4e5a3 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -35,7 +35,7 @@ pub mod prelude { pub use crate::std_traits::*; #[doc(hidden)] pub use crate::{ - reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize, + reflect_trait, FromReflect, GetField, GetTupleStructField, Reflect, ReflectDeserialize, ReflectSerialize, Struct, TupleStruct, }; } @@ -519,6 +519,29 @@ mod tests { assert_eq!(foo, *foo2.downcast::().unwrap()); } + #[test] + fn should_drain_fields() { + let array_value: Box = Box::new([123_i32, 321_i32]); + let fields = array_value.drain(); + assert!(fields[0].reflect_partial_eq(&123_i32).unwrap_or_default()); + assert!(fields[1].reflect_partial_eq(&321_i32).unwrap_or_default()); + + let list_value: Box = Box::new(vec![123_i32, 321_i32]); + let fields = list_value.drain(); + assert!(fields[0].reflect_partial_eq(&123_i32).unwrap_or_default()); + assert!(fields[1].reflect_partial_eq(&321_i32).unwrap_or_default()); + + let tuple_value: Box = Box::new((123_i32, 321_i32)); + let fields = tuple_value.drain(); + assert!(fields[0].reflect_partial_eq(&123_i32).unwrap_or_default()); + assert!(fields[1].reflect_partial_eq(&321_i32).unwrap_or_default()); + + let map_value: Box = Box::new(HashMap::from([(123_i32, 321_i32)])); + let fields = map_value.drain(); + assert!(fields[0].0.reflect_partial_eq(&123_i32).unwrap_or_default()); + assert!(fields[0].1.reflect_partial_eq(&321_i32).unwrap_or_default()); + } + #[test] fn reflect_take() { #[derive(Reflect, Debug, PartialEq)] diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 9e8c0c65f651b..b832e80c37e2a 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -15,6 +15,9 @@ pub trait List: Reflect + Array { /// Appends an element to the list. fn push(&mut self, value: Box); + /// Removes the last element from the list (highest index in the array) and returns it, or [`None`] if it is empty. + fn pop(&mut self) -> Option>; + /// Clones the list, producing a [`DynamicList`]. fn clone_dynamic(&self) -> DynamicList { DynamicList { @@ -134,6 +137,10 @@ impl Array for DynamicList { } } + fn drain(self: Box) -> Vec> { + self.values + } + fn clone_dynamic(&self) -> DynamicArray { DynamicArray { name: self.name.clone(), @@ -151,6 +158,10 @@ impl List for DynamicList { DynamicList::push_box(self, value); } + fn pop(&mut self) -> Option> { + self.values.pop() + } + fn clone_dynamic(&self) -> DynamicList { DynamicList { name: self.name.clone(), diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index 080b0770390b4..9bdeea87f61cb 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -42,6 +42,9 @@ pub trait Map: Reflect { /// Returns an iterator over the key-value pairs of the map. fn iter(&self) -> MapIter; + /// Drain the key-value pairs of this map to get a vector of owned values. + fn drain(self: Box) -> Vec<(Box, Box)>; + /// Clones the map, producing a [`DynamicMap`]. fn clone_dynamic(&self) -> DynamicMap; @@ -226,6 +229,10 @@ impl Map for DynamicMap { } } } + + fn drain(self: Box) -> Vec<(Box, Box)> { + self.values + } } impl Reflect for DynamicMap { diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 96386e85d6bbc..13469c0be3fcb 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -40,6 +40,9 @@ pub trait Tuple: Reflect { /// Returns an iterator over the values of the tuple's fields. fn iter_fields(&self) -> TupleFieldIter; + /// Drain the fields of this tuple to get a vector of owned values. + fn drain(self: Box) -> Vec>; + /// Clones the struct into a [`DynamicTuple`]. fn clone_dynamic(&self) -> DynamicTuple; } @@ -253,6 +256,11 @@ impl Tuple for DynamicTuple { } } + #[inline] + fn drain(self: Box) -> Vec> { + self.fields + } + #[inline] fn clone_dynamic(&self) -> DynamicTuple { DynamicTuple { @@ -451,6 +459,13 @@ macro_rules! impl_reflect_tuple { } } + #[inline] + fn drain(self: Box) -> Vec> { + vec![ + $(Box::new(self.$index),)* + ] + } + #[inline] fn clone_dynamic(&self) -> DynamicTuple { let mut dyn_tuple = DynamicTuple { diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index c2d01aca56c90..30fd1651f2145 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -23,7 +23,6 @@ use bevy_reflect::FromReflect; use bevy_transform::components::GlobalTransform; use bevy_utils::HashSet; use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; -use serde::{Deserialize, Serialize}; use std::{borrow::Cow, ops::Range}; use wgpu::Extent3d; @@ -32,9 +31,8 @@ use wgpu::Extent3d; /// The viewport defines the area on the render target to which the camera renders its image. /// You can overlay multiple cameras in a single window using viewports to create effects like /// split screen, minimaps, and character viewers. -// TODO: remove reflect_value when possible -#[derive(Reflect, Debug, Clone, Serialize, Deserialize)] -#[reflect_value(Default, Serialize, Deserialize)] +#[derive(Reflect, FromReflect, Debug, Clone)] +#[reflect(Default)] pub struct Viewport { /// The physical position to render this viewport to within the [`RenderTarget`] of this [`Camera`]. /// (0,0) corresponds to the top-left corner @@ -72,7 +70,7 @@ pub struct ComputedCameraValues { target_info: Option, } -#[derive(Component, Debug, Reflect, Clone)] +#[derive(Component, Debug, Reflect, FromReflect, Clone)] #[reflect(Component)] pub struct Camera { /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`]. @@ -82,8 +80,6 @@ pub struct Camera { /// If this is set to true, this camera will be rendered to its specified [`RenderTarget`]. If false, this /// camera will not be rendered. pub is_active: bool, - /// The method used to calculate this camera's depth. This will be used for projections and visibility. - pub depth_calculation: DepthCalculation, /// Computed values for this camera, such as the projection matrix and the render target size. #[reflect(ignore)] pub computed: ComputedCameraValues, @@ -100,7 +96,6 @@ impl Default for Camera { viewport: None, computed: Default::default(), target: Default::default(), - depth_calculation: Default::default(), } } } @@ -310,16 +305,6 @@ impl RenderTarget { } } -#[derive(Debug, Clone, Copy, Default, Reflect, FromReflect, Serialize, Deserialize)] -#[reflect(Serialize, Deserialize)] -pub enum DepthCalculation { - /// Pythagorean distance; works everywhere, more expensive to compute. - #[default] - Distance, - /// Optimization for 2D; assuming the camera points towards `-Z`. - ZDifference, -} - pub fn camera_system( mut window_resized_events: EventReader, mut window_created_events: EventReader, @@ -378,7 +363,6 @@ pub fn camera_system( if let Some(size) = camera.logical_viewport_size() { camera_projection.update(size.x, size.y); camera.computed.projection_matrix = camera_projection.get_projection_matrix(); - camera.depth_calculation = camera_projection.depth_calculation(); } } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 37acb0b895ea7..fbaed28a8f3ae 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -21,12 +21,12 @@ 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::() .add_plugin(CameraProjectionPlugin::::default()) diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index bf2a043b9e835..592b4cc8f916b 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; -use super::DepthCalculation; use bevy_app::{App, CoreStage, Plugin, StartupStage}; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_math::Mat4; @@ -42,7 +41,6 @@ impl Plugin for CameraPro pub trait CameraProjection { fn get_projection_matrix(&self) -> Mat4; fn update(&mut self, width: f32, height: f32); - fn depth_calculation(&self) -> DepthCalculation; fn far(&self) -> f32; } @@ -81,13 +79,6 @@ impl CameraProjection for Projection { } } - fn depth_calculation(&self) -> DepthCalculation { - match self { - Projection::Perspective(projection) => projection.depth_calculation(), - Projection::Orthographic(projection) => projection.depth_calculation(), - } - } - fn far(&self) -> f32 { match self { Projection::Perspective(projection) => projection.far(), @@ -120,10 +111,6 @@ impl CameraProjection for PerspectiveProjection { self.aspect_ratio = width / height; } - fn depth_calculation(&self) -> DepthCalculation { - DepthCalculation::Distance - } - fn far(&self) -> f32 { self.far } @@ -179,7 +166,6 @@ pub struct OrthographicProjection { pub window_origin: WindowOrigin, pub scaling_mode: ScalingMode, pub scale: f32, - pub depth_calculation: DepthCalculation, } impl CameraProjection for OrthographicProjection { @@ -245,10 +231,6 @@ impl CameraProjection for OrthographicProjection { } } - fn depth_calculation(&self) -> DepthCalculation { - self.depth_calculation - } - fn far(&self) -> f32 { self.far } @@ -266,7 +248,6 @@ impl Default for OrthographicProjection { window_origin: WindowOrigin::Center, scaling_mode: ScalingMode::WindowSize, scale: 1.0, - depth_calculation: DepthCalculation::Distance, } } } diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index 51c3a21a7cdc9..0362d8386a9e4 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -745,7 +745,7 @@ impl AddAssign for Color { lightness, alpha, } => { - let rhs = rhs.as_linear_rgba_f32(); + let rhs = rhs.as_hsla_f32(); *hue += rhs[0]; *saturation += rhs[1]; *lightness += rhs[2]; @@ -794,7 +794,7 @@ impl Add for Color { lightness, alpha, } => { - let rhs = rhs.as_linear_rgba_f32(); + let rhs = rhs.as_hsla_f32(); Color::Hsla { hue: hue + rhs[0], saturation: saturation + rhs[1], diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5efbb5ba9c03a..fbe5f14923970 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -249,7 +249,7 @@ impl Plugin for RenderPlugin { // prepare let prepare = render_app .schedule - .get_stage_mut::(&RenderStage::Prepare) + .get_stage_mut::(RenderStage::Prepare) .unwrap(); prepare.run(&mut render_app.world); } @@ -262,7 +262,7 @@ impl Plugin for RenderPlugin { // queue let queue = render_app .schedule - .get_stage_mut::(&RenderStage::Queue) + .get_stage_mut::(RenderStage::Queue) .unwrap(); queue.run(&mut render_app.world); } @@ -275,7 +275,7 @@ impl Plugin for RenderPlugin { // phase sort let phase_sort = render_app .schedule - .get_stage_mut::(&RenderStage::PhaseSort) + .get_stage_mut::(RenderStage::PhaseSort) .unwrap(); phase_sort.run(&mut render_app.world); } @@ -288,7 +288,7 @@ impl Plugin for RenderPlugin { // render let render = render_app .schedule - .get_stage_mut::(&RenderStage::Render) + .get_stage_mut::(RenderStage::Render) .unwrap(); render.run(&mut render_app.world); } @@ -301,7 +301,7 @@ impl Plugin for RenderPlugin { // cleanup let cleanup = render_app .schedule - .get_stage_mut::(&RenderStage::Cleanup) + .get_stage_mut::(RenderStage::Cleanup) .unwrap(); cleanup.run(&mut render_app.world); } @@ -335,7 +335,7 @@ struct ScratchMainWorld(World); fn extract(app_world: &mut World, render_app: &mut App) { let extract = render_app .schedule - .get_stage_mut::(&RenderStage::Extract) + .get_stage_mut::(RenderStage::Extract) .unwrap(); // temporarily add the app world to the render world as a resource diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 8f391714d7f26..88a0ef1bc2659 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -110,15 +110,13 @@ impl Mesh { attribute: MeshVertexAttribute, values: impl Into, ) { - let values: VertexAttributeValues = values.into(); - + let values = values.into(); let values_format = VertexFormat::from(&values); if values_format != attribute.format { - error!( - "Invalid attribute format for {}. Given format is {:?} but expected {:?}", + panic!( + "Failed to insert attribute. Invalid attribute format for {}. Given format is {:?} but expected {:?}", attribute.name, values_format, attribute.format ); - panic!("Failed to insert attribute"); } self.attributes diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index 88b19dce1f4b4..39d76120a4aa9 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -12,7 +12,7 @@ use std::{borrow::Cow, fmt::Debug}; use super::EdgeExistence; /// The render graph configures the modular, parallel and re-usable render logic. -/// It is a retained and stateless (nodes itself my have their internal state) structure, +/// It is a retained and stateless (nodes themselves may have their own internal state) structure, /// which can not be modified while it is executed by the graph runner. /// /// The `RenderGraphRunner` is responsible for executing the entire graph each frame. @@ -25,7 +25,7 @@ use super::EdgeExistence; /// Slots describe the render resources created or used by the nodes. /// /// Additionally a render graph can contain multiple sub graphs, which are run by the -/// corresponding nodes. Every render graph can have it’s own optional input node. +/// corresponding nodes. Every render graph can have its own optional input node. /// /// ## Example /// Here is a simple render graph example with two nodes connected by a node edge. diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index eb14f94f1ad2a..bf7b110b1277e 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -14,7 +14,10 @@ use std::cell::Cell; use thread_local::ThreadLocal; use crate::{ - camera::{Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, Projection}, + camera::{ + camera_system, Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, + Projection, + }, mesh::Mesh, primitives::{Aabb, Frustum, Sphere}, }; @@ -186,18 +189,21 @@ impl Plugin for VisibilityPlugin { CoreStage::PostUpdate, update_frusta:: .label(UpdateOrthographicFrusta) + .after(camera_system::) .after(TransformSystem::TransformPropagate), ) .add_system_to_stage( CoreStage::PostUpdate, update_frusta:: .label(UpdatePerspectiveFrusta) + .after(camera_system::) .after(TransformSystem::TransformPropagate), ) .add_system_to_stage( CoreStage::PostUpdate, update_frusta:: .label(UpdateProjectionFrusta) + .after(camera_system::) .after(TransformSystem::TransformPropagate), ) .add_system_to_stage( diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index a7be1a4ab983b..bf0da021b8941 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -57,6 +57,7 @@ impl Plugin for SpritePlugin { shaders.set_untracked(SPRITE_SHADER_HANDLE, sprite_shader); app.add_asset::() .register_type::() + .register_type::() .register_type::() .add_plugin(Mesh2dRenderPlugin) .add_plugin(ColorMaterialPlugin); diff --git a/crates/bevy_sprite/src/mesh2d/color_material.wgsl b/crates/bevy_sprite/src/mesh2d/color_material.wgsl index 3b6a2b5217176..3b5893d4ce285 100644 --- a/crates/bevy_sprite/src/mesh2d/color_material.wgsl +++ b/crates/bevy_sprite/src/mesh2d/color_material.wgsl @@ -26,12 +26,11 @@ struct FragmentInput { @fragment fn fragment(in: FragmentInput) -> @location(0) vec4 { var output_color: vec4 = material.color; - if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) { #ifdef VERTEX_COLORS - output_color = output_color * textureSample(texture, texture_sampler, in.uv) * in.color; -#else - output_color = output_color * textureSample(texture, texture_sampler, in.uv); + output_color = output_color * in.color; #endif + if ((material.flags & COLOR_MATERIAL_FLAGS_TEXTURE_BIT) != 0u) { + output_color = output_color * textureSample(texture, texture_sampler, in.uv); } return output_color; } diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 18513f0d5362d..54927a0077031 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -269,35 +269,36 @@ impl Mesh2dPipeline { bitflags::bitflags! { #[repr(transparent)] // NOTE: Apparently quadro drivers support up to 64x MSAA. - // MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. + // MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. // FIXME: make normals optional? pub struct Mesh2dPipelineKey: u32 { const NONE = 0; - const MSAA_RESERVED_BITS = Mesh2dPipelineKey::MSAA_MASK_BITS << Mesh2dPipelineKey::MSAA_SHIFT_BITS; - const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_MASK_BITS << Mesh2dPipelineKey::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; + const PRIMITIVE_TOPOLOGY_RESERVED_BITS = Self::PRIMITIVE_TOPOLOGY_MASK_BITS << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; } } impl Mesh2dPipelineKey { - const MSAA_MASK_BITS: u32 = 0b111111; - const MSAA_SHIFT_BITS: u32 = 32 - 6; + const MSAA_MASK_BITS: u32 = 0b111; + const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111; const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 = Self::MSAA_SHIFT_BITS - 3; pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - Mesh2dPipelineKey::from_bits(msaa_bits).unwrap() + let msaa_bits = + (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; + Self::from_bits(msaa_bits).unwrap() } pub fn msaa_samples(&self) -> u32 { - ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 + 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { let primitive_topology_bits = ((primitive_topology as u32) & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - Mesh2dPipelineKey::from_bits(primitive_topology_bits).unwrap() + Self::from_bits(primitive_topology_bits).unwrap() } pub fn primitive_topology(&self) -> PrimitiveTopology { diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index c2f98b2aab286..db1dc1a86d753 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -90,25 +90,26 @@ impl FromWorld for SpritePipeline { bitflags::bitflags! { #[repr(transparent)] // NOTE: Apparently quadro drivers support up to 64x MSAA. - // MSAA uses the highest 6 bits for the MSAA sample count - 1 to support up to 64x MSAA. + // MSAA uses the highest 3 bits for the MSAA log2(sample count) to support up to 128x MSAA. pub struct SpritePipelineKey: u32 { const NONE = 0; const COLORED = (1 << 0); - const MSAA_RESERVED_BITS = SpritePipelineKey::MSAA_MASK_BITS << SpritePipelineKey::MSAA_SHIFT_BITS; + const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; } } impl SpritePipelineKey { - const MSAA_MASK_BITS: u32 = 0b111111; - const MSAA_SHIFT_BITS: u32 = 32 - 6; + const MSAA_MASK_BITS: u32 = 0b111; + const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones(); pub fn from_msaa_samples(msaa_samples: u32) -> Self { - let msaa_bits = ((msaa_samples - 1) & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; - SpritePipelineKey::from_bits(msaa_bits).unwrap() + let msaa_bits = + (msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS; + Self::from_bits(msaa_bits).unwrap() } pub fn msaa_samples(&self) -> u32 { - ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) + 1 + 1 << ((self.bits >> Self::MSAA_SHIFT_BITS) & Self::MSAA_MASK_BITS) } } diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index 7da0e54a07f75..018a760793c47 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -36,6 +36,8 @@ impl Plugin for TimePlugin { app.init_resource::