bevy_yoleck/
exclusive_systems.rs

1use std::collections::VecDeque;
2
3use bevy::prelude::*;
4
5pub(crate) struct YoleckExclusiveSystemsPlugin;
6
7impl Plugin for YoleckExclusiveSystemsPlugin {
8    fn build(&self, app: &mut App) {
9        app.init_resource::<YoleckExclusiveSystemsQueue>();
10        app.init_resource::<YoleckEntityCreationExclusiveSystems>();
11    }
12}
13
14/// The result of an exclusive system.
15#[derive(Debug)]
16pub enum YoleckExclusiveSystemDirective {
17    /// An exclusive system needs to return this when it is not done yet and wants to still be
18    /// active in the next frame.
19    Listening,
20    /// An exclusive system needs to return this when it is has nothing more to do.
21    ///
22    /// This means that either the exclusive system received the input it was waiting for (e.g. - a
23    /// user click) or that it is not viable for the currently selected entity.
24    Finished,
25}
26
27pub type YoleckExclusiveSystem = Box<dyn System<In = (), Out = YoleckExclusiveSystemDirective>>;
28
29/// The currently pending exclusive systems.
30///
31/// Other edit systems (exclusive or otherwise) may [`push_back`](Self::push_back) exclusive edit
32/// systems into this queue:
33///
34/// ```no_run
35/// # use bevy::prelude::*;
36/// # use bevy_yoleck::prelude::*;
37/// # use bevy_yoleck::exclusive_systems::*;
38/// # use bevy_yoleck::vpeol::prelude::*;
39/// # #[derive(Component)]
40/// # struct LookingAt2D(Vec2);
41/// fn regular_edit_system(
42///     edit: YoleckEdit<(), With<LookingAt2D>>,
43///     mut ui: ResMut<YoleckUi>,
44///     mut exclusive_queue: ResMut<YoleckExclusiveSystemsQueue>,
45/// ) {
46///     if edit.single().is_err() {
47///         return;
48///     }
49///     if ui.button("Look At").clicked() {
50///         exclusive_queue.push_back(exclusive_system);
51///     }
52/// }
53///
54/// fn exclusive_system(
55///     mut edit: YoleckEdit<&mut LookingAt2D>,
56///     // Getting the actual input is still quite manual. May be chanced in the future.
57///     cameras_query: Query<&VpeolCameraState>,
58///     ui: ResMut<YoleckUi>,
59///     buttons: Res<ButtonInput<MouseButton>>,
60/// ) -> YoleckExclusiveSystemDirective {
61///     let Ok(mut looking_at) = edit.single_mut() else {
62///         return YoleckExclusiveSystemDirective::Finished;
63///     };
64///
65///     let Some(cursor_ray) = cameras_query.iter().find_map(|camera_state| camera_state.cursor_ray) else {
66///         return YoleckExclusiveSystemDirective::Listening;
67///     };
68///     looking_at.0 = cursor_ray.origin.truncate();
69///
70///     if ui.ctx().is_pointer_over_area() {
71///         return YoleckExclusiveSystemDirective::Listening;
72///     }
73///
74///     if buttons.just_released(MouseButton::Left) {
75///         return YoleckExclusiveSystemDirective::Finished;
76///     }
77///
78///     return YoleckExclusiveSystemDirective::Listening;
79/// }
80/// ```
81#[derive(Resource, Default)]
82pub struct YoleckExclusiveSystemsQueue(VecDeque<YoleckExclusiveSystem>);
83
84impl YoleckExclusiveSystemsQueue {
85    /// Add an exclusive system to be ran starting from the next frame.
86    ///
87    /// If there are already exclusive systems running or enqueued, the new one will run after they
88    /// finish.
89    pub fn push_back<P>(&mut self, system: impl IntoSystem<(), YoleckExclusiveSystemDirective, P>) {
90        self.0.push_back(Box::new(IntoSystem::into_system(system)));
91    }
92
93    /// Add an exclusive system to be ran starting from the next frame.
94    ///
95    /// If there are already exclusive systems enqueued, the new one will run before them. If there
96    /// is an exclusive system already running, the new one will only run after it finishes.
97    pub fn push_front<P>(
98        &mut self,
99        system: impl IntoSystem<(), YoleckExclusiveSystemDirective, P>,
100    ) {
101        self.0.push_front(Box::new(IntoSystem::into_system(system)));
102    }
103
104    /// Remove all enqueued exclusive systems.
105    ///
106    /// This does not affect an exclusive system that is already running. That system will keep
107    /// running until it returns [`Finished`](YoleckExclusiveSystemDirective::Finished).
108    pub fn clear(&mut self) {
109        self.0.clear();
110    }
111
112    pub(crate) fn pop_front(&mut self) -> Option<YoleckExclusiveSystem> {
113        self.0.pop_front()
114    }
115}
116
117#[derive(Resource)]
118pub(crate) struct YoleckActiveExclusiveSystem(pub YoleckExclusiveSystem);
119
120/// The exclusive systems that will run automatically when a new entity is created.
121///
122/// Note that this may contain exclusive systems that are not relevant for all entities. These
123/// exclusive systems are expected to return [`Finished`](YoleckExclusiveSystemDirective::Finished)
124/// immediately when they do not apply, so that the next ones would run immediately
125#[derive(Default, Resource)]
126pub struct YoleckEntityCreationExclusiveSystems(
127    #[allow(clippy::type_complexity)]
128    Vec<Box<dyn Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue)>>,
129);
130
131impl YoleckEntityCreationExclusiveSystems {
132    /// Add a modification to the exclusive systems queue when new entities are created.
133    pub fn on_entity_creation(
134        &mut self,
135        dlg: impl 'static + Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue),
136    ) {
137        self.0.push(Box::new(dlg));
138    }
139
140    pub(crate) fn create_queue(&self) -> YoleckExclusiveSystemsQueue {
141        let mut queue = YoleckExclusiveSystemsQueue::default();
142        for dlg in self.0.iter() {
143            dlg(&mut queue);
144        }
145        queue
146    }
147}