1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use std::collections::VecDeque;

use bevy::prelude::*;

pub(crate) struct YoleckExclusiveSystemsPlugin;

impl Plugin for YoleckExclusiveSystemsPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<YoleckExclusiveSystemsQueue>();
        app.init_resource::<YoleckEntityCreationExclusiveSystems>();
    }
}

/// The result of an exclusive system.
#[derive(Debug)]
pub enum YoleckExclusiveSystemDirective {
    /// An exclusive system needs to return this when it is not done yet and wants to still be
    /// active in the next frame.
    Listening,
    /// An exclusive system needs to return this when it is has nothing more to do.
    ///
    /// This means that either the exclusive system received the input it was waiting for (e.g. - a
    /// user click) or that it is not viable for the currently selected entity.
    Finished,
}

pub type YoleckExclusiveSystem = Box<dyn System<In = (), Out = YoleckExclusiveSystemDirective>>;

/// The currently pending exclusive systems.
///
/// Other edit systems (exclusive or otherwise) may [`push_back`](Self::push_back) exclusive edit
/// systems into this queue:
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_yoleck::prelude::*;
/// # use bevy_yoleck::exclusive_systems::*;
/// # use bevy_yoleck::vpeol::prelude::*;
/// # #[derive(Component)]
/// # struct LookingAt2D(Vec2);
/// fn regular_edit_system(
///     edit: YoleckEdit<(), With<LookingAt2D>>,
///     mut ui: ResMut<YoleckUi>,
///     mut exclusive_queue: ResMut<YoleckExclusiveSystemsQueue>,
/// ) {
///     if edit.get_single().is_err() {
///         return;
///     }
///     if ui.button("Look At").clicked() {
///         exclusive_queue.push_back(exclusive_system);
///     }
/// }
///
/// fn exclusive_system(
///     mut edit: YoleckEdit<&mut LookingAt2D>,
///     // Getting the actual input is still quite manual. May be chanced in the future.
///     cameras_query: Query<&VpeolCameraState>,
///     ui: ResMut<YoleckUi>,
///     buttons: Res<ButtonInput<MouseButton>>,
/// ) -> YoleckExclusiveSystemDirective {
///     let Ok(mut looking_at) = edit.get_single_mut() else {
///         return YoleckExclusiveSystemDirective::Finished;
///     };
///
///     let Some(cursor_ray) = cameras_query.iter().find_map(|camera_state| camera_state.cursor_ray) else {
///         return YoleckExclusiveSystemDirective::Listening;
///     };
///     looking_at.0 = cursor_ray.origin.truncate();
///
///     if ui.ctx().is_pointer_over_area() {
///         return YoleckExclusiveSystemDirective::Listening;
///     }
///
///     if buttons.just_released(MouseButton::Left) {
///         return YoleckExclusiveSystemDirective::Finished;
///     }
///
///     return YoleckExclusiveSystemDirective::Listening;
/// }
/// ```
#[derive(Resource, Default)]
pub struct YoleckExclusiveSystemsQueue(VecDeque<YoleckExclusiveSystem>);

impl YoleckExclusiveSystemsQueue {
    /// Add an exclusive system to be ran starting from the next frame.
    ///
    /// If there are already exclusive systems running or enqueued, the new one will run after they
    /// finish.
    pub fn push_back<P>(&mut self, system: impl IntoSystem<(), YoleckExclusiveSystemDirective, P>) {
        self.0.push_back(Box::new(IntoSystem::into_system(system)));
    }

    /// Add an exclusive system to be ran starting from the next frame.
    ///
    /// If there are already exclusive systems enqueued, the new one will run before them. If there
    /// is an exclusive system already running, the new one will only run after it finishes.
    pub fn push_front<P>(
        &mut self,
        system: impl IntoSystem<(), YoleckExclusiveSystemDirective, P>,
    ) {
        self.0.push_front(Box::new(IntoSystem::into_system(system)));
    }

    /// Remove all enqueued exclusive systems.
    ///
    /// This does not affect an exclusive system that is already running. That system will keep
    /// running until it returns [`Finished`](YoleckExclusiveSystemDirective::Finished).
    pub fn clear(&mut self) {
        self.0.clear();
    }

    pub(crate) fn pop_front(&mut self) -> Option<YoleckExclusiveSystem> {
        self.0.pop_front()
    }
}

#[derive(Resource)]
pub(crate) struct YoleckActiveExclusiveSystem(pub YoleckExclusiveSystem);

/// The exclusive systems that will run automatically when a new entity is created.
///
/// Note that this may contain exclusive systems that are not relevant for all entities. These
/// exclusive systems are expected to return [`Finished`](YoleckExclusiveSystemDirective::Finished)
/// immediately when they do not apply, so that the next ones would run immediately
#[derive(Default, Resource)]
pub struct YoleckEntityCreationExclusiveSystems(
    #[allow(clippy::type_complexity)]
    Vec<Box<dyn Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue)>>,
);

impl YoleckEntityCreationExclusiveSystems {
    /// Add a modification to the exclusive systems queue when new entities are created.
    pub fn on_entity_creation(
        &mut self,
        dlg: impl 'static + Sync + Send + Fn(&mut YoleckExclusiveSystemsQueue),
    ) {
        self.0.push(Box::new(dlg));
    }

    pub(crate) fn create_queue(&self) -> YoleckExclusiveSystemsQueue {
        let mut queue = YoleckExclusiveSystemsQueue::default();
        for dlg in self.0.iter() {
            dlg(&mut queue);
        }
        queue
    }
}