Skip to main content

bevy_extract/
extract_component.rs

1use crate::{
2    sync_component::{SyncComponent, SyncComponentPlugin},
3    sync_world::SubEntity,
4    Extract, ExtractSchedule,
5};
6use bevy_app::{App, AppLabel, Plugin};
7use bevy_camera::visibility::ViewVisibility;
8use bevy_ecs::{
9    bundle::NoBundleEffect,
10    prelude::*,
11    query::{QueryFilter, QueryItem, ReadOnlyQueryData},
12};
13use core::marker::PhantomData;
14
15pub use bevy_extract_macros::ExtractComponent;
16
17/// Describes how a component gets extracted from the main app to a sub app.
18///
19/// Therefore the component is transferred from the "app world" into the "sub
20/// world" in the [`ExtractSchedule`] step. This functionality is enabled by
21/// adding [`ExtractComponentPlugin`] with the component type.
22///
23/// The Out type is defined in [`SyncComponent`].
24///
25/// The marker type `F` is only used as a way to bypass the orphan rules. To
26/// implement the trait for a foreign type you can use a local type as the
27/// marker, e.g. the type of the plugin that calls [`ExtractComponentPlugin`].
28pub trait ExtractComponent<L: AppLabel, F = ()>: SyncComponent<L, F> {
29    /// ECS [`ReadOnlyQueryData`] to fetch the components to extract.
30    type QueryData: ReadOnlyQueryData;
31    /// Filters the entities with additional constraints.
32    type QueryFilter: QueryFilter;
33    /// The output from extraction, i.e. [`ExtractComponent::extract_component`].
34    ///
35    /// The output components won't be removed automatically from the sub world if the implementing component is removed,
36    /// unless you set them in the [`SyncComponent::Target`].
37    type Out: Bundle<Effect: NoBundleEffect>;
38    // TODO: https://github.com/rust-lang/rust/issues/29661
39    // type Out: Bundle<Effect: NoBundleEffect> = Self;
40
41    /// Defines how the component is transferred into the "sub world".
42    ///
43    /// Returning `None` based on the queried item will remove the [`SyncComponent::Target`] from the entity in
44    /// the sub world.
45    fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out>;
46}
47
48/// This plugin extracts the components into the sub world for synced
49/// entities. To do so, it sets up the [`ExtractSchedule`] step for the
50/// specified [`ExtractComponent`].
51///
52/// It also registers [`SyncComponentPlugin`](`crate::sync_component::SyncComponentPlugin`) to ensure the extracted components
53/// are deleted if the main world components are removed.
54///
55/// The marker type `F` is only used as a way to bypass the orphan rules. To
56/// implement the trait for a foreign type you can use a local type as the
57/// marker, e.g. the type of the plugin that calls [`ExtractComponentPlugin`].
58pub struct ExtractComponentPlugin<C, L: AppLabel, F = ()> {
59    only_extract_visible: bool,
60    marker: PhantomData<fn() -> (C, L, F)>,
61}
62
63impl<C, L: AppLabel, F> Default for ExtractComponentPlugin<C, L, F> {
64    fn default() -> Self {
65        Self {
66            only_extract_visible: false,
67            marker: PhantomData,
68        }
69    }
70}
71
72impl<C, L: AppLabel, F> ExtractComponentPlugin<C, L, F> {
73    pub fn extract_visible() -> Self {
74        Self {
75            only_extract_visible: true,
76            marker: PhantomData,
77        }
78    }
79}
80
81impl<
82        C: ExtractComponent<L, F>,
83        L: AppLabel + Default + Clone + Copy + Eq,
84        F: 'static + Send + Sync,
85    > Plugin for ExtractComponentPlugin<C, L, F>
86{
87    fn build(&self, app: &mut App) {
88        app.add_plugins(SyncComponentPlugin::<C, L, F>::default());
89
90        if let Some(sub_app) = app.get_sub_app_mut(L::default()) {
91            if self.only_extract_visible {
92                sub_app.add_systems(ExtractSchedule, extract_visible_components::<C, L, F>);
93            } else {
94                sub_app.add_systems(ExtractSchedule, extract_components::<C, L, F>);
95            }
96        }
97    }
98}
99
100/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are synced via [`crate::sync_world::SyncToSubWorld`].
101fn extract_components<C: ExtractComponent<L, F>, L: AppLabel + Clone + Copy + Eq, F>(
102    mut commands: Commands,
103    mut previous_len: Local<usize>,
104    query: Extract<Query<(SubEntity<L>, C::QueryData), C::QueryFilter>>,
105) {
106    let mut values = Vec::with_capacity(*previous_len);
107    for (entity, query_item) in &query {
108        if let Some(component) = C::extract_component(query_item) {
109            values.push((entity, component));
110        } else {
111            commands.entity(entity).remove::<C::Target>();
112        }
113    }
114    *previous_len = values.len();
115    commands.try_insert_batch(values);
116}
117
118/// This system extracts all components of the corresponding [`ExtractComponent`], for entities that are visible and synced via [`crate::sync_world::SyncToSubWorld`].
119fn extract_visible_components<C: ExtractComponent<L, F>, L: AppLabel + Clone + Copy + Eq, F>(
120    mut commands: Commands,
121    mut previous_len: Local<usize>,
122    query: Extract<Query<(SubEntity<L>, &ViewVisibility, C::QueryData), C::QueryFilter>>,
123) {
124    let mut values = Vec::with_capacity(*previous_len);
125    for (entity, view_visibility, query_item) in &query {
126        if view_visibility.get() {
127            if let Some(component) = C::extract_component(query_item) {
128                values.push((entity, component));
129            } else {
130                commands.entity(entity).remove::<C::Target>();
131            }
132        }
133    }
134    *previous_len = values.len();
135    commands.try_insert_batch(values);
136}