bevy_render/
extract_instances.rs

1//! Convenience logic for turning components from the main world into extracted
2//! instances in the render world.
3//!
4//! This is essentially the same as the `extract_component` module, but
5//! higher-performance because it avoids the ECS overhead.
6
7use core::marker::PhantomData;
8
9use bevy_app::{App, Plugin};
10use bevy_derive::{Deref, DerefMut};
11use bevy_ecs::{
12    prelude::Entity,
13    query::{QueryFilter, QueryItem, ReadOnlyQueryData},
14    resource::Resource,
15    system::{Query, ResMut},
16};
17
18use crate::sync_world::MainEntityHashMap;
19use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp};
20
21/// Describes how to extract data needed for rendering from a component or
22/// components.
23///
24/// Before rendering, any applicable components will be transferred from the
25/// main world to the render world in the [`ExtractSchedule`] step.
26///
27/// This is essentially the same as
28/// [`ExtractComponent`](crate::extract_component::ExtractComponent), but
29/// higher-performance because it avoids the ECS overhead.
30pub trait ExtractInstance: Send + Sync + Sized + 'static {
31    /// ECS [`ReadOnlyQueryData`] to fetch the components to extract.
32    type QueryData: ReadOnlyQueryData;
33    /// Filters the entities with additional constraints.
34    type QueryFilter: QueryFilter;
35
36    /// Defines how the component is transferred into the "render world".
37    fn extract(item: QueryItem<'_, Self::QueryData>) -> Option<Self>;
38}
39
40/// This plugin extracts one or more components into the "render world" as
41/// extracted instances.
42///
43/// Therefore it sets up the [`ExtractSchedule`] step for the specified
44/// [`ExtractedInstances`].
45#[derive(Default)]
46pub struct ExtractInstancesPlugin<EI>
47where
48    EI: ExtractInstance,
49{
50    only_extract_visible: bool,
51    marker: PhantomData<fn() -> EI>,
52}
53
54/// Stores all extract instances of a type in the render world.
55#[derive(Resource, Deref, DerefMut)]
56pub struct ExtractedInstances<EI>(MainEntityHashMap<EI>)
57where
58    EI: ExtractInstance;
59
60impl<EI> Default for ExtractedInstances<EI>
61where
62    EI: ExtractInstance,
63{
64    fn default() -> Self {
65        Self(Default::default())
66    }
67}
68
69impl<EI> ExtractInstancesPlugin<EI>
70where
71    EI: ExtractInstance,
72{
73    /// Creates a new [`ExtractInstancesPlugin`] that unconditionally extracts to
74    /// the render world, whether the entity is visible or not.
75    pub fn new() -> Self {
76        Self {
77            only_extract_visible: false,
78            marker: PhantomData,
79        }
80    }
81
82    /// Creates a new [`ExtractInstancesPlugin`] that extracts to the render world
83    /// if and only if the entity it's attached to is visible.
84    pub fn extract_visible() -> Self {
85        Self {
86            only_extract_visible: true,
87            marker: PhantomData,
88        }
89    }
90}
91
92impl<EI> Plugin for ExtractInstancesPlugin<EI>
93where
94    EI: ExtractInstance,
95{
96    fn build(&self, app: &mut App) {
97        if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
98            render_app.init_resource::<ExtractedInstances<EI>>();
99            if self.only_extract_visible {
100                render_app.add_systems(ExtractSchedule, extract_visible::<EI>);
101            } else {
102                render_app.add_systems(ExtractSchedule, extract_all::<EI>);
103            }
104        }
105    }
106}
107
108fn extract_all<EI>(
109    mut extracted_instances: ResMut<ExtractedInstances<EI>>,
110    query: Extract<Query<(Entity, EI::QueryData), EI::QueryFilter>>,
111) where
112    EI: ExtractInstance,
113{
114    extracted_instances.clear();
115    for (entity, other) in &query {
116        if let Some(extract_instance) = EI::extract(other) {
117            extracted_instances.insert(entity.into(), extract_instance);
118        }
119    }
120}
121
122fn extract_visible<EI>(
123    mut extracted_instances: ResMut<ExtractedInstances<EI>>,
124    query: Extract<Query<(Entity, &ViewVisibility, EI::QueryData), EI::QueryFilter>>,
125) where
126    EI: ExtractInstance,
127{
128    extracted_instances.clear();
129    for (entity, view_visibility, other) in &query {
130        if view_visibility.get() {
131            if let Some(extract_instance) = EI::extract(other) {
132                extracted_instances.insert(entity.into(), extract_instance);
133            }
134        }
135    }
136}