Skip to main content

bevy_extract/
extract_instances.rs

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