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}