Merge branch 'main' into Remove-entity-reserving/pending/flushing-system
This commit is contained in:
commit
deb6b4d633
@ -19,6 +19,7 @@ use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||
use bevy_input::ButtonState;
|
||||
use bevy_input_focus::FocusedInput;
|
||||
use bevy_log::warn_once;
|
||||
use bevy_math::ops;
|
||||
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
|
||||
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
|
||||
|
||||
@ -38,7 +39,8 @@ pub enum TrackClick {
|
||||
|
||||
/// A headless slider widget, which can be used to build custom sliders. Sliders have a value
|
||||
/// (represented by the [`SliderValue`] component) and a range (represented by [`SliderRange`]). An
|
||||
/// optional step size can be specified via [`SliderStep`].
|
||||
/// optional step size can be specified via [`SliderStep`], and you can control the rounding
|
||||
/// during dragging with [`SliderPrecision`].
|
||||
///
|
||||
/// You can also control the slider remotely by triggering a [`SetSliderValue`] event on it. This
|
||||
/// can be useful in a console environment for controlling the value gamepad inputs.
|
||||
@ -187,6 +189,25 @@ impl Default for SliderStep {
|
||||
}
|
||||
}
|
||||
|
||||
/// A component which controls the rounding of the slider value during dragging.
|
||||
///
|
||||
/// Stepping is not affected, although presumably the step size will be an integer multiple of the
|
||||
/// rounding factor. This also doesn't prevent the slider value from being set to non-rounded values
|
||||
/// by other means, such as manually entering digits via a numeric input field.
|
||||
///
|
||||
/// The value in this component represents the number of decimal places of desired precision, so a
|
||||
/// value of 2 would round to the nearest 1/100th. A value of -3 would round to the nearest
|
||||
/// thousand.
|
||||
#[derive(Component, Debug, Default, Clone, Copy)]
|
||||
pub struct SliderPrecision(pub i32);
|
||||
|
||||
impl SliderPrecision {
|
||||
fn round(&self, value: f32) -> f32 {
|
||||
let factor = ops::powf(10.0_f32, self.0 as f32);
|
||||
(value * factor).round() / factor
|
||||
}
|
||||
}
|
||||
|
||||
/// Component used to manage the state of a slider during dragging.
|
||||
#[derive(Component, Default)]
|
||||
pub struct CoreSliderDragState {
|
||||
@ -204,6 +225,7 @@ pub(crate) fn slider_on_pointer_down(
|
||||
&SliderValue,
|
||||
&SliderRange,
|
||||
&SliderStep,
|
||||
Option<&SliderPrecision>,
|
||||
&ComputedNode,
|
||||
&ComputedNodeTarget,
|
||||
&UiGlobalTransform,
|
||||
@ -217,8 +239,17 @@ pub(crate) fn slider_on_pointer_down(
|
||||
if q_thumb.contains(trigger.target()) {
|
||||
// Thumb click, stop propagation to prevent track click.
|
||||
trigger.propagate(false);
|
||||
} else if let Ok((slider, value, range, step, node, node_target, transform, disabled)) =
|
||||
q_slider.get(trigger.target())
|
||||
} else if let Ok((
|
||||
slider,
|
||||
value,
|
||||
range,
|
||||
step,
|
||||
precision,
|
||||
node,
|
||||
node_target,
|
||||
transform,
|
||||
disabled,
|
||||
)) = q_slider.get(trigger.target())
|
||||
{
|
||||
// Track click
|
||||
trigger.propagate(false);
|
||||
@ -257,7 +288,9 @@ pub(crate) fn slider_on_pointer_down(
|
||||
value.0 + step.0
|
||||
}
|
||||
}
|
||||
TrackClick::Snap => click_val,
|
||||
TrackClick::Snap => precision
|
||||
.map(|prec| prec.round(click_val))
|
||||
.unwrap_or(click_val),
|
||||
});
|
||||
|
||||
if matches!(slider.on_change, Callback::Ignore) {
|
||||
@ -296,6 +329,7 @@ pub(crate) fn slider_on_drag(
|
||||
&ComputedNode,
|
||||
&CoreSlider,
|
||||
&SliderRange,
|
||||
Option<&SliderPrecision>,
|
||||
&UiGlobalTransform,
|
||||
&mut CoreSliderDragState,
|
||||
Has<InteractionDisabled>,
|
||||
@ -305,7 +339,8 @@ pub(crate) fn slider_on_drag(
|
||||
mut commands: Commands,
|
||||
ui_scale: Res<UiScale>,
|
||||
) {
|
||||
if let Ok((node, slider, range, transform, drag, disabled)) = q_slider.get_mut(trigger.target())
|
||||
if let Ok((node, slider, range, precision, transform, drag, disabled)) =
|
||||
q_slider.get_mut(trigger.target())
|
||||
{
|
||||
trigger.propagate(false);
|
||||
if drag.dragging && !disabled {
|
||||
@ -320,17 +355,22 @@ pub(crate) fn slider_on_drag(
|
||||
let slider_width = ((node.size().x - thumb_size) * node.inverse_scale_factor).max(1.0);
|
||||
let span = range.span();
|
||||
let new_value = if span > 0. {
|
||||
range.clamp(drag.offset + (distance.x * span) / slider_width)
|
||||
drag.offset + (distance.x * span) / slider_width
|
||||
} else {
|
||||
range.start() + span * 0.5
|
||||
};
|
||||
let rounded_value = range.clamp(
|
||||
precision
|
||||
.map(|prec| prec.round(new_value))
|
||||
.unwrap_or(new_value),
|
||||
);
|
||||
|
||||
if matches!(slider.on_change, Callback::Ignore) {
|
||||
commands
|
||||
.entity(trigger.target())
|
||||
.insert(SliderValue(new_value));
|
||||
.insert(SliderValue(rounded_value));
|
||||
} else {
|
||||
commands.notify_with(&slider.on_change, new_value);
|
||||
commands.notify_with(&slider.on_change, rounded_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -491,3 +531,24 @@ impl Plugin for CoreSliderPlugin {
|
||||
.add_observer(slider_on_set_value);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_slider_precision_rounding() {
|
||||
// Test positive precision values (decimal places)
|
||||
let precision_2dp = SliderPrecision(2);
|
||||
assert_eq!(precision_2dp.round(1.234567), 1.23);
|
||||
assert_eq!(precision_2dp.round(1.235), 1.24);
|
||||
|
||||
// Test zero precision (rounds to integers)
|
||||
let precision_0dp = SliderPrecision(0);
|
||||
assert_eq!(precision_0dp.round(1.4), 1.0);
|
||||
|
||||
// Test negative precision (rounds to tens, hundreds, etc.)
|
||||
let precision_neg1 = SliderPrecision(-1);
|
||||
assert_eq!(precision_neg1.round(14.0), 10.0);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ mod core_radio;
|
||||
mod core_scrollbar;
|
||||
mod core_slider;
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_app::{PluginGroup, PluginGroupBuilder};
|
||||
|
||||
pub use callback::{Callback, Notify};
|
||||
pub use core_button::{CoreButton, CoreButtonPlugin};
|
||||
@ -33,21 +33,20 @@ pub use core_scrollbar::{
|
||||
};
|
||||
pub use core_slider::{
|
||||
CoreSlider, CoreSliderDragState, CoreSliderPlugin, CoreSliderThumb, SetSliderValue,
|
||||
SliderRange, SliderStep, SliderValue, TrackClick,
|
||||
SliderPrecision, SliderRange, SliderStep, SliderValue, TrackClick,
|
||||
};
|
||||
|
||||
/// A plugin that registers the observers for all of the core widgets. If you don't want to
|
||||
/// A plugin group that registers the observers for all of the core widgets. If you don't want to
|
||||
/// use all of the widgets, you can import the individual widget plugins instead.
|
||||
pub struct CoreWidgetsPlugin;
|
||||
pub struct CoreWidgetsPlugins;
|
||||
|
||||
impl Plugin for CoreWidgetsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
CoreButtonPlugin,
|
||||
CoreCheckboxPlugin,
|
||||
CoreRadioGroupPlugin,
|
||||
CoreScrollbarPlugin,
|
||||
CoreSliderPlugin,
|
||||
));
|
||||
impl PluginGroup for CoreWidgetsPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(CoreButtonPlugin)
|
||||
.add(CoreCheckboxPlugin)
|
||||
.add(CoreRadioGroupPlugin)
|
||||
.add(CoreScrollbarPlugin)
|
||||
.add(CoreSliderPlugin)
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,14 @@ bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
|
||||
bevy_ui_render = { path = "../bevy_ui_render", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Module containing logic for FPS overlay.
|
||||
|
||||
use bevy_app::{Plugin, Startup, Update};
|
||||
use bevy_asset::Handle;
|
||||
use bevy_asset::{Assets, Handle};
|
||||
use bevy_color::Color;
|
||||
use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
||||
use bevy_ecs::{
|
||||
@ -12,22 +12,31 @@ use bevy_ecs::{
|
||||
query::With,
|
||||
resource::Resource,
|
||||
schedule::{common_conditions::resource_changed, IntoScheduleConfigs},
|
||||
system::{Commands, Query, Res},
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_render::view::Visibility;
|
||||
use bevy_render::{storage::ShaderStorageBuffer, view::Visibility};
|
||||
use bevy_text::{Font, TextColor, TextFont, TextSpan};
|
||||
use bevy_time::Time;
|
||||
use bevy_ui::{
|
||||
widget::{Text, TextUiWriter},
|
||||
GlobalZIndex, Node, PositionType,
|
||||
FlexDirection, GlobalZIndex, Node, PositionType, Val,
|
||||
};
|
||||
use bevy_ui_render::prelude::MaterialNode;
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::frame_time_graph::{
|
||||
FrameTimeGraphConfigUniform, FrameTimeGraphPlugin, FrametimeGraphMaterial,
|
||||
};
|
||||
|
||||
/// [`GlobalZIndex`] used to render the fps overlay.
|
||||
///
|
||||
/// We use a number slightly under `i32::MAX` so you can render on top of it if you really need to.
|
||||
pub const FPS_OVERLAY_ZINDEX: i32 = i32::MAX - 32;
|
||||
|
||||
// Used to scale the frame time graph based on the fps text size
|
||||
const FRAME_TIME_GRAPH_WIDTH_SCALE: f32 = 6.0;
|
||||
const FRAME_TIME_GRAPH_HEIGHT_SCALE: f32 = 2.0;
|
||||
|
||||
/// A plugin that adds an FPS overlay to the Bevy application.
|
||||
///
|
||||
/// This plugin will add the [`FrameTimeDiagnosticsPlugin`] if it wasn't added before.
|
||||
@ -47,12 +56,18 @@ impl Plugin for FpsOverlayPlugin {
|
||||
if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
|
||||
app.add_plugins(FrameTimeDiagnosticsPlugin::default());
|
||||
}
|
||||
|
||||
if !app.is_plugin_added::<FrameTimeGraphPlugin>() {
|
||||
app.add_plugins(FrameTimeGraphPlugin);
|
||||
}
|
||||
|
||||
app.insert_resource(self.config.clone())
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
(customize_text, toggle_display).run_if(resource_changed::<FpsOverlayConfig>),
|
||||
(toggle_display, customize_overlay)
|
||||
.run_if(resource_changed::<FpsOverlayConfig>),
|
||||
update_text,
|
||||
),
|
||||
);
|
||||
@ -72,6 +87,8 @@ pub struct FpsOverlayConfig {
|
||||
///
|
||||
/// Defaults to once every 100 ms.
|
||||
pub refresh_interval: Duration,
|
||||
/// Configuration of the frame time graph
|
||||
pub frame_time_graph_config: FrameTimeGraphConfig,
|
||||
}
|
||||
|
||||
impl Default for FpsOverlayConfig {
|
||||
@ -85,6 +102,43 @@ impl Default for FpsOverlayConfig {
|
||||
text_color: Color::WHITE,
|
||||
enabled: true,
|
||||
refresh_interval: Duration::from_millis(100),
|
||||
// TODO set this to display refresh rate if possible
|
||||
frame_time_graph_config: FrameTimeGraphConfig::target_fps(60.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration of the frame time graph
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FrameTimeGraphConfig {
|
||||
/// Is the graph visible
|
||||
pub enabled: bool,
|
||||
/// The minimum acceptable FPS
|
||||
///
|
||||
/// Anything below this will show a red bar
|
||||
pub min_fps: f32,
|
||||
/// The target FPS
|
||||
///
|
||||
/// Anything above this will show a green bar
|
||||
pub target_fps: f32,
|
||||
}
|
||||
|
||||
impl FrameTimeGraphConfig {
|
||||
/// Constructs a default config for a given target fps
|
||||
pub fn target_fps(target_fps: f32) -> Self {
|
||||
Self {
|
||||
target_fps,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrameTimeGraphConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
min_fps: 30.0,
|
||||
target_fps: 60.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,12 +146,21 @@ impl Default for FpsOverlayConfig {
|
||||
#[derive(Component)]
|
||||
struct FpsText;
|
||||
|
||||
fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
|
||||
#[derive(Component)]
|
||||
struct FrameTimeGraph;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
overlay_config: Res<FpsOverlayConfig>,
|
||||
mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
|
||||
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
|
||||
) {
|
||||
commands
|
||||
.spawn((
|
||||
Node {
|
||||
// We need to make sure the overlay doesn't affect the position of other UI nodes
|
||||
position_type: PositionType::Absolute,
|
||||
flex_direction: FlexDirection::Column,
|
||||
..Default::default()
|
||||
},
|
||||
// Render overlay on top of everything
|
||||
@ -111,6 +174,29 @@ fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
|
||||
FpsText,
|
||||
))
|
||||
.with_child((TextSpan::default(), overlay_config.text_config.clone()));
|
||||
|
||||
let font_size = overlay_config.text_config.font_size;
|
||||
p.spawn((
|
||||
Node {
|
||||
width: Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE),
|
||||
height: Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE),
|
||||
display: if overlay_config.frame_time_graph_config.enabled {
|
||||
bevy_ui::Display::DEFAULT
|
||||
} else {
|
||||
bevy_ui::Display::None
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
MaterialNode::from(frame_time_graph_materials.add(FrametimeGraphMaterial {
|
||||
values: buffers.add(ShaderStorageBuffer::default()),
|
||||
config: FrameTimeGraphConfigUniform::new(
|
||||
overlay_config.frame_time_graph_config.target_fps,
|
||||
overlay_config.frame_time_graph_config.min_fps,
|
||||
true,
|
||||
),
|
||||
})),
|
||||
FrameTimeGraph,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,7 +221,7 @@ fn update_text(
|
||||
}
|
||||
}
|
||||
|
||||
fn customize_text(
|
||||
fn customize_overlay(
|
||||
overlay_config: Res<FpsOverlayConfig>,
|
||||
query: Query<Entity, With<FpsText>>,
|
||||
mut writer: TextUiWriter,
|
||||
@ -151,6 +237,7 @@ fn customize_text(
|
||||
fn toggle_display(
|
||||
overlay_config: Res<FpsOverlayConfig>,
|
||||
mut query: Query<&mut Visibility, With<FpsText>>,
|
||||
mut graph_style: Query<&mut Node, With<FrameTimeGraph>>,
|
||||
) {
|
||||
for mut visibility in &mut query {
|
||||
visibility.set_if_neq(match overlay_config.enabled {
|
||||
@ -158,4 +245,17 @@ fn toggle_display(
|
||||
false => Visibility::Hidden,
|
||||
});
|
||||
}
|
||||
|
||||
if let Ok(mut graph_style) = graph_style.single_mut() {
|
||||
if overlay_config.frame_time_graph_config.enabled {
|
||||
// Scale the frame time graph based on the font size of the overlay
|
||||
let font_size = overlay_config.text_config.font_size;
|
||||
graph_style.width = Val::Px(font_size * FRAME_TIME_GRAPH_WIDTH_SCALE);
|
||||
graph_style.height = Val::Px(font_size * FRAME_TIME_GRAPH_HEIGHT_SCALE);
|
||||
|
||||
graph_style.display = bevy_ui::Display::DEFAULT;
|
||||
} else {
|
||||
graph_style.display = bevy_ui::Display::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
#import bevy_ui::ui_vertex_output::UiVertexOutput
|
||||
|
||||
@group(1) @binding(0) var<storage> values: array<f32>;
|
||||
struct Config {
|
||||
dt_min: f32,
|
||||
dt_max: f32,
|
||||
dt_min_log2: f32,
|
||||
dt_max_log2: f32,
|
||||
proportional_width: u32,
|
||||
}
|
||||
@group(1) @binding(1) var<uniform> config: Config;
|
||||
|
||||
const RED: vec4<f32> = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
const GREEN: vec4<f32> = vec4(0.0, 1.0, 0.0, 1.0);
|
||||
|
||||
// Gets a color based on the delta time
|
||||
// TODO use customizable gradient
|
||||
fn color_from_dt(dt: f32) -> vec4<f32> {
|
||||
return mix(GREEN, RED, dt / config.dt_max);
|
||||
}
|
||||
|
||||
// Draw an SDF square
|
||||
fn sdf_square(pos: vec2<f32>, half_size: vec2<f32>, offset: vec2<f32>) -> f32 {
|
||||
let p = pos - offset;
|
||||
let dist = abs(p) - half_size;
|
||||
let outside_dist = length(max(dist, vec2<f32>(0.0, 0.0)));
|
||||
let inside_dist = min(max(dist.x, dist.y), 0.0);
|
||||
return outside_dist + inside_dist;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: UiVertexOutput) -> @location(0) vec4<f32> {
|
||||
let dt_min = config.dt_min;
|
||||
let dt_max = config.dt_max;
|
||||
let dt_min_log2 = config.dt_min_log2;
|
||||
let dt_max_log2 = config.dt_max_log2;
|
||||
|
||||
// The general algorithm is highly inspired by
|
||||
// <https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times>
|
||||
|
||||
let len = arrayLength(&values);
|
||||
var graph_width = 0.0;
|
||||
for (var i = 0u; i <= len; i += 1u) {
|
||||
let dt = values[len - i];
|
||||
|
||||
var frame_width: f32;
|
||||
if config.proportional_width == 1u {
|
||||
frame_width = (dt / dt_min) / f32(len);
|
||||
} else {
|
||||
frame_width = 0.015;
|
||||
}
|
||||
|
||||
let frame_height_factor = (log2(dt) - dt_min_log2) / (dt_max_log2 - dt_min_log2);
|
||||
let frame_height_factor_norm = min(max(0.0, frame_height_factor), 1.0);
|
||||
let frame_height = mix(0.0, 1.0, frame_height_factor_norm);
|
||||
|
||||
let size = vec2(frame_width, frame_height) / 2.0;
|
||||
let offset = vec2(1.0 - graph_width - size.x, 1. - size.y);
|
||||
if (sdf_square(in.uv, size, offset) < 0.0) {
|
||||
return color_from_dt(dt);
|
||||
}
|
||||
|
||||
graph_width += frame_width;
|
||||
}
|
||||
|
||||
return vec4(0.0, 0.0, 0.0, 0.5);
|
||||
}
|
||||
|
114
crates/bevy_dev_tools/src/frame_time_graph/mod.rs
Normal file
114
crates/bevy_dev_tools/src/frame_time_graph/mod.rs
Normal file
@ -0,0 +1,114 @@
|
||||
//! Module containing logic for the frame time graph
|
||||
|
||||
use bevy_app::{Plugin, Update};
|
||||
use bevy_asset::{load_internal_asset, uuid_handle, Asset, Assets, Handle};
|
||||
use bevy_diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
|
||||
use bevy_ecs::system::{Res, ResMut};
|
||||
use bevy_math::ops::log2;
|
||||
use bevy_reflect::TypePath;
|
||||
use bevy_render::{
|
||||
render_resource::{AsBindGroup, Shader, ShaderRef, ShaderType},
|
||||
storage::ShaderStorageBuffer,
|
||||
};
|
||||
use bevy_ui_render::prelude::{UiMaterial, UiMaterialPlugin};
|
||||
|
||||
use crate::fps_overlay::FpsOverlayConfig;
|
||||
|
||||
const FRAME_TIME_GRAPH_SHADER_HANDLE: Handle<Shader> =
|
||||
uuid_handle!("4e38163a-5782-47a5-af52-d9161472ab59");
|
||||
|
||||
/// Plugin that sets up everything to render the frame time graph material
|
||||
pub struct FrameTimeGraphPlugin;
|
||||
|
||||
impl Plugin for FrameTimeGraphPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
load_internal_asset!(
|
||||
app,
|
||||
FRAME_TIME_GRAPH_SHADER_HANDLE,
|
||||
"frame_time_graph.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
|
||||
// TODO: Use plugin dependencies, see https://github.com/bevyengine/bevy/issues/69
|
||||
if !app.is_plugin_added::<FrameTimeDiagnosticsPlugin>() {
|
||||
panic!("Requires FrameTimeDiagnosticsPlugin");
|
||||
// app.add_plugins(FrameTimeDiagnosticsPlugin);
|
||||
}
|
||||
|
||||
app.add_plugins(UiMaterialPlugin::<FrametimeGraphMaterial>::default())
|
||||
.add_systems(Update, update_frame_time_values);
|
||||
}
|
||||
}
|
||||
|
||||
/// The config values sent to the frame time graph shader
|
||||
#[derive(Debug, Clone, Copy, ShaderType)]
|
||||
pub struct FrameTimeGraphConfigUniform {
|
||||
// minimum expected delta time
|
||||
dt_min: f32,
|
||||
// maximum expected delta time
|
||||
dt_max: f32,
|
||||
dt_min_log2: f32,
|
||||
dt_max_log2: f32,
|
||||
// controls whether or not the bars width are proportional to their delta time
|
||||
proportional_width: u32,
|
||||
}
|
||||
|
||||
impl FrameTimeGraphConfigUniform {
|
||||
/// `proportional_width`: controls whether or not the bars width are proportional to their delta time
|
||||
pub fn new(target_fps: f32, min_fps: f32, proportional_width: bool) -> Self {
|
||||
// we want an upper limit that is above the target otherwise the bars will disappear
|
||||
let dt_min = 1. / (target_fps * 1.2);
|
||||
let dt_max = 1. / min_fps;
|
||||
Self {
|
||||
dt_min,
|
||||
dt_max,
|
||||
dt_min_log2: log2(dt_min),
|
||||
dt_max_log2: log2(dt_max),
|
||||
proportional_width: u32::from(proportional_width),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The material used to render the frame time graph ui node
|
||||
#[derive(AsBindGroup, Asset, TypePath, Debug, Clone)]
|
||||
pub struct FrametimeGraphMaterial {
|
||||
/// The history of the previous frame times value.
|
||||
///
|
||||
/// This should be updated every frame to match the frame time history from the [`DiagnosticsStore`]
|
||||
#[storage(0, read_only)]
|
||||
pub values: Handle<ShaderStorageBuffer>, // Vec<f32>,
|
||||
/// The configuration values used by the shader to control how the graph is rendered
|
||||
#[uniform(1)]
|
||||
pub config: FrameTimeGraphConfigUniform,
|
||||
}
|
||||
|
||||
impl UiMaterial for FrametimeGraphMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
FRAME_TIME_GRAPH_SHADER_HANDLE.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that updates the frame time values sent to the frame time graph
|
||||
fn update_frame_time_values(
|
||||
mut frame_time_graph_materials: ResMut<Assets<FrametimeGraphMaterial>>,
|
||||
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
|
||||
diagnostics_store: Res<DiagnosticsStore>,
|
||||
config: Option<Res<FpsOverlayConfig>>,
|
||||
) {
|
||||
if !config.is_none_or(|c| c.frame_time_graph_config.enabled) {
|
||||
return;
|
||||
}
|
||||
let Some(frame_time) = diagnostics_store.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) else {
|
||||
return;
|
||||
};
|
||||
let frame_times = frame_time
|
||||
.values()
|
||||
// convert to millis
|
||||
.map(|x| *x as f32 / 1000.0)
|
||||
.collect::<Vec<_>>();
|
||||
for (_, material) in frame_time_graph_materials.iter_mut() {
|
||||
let buffer = buffers.get_mut(&material.values).unwrap();
|
||||
|
||||
buffer.set_data(frame_times.clone().as_slice());
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ use bevy_app::prelude::*;
|
||||
pub mod ci_testing;
|
||||
|
||||
pub mod fps_overlay;
|
||||
pub mod frame_time_graph;
|
||||
|
||||
pub mod picking_debug;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
219
crates/bevy_ecs/src/component/clone.rs
Normal file
219
crates/bevy_ecs/src/component/clone.rs
Normal file
@ -0,0 +1,219 @@
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::component::Component;
|
||||
use crate::entity::{ComponentCloneCtx, SourceComponent};
|
||||
|
||||
/// Function type that can be used to clone an entity.
|
||||
pub type ComponentCloneFn = fn(&SourceComponent, &mut ComponentCloneCtx);
|
||||
|
||||
/// The clone behavior to use when cloning a [`Component`].
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum ComponentCloneBehavior {
|
||||
/// Uses the default behavior (which is passed to [`ComponentCloneBehavior::resolve`])
|
||||
#[default]
|
||||
Default,
|
||||
/// Do not clone this component.
|
||||
Ignore,
|
||||
/// Uses a custom [`ComponentCloneFn`].
|
||||
Custom(ComponentCloneFn),
|
||||
}
|
||||
|
||||
impl ComponentCloneBehavior {
|
||||
/// Set clone handler based on `Clone` trait.
|
||||
///
|
||||
/// If set as a handler for a component that is not the same as the one used to create this handler, it will panic.
|
||||
pub fn clone<C: Component + Clone>() -> Self {
|
||||
Self::Custom(component_clone_via_clone::<C>)
|
||||
}
|
||||
|
||||
/// Set clone handler based on `Reflect` trait.
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub fn reflect() -> Self {
|
||||
Self::Custom(component_clone_via_reflect)
|
||||
}
|
||||
|
||||
/// Returns the "global default"
|
||||
pub fn global_default_fn() -> ComponentCloneFn {
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
return component_clone_via_reflect;
|
||||
#[cfg(not(feature = "bevy_reflect"))]
|
||||
return component_clone_ignore;
|
||||
}
|
||||
|
||||
/// Resolves the [`ComponentCloneBehavior`] to a [`ComponentCloneFn`]. If [`ComponentCloneBehavior::Default`] is
|
||||
/// specified, the given `default` function will be used.
|
||||
pub fn resolve(&self, default: ComponentCloneFn) -> ComponentCloneFn {
|
||||
match self {
|
||||
ComponentCloneBehavior::Default => default,
|
||||
ComponentCloneBehavior::Ignore => component_clone_ignore,
|
||||
ComponentCloneBehavior::Custom(custom) => *custom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Component [clone handler function](ComponentCloneFn) implemented using the [`Clone`] trait.
|
||||
/// Can be [set](Component::clone_behavior) as clone handler for the specific component it is implemented for.
|
||||
/// It will panic if set as handler for any other component.
|
||||
///
|
||||
pub fn component_clone_via_clone<C: Clone + Component>(
|
||||
source: &SourceComponent,
|
||||
ctx: &mut ComponentCloneCtx,
|
||||
) {
|
||||
if let Some(component) = source.read::<C>() {
|
||||
ctx.write_target_component(component.clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Component [clone handler function](ComponentCloneFn) implemented using reflect.
|
||||
/// Can be [set](Component::clone_behavior) as clone handler for any registered component,
|
||||
/// but only reflected components will be cloned.
|
||||
///
|
||||
/// To clone a component using this handler, the following must be true:
|
||||
/// - World has [`AppTypeRegistry`](crate::reflect::AppTypeRegistry)
|
||||
/// - Component has [`TypeId`](core::any::TypeId)
|
||||
/// - Component is registered
|
||||
/// - Component has [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) registered
|
||||
/// - Component can be cloned via [`PartialReflect::reflect_clone`] _or_ has one of the following registered: [`ReflectFromReflect`](bevy_reflect::ReflectFromReflect),
|
||||
/// [`ReflectDefault`](bevy_reflect::std_traits::ReflectDefault), [`ReflectFromWorld`](crate::reflect::ReflectFromWorld)
|
||||
///
|
||||
/// If any of the conditions is not satisfied, the component will be skipped.
|
||||
///
|
||||
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
||||
///
|
||||
/// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub fn component_clone_via_reflect(source: &SourceComponent, ctx: &mut ComponentCloneCtx) {
|
||||
let Some(app_registry) = ctx.type_registry().cloned() else {
|
||||
return;
|
||||
};
|
||||
let registry = app_registry.read();
|
||||
let Some(source_component_reflect) = source.read_reflect(®istry) else {
|
||||
return;
|
||||
};
|
||||
let component_info = ctx.component_info();
|
||||
// checked in read_source_component_reflect
|
||||
let type_id = component_info.type_id().unwrap();
|
||||
|
||||
// Try to clone using `reflect_clone`
|
||||
if let Ok(mut component) = source_component_reflect.reflect_clone() {
|
||||
if let Some(reflect_component) =
|
||||
registry.get_type_data::<crate::reflect::ReflectComponent>(type_id)
|
||||
{
|
||||
reflect_component.map_entities(&mut *component, ctx.entity_mapper());
|
||||
}
|
||||
drop(registry);
|
||||
|
||||
ctx.write_target_component_reflect(component);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to clone using ReflectFromReflect
|
||||
if let Some(reflect_from_reflect) =
|
||||
registry.get_type_data::<bevy_reflect::ReflectFromReflect>(type_id)
|
||||
{
|
||||
if let Some(mut component) =
|
||||
reflect_from_reflect.from_reflect(source_component_reflect.as_partial_reflect())
|
||||
{
|
||||
if let Some(reflect_component) =
|
||||
registry.get_type_data::<crate::reflect::ReflectComponent>(type_id)
|
||||
{
|
||||
reflect_component.map_entities(&mut *component, ctx.entity_mapper());
|
||||
}
|
||||
drop(registry);
|
||||
|
||||
ctx.write_target_component_reflect(component);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Else, try to clone using ReflectDefault
|
||||
if let Some(reflect_default) =
|
||||
registry.get_type_data::<bevy_reflect::std_traits::ReflectDefault>(type_id)
|
||||
{
|
||||
let mut component = reflect_default.default();
|
||||
component.apply(source_component_reflect.as_partial_reflect());
|
||||
drop(registry);
|
||||
ctx.write_target_component_reflect(component);
|
||||
return;
|
||||
}
|
||||
// Otherwise, try to clone using ReflectFromWorld
|
||||
if let Some(reflect_from_world) =
|
||||
registry.get_type_data::<crate::reflect::ReflectFromWorld>(type_id)
|
||||
{
|
||||
use crate::{entity::EntityMapper, world::World};
|
||||
|
||||
let reflect_from_world = reflect_from_world.clone();
|
||||
let source_component_cloned = source_component_reflect.to_dynamic();
|
||||
let component_layout = component_info.layout();
|
||||
let target = ctx.target();
|
||||
let component_id = ctx.component_id();
|
||||
drop(registry);
|
||||
ctx.queue_deferred(move |world: &mut World, mapper: &mut dyn EntityMapper| {
|
||||
let mut component = reflect_from_world.from_world(world);
|
||||
assert_eq!(type_id, (*component).type_id());
|
||||
component.apply(source_component_cloned.as_partial_reflect());
|
||||
if let Some(reflect_component) = app_registry
|
||||
.read()
|
||||
.get_type_data::<crate::reflect::ReflectComponent>(type_id)
|
||||
{
|
||||
reflect_component.map_entities(&mut *component, mapper);
|
||||
}
|
||||
// SAFETY:
|
||||
// - component_id is from the same world as target entity
|
||||
// - component is a valid value represented by component_id
|
||||
unsafe {
|
||||
use alloc::boxed::Box;
|
||||
use bevy_ptr::OwningPtr;
|
||||
|
||||
let raw_component_ptr =
|
||||
core::ptr::NonNull::new_unchecked(Box::into_raw(component).cast::<u8>());
|
||||
world
|
||||
.entity_mut(target)
|
||||
.insert_by_id(component_id, OwningPtr::new(raw_component_ptr));
|
||||
|
||||
if component_layout.size() > 0 {
|
||||
// Ensure we don't attempt to deallocate zero-sized components
|
||||
alloc::alloc::dealloc(raw_component_ptr.as_ptr(), component_layout);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Noop implementation of component clone handler function.
|
||||
///
|
||||
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
||||
pub fn component_clone_ignore(_source: &SourceComponent, _ctx: &mut ComponentCloneCtx) {}
|
||||
|
||||
/// Wrapper for components clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub struct DefaultCloneBehaviorSpecialization<T>(PhantomData<T>);
|
||||
|
||||
impl<T> Default for DefaultCloneBehaviorSpecialization<T> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Base trait for components clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait DefaultCloneBehaviorBase {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Default
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized trait for components clone specialization using autoderef.
|
||||
#[doc(hidden)]
|
||||
pub trait DefaultCloneBehaviorViaClone {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::clone::<C>()
|
||||
}
|
||||
}
|
721
crates/bevy_ecs/src/component/info.rs
Normal file
721
crates/bevy_ecs/src/component/info.rs
Normal file
@ -0,0 +1,721 @@
|
||||
use alloc::{borrow::Cow, vec::Vec};
|
||||
use bevy_platform::{collections::HashSet, sync::PoisonError};
|
||||
use bevy_ptr::OwningPtr;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::{prelude::DebugName, TypeIdMap};
|
||||
use core::{
|
||||
alloc::Layout,
|
||||
any::{Any, TypeId},
|
||||
fmt::Debug,
|
||||
mem::needs_drop,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
archetype::ArchetypeFlags,
|
||||
component::{
|
||||
Component, ComponentCloneBehavior, ComponentMutability, QueuedComponents,
|
||||
RequiredComponents, StorageType,
|
||||
},
|
||||
lifecycle::ComponentHooks,
|
||||
query::DebugCheckedUnwrap as _,
|
||||
resource::Resource,
|
||||
storage::SparseSetIndex,
|
||||
};
|
||||
|
||||
/// Stores metadata for a type of component or resource stored in a specific [`World`](crate::world::World).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComponentInfo {
|
||||
pub(super) id: ComponentId,
|
||||
pub(super) descriptor: ComponentDescriptor,
|
||||
pub(super) hooks: ComponentHooks,
|
||||
pub(super) required_components: RequiredComponents,
|
||||
pub(super) required_by: HashSet<ComponentId>,
|
||||
}
|
||||
|
||||
impl ComponentInfo {
|
||||
/// Returns a value uniquely identifying the current component.
|
||||
#[inline]
|
||||
pub fn id(&self) -> ComponentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Returns the name of the current component.
|
||||
#[inline]
|
||||
pub fn name(&self) -> DebugName {
|
||||
self.descriptor.name.clone()
|
||||
}
|
||||
|
||||
/// Returns `true` if the current component is mutable.
|
||||
#[inline]
|
||||
pub fn mutable(&self) -> bool {
|
||||
self.descriptor.mutable
|
||||
}
|
||||
|
||||
/// Returns [`ComponentCloneBehavior`] of the current component.
|
||||
#[inline]
|
||||
pub fn clone_behavior(&self) -> &ComponentCloneBehavior {
|
||||
&self.descriptor.clone_behavior
|
||||
}
|
||||
|
||||
/// Returns the [`TypeId`] of the underlying component type.
|
||||
/// Returns `None` if the component does not correspond to a Rust type.
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> Option<TypeId> {
|
||||
self.descriptor.type_id
|
||||
}
|
||||
|
||||
/// Returns the layout used to store values of this component in memory.
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.descriptor.layout
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get the function which should be called to clean up values of
|
||||
/// the underlying component type. This maps to the
|
||||
/// [`Drop`] implementation for 'normal' Rust components
|
||||
///
|
||||
/// Returns `None` if values of the underlying component type don't
|
||||
/// need to be dropped, e.g. as reported by [`needs_drop`].
|
||||
pub fn drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
|
||||
self.descriptor.drop
|
||||
}
|
||||
|
||||
/// Returns a value indicating the storage strategy for the current component.
|
||||
#[inline]
|
||||
pub fn storage_type(&self) -> StorageType {
|
||||
self.descriptor.storage_type
|
||||
}
|
||||
|
||||
/// Returns `true` if the underlying component type can be freely shared between threads.
|
||||
/// If this returns `false`, then extra care must be taken to ensure that components
|
||||
/// are not accessed from the wrong thread.
|
||||
#[inline]
|
||||
pub fn is_send_and_sync(&self) -> bool {
|
||||
self.descriptor.is_send_and_sync
|
||||
}
|
||||
|
||||
/// Create a new [`ComponentInfo`].
|
||||
pub(crate) fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self {
|
||||
ComponentInfo {
|
||||
id,
|
||||
descriptor,
|
||||
hooks: Default::default(),
|
||||
required_components: Default::default(),
|
||||
required_by: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the given flags to include any [`ComponentHook`](crate::component::ComponentHook) registered to self
|
||||
#[inline]
|
||||
pub(crate) fn update_archetype_flags(&self, flags: &mut ArchetypeFlags) {
|
||||
if self.hooks().on_add.is_some() {
|
||||
flags.insert(ArchetypeFlags::ON_ADD_HOOK);
|
||||
}
|
||||
if self.hooks().on_insert.is_some() {
|
||||
flags.insert(ArchetypeFlags::ON_INSERT_HOOK);
|
||||
}
|
||||
if self.hooks().on_replace.is_some() {
|
||||
flags.insert(ArchetypeFlags::ON_REPLACE_HOOK);
|
||||
}
|
||||
if self.hooks().on_remove.is_some() {
|
||||
flags.insert(ArchetypeFlags::ON_REMOVE_HOOK);
|
||||
}
|
||||
if self.hooks().on_despawn.is_some() {
|
||||
flags.insert(ArchetypeFlags::ON_DESPAWN_HOOK);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a reference to the collection of hooks associated with this [`Component`]
|
||||
pub fn hooks(&self) -> &ComponentHooks {
|
||||
&self.hooks
|
||||
}
|
||||
|
||||
/// Retrieves the [`RequiredComponents`] collection, which contains all required components (and their constructors)
|
||||
/// needed by this component. This includes _recursive_ required components.
|
||||
pub fn required_components(&self) -> &RequiredComponents {
|
||||
&self.required_components
|
||||
}
|
||||
}
|
||||
|
||||
/// A value which uniquely identifies the type of a [`Component`] or [`Resource`] within a
|
||||
/// [`World`](crate::world::World).
|
||||
///
|
||||
/// Each time a new `Component` type is registered within a `World` using
|
||||
/// e.g. [`World::register_component`](crate::world::World::register_component) or
|
||||
/// [`World::register_component_with_descriptor`](crate::world::World::register_component_with_descriptor)
|
||||
/// or a Resource with e.g. [`World::init_resource`](crate::world::World::init_resource),
|
||||
/// a corresponding `ComponentId` is created to track it.
|
||||
///
|
||||
/// While the distinction between `ComponentId` and [`TypeId`] may seem superficial, breaking them
|
||||
/// into two separate but related concepts allows components to exist outside of Rust's type system.
|
||||
/// Each Rust type registered as a `Component` will have a corresponding `ComponentId`, but additional
|
||||
/// `ComponentId`s may exist in a `World` to track components which cannot be
|
||||
/// represented as Rust types for scripting or other advanced use-cases.
|
||||
///
|
||||
/// A `ComponentId` is tightly coupled to its parent `World`. Attempting to use a `ComponentId` from
|
||||
/// one `World` to access the metadata of a `Component` in a different `World` is undefined behavior
|
||||
/// and must not be attempted.
|
||||
///
|
||||
/// Given a type `T` which implements [`Component`], the `ComponentId` for `T` can be retrieved
|
||||
/// from a `World` using [`World::component_id()`](crate::world::World::component_id) or via [`Components::component_id()`].
|
||||
/// Access to the `ComponentId` for a [`Resource`] is available via [`Components::resource_id()`].
|
||||
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Debug, Hash, PartialEq, Clone)
|
||||
)]
|
||||
pub struct ComponentId(pub(super) usize);
|
||||
|
||||
impl ComponentId {
|
||||
/// Creates a new [`ComponentId`].
|
||||
///
|
||||
/// The `index` is a unique value associated with each type of component in a given world.
|
||||
/// Usually, this value is taken from a counter incremented for each type of component registered with the world.
|
||||
#[inline]
|
||||
pub const fn new(index: usize) -> ComponentId {
|
||||
ComponentId(index)
|
||||
}
|
||||
|
||||
/// Returns the index of the current component.
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseSetIndex for ComponentId {
|
||||
#[inline]
|
||||
fn sparse_set_index(&self) -> usize {
|
||||
self.index()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_sparse_set_index(value: usize) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A value describing a component or resource, which may or may not correspond to a Rust type.
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentDescriptor {
|
||||
name: DebugName,
|
||||
// SAFETY: This must remain private. It must match the statically known StorageType of the
|
||||
// associated rust component type if one exists.
|
||||
storage_type: StorageType,
|
||||
// SAFETY: This must remain private. It must only be set to "true" if this component is
|
||||
// actually Send + Sync
|
||||
is_send_and_sync: bool,
|
||||
type_id: Option<TypeId>,
|
||||
layout: Layout,
|
||||
// SAFETY: this function must be safe to call with pointers pointing to items of the type
|
||||
// this descriptor describes.
|
||||
// None if the underlying type doesn't need to be dropped
|
||||
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
||||
mutable: bool,
|
||||
clone_behavior: ComponentCloneBehavior,
|
||||
}
|
||||
|
||||
// We need to ignore the `drop` field in our `Debug` impl
|
||||
impl Debug for ComponentDescriptor {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("ComponentDescriptor")
|
||||
.field("name", &self.name)
|
||||
.field("storage_type", &self.storage_type)
|
||||
.field("is_send_and_sync", &self.is_send_and_sync)
|
||||
.field("type_id", &self.type_id)
|
||||
.field("layout", &self.layout)
|
||||
.field("mutable", &self.mutable)
|
||||
.field("clone_behavior", &self.clone_behavior)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDescriptor {
|
||||
/// # Safety
|
||||
///
|
||||
/// `x` must point to a valid value of type `T`.
|
||||
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
|
||||
// SAFETY: Contract is required to be upheld by the caller.
|
||||
unsafe {
|
||||
x.drop_as::<T>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `ComponentDescriptor` for the type `T`.
|
||||
pub fn new<T: Component>() -> Self {
|
||||
Self {
|
||||
name: DebugName::type_name::<T>(),
|
||||
storage_type: T::STORAGE_TYPE,
|
||||
is_send_and_sync: true,
|
||||
type_id: Some(TypeId::of::<T>()),
|
||||
layout: Layout::new::<T>(),
|
||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
||||
mutable: T::Mutability::MUTABLE,
|
||||
clone_behavior: T::clone_behavior(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `ComponentDescriptor`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - the `drop` fn must be usable on a pointer with a value of the layout `layout`
|
||||
/// - the component type must be safe to access from any thread (Send + Sync in rust terms)
|
||||
pub unsafe fn new_with_layout(
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
storage_type: StorageType,
|
||||
layout: Layout,
|
||||
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
|
||||
mutable: bool,
|
||||
clone_behavior: ComponentCloneBehavior,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into().into(),
|
||||
storage_type,
|
||||
is_send_and_sync: true,
|
||||
type_id: None,
|
||||
layout,
|
||||
drop,
|
||||
mutable,
|
||||
clone_behavior,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `ComponentDescriptor` for a resource.
|
||||
///
|
||||
/// The [`StorageType`] for resources is always [`StorageType::Table`].
|
||||
pub fn new_resource<T: Resource>() -> Self {
|
||||
Self {
|
||||
name: DebugName::type_name::<T>(),
|
||||
// PERF: `SparseStorage` may actually be a more
|
||||
// reasonable choice as `storage_type` for resources.
|
||||
storage_type: StorageType::Table,
|
||||
is_send_and_sync: true,
|
||||
type_id: Some(TypeId::of::<T>()),
|
||||
layout: Layout::new::<T>(),
|
||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
||||
mutable: true,
|
||||
clone_behavior: ComponentCloneBehavior::Default,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
|
||||
Self {
|
||||
name: DebugName::type_name::<T>(),
|
||||
storage_type,
|
||||
is_send_and_sync: false,
|
||||
type_id: Some(TypeId::of::<T>()),
|
||||
layout: Layout::new::<T>(),
|
||||
drop: needs_drop::<T>().then_some(Self::drop_ptr::<T> as _),
|
||||
mutable: true,
|
||||
clone_behavior: ComponentCloneBehavior::Default,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a value indicating the storage strategy for the current component.
|
||||
#[inline]
|
||||
pub fn storage_type(&self) -> StorageType {
|
||||
self.storage_type
|
||||
}
|
||||
|
||||
/// Returns the [`TypeId`] of the underlying component type.
|
||||
/// Returns `None` if the component does not correspond to a Rust type.
|
||||
#[inline]
|
||||
pub fn type_id(&self) -> Option<TypeId> {
|
||||
self.type_id
|
||||
}
|
||||
|
||||
/// Returns the name of the current component.
|
||||
#[inline]
|
||||
pub fn name(&self) -> DebugName {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
/// Returns whether this component is mutable.
|
||||
#[inline]
|
||||
pub fn mutable(&self) -> bool {
|
||||
self.mutable
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores metadata associated with each kind of [`Component`] in a given [`World`](crate::world::World).
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Components {
|
||||
pub(super) components: Vec<Option<ComponentInfo>>,
|
||||
pub(super) indices: TypeIdMap<ComponentId>,
|
||||
pub(super) resource_indices: TypeIdMap<ComponentId>,
|
||||
// This is kept internal and local to verify that no deadlocks can occor.
|
||||
pub(super) queued: bevy_platform::sync::RwLock<QueuedComponents>,
|
||||
}
|
||||
|
||||
impl Components {
|
||||
/// This registers any descriptor, component or resource.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The id must have never been registered before. This must be a fresh registration.
|
||||
#[inline]
|
||||
pub(super) unsafe fn register_component_inner(
|
||||
&mut self,
|
||||
id: ComponentId,
|
||||
descriptor: ComponentDescriptor,
|
||||
) {
|
||||
let info = ComponentInfo::new(id, descriptor);
|
||||
let least_len = id.0 + 1;
|
||||
if self.components.len() < least_len {
|
||||
self.components.resize_with(least_len, || None);
|
||||
}
|
||||
// SAFETY: We just extended the vec to make this index valid.
|
||||
let slot = unsafe { self.components.get_mut(id.0).debug_checked_unwrap() };
|
||||
// Caller ensures id is unique
|
||||
debug_assert!(slot.is_none());
|
||||
*slot = Some(info);
|
||||
}
|
||||
|
||||
/// Returns the number of components registered or queued with this instance.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.num_queued() + self.num_registered()
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no components registered or queued with this instance. Otherwise, this returns `false`.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Returns the number of components registered with this instance.
|
||||
#[inline]
|
||||
pub fn num_queued(&self) -> usize {
|
||||
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
|
||||
queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any components registered with this instance. Otherwise, this returns `false`.
|
||||
#[inline]
|
||||
pub fn any_queued(&self) -> bool {
|
||||
self.num_queued() > 0
|
||||
}
|
||||
|
||||
/// A faster version of [`Self::num_queued`].
|
||||
#[inline]
|
||||
pub fn num_queued_mut(&mut self) -> usize {
|
||||
let queued = self
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len()
|
||||
}
|
||||
|
||||
/// A faster version of [`Self::any_queued`].
|
||||
#[inline]
|
||||
pub fn any_queued_mut(&mut self) -> bool {
|
||||
self.num_queued_mut() > 0
|
||||
}
|
||||
|
||||
/// Returns the number of components registered with this instance.
|
||||
#[inline]
|
||||
pub fn num_registered(&self) -> usize {
|
||||
self.components.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if there are any components registered with this instance. Otherwise, this returns `false`.
|
||||
#[inline]
|
||||
pub fn any_registered(&self) -> bool {
|
||||
self.num_registered() > 0
|
||||
}
|
||||
|
||||
/// Gets the metadata associated with the given component, if it is registered.
|
||||
/// This will return `None` if the id is not registered or is queued.
|
||||
///
|
||||
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
|
||||
#[inline]
|
||||
pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> {
|
||||
self.components.get(id.0).and_then(|info| info.as_ref())
|
||||
}
|
||||
|
||||
/// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present.
|
||||
/// This will return `None` only if the id is neither registered nor queued to be registered.
|
||||
///
|
||||
/// Currently, the [`Cow`] will be [`Cow::Owned`] if and only if the component is queued. It will be [`Cow::Borrowed`] otherwise.
|
||||
///
|
||||
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
|
||||
#[inline]
|
||||
pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, ComponentDescriptor>> {
|
||||
self.components
|
||||
.get(id.0)
|
||||
.and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor)))
|
||||
.or_else(|| {
|
||||
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
|
||||
// first check components, then resources, then dynamic
|
||||
queued
|
||||
.components
|
||||
.values()
|
||||
.chain(queued.resources.values())
|
||||
.chain(queued.dynamic_registrations.iter())
|
||||
.find(|queued| queued.id == id)
|
||||
.map(|queued| Cow::Owned(queued.descriptor.clone()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the name of the component with this [`ComponentId`] if it is present.
|
||||
/// This will return `None` only if the id is neither registered nor queued to be registered.
|
||||
///
|
||||
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
|
||||
#[inline]
|
||||
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<DebugName> {
|
||||
self.components
|
||||
.get(id.0)
|
||||
.and_then(|info| info.as_ref().map(|info| info.descriptor.name()))
|
||||
.or_else(|| {
|
||||
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
|
||||
// first check components, then resources, then dynamic
|
||||
queued
|
||||
.components
|
||||
.values()
|
||||
.chain(queued.resources.values())
|
||||
.chain(queued.dynamic_registrations.iter())
|
||||
.find(|queued| queued.id == id)
|
||||
.map(|queued| queued.descriptor.name.clone())
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets the metadata associated with the given component.
|
||||
/// # Safety
|
||||
///
|
||||
/// `id` must be a valid and fully registered [`ComponentId`].
|
||||
#[inline]
|
||||
pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo {
|
||||
// SAFETY: The caller ensures `id` is valid.
|
||||
unsafe {
|
||||
self.components
|
||||
.get(id.0)
|
||||
.debug_checked_unwrap()
|
||||
.as_ref()
|
||||
.debug_checked_unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_hooks_mut(&mut self, id: ComponentId) -> Option<&mut ComponentHooks> {
|
||||
self.components
|
||||
.get_mut(id.0)
|
||||
.and_then(|info| info.as_mut().map(|info| &mut info.hooks))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_required_components_mut(
|
||||
&mut self,
|
||||
id: ComponentId,
|
||||
) -> Option<&mut RequiredComponents> {
|
||||
self.components
|
||||
.get_mut(id.0)
|
||||
.and_then(|info| info.as_mut().map(|info| &mut info.required_components))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_required_by(&self, id: ComponentId) -> Option<&HashSet<ComponentId>> {
|
||||
self.components
|
||||
.get(id.0)
|
||||
.and_then(|info| info.as_ref().map(|info| &info.required_by))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get_required_by_mut(
|
||||
&mut self,
|
||||
id: ComponentId,
|
||||
) -> Option<&mut HashSet<ComponentId>> {
|
||||
self.components
|
||||
.get_mut(id.0)
|
||||
.and_then(|info| info.as_mut().map(|info| &mut info.required_by))
|
||||
}
|
||||
|
||||
/// Returns true if the [`ComponentId`] is fully registered and valid.
|
||||
/// Ids may be invalid if they are still queued to be registered.
|
||||
/// Those ids are still correct, but they are not usable in every context yet.
|
||||
#[inline]
|
||||
pub fn is_id_valid(&self, id: ComponentId) -> bool {
|
||||
self.components.get(id.0).is_some_and(Option::is_some)
|
||||
}
|
||||
|
||||
/// Type-erased equivalent of [`Components::valid_component_id()`].
|
||||
#[inline]
|
||||
pub fn get_valid_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.indices.get(&type_id).copied()
|
||||
}
|
||||
|
||||
/// Returns the [`ComponentId`] of the given [`Component`] type `T` if it is fully registered.
|
||||
/// If you want to include queued registration, see [`Components::component_id()`].
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// let component_a_id = world.register_component::<ComponentA>();
|
||||
///
|
||||
/// assert_eq!(component_a_id, world.components().valid_component_id::<ComponentA>().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::get_valid_id()`]
|
||||
/// * [`Components::valid_resource_id()`]
|
||||
/// * [`World::component_id()`](crate::world::World::component_id)
|
||||
#[inline]
|
||||
pub fn valid_component_id<T: Component>(&self) -> Option<ComponentId> {
|
||||
self.get_valid_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Type-erased equivalent of [`Components::valid_resource_id()`].
|
||||
#[inline]
|
||||
pub fn get_valid_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.resource_indices.get(&type_id).copied()
|
||||
}
|
||||
|
||||
/// Returns the [`ComponentId`] of the given [`Resource`] type `T` if it is fully registered.
|
||||
/// If you want to include queued registration, see [`Components::resource_id()`].
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct ResourceA;
|
||||
///
|
||||
/// let resource_a_id = world.init_resource::<ResourceA>();
|
||||
///
|
||||
/// assert_eq!(resource_a_id, world.components().valid_resource_id::<ResourceA>().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::valid_component_id()`]
|
||||
/// * [`Components::get_resource_id()`]
|
||||
#[inline]
|
||||
pub fn valid_resource_id<T: Resource>(&self) -> Option<ComponentId> {
|
||||
self.get_valid_resource_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Type-erased equivalent of [`Components::component_id()`].
|
||||
#[inline]
|
||||
pub fn get_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.indices.get(&type_id).copied().or_else(|| {
|
||||
self.queued
|
||||
.read()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.components
|
||||
.get(&type_id)
|
||||
.map(|queued| queued.id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the [`ComponentId`] of the given [`Component`] type `T`.
|
||||
///
|
||||
/// The returned `ComponentId` is specific to the `Components` instance
|
||||
/// it was retrieved from and should not be used with another `Components`
|
||||
/// instance.
|
||||
///
|
||||
/// Returns [`None`] if the `Component` type has not yet been initialized using
|
||||
/// [`ComponentsRegistrator::register_component()`](super::ComponentsRegistrator::register_component) or
|
||||
/// [`ComponentsQueuedRegistrator::queue_register_component()`](super::ComponentsQueuedRegistrator::queue_register_component).
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// let component_a_id = world.register_component::<ComponentA>();
|
||||
///
|
||||
/// assert_eq!(component_a_id, world.components().component_id::<ComponentA>().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::get_id()`]
|
||||
/// * [`Components::resource_id()`]
|
||||
/// * [`World::component_id()`](crate::world::World::component_id)
|
||||
#[inline]
|
||||
pub fn component_id<T: Component>(&self) -> Option<ComponentId> {
|
||||
self.get_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Type-erased equivalent of [`Components::resource_id()`].
|
||||
#[inline]
|
||||
pub fn get_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
|
||||
self.resource_indices.get(&type_id).copied().or_else(|| {
|
||||
self.queued
|
||||
.read()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.resources
|
||||
.get(&type_id)
|
||||
.map(|queued| queued.id)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the [`ComponentId`] of the given [`Resource`] type `T`.
|
||||
///
|
||||
/// The returned `ComponentId` is specific to the `Components` instance
|
||||
/// it was retrieved from and should not be used with another `Components`
|
||||
/// instance.
|
||||
///
|
||||
/// Returns [`None`] if the `Resource` type has not yet been initialized using
|
||||
/// [`ComponentsRegistrator::register_resource()`](super::ComponentsRegistrator::register_resource) or
|
||||
/// [`ComponentsQueuedRegistrator::queue_register_resource()`](super::ComponentsQueuedRegistrator::queue_register_resource).
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct ResourceA;
|
||||
///
|
||||
/// let resource_a_id = world.init_resource::<ResourceA>();
|
||||
///
|
||||
/// assert_eq!(resource_a_id, world.components().resource_id::<ResourceA>().unwrap())
|
||||
/// ```
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::component_id()`]
|
||||
/// * [`Components::get_resource_id()`]
|
||||
#[inline]
|
||||
pub fn resource_id<T: Resource>(&self) -> Option<ComponentId> {
|
||||
self.get_resource_id(TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`ComponentDescriptor`] must match the [`TypeId`].
|
||||
/// The [`ComponentId`] must be unique.
|
||||
/// The [`TypeId`] and [`ComponentId`] must not be registered or queued.
|
||||
#[inline]
|
||||
pub(super) unsafe fn register_resource_unchecked(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
component_id: ComponentId,
|
||||
descriptor: ComponentDescriptor,
|
||||
) {
|
||||
// SAFETY: ensured by caller
|
||||
unsafe {
|
||||
self.register_component_inner(component_id, descriptor);
|
||||
}
|
||||
let prev = self.resource_indices.insert(type_id, component_id);
|
||||
debug_assert!(prev.is_none());
|
||||
}
|
||||
|
||||
/// Gets an iterator over all components fully registered with this instance.
|
||||
pub fn iter_registered(&self) -> impl Iterator<Item = &ComponentInfo> + '_ {
|
||||
self.components.iter().filter_map(Option::as_ref)
|
||||
}
|
||||
}
|
758
crates/bevy_ecs/src/component/mod.rs
Normal file
758
crates/bevy_ecs/src/component/mod.rs
Normal file
@ -0,0 +1,758 @@
|
||||
//! Types for declaring and storing [`Component`]s.
|
||||
|
||||
mod clone;
|
||||
mod info;
|
||||
mod register;
|
||||
mod required;
|
||||
mod tick;
|
||||
|
||||
pub use clone::*;
|
||||
pub use info::*;
|
||||
pub use register::*;
|
||||
pub use required::*;
|
||||
pub use tick::*;
|
||||
|
||||
use crate::{
|
||||
entity::EntityMapper,
|
||||
lifecycle::ComponentHook,
|
||||
system::{Local, SystemParam},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
pub use bevy_ecs_macros::Component;
|
||||
use core::{fmt::Debug, marker::PhantomData, ops::Deref};
|
||||
|
||||
/// A data type that can be used to store data for an [entity].
|
||||
///
|
||||
/// `Component` is a [derivable trait]: this means that a data type can implement it by applying a `#[derive(Component)]` attribute to it.
|
||||
/// However, components must always satisfy the `Send + Sync + 'static` trait bounds.
|
||||
///
|
||||
/// [entity]: crate::entity
|
||||
/// [derivable trait]: https://doc.rust-lang.org/book/appendix-03-derivable-traits.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Components can take many forms: they are usually structs, but can also be of every other kind of data type, like enums or zero sized types.
|
||||
/// The following examples show how components are laid out in code.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # struct Color;
|
||||
/// #
|
||||
/// // A component can contain data...
|
||||
/// #[derive(Component)]
|
||||
/// struct LicensePlate(String);
|
||||
///
|
||||
/// // ... but it can also be a zero-sized marker.
|
||||
/// #[derive(Component)]
|
||||
/// struct Car;
|
||||
///
|
||||
/// // Components can also be structs with named fields...
|
||||
/// #[derive(Component)]
|
||||
/// struct VehiclePerformance {
|
||||
/// acceleration: f32,
|
||||
/// top_speed: f32,
|
||||
/// handling: f32,
|
||||
/// }
|
||||
///
|
||||
/// // ... or enums.
|
||||
/// #[derive(Component)]
|
||||
/// enum WheelCount {
|
||||
/// Two,
|
||||
/// Three,
|
||||
/// Four,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Component and data access
|
||||
///
|
||||
/// Components can be marked as immutable by adding the `#[component(immutable)]`
|
||||
/// attribute when using the derive macro.
|
||||
/// See the documentation for [`ComponentMutability`] for more details around this
|
||||
/// feature.
|
||||
///
|
||||
/// See the [`entity`] module level documentation to learn how to add or remove components from an entity.
|
||||
///
|
||||
/// See the documentation for [`Query`] to learn how to access component data from a system.
|
||||
///
|
||||
/// [`entity`]: crate::entity#usage
|
||||
/// [`Query`]: crate::system::Query
|
||||
/// [`ComponentMutability`]: crate::component::ComponentMutability
|
||||
///
|
||||
/// # Choosing a storage type
|
||||
///
|
||||
/// Components can be stored in the world using different strategies with their own performance implications.
|
||||
/// By default, components are added to the [`Table`] storage, which is optimized for query iteration.
|
||||
///
|
||||
/// Alternatively, components can be added to the [`SparseSet`] storage, which is optimized for component insertion and removal.
|
||||
/// This is achieved by adding an additional `#[component(storage = "SparseSet")]` attribute to the derive one:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(storage = "SparseSet")]
|
||||
/// struct ComponentA;
|
||||
/// ```
|
||||
///
|
||||
/// [`Table`]: crate::storage::Table
|
||||
/// [`SparseSet`]: crate::storage::SparseSet
|
||||
///
|
||||
/// # Required Components
|
||||
///
|
||||
/// Components can specify Required Components. If some [`Component`] `A` requires [`Component`] `B`, then when `A` is inserted,
|
||||
/// `B` will _also_ be initialized and inserted (if it was not manually specified).
|
||||
///
|
||||
/// The [`Default`] constructor will be used to initialize the component, by default:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// #[require(B)]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// struct B(usize);
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // This will implicitly also insert B with the Default constructor
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&B(0), world.entity(id).get::<B>().unwrap());
|
||||
///
|
||||
/// // This will _not_ implicitly insert B, because it was already provided
|
||||
/// world.spawn((A, B(11)));
|
||||
/// ```
|
||||
///
|
||||
/// Components can have more than one required component:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// #[require(B, C)]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// #[require(C)]
|
||||
/// struct B(usize);
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// struct C(u32);
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // This will implicitly also insert B and C with their Default constructors
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&B(0), world.entity(id).get::<B>().unwrap());
|
||||
/// assert_eq!(&C(0), world.entity(id).get::<C>().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// You can define inline component values that take the following forms:
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// #[require(
|
||||
/// B(1), // tuple structs
|
||||
/// C { // named-field structs
|
||||
/// x: 1,
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// D::One, // enum variants
|
||||
/// E::ONE, // associated consts
|
||||
/// F::new(1) // constructors
|
||||
/// )]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// struct B(u8);
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug, Default)]
|
||||
/// struct C {
|
||||
/// x: u8,
|
||||
/// y: u8,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// enum D {
|
||||
/// Zero,
|
||||
/// One,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// struct E(u8);
|
||||
///
|
||||
/// impl E {
|
||||
/// pub const ONE: Self = Self(1);
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// struct F(u8);
|
||||
///
|
||||
/// impl F {
|
||||
/// fn new(value: u8) -> Self {
|
||||
/// Self(value)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&B(1), world.entity(id).get::<B>().unwrap());
|
||||
/// assert_eq!(&C { x: 1, y: 0 }, world.entity(id).get::<C>().unwrap());
|
||||
/// assert_eq!(&D::One, world.entity(id).get::<D>().unwrap());
|
||||
/// assert_eq!(&E(1), world.entity(id).get::<E>().unwrap());
|
||||
/// assert_eq!(&F(1), world.entity(id).get::<F>().unwrap());
|
||||
/// ````
|
||||
///
|
||||
///
|
||||
/// You can also define arbitrary expressions by using `=`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// #[require(C = init_c())]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// #[require(C = C(20))]
|
||||
/// struct B;
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// struct C(usize);
|
||||
///
|
||||
/// fn init_c() -> C {
|
||||
/// C(10)
|
||||
/// }
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // This will implicitly also insert C with the init_c() constructor
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&C(10), world.entity(id).get::<C>().unwrap());
|
||||
///
|
||||
/// // This will implicitly also insert C with the `|| C(20)` constructor closure
|
||||
/// let id = world.spawn(B).id();
|
||||
/// assert_eq!(&C(20), world.entity(id).get::<C>().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// Required components are _recursive_. This means, if a Required Component has required components,
|
||||
/// those components will _also_ be inserted if they are missing:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// #[require(B)]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// #[require(C)]
|
||||
/// struct B(usize);
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// struct C(u32);
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // This will implicitly also insert B and C with their Default constructors
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&B(0), world.entity(id).get::<B>().unwrap());
|
||||
/// assert_eq!(&C(0), world.entity(id).get::<C>().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// Note that cycles in the "component require tree" will result in stack overflows when attempting to
|
||||
/// insert a component.
|
||||
///
|
||||
/// This "multiple inheritance" pattern does mean that it is possible to have duplicate requires for a given type
|
||||
/// at different levels of the inheritance tree:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// struct X(usize);
|
||||
///
|
||||
/// #[derive(Component, Default)]
|
||||
/// #[require(X(1))]
|
||||
/// struct Y;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// #[require(
|
||||
/// Y,
|
||||
/// X(2),
|
||||
/// )]
|
||||
/// struct Z;
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // In this case, the x2 constructor is used for X
|
||||
/// let id = world.spawn(Z).id();
|
||||
/// assert_eq!(2, world.entity(id).get::<X>().unwrap().0);
|
||||
/// ```
|
||||
///
|
||||
/// In general, this shouldn't happen often, but when it does the algorithm for choosing the constructor from the tree is simple and predictable:
|
||||
/// 1. A constructor from a direct `#[require()]`, if one exists, is selected with priority.
|
||||
/// 2. Otherwise, perform a Depth First Search on the tree of requirements and select the first one found.
|
||||
///
|
||||
/// From a user perspective, just think about this as the following:
|
||||
/// 1. Specifying a required component constructor for Foo directly on a spawned component Bar will result in that constructor being used (and overriding existing constructors lower in the inheritance tree). This is the classic "inheritance override" behavior people expect.
|
||||
/// 2. For cases where "multiple inheritance" results in constructor clashes, Components should be listed in "importance order". List a component earlier in the requirement list to initialize its inheritance tree earlier.
|
||||
///
|
||||
/// ## Registering required components at runtime
|
||||
///
|
||||
/// In most cases, required components should be registered using the `require` attribute as shown above.
|
||||
/// However, in some cases, it may be useful to register required components at runtime.
|
||||
///
|
||||
/// This can be done through [`World::register_required_components`] or [`World::register_required_components_with`]
|
||||
/// for the [`Default`] and custom constructors respectively:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #[derive(Component)]
|
||||
/// struct A;
|
||||
///
|
||||
/// #[derive(Component, Default, PartialEq, Eq, Debug)]
|
||||
/// struct B(usize);
|
||||
///
|
||||
/// #[derive(Component, PartialEq, Eq, Debug)]
|
||||
/// struct C(u32);
|
||||
///
|
||||
/// # let mut world = World::default();
|
||||
/// // Register B as required by A and C as required by B.
|
||||
/// world.register_required_components::<A, B>();
|
||||
/// world.register_required_components_with::<B, C>(|| C(2));
|
||||
///
|
||||
/// // This will implicitly also insert B with its Default constructor
|
||||
/// // and C with the custom constructor defined by B.
|
||||
/// let id = world.spawn(A).id();
|
||||
/// assert_eq!(&B(0), world.entity(id).get::<B>().unwrap());
|
||||
/// assert_eq!(&C(2), world.entity(id).get::<C>().unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// Similar rules as before apply to duplicate requires fer a given type at different levels
|
||||
/// of the inheritance tree. `A` requiring `C` directly would take precedence over indirectly
|
||||
/// requiring it through `A` requiring `B` and `B` requiring `C`.
|
||||
///
|
||||
/// Unlike with the `require` attribute, directly requiring the same component multiple times
|
||||
/// for the same component will result in a panic. This is done to prevent conflicting constructors
|
||||
/// and confusing ordering dependencies.
|
||||
///
|
||||
/// Note that requirements must currently be registered before the requiring component is inserted
|
||||
/// into the world for the first time. Registering requirements after this will lead to a panic.
|
||||
///
|
||||
/// # Relationships between Entities
|
||||
///
|
||||
/// Sometimes it is useful to define relationships between entities. A common example is the
|
||||
/// parent / child relationship. Since Components are how data is stored for Entities, one might
|
||||
/// naturally think to create a Component which has a field of type [`Entity`].
|
||||
///
|
||||
/// To facilitate this pattern, Bevy provides the [`Relationship`](`crate::relationship::Relationship`)
|
||||
/// trait. You can derive the [`Relationship`](`crate::relationship::Relationship`) and
|
||||
/// [`RelationshipTarget`](`crate::relationship::RelationshipTarget`) traits in addition to the
|
||||
/// Component trait in order to implement data driven relationships between entities, see the trait
|
||||
/// docs for more details.
|
||||
///
|
||||
/// In addition, Bevy provides canonical implementations of the parent / child relationship via the
|
||||
/// [`ChildOf`](crate::hierarchy::ChildOf) [`Relationship`](crate::relationship::Relationship) and
|
||||
/// the [`Children`](crate::hierarchy::Children)
|
||||
/// [`RelationshipTarget`](crate::relationship::RelationshipTarget).
|
||||
///
|
||||
/// # Adding component's hooks
|
||||
///
|
||||
/// See [`ComponentHooks`] for a detailed explanation of component's hooks.
|
||||
///
|
||||
/// Alternatively to the example shown in [`ComponentHooks`]' documentation, hooks can be configured using following attributes:
|
||||
/// - `#[component(on_add = on_add_function)]`
|
||||
/// - `#[component(on_insert = on_insert_function)]`
|
||||
/// - `#[component(on_replace = on_replace_function)]`
|
||||
/// - `#[component(on_remove = on_remove_function)]`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::lifecycle::HookContext;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// # use bevy_ecs::entity::Entity;
|
||||
/// # use bevy_ecs::component::ComponentId;
|
||||
/// # use core::panic::Location;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(on_add = my_on_add_hook)]
|
||||
/// #[component(on_insert = my_on_insert_hook)]
|
||||
/// // Another possible way of configuring hooks:
|
||||
/// // #[component(on_add = my_on_add_hook, on_insert = my_on_insert_hook)]
|
||||
/// //
|
||||
/// // We don't have a replace or remove hook, so we can leave them out:
|
||||
/// // #[component(on_replace = my_on_replace_hook, on_remove = my_on_remove_hook)]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// fn my_on_add_hook(world: DeferredWorld, context: HookContext) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// // You can also destructure items directly in the signature
|
||||
/// fn my_on_insert_hook(world: DeferredWorld, HookContext { caller, .. }: HookContext) {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This also supports function calls that yield closures
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::lifecycle::HookContext;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(on_add = my_msg_hook("hello"))]
|
||||
/// #[component(on_despawn = my_msg_hook("yoink"))]
|
||||
/// struct ComponentA;
|
||||
///
|
||||
/// // a hook closure generating function
|
||||
/// fn my_msg_hook(message: &'static str) -> impl Fn(DeferredWorld, HookContext) {
|
||||
/// move |_world, _ctx| {
|
||||
/// println!("{message}");
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
/// # Setting the clone behavior
|
||||
///
|
||||
/// You can specify how the [`Component`] is cloned when deriving it.
|
||||
///
|
||||
/// Your options are the functions and variants of [`ComponentCloneBehavior`]
|
||||
/// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority.
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// #[component(clone_behavior = Ignore)]
|
||||
/// struct MyComponent;
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// # Implementing the trait for foreign types
|
||||
///
|
||||
/// As a consequence of the [orphan rule], it is not possible to separate into two different crates the implementation of `Component` from the definition of a type.
|
||||
/// This means that it is not possible to directly have a type defined in a third party library as a component.
|
||||
/// This important limitation can be easily worked around using the [newtype pattern]:
|
||||
/// this makes it possible to locally define and implement `Component` for a tuple struct that wraps the foreign type.
|
||||
/// The following example gives a demonstration of this pattern.
|
||||
///
|
||||
/// ```
|
||||
/// // `Component` is defined in the `bevy_ecs` crate.
|
||||
/// use bevy_ecs::component::Component;
|
||||
///
|
||||
/// // `Duration` is defined in the `std` crate.
|
||||
/// use std::time::Duration;
|
||||
///
|
||||
/// // It is not possible to implement `Component` for `Duration` from this position, as they are
|
||||
/// // both foreign items, defined in an external crate. However, nothing prevents to define a new
|
||||
/// // `Cooldown` type that wraps `Duration`. As `Cooldown` is defined in a local crate, it is
|
||||
/// // possible to implement `Component` for it.
|
||||
/// #[derive(Component)]
|
||||
/// struct Cooldown(Duration);
|
||||
/// ```
|
||||
///
|
||||
/// [orphan rule]: https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type
|
||||
/// [newtype pattern]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-the-newtype-pattern-to-implement-external-traits-on-external-types
|
||||
///
|
||||
/// # `!Sync` Components
|
||||
/// A `!Sync` type cannot implement `Component`. However, it is possible to wrap a `Send` but not `Sync`
|
||||
/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only
|
||||
/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple
|
||||
/// threads.
|
||||
///
|
||||
/// This will fail to compile since `RefCell` is `!Sync`.
|
||||
/// ```compile_fail
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// #[derive(Component)]
|
||||
/// struct NotSync {
|
||||
/// counter: RefCell<usize>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This will compile since the `RefCell` is wrapped with `SyncCell`.
|
||||
/// ```
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// use bevy_platform::cell::SyncCell;
|
||||
///
|
||||
/// // This will compile.
|
||||
/// #[derive(Component)]
|
||||
/// struct ActuallySync {
|
||||
/// counter: SyncCell<RefCell<usize>>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`SyncCell`]: bevy_platform::cell::SyncCell
|
||||
/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
|
||||
/// [`ComponentHooks`]: crate::lifecycle::ComponentHooks
|
||||
#[diagnostic::on_unimplemented(
|
||||
message = "`{Self}` is not a `Component`",
|
||||
label = "invalid `Component`",
|
||||
note = "consider annotating `{Self}` with `#[derive(Component)]`"
|
||||
)]
|
||||
pub trait Component: Send + Sync + 'static {
|
||||
/// A constant indicating the storage type used for this component.
|
||||
const STORAGE_TYPE: StorageType;
|
||||
|
||||
/// A marker type to assist Bevy with determining if this component is
|
||||
/// mutable, or immutable. Mutable components will have [`Component<Mutability = Mutable>`],
|
||||
/// while immutable components will instead have [`Component<Mutability = Immutable>`].
|
||||
///
|
||||
/// * For a component to be mutable, this type must be [`Mutable`].
|
||||
/// * For a component to be immutable, this type must be [`Immutable`].
|
||||
type Mutability: ComponentMutability;
|
||||
|
||||
/// Gets the `on_add` [`ComponentHook`] for this [`Component`] if one is defined.
|
||||
fn on_add() -> Option<ComponentHook> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets the `on_insert` [`ComponentHook`] for this [`Component`] if one is defined.
|
||||
fn on_insert() -> Option<ComponentHook> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets the `on_replace` [`ComponentHook`] for this [`Component`] if one is defined.
|
||||
fn on_replace() -> Option<ComponentHook> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets the `on_remove` [`ComponentHook`] for this [`Component`] if one is defined.
|
||||
fn on_remove() -> Option<ComponentHook> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets the `on_despawn` [`ComponentHook`] for this [`Component`] if one is defined.
|
||||
fn on_despawn() -> Option<ComponentHook> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Registers required components.
|
||||
fn register_required_components(
|
||||
_component_id: ComponentId,
|
||||
_components: &mut ComponentsRegistrator,
|
||||
_required_components: &mut RequiredComponents,
|
||||
_inheritance_depth: u16,
|
||||
_recursion_check_stack: &mut Vec<ComponentId>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Called when registering this component, allowing to override clone function (or disable cloning altogether) for this component.
|
||||
///
|
||||
/// See [Handlers section of `EntityClonerBuilder`](crate::entity::EntityClonerBuilder#handlers) to understand how this affects handler priority.
|
||||
#[inline]
|
||||
fn clone_behavior() -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Default
|
||||
}
|
||||
|
||||
/// Maps the entities on this component using the given [`EntityMapper`]. This is used to remap entities in contexts like scenes and entity cloning.
|
||||
/// When deriving [`Component`], this is populated by annotating fields containing entities with `#[entities]`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{component::Component, entity::Entity};
|
||||
/// #[derive(Component)]
|
||||
/// struct Inventory {
|
||||
/// #[entities]
|
||||
/// items: Vec<Entity>
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Fields with `#[entities]` must implement [`MapEntities`](crate::entity::MapEntities).
|
||||
///
|
||||
/// Bevy provides various implementations of [`MapEntities`](crate::entity::MapEntities), so that arbitrary combinations like these are supported with `#[entities]`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::{component::Component, entity::Entity};
|
||||
/// #[derive(Component)]
|
||||
/// struct Inventory {
|
||||
/// #[entities]
|
||||
/// items: Vec<Option<Entity>>
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You might need more specialized logic. A likely cause of this is your component contains collections of entities that
|
||||
/// don't implement [`MapEntities`](crate::entity::MapEntities). In that case, you can annotate your component with
|
||||
/// `#[component(map_entities)]`. Using this attribute, you must implement `MapEntities` for the
|
||||
/// component itself, and this method will simply call that implementation.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{component::Component, entity::{Entity, MapEntities, EntityMapper}};
|
||||
/// # use std::collections::HashMap;
|
||||
/// #[derive(Component)]
|
||||
/// #[component(map_entities)]
|
||||
/// struct Inventory {
|
||||
/// items: HashMap<Entity, usize>
|
||||
/// }
|
||||
///
|
||||
/// impl MapEntities for Inventory {
|
||||
/// fn map_entities<M: EntityMapper>(&mut self, entity_mapper: &mut M) {
|
||||
/// self.items = self.items
|
||||
/// .drain()
|
||||
/// .map(|(id, count)|(entity_mapper.get_mapped(id), count))
|
||||
/// .collect();
|
||||
/// }
|
||||
/// }
|
||||
/// # let a = Entity::from_bits(0x1_0000_0001);
|
||||
/// # let b = Entity::from_bits(0x1_0000_0002);
|
||||
/// # let mut inv = Inventory { items: Default::default() };
|
||||
/// # inv.items.insert(a, 10);
|
||||
/// # <Inventory as Component>::map_entities(&mut inv, &mut (a,b));
|
||||
/// # assert_eq!(inv.items.get(&b), Some(&10));
|
||||
/// ````
|
||||
///
|
||||
/// Alternatively, you can specify the path to a function with `#[component(map_entities = function_path)]`, similar to component hooks.
|
||||
/// In this case, the inputs of the function should mirror the inputs to this method, with the second parameter being generic.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{component::Component, entity::{Entity, MapEntities, EntityMapper}};
|
||||
/// # use std::collections::HashMap;
|
||||
/// #[derive(Component)]
|
||||
/// #[component(map_entities = map_the_map)]
|
||||
/// // Also works: map_the_map::<M> or map_the_map::<_>
|
||||
/// struct Inventory {
|
||||
/// items: HashMap<Entity, usize>
|
||||
/// }
|
||||
///
|
||||
/// fn map_the_map<M: EntityMapper>(inv: &mut Inventory, entity_mapper: &mut M) {
|
||||
/// inv.items = inv.items
|
||||
/// .drain()
|
||||
/// .map(|(id, count)|(entity_mapper.get_mapped(id), count))
|
||||
/// .collect();
|
||||
/// }
|
||||
/// # let a = Entity::from_bits(0x1_0000_0001);
|
||||
/// # let b = Entity::from_bits(0x1_0000_0002);
|
||||
/// # let mut inv = Inventory { items: Default::default() };
|
||||
/// # inv.items.insert(a, 10);
|
||||
/// # <Inventory as Component>::map_entities(&mut inv, &mut (a,b));
|
||||
/// # assert_eq!(inv.items.get(&b), Some(&10));
|
||||
/// ````
|
||||
///
|
||||
/// You can use the turbofish (`::<A,B,C>`) to specify parameters when a function is generic, using either M or _ for the type of the mapper parameter.
|
||||
#[inline]
|
||||
fn map_entities<E: EntityMapper>(_this: &mut Self, _mapper: &mut E) {}
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait Seal {}
|
||||
}
|
||||
|
||||
/// The mutability option for a [`Component`]. This can either be:
|
||||
/// * [`Mutable`]
|
||||
/// * [`Immutable`]
|
||||
///
|
||||
/// This is controlled through either [`Component::Mutability`] or `#[component(immutable)]`
|
||||
/// when using the derive macro.
|
||||
///
|
||||
/// Immutable components are guaranteed to never have an exclusive reference,
|
||||
/// `&mut ...`, created while inserted onto an entity.
|
||||
/// In all other ways, they are identical to mutable components.
|
||||
/// This restriction allows hooks to observe all changes made to an immutable
|
||||
/// component, effectively turning the `Insert` and `Replace` hooks into a
|
||||
/// `OnMutate` hook.
|
||||
/// This is not practical for mutable components, as the runtime cost of invoking
|
||||
/// a hook for every exclusive reference created would be far too high.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
/// #[component(immutable)]
|
||||
/// struct ImmutableFoo;
|
||||
/// ```
|
||||
pub trait ComponentMutability: private::Seal + 'static {
|
||||
/// Boolean to indicate if this mutability setting implies a mutable or immutable
|
||||
/// component.
|
||||
const MUTABLE: bool;
|
||||
}
|
||||
|
||||
/// Parameter indicating a [`Component`] is immutable.
|
||||
///
|
||||
/// See [`ComponentMutability`] for details.
|
||||
pub struct Immutable;
|
||||
|
||||
impl private::Seal for Immutable {}
|
||||
|
||||
impl ComponentMutability for Immutable {
|
||||
const MUTABLE: bool = false;
|
||||
}
|
||||
|
||||
/// Parameter indicating a [`Component`] is mutable.
|
||||
///
|
||||
/// See [`ComponentMutability`] for details.
|
||||
pub struct Mutable;
|
||||
|
||||
impl private::Seal for Mutable {}
|
||||
|
||||
impl ComponentMutability for Mutable {
|
||||
const MUTABLE: bool = true;
|
||||
}
|
||||
|
||||
/// The storage used for a specific component type.
|
||||
///
|
||||
/// # Examples
|
||||
/// The [`StorageType`] for a component is configured via the derive attribute
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, component::*};
|
||||
/// #[derive(Component)]
|
||||
/// #[component(storage = "SparseSet")]
|
||||
/// struct A;
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub enum StorageType {
|
||||
/// Provides fast and cache-friendly iteration, but slower addition and removal of components.
|
||||
/// This is the default storage type.
|
||||
#[default]
|
||||
Table,
|
||||
/// Provides fast addition and removal of components, but slower iteration.
|
||||
SparseSet,
|
||||
}
|
||||
|
||||
/// A [`SystemParam`] that provides access to the [`ComponentId`] for a specific component type.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use bevy_ecs::{system::Local, component::{Component, ComponentId, ComponentIdFor}};
|
||||
/// #[derive(Component)]
|
||||
/// struct Player;
|
||||
/// fn my_system(component_id: ComponentIdFor<Player>) {
|
||||
/// let component_id: ComponentId = component_id.get();
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(SystemParam)]
|
||||
pub struct ComponentIdFor<'s, T: Component>(Local<'s, InitComponentId<T>>);
|
||||
|
||||
impl<T: Component> ComponentIdFor<'_, T> {
|
||||
/// Gets the [`ComponentId`] for the type `T`.
|
||||
#[inline]
|
||||
pub fn get(&self) -> ComponentId {
|
||||
**self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Deref for ComponentIdFor<'_, T> {
|
||||
type Target = ComponentId;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0.component_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> From<ComponentIdFor<'_, T>> for ComponentId {
|
||||
#[inline]
|
||||
fn from(to_component_id: ComponentIdFor<T>) -> ComponentId {
|
||||
*to_component_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the [`ComponentId`] for a specific type when used with [`FromWorld`].
|
||||
struct InitComponentId<T: Component> {
|
||||
component_id: ComponentId,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> FromWorld for InitComponentId<T> {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
Self {
|
||||
component_id: world.register_component::<T>(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
679
crates/bevy_ecs/src/component/register.rs
Normal file
679
crates/bevy_ecs/src/component/register.rs
Normal file
@ -0,0 +1,679 @@
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use bevy_platform::sync::PoisonError;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::any::Any;
|
||||
use core::ops::DerefMut;
|
||||
use core::{any::TypeId, fmt::Debug, ops::Deref};
|
||||
|
||||
use crate::query::DebugCheckedUnwrap as _;
|
||||
use crate::{
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentId, Components, RequiredComponents, StorageType,
|
||||
},
|
||||
resource::Resource,
|
||||
};
|
||||
|
||||
/// Generates [`ComponentId`]s.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ComponentIds {
|
||||
next: bevy_platform::sync::atomic::AtomicUsize,
|
||||
}
|
||||
|
||||
impl ComponentIds {
|
||||
/// Peeks the next [`ComponentId`] to be generated without generating it.
|
||||
pub fn peek(&self) -> ComponentId {
|
||||
ComponentId(
|
||||
self.next
|
||||
.load(bevy_platform::sync::atomic::Ordering::Relaxed),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates and returns the next [`ComponentId`].
|
||||
pub fn next(&self) -> ComponentId {
|
||||
ComponentId(
|
||||
self.next
|
||||
.fetch_add(1, bevy_platform::sync::atomic::Ordering::Relaxed),
|
||||
)
|
||||
}
|
||||
|
||||
/// Peeks the next [`ComponentId`] to be generated without generating it.
|
||||
pub fn peek_mut(&mut self) -> ComponentId {
|
||||
ComponentId(*self.next.get_mut())
|
||||
}
|
||||
|
||||
/// Generates and returns the next [`ComponentId`].
|
||||
pub fn next_mut(&mut self) -> ComponentId {
|
||||
let id = self.next.get_mut();
|
||||
let result = ComponentId(*id);
|
||||
*id += 1;
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns the number of [`ComponentId`]s generated.
|
||||
pub fn len(&self) -> usize {
|
||||
self.peek().0
|
||||
}
|
||||
|
||||
/// Returns true if and only if no ids have been generated.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Components`] wrapper that enables additional features, like registration.
|
||||
pub struct ComponentsRegistrator<'w> {
|
||||
components: &'w mut Components,
|
||||
ids: &'w mut ComponentIds,
|
||||
}
|
||||
|
||||
impl Deref for ComponentsRegistrator<'_> {
|
||||
type Target = Components;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.components
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ComponentsRegistrator<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.components
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> ComponentsRegistrator<'w> {
|
||||
/// Constructs a new [`ComponentsRegistrator`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`Components`] and [`ComponentIds`] must match.
|
||||
/// For example, they must be from the same world.
|
||||
pub unsafe fn new(components: &'w mut Components, ids: &'w mut ComponentIds) -> Self {
|
||||
Self { components, ids }
|
||||
}
|
||||
|
||||
/// Converts this [`ComponentsRegistrator`] into a [`ComponentsQueuedRegistrator`].
|
||||
/// This is intended for use to pass this value to a function that requires [`ComponentsQueuedRegistrator`].
|
||||
/// It is generally not a good idea to queue a registration when you can instead register directly on this type.
|
||||
pub fn as_queued(&self) -> ComponentsQueuedRegistrator<'_> {
|
||||
// SAFETY: ensured by the caller that created self.
|
||||
unsafe { ComponentsQueuedRegistrator::new(self.components, self.ids) }
|
||||
}
|
||||
|
||||
/// Applies every queued registration.
|
||||
/// This ensures that every valid [`ComponentId`] is registered,
|
||||
/// enabling retrieving [`ComponentInfo`](super::ComponentInfo), etc.
|
||||
pub fn apply_queued_registrations(&mut self) {
|
||||
if !self.any_queued_mut() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note:
|
||||
//
|
||||
// This is not just draining the queue. We need to empty the queue without removing the information from `Components`.
|
||||
// If we drained directly, we could break invariance.
|
||||
//
|
||||
// For example, say `ComponentA` and `ComponentB` are queued, and `ComponentA` requires `ComponentB`.
|
||||
// If we drain directly, and `ComponentA` was the first to be registered, then, when `ComponentA`
|
||||
// registers `ComponentB` in `Component::register_required_components`,
|
||||
// `Components` will not know that `ComponentB` was queued
|
||||
// (since it will have been drained from the queue.)
|
||||
// If that happened, `Components` would assign a new `ComponentId` to `ComponentB`
|
||||
// which would be *different* than the id it was assigned in the queue.
|
||||
// Then, when the drain iterator gets to `ComponentB`,
|
||||
// it would be unsafely registering `ComponentB`, which is already registered.
|
||||
//
|
||||
// As a result, we need to pop from each queue one by one instead of draining.
|
||||
|
||||
// components
|
||||
while let Some(registrator) = {
|
||||
let queued = self
|
||||
.components
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
queued.components.keys().next().copied().map(|type_id| {
|
||||
// SAFETY: the id just came from a valid iterator.
|
||||
unsafe { queued.components.remove(&type_id).debug_checked_unwrap() }
|
||||
})
|
||||
} {
|
||||
registrator.register(self);
|
||||
}
|
||||
|
||||
// resources
|
||||
while let Some(registrator) = {
|
||||
let queued = self
|
||||
.components
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
queued.resources.keys().next().copied().map(|type_id| {
|
||||
// SAFETY: the id just came from a valid iterator.
|
||||
unsafe { queued.resources.remove(&type_id).debug_checked_unwrap() }
|
||||
})
|
||||
} {
|
||||
registrator.register(self);
|
||||
}
|
||||
|
||||
// dynamic
|
||||
let queued = &mut self
|
||||
.components
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner);
|
||||
if !queued.dynamic_registrations.is_empty() {
|
||||
for registrator in core::mem::take(&mut queued.dynamic_registrations) {
|
||||
registrator.register(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a [`Component`] of type `T` with this instance.
|
||||
/// If a component of this type has already been registered, this will return
|
||||
/// the ID of the pre-existing component.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::component_id()`]
|
||||
/// * [`ComponentsRegistrator::register_component_with_descriptor()`]
|
||||
#[inline]
|
||||
pub fn register_component<T: Component>(&mut self) -> ComponentId {
|
||||
self.register_component_checked::<T>(&mut Vec::new())
|
||||
}
|
||||
|
||||
/// Same as [`Self::register_component_unchecked`] but keeps a checks for safety.
|
||||
#[inline]
|
||||
pub(super) fn register_component_checked<T: Component>(
|
||||
&mut self,
|
||||
recursion_check_stack: &mut Vec<ComponentId>,
|
||||
) -> ComponentId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
if let Some(id) = self.indices.get(&type_id) {
|
||||
return *id;
|
||||
}
|
||||
|
||||
if let Some(registrator) = self
|
||||
.components
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.components
|
||||
.remove(&type_id)
|
||||
{
|
||||
// If we are trying to register something that has already been queued, we respect the queue.
|
||||
// Just like if we are trying to register something that already is, we respect the first registration.
|
||||
return registrator.register(self);
|
||||
}
|
||||
|
||||
let id = self.ids.next_mut();
|
||||
// SAFETY: The component is not currently registered, and the id is fresh.
|
||||
unsafe {
|
||||
self.register_component_unchecked::<T>(recursion_check_stack, id);
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Neither this component, nor its id may be registered or queued. This must be a new registration.
|
||||
#[inline]
|
||||
unsafe fn register_component_unchecked<T: Component>(
|
||||
&mut self,
|
||||
recursion_check_stack: &mut Vec<ComponentId>,
|
||||
id: ComponentId,
|
||||
) {
|
||||
// SAFETY: ensured by caller.
|
||||
unsafe {
|
||||
self.register_component_inner(id, ComponentDescriptor::new::<T>());
|
||||
}
|
||||
let type_id = TypeId::of::<T>();
|
||||
let prev = self.indices.insert(type_id, id);
|
||||
debug_assert!(prev.is_none());
|
||||
|
||||
let mut required_components = RequiredComponents::default();
|
||||
T::register_required_components(
|
||||
id,
|
||||
self,
|
||||
&mut required_components,
|
||||
0,
|
||||
recursion_check_stack,
|
||||
);
|
||||
// SAFETY: we just inserted it in `register_component_inner`
|
||||
let info = unsafe {
|
||||
&mut self
|
||||
.components
|
||||
.components
|
||||
.get_mut(id.0)
|
||||
.debug_checked_unwrap()
|
||||
.as_mut()
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
|
||||
info.hooks.update_from_component::<T>();
|
||||
|
||||
info.required_components = required_components;
|
||||
}
|
||||
|
||||
/// Registers a component described by `descriptor`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If this method is called multiple times with identical descriptors, a distinct [`ComponentId`]
|
||||
/// will be created for each one.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::component_id()`]
|
||||
/// * [`ComponentsRegistrator::register_component()`]
|
||||
#[inline]
|
||||
pub fn register_component_with_descriptor(
|
||||
&mut self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next_mut();
|
||||
// SAFETY: The id is fresh.
|
||||
unsafe {
|
||||
self.register_component_inner(id, descriptor);
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
/// Registers a [`Resource`] of type `T` with this instance.
|
||||
/// If a resource of this type has already been registered, this will return
|
||||
/// the ID of the pre-existing resource.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::resource_id()`]
|
||||
/// * [`ComponentsRegistrator::register_resource_with_descriptor()`]
|
||||
#[inline]
|
||||
pub fn register_resource<T: Resource>(&mut self) -> ComponentId {
|
||||
// SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`]
|
||||
unsafe {
|
||||
self.register_resource_with(TypeId::of::<T>(), || {
|
||||
ComponentDescriptor::new_resource::<T>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a [non-send resource](crate::system::NonSend) of type `T` with this instance.
|
||||
/// If a resource of this type has already been registered, this will return
|
||||
/// the ID of the pre-existing resource.
|
||||
#[inline]
|
||||
pub fn register_non_send<T: Any>(&mut self) -> ComponentId {
|
||||
// SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`]
|
||||
unsafe {
|
||||
self.register_resource_with(TypeId::of::<T>(), || {
|
||||
ComponentDescriptor::new_non_send::<T>(StorageType::default())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as [`Components::register_resource_unchecked`] but handles safety.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`ComponentDescriptor`] must match the [`TypeId`].
|
||||
#[inline]
|
||||
unsafe fn register_resource_with(
|
||||
&mut self,
|
||||
type_id: TypeId,
|
||||
descriptor: impl FnOnce() -> ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
if let Some(id) = self.resource_indices.get(&type_id) {
|
||||
return *id;
|
||||
}
|
||||
|
||||
if let Some(registrator) = self
|
||||
.components
|
||||
.queued
|
||||
.get_mut()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.resources
|
||||
.remove(&type_id)
|
||||
{
|
||||
// If we are trying to register something that has already been queued, we respect the queue.
|
||||
// Just like if we are trying to register something that already is, we respect the first registration.
|
||||
return registrator.register(self);
|
||||
}
|
||||
|
||||
let id = self.ids.next_mut();
|
||||
// SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`]
|
||||
unsafe {
|
||||
self.register_resource_unchecked(type_id, id, descriptor());
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
/// Registers a [`Resource`] described by `descriptor`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If this method is called multiple times with identical descriptors, a distinct [`ComponentId`]
|
||||
/// will be created for each one.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Components::resource_id()`]
|
||||
/// * [`ComponentsRegistrator::register_resource()`]
|
||||
#[inline]
|
||||
pub fn register_resource_with_descriptor(
|
||||
&mut self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next_mut();
|
||||
// SAFETY: The id is fresh.
|
||||
unsafe {
|
||||
self.register_component_inner(id, descriptor);
|
||||
}
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// A queued component registration.
|
||||
pub(super) struct QueuedRegistration {
|
||||
pub(super) registrator:
|
||||
Box<dyn FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor)>,
|
||||
pub(super) id: ComponentId,
|
||||
pub(super) descriptor: ComponentDescriptor,
|
||||
}
|
||||
|
||||
impl QueuedRegistration {
|
||||
/// Creates the [`QueuedRegistration`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// [`ComponentId`] must be unique.
|
||||
unsafe fn new(
|
||||
id: ComponentId,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
registrator: Box::new(func),
|
||||
id,
|
||||
descriptor,
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the registration, returning the now valid [`ComponentId`].
|
||||
pub(super) fn register(self, registrator: &mut ComponentsRegistrator) -> ComponentId {
|
||||
(self.registrator)(registrator, self.id, self.descriptor);
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows queuing components to be registered.
|
||||
#[derive(Default)]
|
||||
pub struct QueuedComponents {
|
||||
pub(super) components: TypeIdMap<QueuedRegistration>,
|
||||
pub(super) resources: TypeIdMap<QueuedRegistration>,
|
||||
pub(super) dynamic_registrations: Vec<QueuedRegistration>,
|
||||
}
|
||||
|
||||
impl Debug for QueuedComponents {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let components = self
|
||||
.components
|
||||
.iter()
|
||||
.map(|(type_id, queued)| (type_id, queued.id))
|
||||
.collect::<Vec<_>>();
|
||||
let resources = self
|
||||
.resources
|
||||
.iter()
|
||||
.map(|(type_id, queued)| (type_id, queued.id))
|
||||
.collect::<Vec<_>>();
|
||||
let dynamic_registrations = self
|
||||
.dynamic_registrations
|
||||
.iter()
|
||||
.map(|queued| queued.id)
|
||||
.collect::<Vec<_>>();
|
||||
write!(f, "components: {components:?}, resources: {resources:?}, dynamic_registrations: {dynamic_registrations:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that enables queuing registration in [`Components`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// These queued registrations return [`ComponentId`]s.
|
||||
/// These ids are not yet valid, but they will become valid
|
||||
/// when either [`ComponentsRegistrator::apply_queued_registrations`] is called or the same registration is made directly.
|
||||
/// In either case, the returned [`ComponentId`]s will be correct, but they are not correct yet.
|
||||
///
|
||||
/// Generally, that means these [`ComponentId`]s can be safely used for read-only purposes.
|
||||
/// Modifying the contents of the world through these [`ComponentId`]s directly without waiting for them to be fully registered
|
||||
/// and without then confirming that they have been fully registered is not supported.
|
||||
/// Hence, extra care is needed with these [`ComponentId`]s to ensure all safety rules are followed.
|
||||
///
|
||||
/// As a rule of thumb, if you have mutable access to [`ComponentsRegistrator`], prefer to use that instead.
|
||||
/// Use this only if you need to know the id of a component but do not need to modify the contents of the world based on that id.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ComponentsQueuedRegistrator<'w> {
|
||||
components: &'w Components,
|
||||
ids: &'w ComponentIds,
|
||||
}
|
||||
|
||||
impl Deref for ComponentsQueuedRegistrator<'_> {
|
||||
type Target = Components;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.components
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> ComponentsQueuedRegistrator<'w> {
|
||||
/// Constructs a new [`ComponentsQueuedRegistrator`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`Components`] and [`ComponentIds`] must match.
|
||||
/// For example, they must be from the same world.
|
||||
pub unsafe fn new(components: &'w Components, ids: &'w ComponentIds) -> Self {
|
||||
Self { components, ids }
|
||||
}
|
||||
|
||||
/// Queues this function to run as a component registrator.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`TypeId`] must not already be registered or queued as a component.
|
||||
unsafe fn force_register_arbitrary_component(
|
||||
&self,
|
||||
type_id: TypeId,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next();
|
||||
self.components
|
||||
.queued
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.components
|
||||
.insert(
|
||||
type_id,
|
||||
// SAFETY: The id was just generated.
|
||||
unsafe { QueuedRegistration::new(id, descriptor, func) },
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
/// Queues this function to run as a resource registrator.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The [`TypeId`] must not already be registered or queued as a resource.
|
||||
unsafe fn force_register_arbitrary_resource(
|
||||
&self,
|
||||
type_id: TypeId,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next();
|
||||
self.components
|
||||
.queued
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.resources
|
||||
.insert(
|
||||
type_id,
|
||||
// SAFETY: The id was just generated.
|
||||
unsafe { QueuedRegistration::new(id, descriptor, func) },
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
/// Queues this function to run as a dynamic registrator.
|
||||
fn force_register_arbitrary_dynamic(
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
|
||||
) -> ComponentId {
|
||||
let id = self.ids.next();
|
||||
self.components
|
||||
.queued
|
||||
.write()
|
||||
.unwrap_or_else(PoisonError::into_inner)
|
||||
.dynamic_registrations
|
||||
.push(
|
||||
// SAFETY: The id was just generated.
|
||||
unsafe { QueuedRegistration::new(id, descriptor, func) },
|
||||
);
|
||||
id
|
||||
}
|
||||
|
||||
/// This is a queued version of [`ComponentsRegistrator::register_component`].
|
||||
/// This will reserve an id and queue the registration.
|
||||
/// These registrations will be carried out at the next opportunity.
|
||||
///
|
||||
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
|
||||
/// See type level docs for details.
|
||||
#[inline]
|
||||
pub fn queue_register_component<T: Component>(&self) -> ComponentId {
|
||||
self.component_id::<T>().unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_component(
|
||||
TypeId::of::<T>(),
|
||||
ComponentDescriptor::new::<T>(),
|
||||
|registrator, id, _descriptor| {
|
||||
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
|
||||
#[expect(unused_unsafe, reason = "More precise to specify.")]
|
||||
unsafe {
|
||||
registrator.register_component_unchecked::<T>(&mut Vec::new(), id);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This is a queued version of [`ComponentsRegistrator::register_component_with_descriptor`].
|
||||
/// This will reserve an id and queue the registration.
|
||||
/// These registrations will be carried out at the next opportunity.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
|
||||
/// See type level docs for details.
|
||||
#[inline]
|
||||
pub fn queue_register_component_with_descriptor(
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
// SAFETY: Id uniqueness handled by caller.
|
||||
unsafe {
|
||||
registrator.register_component_inner(id, descriptor);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This is a queued version of [`ComponentsRegistrator::register_resource`].
|
||||
/// This will reserve an id and queue the registration.
|
||||
/// These registrations will be carried out at the next opportunity.
|
||||
///
|
||||
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
|
||||
/// See type level docs for details.
|
||||
#[inline]
|
||||
pub fn queue_register_resource<T: Resource>(&self) -> ComponentId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
self.get_resource_id(type_id).unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_resource(
|
||||
type_id,
|
||||
ComponentDescriptor::new_resource::<T>(),
|
||||
move |registrator, id, descriptor| {
|
||||
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
|
||||
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
|
||||
#[expect(unused_unsafe, reason = "More precise to specify.")]
|
||||
unsafe {
|
||||
registrator.register_resource_unchecked(type_id, id, descriptor);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This is a queued version of [`ComponentsRegistrator::register_non_send`].
|
||||
/// This will reserve an id and queue the registration.
|
||||
/// These registrations will be carried out at the next opportunity.
|
||||
///
|
||||
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
|
||||
/// See type level docs for details.
|
||||
#[inline]
|
||||
pub fn queue_register_non_send<T: Any>(&self) -> ComponentId {
|
||||
let type_id = TypeId::of::<T>();
|
||||
self.get_resource_id(type_id).unwrap_or_else(|| {
|
||||
// SAFETY: We just checked that this type was not in the queue.
|
||||
unsafe {
|
||||
self.force_register_arbitrary_resource(
|
||||
type_id,
|
||||
ComponentDescriptor::new_non_send::<T>(StorageType::default()),
|
||||
move |registrator, id, descriptor| {
|
||||
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
|
||||
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
|
||||
#[expect(unused_unsafe, reason = "More precise to specify.")]
|
||||
unsafe {
|
||||
registrator.register_resource_unchecked(type_id, id, descriptor);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// This is a queued version of [`ComponentsRegistrator::register_resource_with_descriptor`].
|
||||
/// This will reserve an id and queue the registration.
|
||||
/// These registrations will be carried out at the next opportunity.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
|
||||
/// See type level docs for details.
|
||||
#[inline]
|
||||
pub fn queue_register_resource_with_descriptor(
|
||||
&self,
|
||||
descriptor: ComponentDescriptor,
|
||||
) -> ComponentId {
|
||||
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
|
||||
// SAFETY: Id uniqueness handled by caller.
|
||||
unsafe {
|
||||
registrator.register_component_inner(id, descriptor);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
536
crates/bevy_ecs/src/component/required.rs
Normal file
536
crates/bevy_ecs/src/component/required.rs
Normal file
@ -0,0 +1,536 @@
|
||||
use alloc::{format, vec::Vec};
|
||||
use bevy_platform::{collections::HashMap, sync::Arc};
|
||||
use bevy_ptr::OwningPtr;
|
||||
use core::fmt::Debug;
|
||||
use smallvec::SmallVec;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
bundle::BundleInfo,
|
||||
change_detection::MaybeLocation,
|
||||
component::{Component, ComponentId, Components, ComponentsRegistrator, Tick},
|
||||
entity::Entity,
|
||||
query::DebugCheckedUnwrap as _,
|
||||
storage::{SparseSets, Table, TableRow},
|
||||
};
|
||||
|
||||
impl Components {
|
||||
/// Registers the given component `R` and [required components] inherited from it as required by `T`.
|
||||
///
|
||||
/// When `T` is added to an entity, `R` will also be added if it was not already provided.
|
||||
/// The given `constructor` will be used for the creation of `R`.
|
||||
///
|
||||
/// [required components]: Component#required-components
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The given component IDs `required` and `requiree` must be valid.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a [`RequiredComponentsError`] if the `required` component is already a directly required component for the `requiree`.
|
||||
///
|
||||
/// Indirect requirements through other components are allowed. In those cases, the more specific
|
||||
/// registration will be used.
|
||||
pub(crate) unsafe fn register_required_components<R: Component>(
|
||||
&mut self,
|
||||
requiree: ComponentId,
|
||||
required: ComponentId,
|
||||
constructor: fn() -> R,
|
||||
) -> Result<(), RequiredComponentsError> {
|
||||
// SAFETY: The caller ensures that the `requiree` is valid.
|
||||
let required_components = unsafe {
|
||||
self.get_required_components_mut(requiree)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
|
||||
// Cannot directly require the same component twice.
|
||||
if required_components
|
||||
.0
|
||||
.get(&required)
|
||||
.is_some_and(|c| c.inheritance_depth == 0)
|
||||
{
|
||||
return Err(RequiredComponentsError::DuplicateRegistration(
|
||||
requiree, required,
|
||||
));
|
||||
}
|
||||
|
||||
// Register the required component for the requiree.
|
||||
// This is a direct requirement with a depth of `0`.
|
||||
required_components.register_by_id(required, constructor, 0);
|
||||
|
||||
// Add the requiree to the list of components that require the required component.
|
||||
// SAFETY: The component is in the list of required components, so it must exist already.
|
||||
let required_by = unsafe { self.get_required_by_mut(required).debug_checked_unwrap() };
|
||||
required_by.insert(requiree);
|
||||
|
||||
let mut required_components_tmp = RequiredComponents::default();
|
||||
// SAFETY: The caller ensures that the `requiree` and `required` components are valid.
|
||||
let inherited_requirements = unsafe {
|
||||
self.register_inherited_required_components(
|
||||
requiree,
|
||||
required,
|
||||
&mut required_components_tmp,
|
||||
)
|
||||
};
|
||||
|
||||
// SAFETY: The caller ensures that the `requiree` is valid.
|
||||
let required_components = unsafe {
|
||||
self.get_required_components_mut(requiree)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
required_components.0.extend(required_components_tmp.0);
|
||||
|
||||
// Propagate the new required components up the chain to all components that require the requiree.
|
||||
if let Some(required_by) = self
|
||||
.get_required_by(requiree)
|
||||
.map(|set| set.iter().copied().collect::<SmallVec<[ComponentId; 8]>>())
|
||||
{
|
||||
// `required` is now required by anything that `requiree` was required by.
|
||||
self.get_required_by_mut(required)
|
||||
.unwrap()
|
||||
.extend(required_by.iter().copied());
|
||||
for &required_by_id in required_by.iter() {
|
||||
// SAFETY: The component is in the list of required components, so it must exist already.
|
||||
let required_components = unsafe {
|
||||
self.get_required_components_mut(required_by_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
|
||||
// Register the original required component in the "parent" of the requiree.
|
||||
// The inheritance depth is 1 deeper than the `requiree` wrt `required_by_id`.
|
||||
let depth = required_components.0.get(&requiree).expect("requiree is required by required_by_id, so its required_components must include requiree").inheritance_depth;
|
||||
required_components.register_by_id(required, constructor, depth + 1);
|
||||
|
||||
for (component_id, component) in inherited_requirements.iter() {
|
||||
// Register the required component.
|
||||
// The inheritance depth of inherited components is whatever the requiree's
|
||||
// depth is relative to `required_by_id`, plus the inheritance depth of the
|
||||
// inherited component relative to the requiree, plus 1 to account for the
|
||||
// requiree in between.
|
||||
// SAFETY: Component ID and constructor match the ones on the original requiree.
|
||||
// The original requiree is responsible for making sure the registration is safe.
|
||||
unsafe {
|
||||
required_components.register_dynamic_with(
|
||||
*component_id,
|
||||
component.inheritance_depth + depth + 1,
|
||||
|| component.constructor.clone(),
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Registers the components inherited from `required` for the given `requiree`,
|
||||
/// returning the requirements in a list.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The given component IDs `requiree` and `required` must be valid.
|
||||
unsafe fn register_inherited_required_components(
|
||||
&mut self,
|
||||
requiree: ComponentId,
|
||||
required: ComponentId,
|
||||
required_components: &mut RequiredComponents,
|
||||
) -> Vec<(ComponentId, RequiredComponent)> {
|
||||
// Get required components inherited from the `required` component.
|
||||
// SAFETY: The caller ensures that the `required` component is valid.
|
||||
let required_component_info = unsafe { self.get_info(required).debug_checked_unwrap() };
|
||||
let inherited_requirements: Vec<(ComponentId, RequiredComponent)> = required_component_info
|
||||
.required_components()
|
||||
.0
|
||||
.iter()
|
||||
.map(|(component_id, required_component)| {
|
||||
(
|
||||
*component_id,
|
||||
RequiredComponent {
|
||||
constructor: required_component.constructor.clone(),
|
||||
// Add `1` to the inheritance depth since this will be registered
|
||||
// for the component that requires `required`.
|
||||
inheritance_depth: required_component.inheritance_depth + 1,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Register the new required components.
|
||||
for (component_id, component) in inherited_requirements.iter() {
|
||||
// Register the required component for the requiree.
|
||||
// SAFETY: Component ID and constructor match the ones on the original requiree.
|
||||
unsafe {
|
||||
required_components.register_dynamic_with(
|
||||
*component_id,
|
||||
component.inheritance_depth,
|
||||
|| component.constructor.clone(),
|
||||
);
|
||||
};
|
||||
|
||||
// Add the requiree to the list of components that require the required component.
|
||||
// SAFETY: The caller ensures that the required components are valid.
|
||||
let required_by = unsafe {
|
||||
self.get_required_by_mut(*component_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
required_by.insert(requiree);
|
||||
}
|
||||
|
||||
inherited_requirements
|
||||
}
|
||||
|
||||
/// Registers the given component `R` and [required components] inherited from it as required by `T`,
|
||||
/// and adds `T` to their lists of requirees.
|
||||
///
|
||||
/// The given `inheritance_depth` determines how many levels of inheritance deep the requirement is.
|
||||
/// A direct requirement has a depth of `0`, and each level of inheritance increases the depth by `1`.
|
||||
/// Lower depths are more specific requirements, and can override existing less specific registrations.
|
||||
///
|
||||
/// This method does *not* register any components as required by components that require `T`.
|
||||
///
|
||||
/// [required component]: Component#required-components
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The given component IDs `required` and `requiree` must be valid.
|
||||
pub(crate) unsafe fn register_required_components_manual_unchecked<R: Component>(
|
||||
&mut self,
|
||||
requiree: ComponentId,
|
||||
required: ComponentId,
|
||||
required_components: &mut RequiredComponents,
|
||||
constructor: fn() -> R,
|
||||
inheritance_depth: u16,
|
||||
) {
|
||||
// Components cannot require themselves.
|
||||
if required == requiree {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the required component `R` for the requiree.
|
||||
required_components.register_by_id(required, constructor, inheritance_depth);
|
||||
|
||||
// Add the requiree to the list of components that require `R`.
|
||||
// SAFETY: The caller ensures that the component ID is valid.
|
||||
// Assuming it is valid, the component is in the list of required components, so it must exist already.
|
||||
let required_by = unsafe { self.get_required_by_mut(required).debug_checked_unwrap() };
|
||||
required_by.insert(requiree);
|
||||
|
||||
self.register_inherited_required_components(requiree, required, required_components);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w> ComponentsRegistrator<'w> {
|
||||
// NOTE: This should maybe be private, but it is currently public so that `bevy_ecs_macros` can use it.
|
||||
// We can't directly move this there either, because this uses `Components::get_required_by_mut`,
|
||||
// which is private, and could be equally risky to expose to users.
|
||||
/// Registers the given component `R` and [required components] inherited from it as required by `T`,
|
||||
/// and adds `T` to their lists of requirees.
|
||||
///
|
||||
/// The given `inheritance_depth` determines how many levels of inheritance deep the requirement is.
|
||||
/// A direct requirement has a depth of `0`, and each level of inheritance increases the depth by `1`.
|
||||
/// Lower depths are more specific requirements, and can override existing less specific registrations.
|
||||
///
|
||||
/// The `recursion_check_stack` allows checking whether this component tried to register itself as its
|
||||
/// own (indirect) required component.
|
||||
///
|
||||
/// This method does *not* register any components as required by components that require `T`.
|
||||
///
|
||||
/// Only use this method if you know what you are doing. In most cases, you should instead use [`World::register_required_components`],
|
||||
/// or the equivalent method in `bevy_app::App`.
|
||||
///
|
||||
/// [required component]: Component#required-components
|
||||
#[doc(hidden)]
|
||||
pub fn register_required_components_manual<T: Component, R: Component>(
|
||||
&mut self,
|
||||
required_components: &mut RequiredComponents,
|
||||
constructor: fn() -> R,
|
||||
inheritance_depth: u16,
|
||||
recursion_check_stack: &mut Vec<ComponentId>,
|
||||
) {
|
||||
let requiree = self.register_component_checked::<T>(recursion_check_stack);
|
||||
let required = self.register_component_checked::<R>(recursion_check_stack);
|
||||
|
||||
// SAFETY: We just created the components.
|
||||
unsafe {
|
||||
self.register_required_components_manual_unchecked::<R>(
|
||||
requiree,
|
||||
required,
|
||||
required_components,
|
||||
constructor,
|
||||
inheritance_depth,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error returned when the registration of a required component fails.
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum RequiredComponentsError {
|
||||
/// The component is already a directly required component for the requiree.
|
||||
#[error("Component {0:?} already directly requires component {1:?}")]
|
||||
DuplicateRegistration(ComponentId, ComponentId),
|
||||
/// An archetype with the component that requires other components already exists
|
||||
#[error("An archetype with the component {0:?} that requires other components already exists")]
|
||||
ArchetypeExists(ComponentId),
|
||||
}
|
||||
|
||||
/// A Required Component constructor. See [`Component`] for details.
|
||||
#[derive(Clone)]
|
||||
pub struct RequiredComponentConstructor(
|
||||
pub Arc<dyn Fn(&mut Table, &mut SparseSets, Tick, TableRow, Entity, MaybeLocation)>,
|
||||
);
|
||||
|
||||
impl RequiredComponentConstructor {
|
||||
/// # Safety
|
||||
/// This is intended to only be called in the context of [`BundleInfo::write_components`] to initialized required components.
|
||||
/// Calling it _anywhere else_ should be considered unsafe.
|
||||
///
|
||||
/// `table_row` and `entity` must correspond to a valid entity that currently needs a component initialized via the constructor stored
|
||||
/// on this [`RequiredComponentConstructor`]. The stored constructor must correspond to a component on `entity` that needs initialization.
|
||||
/// `table` and `sparse_sets` must correspond to storages on a world where `entity` needs this required component initialized.
|
||||
///
|
||||
/// Again, don't call this anywhere but [`BundleInfo::write_components`].
|
||||
pub(crate) unsafe fn initialize(
|
||||
&self,
|
||||
table: &mut Table,
|
||||
sparse_sets: &mut SparseSets,
|
||||
change_tick: Tick,
|
||||
table_row: TableRow,
|
||||
entity: Entity,
|
||||
caller: MaybeLocation,
|
||||
) {
|
||||
(self.0)(table, sparse_sets, change_tick, table_row, entity, caller);
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata associated with a required component. See [`Component`] for details.
|
||||
#[derive(Clone)]
|
||||
pub struct RequiredComponent {
|
||||
/// The constructor used for the required component.
|
||||
pub constructor: RequiredComponentConstructor,
|
||||
|
||||
/// The depth of the component requirement in the requirement hierarchy for this component.
|
||||
/// This is used for determining which constructor is used in cases where there are duplicate requires.
|
||||
///
|
||||
/// For example, consider the inheritance tree `X -> Y -> Z`, where `->` indicates a requirement.
|
||||
/// `X -> Y` and `Y -> Z` are direct requirements with a depth of 0, while `Z` is only indirectly
|
||||
/// required for `X` with a depth of `1`.
|
||||
///
|
||||
/// In cases where there are multiple conflicting requirements with the same depth, a higher priority
|
||||
/// will be given to components listed earlier in the `require` attribute, or to the latest added requirement
|
||||
/// if registered at runtime.
|
||||
pub inheritance_depth: u16,
|
||||
}
|
||||
|
||||
/// The collection of metadata for components that are required for a given component.
|
||||
///
|
||||
/// For more information, see the "Required Components" section of [`Component`].
|
||||
#[derive(Default, Clone)]
|
||||
pub struct RequiredComponents(pub(crate) HashMap<ComponentId, RequiredComponent>);
|
||||
|
||||
impl Debug for RequiredComponents {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_tuple("RequiredComponents")
|
||||
.field(&self.0.keys())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl RequiredComponents {
|
||||
/// Registers a required component.
|
||||
///
|
||||
/// If the component is already registered, it will be overwritten if the given inheritance depth
|
||||
/// is smaller than the depth of the existing registration. Otherwise, the new registration will be ignored.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `component_id` must match the type initialized by `constructor`.
|
||||
/// `constructor` _must_ initialize a component for `component_id` in such a way that
|
||||
/// matches the storage type of the component. It must only use the given `table_row` or `Entity` to
|
||||
/// initialize the storage for `component_id` corresponding to the given entity.
|
||||
pub unsafe fn register_dynamic_with(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
inheritance_depth: u16,
|
||||
constructor: impl FnOnce() -> RequiredComponentConstructor,
|
||||
) {
|
||||
let entry = self.0.entry(component_id);
|
||||
match entry {
|
||||
bevy_platform::collections::hash_map::Entry::Occupied(mut occupied) => {
|
||||
let current = occupied.get_mut();
|
||||
if current.inheritance_depth > inheritance_depth {
|
||||
*current = RequiredComponent {
|
||||
constructor: constructor(),
|
||||
inheritance_depth,
|
||||
}
|
||||
}
|
||||
}
|
||||
bevy_platform::collections::hash_map::Entry::Vacant(vacant) => {
|
||||
vacant.insert(RequiredComponent {
|
||||
constructor: constructor(),
|
||||
inheritance_depth,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a required component.
|
||||
///
|
||||
/// If the component is already registered, it will be overwritten if the given inheritance depth
|
||||
/// is smaller than the depth of the existing registration. Otherwise, the new registration will be ignored.
|
||||
pub fn register<C: Component>(
|
||||
&mut self,
|
||||
components: &mut ComponentsRegistrator,
|
||||
constructor: fn() -> C,
|
||||
inheritance_depth: u16,
|
||||
) {
|
||||
let component_id = components.register_component::<C>();
|
||||
self.register_by_id(component_id, constructor, inheritance_depth);
|
||||
}
|
||||
|
||||
/// Registers the [`Component`] with the given ID as required if it exists.
|
||||
///
|
||||
/// If the component is already registered, it will be overwritten if the given inheritance depth
|
||||
/// is smaller than the depth of the existing registration. Otherwise, the new registration will be ignored.
|
||||
pub fn register_by_id<C: Component>(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
constructor: fn() -> C,
|
||||
inheritance_depth: u16,
|
||||
) {
|
||||
let erased = || {
|
||||
RequiredComponentConstructor({
|
||||
// `portable-atomic-util` `Arc` is not able to coerce an unsized
|
||||
// type like `std::sync::Arc` can. Creating a `Box` first does the
|
||||
// coercion.
|
||||
//
|
||||
// This would be resolved by https://github.com/rust-lang/rust/issues/123430
|
||||
|
||||
#[cfg(not(target_has_atomic = "ptr"))]
|
||||
use alloc::boxed::Box;
|
||||
|
||||
type Constructor = dyn for<'a, 'b> Fn(
|
||||
&'a mut Table,
|
||||
&'b mut SparseSets,
|
||||
Tick,
|
||||
TableRow,
|
||||
Entity,
|
||||
MaybeLocation,
|
||||
);
|
||||
|
||||
#[cfg(not(target_has_atomic = "ptr"))]
|
||||
type Intermediate<T> = Box<T>;
|
||||
|
||||
#[cfg(target_has_atomic = "ptr")]
|
||||
type Intermediate<T> = Arc<T>;
|
||||
|
||||
let boxed: Intermediate<Constructor> = Intermediate::new(
|
||||
move |table, sparse_sets, change_tick, table_row, entity, caller| {
|
||||
OwningPtr::make(constructor(), |ptr| {
|
||||
// SAFETY: This will only be called in the context of `BundleInfo::write_components`, which will
|
||||
// pass in a valid table_row and entity requiring a C constructor
|
||||
// C::STORAGE_TYPE is the storage type associated with `component_id` / `C`
|
||||
// `ptr` points to valid `C` data, which matches the type associated with `component_id`
|
||||
unsafe {
|
||||
BundleInfo::initialize_required_component(
|
||||
table,
|
||||
sparse_sets,
|
||||
change_tick,
|
||||
table_row,
|
||||
entity,
|
||||
component_id,
|
||||
C::STORAGE_TYPE,
|
||||
ptr,
|
||||
caller,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
Arc::from(boxed)
|
||||
})
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// `component_id` matches the type initialized by the `erased` constructor above.
|
||||
// `erased` initializes a component for `component_id` in such a way that
|
||||
// matches the storage type of the component. It only uses the given `table_row` or `Entity` to
|
||||
// initialize the storage corresponding to the given entity.
|
||||
unsafe { self.register_dynamic_with(component_id, inheritance_depth, erased) };
|
||||
}
|
||||
|
||||
/// Iterates the ids of all required components. This includes recursive required components.
|
||||
pub fn iter_ids(&self) -> impl Iterator<Item = ComponentId> + '_ {
|
||||
self.0.keys().copied()
|
||||
}
|
||||
|
||||
/// Removes components that are explicitly provided in a given [`Bundle`]. These components should
|
||||
/// be logically treated as normal components, not "required components".
|
||||
///
|
||||
/// [`Bundle`]: crate::bundle::Bundle
|
||||
pub(crate) fn remove_explicit_components(&mut self, components: &[ComponentId]) {
|
||||
for component in components {
|
||||
self.0.remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges `required_components` into this collection. This only inserts a required component
|
||||
/// if it _did not already exist_ *or* if the required component is more specific than the existing one
|
||||
/// (in other words, if the inheritance depth is smaller).
|
||||
///
|
||||
/// See [`register_dynamic_with`](Self::register_dynamic_with) for details.
|
||||
pub(crate) fn merge(&mut self, required_components: &RequiredComponents) {
|
||||
for (
|
||||
component_id,
|
||||
RequiredComponent {
|
||||
constructor,
|
||||
inheritance_depth,
|
||||
},
|
||||
) in required_components.0.iter()
|
||||
{
|
||||
// SAFETY: This exact registration must have been done on `required_components`, so safety is ensured by that caller.
|
||||
unsafe {
|
||||
self.register_dynamic_with(*component_id, *inheritance_depth, || {
|
||||
constructor.clone()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This should maybe be private, but it is currently public so that `bevy_ecs_macros` can use it.
|
||||
// This exists as a standalone function instead of being inlined into the component derive macro so as
|
||||
// to reduce the amount of generated code.
|
||||
#[doc(hidden)]
|
||||
pub fn enforce_no_required_components_recursion(
|
||||
components: &Components,
|
||||
recursion_check_stack: &[ComponentId],
|
||||
) {
|
||||
if let Some((&requiree, check)) = recursion_check_stack.split_last() {
|
||||
if let Some(direct_recursion) = check
|
||||
.iter()
|
||||
.position(|&id| id == requiree)
|
||||
.map(|index| index == check.len() - 1)
|
||||
{
|
||||
panic!(
|
||||
"Recursive required components detected: {}\nhelp: {}",
|
||||
recursion_check_stack
|
||||
.iter()
|
||||
.map(|id| format!("{}", components.get_name(*id).unwrap().shortname()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" → "),
|
||||
if direct_recursion {
|
||||
format!(
|
||||
"Remove require({}).",
|
||||
components.get_name(requiree).unwrap().shortname()
|
||||
)
|
||||
} else {
|
||||
"If this is intentional, consider merging the components.".into()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
199
crates/bevy_ecs/src/component/tick.rs
Normal file
199
crates/bevy_ecs/src/component/tick.rs
Normal file
@ -0,0 +1,199 @@
|
||||
use bevy_ecs_macros::Event;
|
||||
use bevy_ptr::UnsafeCellDeref;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use core::cell::UnsafeCell;
|
||||
|
||||
use crate::change_detection::MAX_CHANGE_AGE;
|
||||
|
||||
/// A value that tracks when a system ran relative to other systems.
|
||||
/// This is used to power change detection.
|
||||
///
|
||||
/// *Note* that a system that hasn't been run yet has a `Tick` of 0.
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Debug, Hash, PartialEq, Clone)
|
||||
)]
|
||||
pub struct Tick {
|
||||
tick: u32,
|
||||
}
|
||||
|
||||
impl Tick {
|
||||
/// The maximum relative age for a change tick.
|
||||
/// The value of this is equal to [`MAX_CHANGE_AGE`].
|
||||
///
|
||||
/// Since change detection will not work for any ticks older than this,
|
||||
/// ticks are periodically scanned to ensure their relative values are below this.
|
||||
pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
|
||||
|
||||
/// Creates a new [`Tick`] wrapping the given value.
|
||||
#[inline]
|
||||
pub const fn new(tick: u32) -> Self {
|
||||
Self { tick }
|
||||
}
|
||||
|
||||
/// Gets the value of this change tick.
|
||||
#[inline]
|
||||
pub const fn get(self) -> u32 {
|
||||
self.tick
|
||||
}
|
||||
|
||||
/// Sets the value of this change tick.
|
||||
#[inline]
|
||||
pub fn set(&mut self, tick: u32) {
|
||||
self.tick = tick;
|
||||
}
|
||||
|
||||
/// Returns `true` if this `Tick` occurred since the system's `last_run`.
|
||||
///
|
||||
/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
|
||||
#[inline]
|
||||
pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
|
||||
// This works even with wraparound because the world tick (`this_run`) is always "newer" than
|
||||
// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
|
||||
// so they never get older than `u32::MAX` (the difference would overflow).
|
||||
//
|
||||
// The clamp here ensures determinism (since scans could differ between app runs).
|
||||
let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
|
||||
let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
|
||||
|
||||
ticks_since_system > ticks_since_insert
|
||||
}
|
||||
|
||||
/// Returns a change tick representing the relationship between `self` and `other`.
|
||||
#[inline]
|
||||
pub(crate) fn relative_to(self, other: Self) -> Self {
|
||||
let tick = self.tick.wrapping_sub(other.tick);
|
||||
Self { tick }
|
||||
}
|
||||
|
||||
/// Wraps this change tick's value if it exceeds [`Tick::MAX`].
|
||||
///
|
||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||
#[inline]
|
||||
pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
|
||||
let age = check.present_tick().relative_to(*self);
|
||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||
// so long as this check always runs before that can happen.
|
||||
if age.get() > Self::MAX.get() {
|
||||
*self = check.present_tick().relative_to(Self::MAX);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
|
||||
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
|
||||
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
|
||||
/// ticks automatically updated via [`World::check_change_ticks`](crate::world::World::check_change_ticks),
|
||||
/// possibly causing `Tick`-related bugs on long-running apps.
|
||||
///
|
||||
/// To fix that, add an observer for this event that calls the schedule's
|
||||
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::component::CheckChangeTicks;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct CustomSchedule(Schedule);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// world.add_observer(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
|
||||
/// schedule.0.check_change_ticks(*check);
|
||||
/// });
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Event)]
|
||||
pub struct CheckChangeTicks(pub(crate) Tick);
|
||||
|
||||
impl CheckChangeTicks {
|
||||
/// Get the present `Tick` that other ticks get compared to.
|
||||
pub fn present_tick(self) -> Tick {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TickCells<'a> {
|
||||
/// The tick indicating when the value was added to the world.
|
||||
pub added: &'a UnsafeCell<Tick>,
|
||||
/// The tick indicating the last time the value was modified.
|
||||
pub changed: &'a UnsafeCell<Tick>,
|
||||
}
|
||||
|
||||
impl<'a> TickCells<'a> {
|
||||
/// # Safety
|
||||
/// All cells contained within must uphold the safety invariants of [`UnsafeCellDeref::read`].
|
||||
#[inline]
|
||||
pub(crate) unsafe fn read(&self) -> ComponentTicks {
|
||||
ComponentTicks {
|
||||
// SAFETY: The callers uphold the invariants for `read`.
|
||||
added: unsafe { self.added.read() },
|
||||
// SAFETY: The callers uphold the invariants for `read`.
|
||||
changed: unsafe { self.changed.read() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Records when a component or resource was added and when it was last mutably dereferenced (or added).
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, Clone))]
|
||||
pub struct ComponentTicks {
|
||||
/// Tick recording the time this component or resource was added.
|
||||
pub added: Tick,
|
||||
|
||||
/// Tick recording the time this component or resource was most recently changed.
|
||||
pub changed: Tick,
|
||||
}
|
||||
|
||||
impl ComponentTicks {
|
||||
/// Returns `true` if the component or resource was added after the system last ran
|
||||
/// (or the system is running for the first time).
|
||||
#[inline]
|
||||
pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||
self.added.is_newer_than(last_run, this_run)
|
||||
}
|
||||
|
||||
/// Returns `true` if the component or resource was added or mutably dereferenced after the system last ran
|
||||
/// (or the system is running for the first time).
|
||||
#[inline]
|
||||
pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||
self.changed.is_newer_than(last_run, this_run)
|
||||
}
|
||||
|
||||
/// Creates a new instance with the same change tick for `added` and `changed`.
|
||||
pub fn new(change_tick: Tick) -> Self {
|
||||
Self {
|
||||
added: change_tick,
|
||||
changed: change_tick,
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually sets the change tick.
|
||||
///
|
||||
/// This is normally done automatically via the [`DerefMut`](core::ops::DerefMut) implementation
|
||||
/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
|
||||
/// However, components and resources that make use of interior mutability might require manual updates.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// # use bevy_ecs::{world::World, component::ComponentTicks};
|
||||
/// let world: World = unimplemented!();
|
||||
/// let component_ticks: ComponentTicks = unimplemented!();
|
||||
///
|
||||
/// component_ticks.set_changed(world.read_change_tick());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set_changed(&mut self, change_tick: Tick) {
|
||||
self.changed = change_tick;
|
||||
}
|
||||
}
|
@ -731,42 +731,16 @@ impl<'de> Deserialize<'de> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs the full entity identifier, including the index, generation, and the raw bits.
|
||||
/// Outputs the short entity identifier, including the index and generation.
|
||||
///
|
||||
/// This takes the format: `{index}v{generation}#{bits}`.
|
||||
/// This takes the format: `{index}v{generation}`.
|
||||
///
|
||||
/// For [`Entity::PLACEHOLDER`], this outputs `PLACEHOLDER`.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// Prefer to use this format for debugging and logging purposes. Because the output contains
|
||||
/// the raw bits, it is easy to check it against serialized scene data.
|
||||
///
|
||||
/// Example serialized scene data:
|
||||
/// ```text
|
||||
/// (
|
||||
/// ...
|
||||
/// entities: {
|
||||
/// 4294967297: ( <--- Raw Bits
|
||||
/// components: {
|
||||
/// ...
|
||||
/// ),
|
||||
/// ...
|
||||
/// )
|
||||
/// ```
|
||||
/// For a unique [`u64`] representation, use [`Entity::to_bits`].
|
||||
impl fmt::Debug for Entity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self == &Self::PLACEHOLDER {
|
||||
write!(f, "PLACEHOLDER")
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}v{}#{}",
|
||||
self.index(),
|
||||
self.generation(),
|
||||
self.to_bits()
|
||||
)
|
||||
}
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1585,7 +1559,7 @@ mod tests {
|
||||
fn entity_debug() {
|
||||
let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));
|
||||
let string = format!("{entity:?}");
|
||||
assert_eq!(string, "42v0#4294967253");
|
||||
assert_eq!(string, "42v0");
|
||||
|
||||
let entity = Entity::PLACEHOLDER;
|
||||
let string = format!("{entity:?}");
|
||||
|
@ -746,11 +746,11 @@ fn early_sweep_material_instances<M>(
|
||||
/// preparation for a new frame.
|
||||
pub(crate) fn late_sweep_material_instances(
|
||||
mut material_instances: ResMut<RenderMaterialInstances>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
|
||||
) {
|
||||
let last_change_tick = material_instances.current_change_tick;
|
||||
|
||||
for entity in removed_visibilities_query.read() {
|
||||
for entity in removed_meshes_query.read() {
|
||||
if let Entry::Occupied(occupied_entry) = material_instances.instances.entry(entity.into()) {
|
||||
// Only sweep the entry if it wasn't updated this frame. It's
|
||||
// possible that a `ViewVisibility` component was removed and
|
||||
|
@ -1452,8 +1452,6 @@ pub fn extract_meshes_for_gpu_building(
|
||||
>,
|
||||
>,
|
||||
all_meshes_query: Extract<Query<GpuMeshExtractionQuery>>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_global_transforms_query: Extract<RemovedComponents<GlobalTransform>>,
|
||||
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
|
||||
gpu_culling_query: Extract<Query<(), (With<Camera>, Without<NoIndirectDrawing>)>>,
|
||||
meshes_to_reextract_next_frame: ResMut<MeshesToReextractNextFrame>,
|
||||
@ -1509,11 +1507,7 @@ pub fn extract_meshes_for_gpu_building(
|
||||
}
|
||||
|
||||
// Also record info about each mesh that became invisible.
|
||||
for entity in removed_visibilities_query
|
||||
.read()
|
||||
.chain(removed_global_transforms_query.read())
|
||||
.chain(removed_meshes_query.read())
|
||||
{
|
||||
for entity in removed_meshes_query.read() {
|
||||
// Only queue a mesh for removal if we didn't pick it up above.
|
||||
// It's possible that a necessary component was removed and re-added in
|
||||
// the same frame.
|
||||
|
@ -62,6 +62,22 @@ fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: v
|
||||
return sample_shadow_cubemap(frag_ls * flip_z, distance_to_light, depth, light_id);
|
||||
}
|
||||
|
||||
// this method of constructing a basis from a vec3 is used by glam::Vec3::any_orthonormal_pair
|
||||
// so we reproduce it here to avoid a mismatch if glam changes. we also switch the handedness
|
||||
// the construction of the orthonormal basis up and right vectors needs to precisely mirror the code
|
||||
// in bevy_light/spot_light.rs:spot_light_world_from_view
|
||||
fn spot_light_world_from_view(fwd: vec3<f32>) -> mat3x3<f32> {
|
||||
var sign = -1.0;
|
||||
if (fwd.z >= 0.0) {
|
||||
sign = 1.0;
|
||||
}
|
||||
let a = -1.0 / (fwd.z + sign);
|
||||
let b = fwd.x * fwd.y * a;
|
||||
let up_dir = vec3<f32>(1.0 + sign * fwd.x * fwd.x * a, sign * b, -sign * fwd.x);
|
||||
let right_dir = vec3<f32>(-b, -sign - fwd.y * fwd.y * a, fwd.y);
|
||||
return mat3x3<f32>(right_dir, up_dir, fwd);
|
||||
}
|
||||
|
||||
fn fetch_spot_shadow(
|
||||
light_id: u32,
|
||||
frag_position: vec4<f32>,
|
||||
@ -88,17 +104,7 @@ fn fetch_spot_shadow(
|
||||
+ ((*light).shadow_depth_bias * normalize(surface_to_light))
|
||||
+ (surface_normal.xyz * (*light).shadow_normal_bias) * distance_to_light;
|
||||
|
||||
// the construction of the up and right vectors needs to precisely mirror the code
|
||||
// in render/light.rs:spot_light_view_matrix
|
||||
var sign = -1.0;
|
||||
if (fwd.z >= 0.0) {
|
||||
sign = 1.0;
|
||||
}
|
||||
let a = -1.0 / (fwd.z + sign);
|
||||
let b = fwd.x * fwd.y * a;
|
||||
let up_dir = vec3<f32>(1.0 + sign * fwd.x * fwd.x * a, sign * b, -sign * fwd.x);
|
||||
let right_dir = vec3<f32>(-b, -sign - fwd.y * fwd.y * a, fwd.y);
|
||||
let light_inv_rot = mat3x3<f32>(right_dir, up_dir, fwd);
|
||||
let light_inv_rot = spot_light_world_from_view(fwd);
|
||||
|
||||
// because the matrix is a pure rotation matrix, the inverse is just the transpose, and to calculate
|
||||
// the product of the transpose with a vector we can just post-multiply instead of pre-multiplying.
|
||||
|
@ -309,7 +309,6 @@ pub fn extract_skins(
|
||||
skinned_mesh_inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
|
||||
changed_transforms: Extract<Query<(Entity, &GlobalTransform), Changed<GlobalTransform>>>,
|
||||
joints: Extract<Query<&GlobalTransform>>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_skinned_meshes_query: Extract<RemovedComponents<SkinnedMesh>>,
|
||||
) {
|
||||
let skin_uniforms = skin_uniforms.into_inner();
|
||||
@ -335,10 +334,7 @@ pub fn extract_skins(
|
||||
);
|
||||
|
||||
// Delete skins that became invisible.
|
||||
for skinned_mesh_entity in removed_visibilities_query
|
||||
.read()
|
||||
.chain(removed_skinned_meshes_query.read())
|
||||
{
|
||||
for skinned_mesh_entity in removed_skinned_meshes_query.read() {
|
||||
// Only remove a skin if we didn't pick it up in `add_or_delete_skins`.
|
||||
// It's possible that a necessary component was removed and re-added in
|
||||
// the same frame.
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
//! Provides raytraced lighting.
|
||||
//!
|
||||
//! See [`SolariPlugin`] for more info.
|
||||
//! See [`SolariPlugins`] for more info.
|
||||
//!
|
||||
//! 
|
||||
pub mod pathtracer;
|
||||
@ -13,33 +13,35 @@ pub mod scene;
|
||||
///
|
||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||
pub mod prelude {
|
||||
pub use super::SolariPlugin;
|
||||
pub use super::SolariPlugins;
|
||||
pub use crate::realtime::SolariLighting;
|
||||
pub use crate::scene::RaytracingMesh3d;
|
||||
}
|
||||
|
||||
use crate::realtime::SolariLightingPlugin;
|
||||
use crate::scene::RaytracingScenePlugin;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_app::{PluginGroup, PluginGroupBuilder};
|
||||
use bevy_render::settings::WgpuFeatures;
|
||||
|
||||
/// An experimental plugin for raytraced lighting.
|
||||
/// An experimental set of plugins for raytraced lighting.
|
||||
///
|
||||
/// This plugin provides:
|
||||
/// This plugin group provides:
|
||||
/// * [`SolariLightingPlugin`] - Raytraced direct and indirect lighting (indirect lighting not yet implemented).
|
||||
/// * [`RaytracingScenePlugin`] - BLAS building, resource and lighting binding.
|
||||
/// * [`pathtracer::PathtracingPlugin`] - A non-realtime pathtracer for validation purposes.
|
||||
/// * [`pathtracer::PathtracingPlugin`] - A non-realtime pathtracer for validation purposes (not added by default).
|
||||
///
|
||||
/// To get started, add `RaytracingMesh3d` and `MeshMaterial3d::<StandardMaterial>` to your entities.
|
||||
pub struct SolariPlugin;
|
||||
pub struct SolariPlugins;
|
||||
|
||||
impl Plugin for SolariPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((RaytracingScenePlugin, SolariLightingPlugin));
|
||||
impl PluginGroup for SolariPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(RaytracingScenePlugin)
|
||||
.add(SolariLightingPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
impl SolariPlugin {
|
||||
impl SolariPlugins {
|
||||
/// [`WgpuFeatures`] required for this plugin to function.
|
||||
pub fn required_wgpu_features() -> WgpuFeatures {
|
||||
WgpuFeatures::EXPERIMENTAL_RAY_TRACING_ACCELERATION_STRUCTURE
|
||||
|
@ -2,7 +2,7 @@ mod extract;
|
||||
mod node;
|
||||
mod prepare;
|
||||
|
||||
use crate::SolariPlugin;
|
||||
use crate::SolariPlugins;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::embedded_asset;
|
||||
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
|
||||
@ -37,10 +37,10 @@ impl Plugin for PathtracingPlugin {
|
||||
|
||||
let render_device = render_app.world().resource::<RenderDevice>();
|
||||
let features = render_device.features();
|
||||
if !features.contains(SolariPlugin::required_wgpu_features()) {
|
||||
if !features.contains(SolariPlugins::required_wgpu_features()) {
|
||||
warn!(
|
||||
"PathtracingPlugin not loaded. GPU lacks support for required features: {:?}.",
|
||||
SolariPlugin::required_wgpu_features().difference(features)
|
||||
SolariPlugins::required_wgpu_features().difference(features)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ mod extract;
|
||||
mod node;
|
||||
mod prepare;
|
||||
|
||||
use crate::SolariPlugin;
|
||||
use crate::SolariPlugins;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::embedded_asset;
|
||||
use bevy_core_pipeline::{
|
||||
@ -38,10 +38,10 @@ impl Plugin for SolariLightingPlugin {
|
||||
|
||||
let render_device = render_app.world().resource::<RenderDevice>();
|
||||
let features = render_device.features();
|
||||
if !features.contains(SolariPlugin::required_wgpu_features()) {
|
||||
if !features.contains(SolariPlugins::required_wgpu_features()) {
|
||||
warn!(
|
||||
"SolariLightingPlugin not loaded. GPU lacks support for required features: {:?}.",
|
||||
SolariPlugin::required_wgpu_features().difference(features)
|
||||
SolariPlugins::required_wgpu_features().difference(features)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ mod types;
|
||||
pub use binder::RaytracingSceneBindings;
|
||||
pub use types::RaytracingMesh3d;
|
||||
|
||||
use crate::SolariPlugin;
|
||||
use crate::SolariPlugins;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||
use bevy_render::{
|
||||
@ -41,10 +41,10 @@ impl Plugin for RaytracingScenePlugin {
|
||||
let render_app = app.sub_app_mut(RenderApp);
|
||||
let render_device = render_app.world().resource::<RenderDevice>();
|
||||
let features = render_device.features();
|
||||
if !features.contains(SolariPlugin::required_wgpu_features()) {
|
||||
if !features.contains(SolariPlugins::required_wgpu_features()) {
|
||||
warn!(
|
||||
"RaytracingScenePlugin not loaded. GPU lacks support for required features: {:?}.",
|
||||
SolariPlugin::required_wgpu_features().difference(features)
|
||||
SolariPlugins::required_wgpu_features().difference(features)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -331,7 +331,6 @@ pub fn extract_mesh_materials_2d<M: Material2d>(
|
||||
Or<(Changed<ViewVisibility>, Changed<MeshMaterial2d<M>>)>,
|
||||
>,
|
||||
>,
|
||||
mut removed_visibilities_query: Extract<RemovedComponents<ViewVisibility>>,
|
||||
mut removed_materials_query: Extract<RemovedComponents<MeshMaterial2d<M>>>,
|
||||
) {
|
||||
for (entity, view_visibility, material) in &changed_meshes_query {
|
||||
@ -342,10 +341,7 @@ pub fn extract_mesh_materials_2d<M: Material2d>(
|
||||
}
|
||||
}
|
||||
|
||||
for entity in removed_visibilities_query
|
||||
.read()
|
||||
.chain(removed_materials_query.read())
|
||||
{
|
||||
for entity in removed_materials_query.read() {
|
||||
// Only queue a mesh for removal if we didn't pick it up above.
|
||||
// It's possible that a necessary component was removed and re-added in
|
||||
// the same frame.
|
||||
|
@ -13,41 +13,30 @@ fn main() {
|
||||
fn draw_cursor(
|
||||
camera_query: Single<(&Camera, &GlobalTransform)>,
|
||||
ground: Single<&GlobalTransform, With<Ground>>,
|
||||
windows: Query<&Window>,
|
||||
window: Single<&Window>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
let Ok(windows) = windows.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (camera, camera_transform) = *camera_query;
|
||||
|
||||
let Some(cursor_position) = windows.cursor_position() else {
|
||||
return;
|
||||
};
|
||||
if let Some(cursor_position) = window.cursor_position()
|
||||
// Calculate a ray pointing from the camera into the world based on the cursor's position.
|
||||
&& let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position)
|
||||
// Calculate if and at what distance the ray is hitting the ground plane.
|
||||
&& let Some(distance) =
|
||||
ray.intersect_plane(ground.translation(), InfinitePlane3d::new(ground.up()))
|
||||
{
|
||||
let point = ray.get_point(distance);
|
||||
|
||||
// Calculate a ray pointing from the camera into the world based on the cursor's position.
|
||||
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Calculate if and where the ray is hitting the ground plane.
|
||||
let Some(distance) =
|
||||
ray.intersect_plane(ground.translation(), InfinitePlane3d::new(ground.up()))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let point = ray.get_point(distance);
|
||||
|
||||
// Draw a circle just above the ground plane at that position.
|
||||
gizmos.circle(
|
||||
Isometry3d::new(
|
||||
point + ground.up() * 0.01,
|
||||
Quat::from_rotation_arc(Vec3::Z, ground.up().as_vec3()),
|
||||
),
|
||||
0.2,
|
||||
Color::WHITE,
|
||||
);
|
||||
// Draw a circle just above the ground plane at that position.
|
||||
gizmos.circle(
|
||||
Isometry3d::new(
|
||||
point + ground.up() * 0.01,
|
||||
Quat::from_rotation_arc(Vec3::Z, ground.up().as_vec3()),
|
||||
),
|
||||
0.2,
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
@ -10,7 +10,7 @@ use bevy::{
|
||||
scene::SceneInstanceReady,
|
||||
solari::{
|
||||
pathtracer::{Pathtracer, PathtracingPlugin},
|
||||
prelude::{RaytracingMesh3d, SolariLighting, SolariPlugin},
|
||||
prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins},
|
||||
},
|
||||
};
|
||||
use camera_controller::{CameraController, CameraControllerPlugin};
|
||||
@ -28,7 +28,7 @@ fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins((DefaultPlugins, SolariPlugin, CameraControllerPlugin))
|
||||
app.add_plugins((DefaultPlugins, SolariPlugins, CameraControllerPlugin))
|
||||
.insert_resource(args)
|
||||
.add_systems(Startup, setup);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Showcase how to use and configure FPS overlay.
|
||||
|
||||
use bevy::{
|
||||
dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin},
|
||||
dev_tools::fps_overlay::{FpsOverlayConfig, FpsOverlayPlugin, FrameTimeGraphConfig},
|
||||
prelude::*,
|
||||
text::FontSmoothing,
|
||||
};
|
||||
@ -33,6 +33,13 @@ fn main() {
|
||||
// We can also set the refresh interval for the FPS counter
|
||||
refresh_interval: core::time::Duration::from_millis(100),
|
||||
enabled: true,
|
||||
frame_time_graph_config: FrameTimeGraphConfig {
|
||||
enabled: true,
|
||||
// The minimum acceptable fps
|
||||
min_fps: 30.0,
|
||||
// The target fps
|
||||
target_fps: 144.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
))
|
||||
@ -52,7 +59,8 @@ fn setup(mut commands: Commands) {
|
||||
"Press 1 to toggle the overlay color.\n",
|
||||
"Press 2 to decrease the overlay size.\n",
|
||||
"Press 3 to increase the overlay size.\n",
|
||||
"Press 4 to toggle the overlay visibility."
|
||||
"Press 4 to toggle the text visibility.\n",
|
||||
"Press 5 to toggle the frame time graph."
|
||||
)),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
@ -81,4 +89,7 @@ fn customize_config(input: Res<ButtonInput<KeyCode>>, mut overlay: ResMut<FpsOve
|
||||
if input.just_pressed(KeyCode::Digit4) {
|
||||
overlay.enabled = !overlay.enabled;
|
||||
}
|
||||
if input.just_released(KeyCode::Digit5) {
|
||||
overlay.frame_time_graph_config.enabled = !overlay.frame_time_graph_config.enabled;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use bevy::{
|
||||
color::palettes::basic::*,
|
||||
core_widgets::{
|
||||
Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider,
|
||||
CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugin, SliderRange, SliderValue,
|
||||
CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugins, SliderRange, SliderValue,
|
||||
TrackClick,
|
||||
},
|
||||
input_focus::{
|
||||
@ -21,7 +21,7 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
CoreWidgetsPlugin,
|
||||
CoreWidgetsPlugins,
|
||||
InputDispatchPlugin,
|
||||
TabNavigationPlugin,
|
||||
))
|
||||
|
@ -3,7 +3,7 @@
|
||||
use bevy::{
|
||||
color::palettes::basic::*,
|
||||
core_widgets::{
|
||||
Callback, CoreButton, CoreCheckbox, CoreSlider, CoreSliderThumb, CoreWidgetsPlugin,
|
||||
Callback, CoreButton, CoreCheckbox, CoreSlider, CoreSliderThumb, CoreWidgetsPlugins,
|
||||
SliderRange, SliderValue,
|
||||
},
|
||||
ecs::system::SystemId,
|
||||
@ -21,7 +21,7 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
CoreWidgetsPlugin,
|
||||
CoreWidgetsPlugins,
|
||||
InputDispatchPlugin,
|
||||
TabNavigationPlugin,
|
||||
))
|
||||
|
@ -1,7 +1,9 @@
|
||||
//! This example shows off the various Bevy Feathers widgets.
|
||||
|
||||
use bevy::{
|
||||
core_widgets::{Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugin, SliderStep},
|
||||
core_widgets::{
|
||||
Callback, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderPrecision, SliderStep,
|
||||
},
|
||||
feathers::{
|
||||
controls::{
|
||||
button, checkbox, radio, slider, toggle_switch, ButtonProps, ButtonVariant,
|
||||
@ -25,7 +27,7 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
CoreWidgetsPlugin,
|
||||
CoreWidgetsPlugins,
|
||||
InputDispatchPlugin,
|
||||
TabNavigationPlugin,
|
||||
FeathersPlugin,
|
||||
@ -259,7 +261,7 @@ fn demo_root(commands: &mut Commands) -> impl Bundle {
|
||||
value: 20.0,
|
||||
..default()
|
||||
},
|
||||
SliderStep(10.)
|
||||
(SliderStep(10.), SliderPrecision(2)),
|
||||
),
|
||||
]
|
||||
),],
|
||||
|
116
release-content/migration-guides/render_startup.md
Normal file
116
release-content/migration-guides/render_startup.md
Normal file
@ -0,0 +1,116 @@
|
||||
---
|
||||
title: Many render resources now initialized in `RenderStartup`
|
||||
pull_requests: [19841, 19926, 19885, 19886, 19897, 19898, 19901]
|
||||
---
|
||||
|
||||
Many render resources are **no longer present** during `Plugin::finish`. Instead they are
|
||||
initialized during `RenderStartup` (which occurs once the app starts running). If you only access
|
||||
the resource during the `Render` schedule, then there should be no change. However, if you need one
|
||||
of these render resources to initialize your own resource, you will need to convert your resource
|
||||
initialization into a system.
|
||||
|
||||
The following are the (public) resources that are now initialized in `RenderStartup`.
|
||||
|
||||
- `CasPipeline`
|
||||
- `FxaaPipeline`
|
||||
- `SmaaPipelines`
|
||||
- `TaaPipeline`
|
||||
- `BoxShadowPipeline`
|
||||
- `GradientPipeline`
|
||||
- `UiPipeline`
|
||||
- `UiMaterialPipeline<M>`
|
||||
- `UiTextureSlicePipeline`
|
||||
|
||||
The vast majority of cases for initializing render resources look like so (in Bevy 0.16):
|
||||
|
||||
```rust
|
||||
impl Plugin for MyRenderingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Do nothing??
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<MyRenderResource>();
|
||||
render_app.add_systems(Render, my_render_system);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyRenderResource {
|
||||
...
|
||||
}
|
||||
|
||||
impl FromWorld for MyRenderResource {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let render_adapter = world.resource::<RenderAdapter>();
|
||||
let asset_server = world.resource::<AssetServer>();
|
||||
|
||||
MyRenderResource {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The two main things to focus on are:
|
||||
|
||||
1. The resource implements the `FromWorld` trait which collects all its dependent resources (most
|
||||
commonly, `RenderDevice`), and then creates an instance of the resource.
|
||||
2. The plugin adds its systems and resources in `Plugin::finish`.
|
||||
|
||||
First, we need to rewrite our `FromWorld` implementation as a system. This generally means
|
||||
converting calls to `World::resource` into system params, and then using `Commands` to insert the
|
||||
resource. In the above case, that would look like:
|
||||
|
||||
```rust
|
||||
// Just a regular old system!!
|
||||
fn init_my_resource(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_adapter: Res<RenderAdapter>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
commands.insert_resource(MyRenderResource {
|
||||
...
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Each case will be slightly different. Two notes to be wary of:
|
||||
|
||||
1. Functions that accept `&RenderDevice` for example may no longer compile after switching to
|
||||
`Res<RenderDevice>`. This can be resolved by passing `&render_device` instead of
|
||||
`render_device`.
|
||||
2. If you are using `load_embedded_asset(world, "my_asset.png")`, you may need to first add
|
||||
`asset_server` as a system param, then change this to
|
||||
`load_embedded_asset(asset_server.as_ref(), "my_asset.png")`.
|
||||
|
||||
Now that we have our initialization system, we just need to add the system to `RenderStartup`:
|
||||
|
||||
```rust
|
||||
impl Plugin for MyRenderingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app
|
||||
.add_systems(RenderStartup, init_my_resource)
|
||||
.add_systems(Render, my_render_system);
|
||||
}
|
||||
|
||||
// No more finish!!
|
||||
}
|
||||
```
|
||||
|
||||
In addition, if your resource requires one of the affected systems above, you will need to use
|
||||
system ordering to ensure your resource initializes after the other system. For example, if your
|
||||
system uses `Res<UiPipeline>`, you will need to add an ordering like:
|
||||
|
||||
```rust
|
||||
render_app.add_systems(RenderStartup, init_my_resource.after(init_ui_pipeline));
|
||||
```
|
18
release-content/release-notes/frame_time_graph.md
Normal file
18
release-content/release-notes/frame_time_graph.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Frame Time Graph
|
||||
authors: ["@IceSentry", "@Zeophlite"]
|
||||
pull_requests: [12561, 19277]
|
||||
---
|
||||
|
||||
(TODO: Embed frame time graph gif from 12561)
|
||||
|
||||
Frame time is often more important to know than FPS but because of the temporal nature of it, just seeing a number is not enough.
|
||||
Seeing a graph that shows the history makes it easier to reason about performance.
|
||||
|
||||
Enable the `bevy_dev_tools` feature, and add in `FpsOverlayPlugin` to add a bar graph of the frame time history.
|
||||
Each bar is scaled based on the frame time where a bigger frame time will give a taller and wider bar.
|
||||
|
||||
The color also scales with that frame time where red is at or bellow the minimum target fps and green is at or above the target maximum frame rate.
|
||||
Anything between those 2 values will be interpolated between green and red based on the frame time.
|
||||
|
||||
The algorithm is highly inspired by [Adam Sawicki's article on visualizing frame times](https://asawicki.info/news_1758_an_idea_for_visualization_of_frame_times).
|
@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Headless Widgets
|
||||
authors: ["@viridia", "@ickshonpe", "@alice-i-cecile"]
|
||||
pull_requests: [19366, 19584, 19665, 19778, 19803]
|
||||
pull_requests: [19366, 19584, 19665, 19778, 19803, 20032, 20036]
|
||||
---
|
||||
|
||||
Bevy's `Button` and `Interaction` components have been around for a long time. Unfortunately
|
||||
|
103
release-content/release-notes/render_startup.md
Normal file
103
release-content/release-notes/render_startup.md
Normal file
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: `RenderStartup` and making the renderer my ECS-y
|
||||
authors: ["@IceSentry", "@andriyDev"]
|
||||
pull_requests: [19841, 19926, 19885, 19886, 19897, 19898, 19901]
|
||||
---
|
||||
|
||||
Previous rendering code looked quite different from other Bevy code. In general, resources were
|
||||
initialized with the `FromWorld` trait (where most Bevy code only uses the `Default` trait for most
|
||||
resources) and systems/resources were added in `Plugin::finish` (where nearly all Bevy code does not
|
||||
use `Plugin::finish` at all). This difference with Bevy code can make it harder for new developers
|
||||
to learn rendering, and can result in "cargo cult" copying of rendering code (e.g., "is it important
|
||||
to be using `FromWorld` here? Better to be safe and just do what the rendering code is doing!").
|
||||
|
||||
As a step towards making the renderer more accessible (and maintainable), we have introduced the
|
||||
`RenderStartup` schedule and ported many rendering resources to be initialized in `RenderStartup`
|
||||
with systems! This has several benefits:
|
||||
|
||||
1. Creating resources in systems makes it clearer that rendering resources **are just regular
|
||||
resources**. Hopefully, this better communicates that how you initialize these resources is
|
||||
totally up to you!
|
||||
2. We can now use the system ordering API to ensure that resources are initialized in the correct
|
||||
order. For example, we can do `init_material_pipeline.after(init_mesh_pipeline)` if we need the
|
||||
mesh pipeline to initialize the material pipeline.
|
||||
3. These initialization systems clearly describe what resources they require through their argument
|
||||
list. If a system has an argument of `deferred_lighting_layout: Res<DeferredLightingLayout>`, it
|
||||
clearly documents that this system needs to be run **after** we initialize the
|
||||
`DeferredLightingLayout`.
|
||||
|
||||
We want developers to become more familiar and comfortable with Bevy's rendering stack, and hope
|
||||
that bringing the renderer closer to regular ECS code will encourage that. Code that previously looked
|
||||
like this (in Bevy 0.16):
|
||||
|
||||
```rust
|
||||
impl Plugin for MyRenderingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Do nothing??
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<MyRenderResource>();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyRenderResource {
|
||||
...
|
||||
}
|
||||
|
||||
impl FromWorld for MyRenderResource {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
let render_adapter = world.resource::<RenderAdapter>();
|
||||
let asset_server = world.resource::<AssetServer>();
|
||||
|
||||
MyRenderResource {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Can now be written like:
|
||||
|
||||
```rust
|
||||
impl Plugin for MyRenderingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.add_systems(RenderStartup, init_my_resource);
|
||||
}
|
||||
|
||||
// No more finish!!
|
||||
}
|
||||
|
||||
pub struct MyRenderResource {
|
||||
...
|
||||
}
|
||||
|
||||
// Just a regular old system!!
|
||||
fn init_my_resource(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_adapter: Res<RenderAdapter>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
commands.insert_resource(MyRenderResource {
|
||||
...
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
We highly encourage users to port their own rendering resources to this new system approach (and for
|
||||
resources whose initialization depends on a Bevy core resource, it may be required). In fact, we
|
||||
encourage users to abandon `Plugin::finish` entirely and move all their system and resource
|
||||
initializations for rendering into `Plugin::build` instead.
|
||||
|
||||
As stated before, we've ported many resources to be initialized in `RenderStartup`. See the
|
||||
migration guide for a full list of affected resources.
|
Loading…
Reference in New Issue
Block a user