From 3046e58b57a79da584ce1bac6bc42ff5e1f64a94 Mon Sep 17 00:00:00 2001 From: Vincent Junge Date: Fri, 14 Jul 2023 02:54:21 +0200 Subject: [PATCH 1/9] fix gizmos during fixed update --- crates/bevy_gizmos/Cargo.toml | 2 + crates/bevy_gizmos/src/gizmos.rs | 82 ++++++++++++++++++++++++-- crates/bevy_gizmos/src/lib.rs | 29 ++++++++- crates/bevy_internal/Cargo.toml | 2 +- crates/bevy_time/src/fixed_timestep.rs | 18 ++++++ 5 files changed, 124 insertions(+), 9 deletions(-) diff --git a/crates/bevy_gizmos/Cargo.toml b/crates/bevy_gizmos/Cargo.toml index 4a40902afae7b..9f677a6845552 100644 --- a/crates/bevy_gizmos/Cargo.toml +++ b/crates/bevy_gizmos/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["bevy"] [features] webgl = [] +fixed_update = ["dep:bevy_time"] [dependencies] # Bevy @@ -25,3 +26,4 @@ bevy_core = { path = "../bevy_core", version = "0.12.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.12.0-dev" } bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.12.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.12.0-dev" } +bevy_time = { path = "../bevy_time", version = "0.12.0-dev", optional = true } diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index c2258bc0673f3..19eb3f39cbca0 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -3,12 +3,14 @@ use std::{f32::consts::TAU, iter}; use bevy_ecs::{ - system::{Deferred, Resource, SystemBuffer, SystemMeta, SystemParam}, - world::World, + component::Tick, + system::{Resource, SystemBuffer, SystemMeta, SystemParam}, + world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_math::{Mat2, Quat, Vec2, Vec3}; use bevy_render::color::Color; use bevy_transform::TransformPoint; +use bevy_utils::default; type PositionItem = [f32; 3]; type ColorItem = [f32; 4]; @@ -16,6 +18,13 @@ type ColorItem = [f32; 4]; const DEFAULT_CIRCLE_SEGMENTS: usize = 32; #[derive(Resource, Default)] +pub(crate) struct GizmoStorages { + pub frame: GizmoStorage, + pub fixed_update_tick: u64, + pub fixed_update: GizmoStorage, +} + +#[derive(Default)] pub(crate) struct GizmoStorage { pub list_positions: Vec, pub list_colors: Vec, @@ -28,13 +37,14 @@ pub(crate) struct GizmoStorage { /// They are drawn in immediate mode, which means they will be rendered only for /// the frames in which they are spawned. /// Gizmos should be spawned before the [`Last`](bevy_app::Last) schedule to ensure they are drawn. -#[derive(SystemParam)] pub struct Gizmos<'s> { - buffer: Deferred<'s, GizmoBuffer>, + buffer: &'s mut GizmoBuffer, } #[derive(Default)] struct GizmoBuffer { + /// Which fixed update tick this belongs to, `None` if this isn't from a fixed update. + fixed_time_update: Option, list_positions: Vec, list_colors: Vec, strip_positions: Vec, @@ -43,7 +53,22 @@ struct GizmoBuffer { impl SystemBuffer for GizmoBuffer { fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { - let mut storage = world.resource_mut::(); + let mut storages = world.resource_mut::(); + + let storage = if let Some(tick) = self.fixed_time_update { + // If a new fixed update has begun, clear gizmos from previous fixed update + if storages.fixed_update_tick < tick { + storages.fixed_update_tick = tick; + storages.fixed_update.list_positions.clear(); + storages.fixed_update.list_colors.clear(); + storages.fixed_update.strip_positions.clear(); + storages.fixed_update.strip_colors.clear(); + } + &mut storages.fixed_update + } else { + &mut storages.frame + }; + storage.list_positions.append(&mut self.list_positions); storage.list_colors.append(&mut self.list_colors); storage.strip_positions.append(&mut self.strip_positions); @@ -51,6 +76,53 @@ impl SystemBuffer for GizmoBuffer { } } +// Wrap to keep GizmoBuffer hidden +const _: () = { + pub struct Wrap(GizmoBuffer); + + // SAFETY: Only local state is accessed. + unsafe impl SystemParam for Gizmos<'_> { + type State = Wrap; + type Item<'w, 's> = Gizmos<'s>; + + fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { + #[cfg(not(feature = "fixed_update"))] + let fixed_time_update = None; + #[cfg(feature = "fixed_update")] + let fixed_time_update = + if world.contains_resource::() { + world + .get_resource::() + .map(|time| time.times_expended()) + } else { + None + }; + Wrap(GizmoBuffer { + fixed_time_update, + list_positions: default(), + list_colors: default(), + strip_positions: default(), + strip_colors: default(), + }) + } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + state.0.apply(system_meta, world); + } + + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + _system_meta: &SystemMeta, + _world: UnsafeWorldCell<'w>, + _change_tick: Tick, + ) -> Self::Item<'w, 's> { + Gizmos { + buffer: &mut state.0, + } + } + } +}; + impl<'s> Gizmos<'s> { /// Draw a line in 3D from `start` to `end`. /// diff --git a/crates/bevy_gizmos/src/lib.rs b/crates/bevy_gizmos/src/lib.rs index 631cb058fa4ac..8a5c20e85bc35 100644 --- a/crates/bevy_gizmos/src/lib.rs +++ b/crates/bevy_gizmos/src/lib.rs @@ -14,6 +14,9 @@ //! # bevy_ecs::system::assert_is_system(system); //! ``` //! +//! With the `fixed_update` feature (enabled by default by the bevy main crate), +//! gizmos drawn during `FixedUpdate` last until the next fixed update. +//! //! See the documentation on [`Gizmos`](crate::gizmos::Gizmos) for more examples. use std::mem; @@ -62,7 +65,7 @@ mod pipeline_2d; #[cfg(feature = "bevy_pbr")] mod pipeline_3d; -use gizmos::{GizmoStorage, Gizmos}; +use gizmos::{GizmoStorages, Gizmos}; /// The `bevy_gizmos` prelude. pub mod prelude { @@ -85,7 +88,7 @@ impl Plugin for GizmoPlugin { .add_plugins(RenderAssetPlugin::::default()) .init_resource::() .init_resource::() - .init_resource::() + .init_resource::() .add_systems(Last, update_gizmo_meshes) .add_systems( PostUpdate, @@ -96,6 +99,11 @@ impl Plugin for GizmoPlugin { .after(TransformSystem::TransformPropagate), ); + // Ensure gizmos from prefious fixed update are cleaned up if no other system + // accesses gizmos during fixed update any more + #[cfg(feature = "fixed_update")] + app.add_systems(bevy_app::FixedUpdate, |_: Gizmos| ()); + let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; @@ -275,8 +283,23 @@ struct LineGizmoHandles { fn update_gizmo_meshes( mut line_gizmos: ResMut>, mut handles: ResMut, - mut storage: ResMut, + mut storages: ResMut, ) { + // Combine gizmos for this frame (which get cleared here) with the ones from the last fixed update (which get cleared during system buffer application) + let mut storage = mem::take(&mut storages.frame); + storage + .list_positions + .extend_from_slice(&storages.fixed_update.list_positions); + storage + .list_colors + .extend_from_slice(&storages.fixed_update.list_colors); + storage + .strip_positions + .extend_from_slice(&storages.fixed_update.strip_positions); + storage + .strip_colors + .extend_from_slice(&storages.fixed_update.strip_colors); + if storage.list_positions.is_empty() { handles.list = None; } else if let Some(handle) = handles.list.as_ref() { diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 28252e43d37c1..cf6fca72168c2 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -139,4 +139,4 @@ bevy_text = { path = "../bevy_text", optional = true, version = "0.12.0-dev" } bevy_ui = { path = "../bevy_ui", optional = true, version = "0.12.0-dev" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.12.0-dev" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.12.0-dev" } -bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.0-dev", default-features = false } +bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.12.0-dev", default-features = false, features = ["fixed_update"] } diff --git a/crates/bevy_time/src/fixed_timestep.rs b/crates/bevy_time/src/fixed_timestep.rs index 655a19ba774d7..83094eee83ace 100644 --- a/crates/bevy_time/src/fixed_timestep.rs +++ b/crates/bevy_time/src/fixed_timestep.rs @@ -38,6 +38,7 @@ use thiserror::Error; #[derive(Resource, Debug)] pub struct FixedTime { accumulated: Duration, + total_ticks: u64, /// The amount of time spanned by each fixed update. /// Defaults to 1/60th of a second. /// @@ -51,6 +52,7 @@ impl FixedTime { FixedTime { accumulated: Duration::ZERO, period, + total_ticks: 0, } } @@ -59,6 +61,7 @@ impl FixedTime { FixedTime { accumulated: Duration::ZERO, period: Duration::from_secs_f32(period), + total_ticks: 0, } } @@ -77,6 +80,11 @@ impl FixedTime { self.accumulated } + /// Returns how often this has expended a period of time. + pub fn times_expended(&self) -> u64 { + self.total_ticks + } + /// Attempts to advance by a single period. This will return [`FixedUpdateError`] if there is not enough /// accumulated time -- in other words, if advancing time would put the fixed update schedule /// ahead of the main schedule. @@ -85,6 +93,7 @@ impl FixedTime { pub fn expend(&mut self) -> Result<(), FixedUpdateError> { if let Some(new_value) = self.accumulated.checked_sub(self.period) { self.accumulated = new_value; + self.total_ticks += 1; Ok(()) } else { Err(FixedUpdateError::NotEnoughTime { @@ -100,10 +109,15 @@ impl Default for FixedTime { FixedTime { accumulated: Duration::ZERO, period: Duration::from_secs_f32(1. / 60.), + total_ticks: 0, } } } +/// Indicates that [`run_fixed_update_schedule`] is currently active. +#[derive(Resource)] +pub struct FixedUpdateScheduleIsCurrentlyRunning; + /// An error returned when working with [`FixedTime`]. #[derive(Debug, Error)] pub enum FixedUpdateError { @@ -121,6 +135,8 @@ pub enum FixedUpdateError { /// /// For more information, see the [module-level documentation](self). pub fn run_fixed_update_schedule(world: &mut World) { + world.insert_resource(FixedUpdateScheduleIsCurrentlyRunning); + // Tick the time let delta_time = world.resource::