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