Skip to content

Commit

Permalink
fix gizmos during fixed update
Browse files Browse the repository at this point in the history
  • Loading branch information
SpecificProtagonist committed Jul 14, 2023
1 parent cd0a642 commit 0495ae7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 9 deletions.
2 changes: 2 additions & 0 deletions crates/bevy_gizmos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ keywords = ["bevy"]

[features]
webgl = []
fixed_update = ["dep:bevy_time"]

[dependencies]
# Bevy
Expand All @@ -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 }
82 changes: 77 additions & 5 deletions crates/bevy_gizmos/src/gizmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,28 @@
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];

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<PositionItem>,
pub list_colors: Vec<ColorItem>,
Expand All @@ -24,13 +33,14 @@ pub(crate) struct GizmoStorage {
}

/// A [`SystemParam`](bevy_ecs::system::SystemParam) for drawing gizmos.
#[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<u64>,
list_positions: Vec<PositionItem>,
list_colors: Vec<ColorItem>,
strip_positions: Vec<PositionItem>,
Expand All @@ -39,14 +49,76 @@ struct GizmoBuffer {

impl SystemBuffer for GizmoBuffer {
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
let mut storage = world.resource_mut::<GizmoStorage>();
let mut storages = world.resource_mut::<GizmoStorages>();

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);
storage.strip_colors.append(&mut self.strip_colors);
}
}

// 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_tick = None;
#[cfg(feature = "fixed_update")]
let fixed_time_update =
if world.contains_resource::<bevy_time::fixed_timestep::FixedUpdateScheduleIsCurrentlyRunning>() {
world
.get_resource::<bevy_time::prelude::FixedTime>()
.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 from `start` to `end`.
///
Expand Down
29 changes: 26 additions & 3 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,7 +62,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 {
Expand All @@ -82,7 +85,7 @@ impl Plugin for GizmoPlugin {
.add_plugins(RenderAssetPlugin::<LineGizmo>::default())
.init_resource::<LineGizmoHandles>()
.init_resource::<GizmoConfig>()
.init_resource::<GizmoStorage>()
.init_resource::<GizmoStorages>()
.add_systems(Last, update_gizmo_meshes)
.add_systems(
Update,
Expand All @@ -92,6 +95,11 @@ impl Plugin for GizmoPlugin {
),
);

// 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; };

render_app
Expand Down Expand Up @@ -260,8 +268,23 @@ struct LineGizmoHandles {
fn update_gizmo_meshes(
mut line_gizmos: ResMut<Assets<LineGizmo>>,
mut handles: ResMut<LineGizmoHandles>,
mut storage: ResMut<GizmoStorage>,
mut storages: ResMut<GizmoStorages>,
) {
// 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() {
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
18 changes: 18 additions & 0 deletions crates/bevy_time/src/fixed_timestep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use thiserror::Error;
#[derive(Resource, Debug)]
pub struct FixedTime {
accumulated: Duration,
total_ticks: u64,
/// Defaults to 1/60th of a second.
/// To configure this value, simply mutate or overwrite this resource.
pub period: Duration,
Expand All @@ -42,6 +43,7 @@ impl FixedTime {
FixedTime {
accumulated: Duration::ZERO,
period,
total_ticks: 0,
}
}

Expand All @@ -50,6 +52,7 @@ impl FixedTime {
FixedTime {
accumulated: Duration::ZERO,
period: Duration::from_secs_f32(period),
total_ticks: 0,
}
}

Expand All @@ -63,13 +66,19 @@ impl FixedTime {
self.accumulated
}

/// Returns how often this has expended a period of time.
pub fn times_expended(&self) -> u64 {
self.total_ticks
}

/// Expends one `period` of accumulated time.
///
/// [`Err(FixedUpdateError`)] will be returned if there is
/// not enough accumulated time to span an entire period.
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 {
Expand All @@ -85,10 +94,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 {
Expand All @@ -101,6 +115,8 @@ pub enum FixedUpdateError {

/// Ticks the [`FixedTime`] resource then runs the [`FixedUpdate`].
pub fn run_fixed_update_schedule(world: &mut World) {
world.insert_resource(FixedUpdateScheduleIsCurrentlyRunning);

// Tick the time
let delta_time = world.resource::<Time>().delta();
let mut fixed_time = world.resource_mut::<FixedTime>();
Expand All @@ -112,6 +128,8 @@ pub fn run_fixed_update_schedule(world: &mut World) {
schedule.run(world);
}
});

world.remove_resource::<FixedUpdateScheduleIsCurrentlyRunning>();
}

#[cfg(test)]
Expand Down

0 comments on commit 0495ae7

Please sign in to comment.