1use bevy::app::{App, Plugin};
2use bevy::prelude::{Event, IntoSystemConfigs, EventReader, EventWriter, PreUpdate, ResMut, Resource};
3
4use crate::counter::UndoCounter;
5use crate::request::RequestUndoEvent;
6use crate::reserve::{RequestCommitReservationsEvent, RequestCommitReservationsFromSchedulerEvent, ReserveCounter};
7use crate::undo_event::UndoEvent;
8
9mod counter;
10mod extension;
11mod request;
12mod undo_event;
13mod reserve;
14
15pub mod prelude {
16 pub use crate::extension::AppUndoEx;
17 pub use crate::request::{UndoRequester};
18 pub use crate::undo_event::{UndoReserveCommitter, UndoScheduler};
19 #[cfg(feature = "callback_event")]
20 pub use crate::undo_event::callback::UndoCallbackEvent;
21 pub use crate::UndoPlugin;
22}
23
24
25#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash)]
27pub struct UndoPlugin;
28
29
30impl Plugin for UndoPlugin {
31 fn build(&self, app: &mut App) {
32 app
33 .add_event::<RequestUndoEvent>()
34 .add_event::<CommitReservationsEvent>()
35 .add_event::<DecrementCounterEvent>()
36 .add_event::<RequestCommitReservationsFromSchedulerEvent>()
37 .add_event::<RequestCommitReservationsEvent>()
38 .init_resource::<UndoCounter>()
39 .add_systems(PreUpdate, (
40 decrement_counter,
41 reserve_reset_system
42 ).chain());
43
44 #[cfg(feature = "callback_event")]
45 app.add_plugins(crate::undo_event::callback::UndoCallbackEventPlugin);
46 }
47}
48
49
50#[derive(Resource)]
51struct UndoRegisteredArea<T: Event + Clone>(Vec<UndoEvent<T>>);
52
53
54impl<T: Event + Clone> Default for UndoRegisteredArea<T> {
55 #[inline(always)]
56 fn default() -> Self {
57 Self(vec![])
58 }
59}
60
61
62impl<E: Event + Clone> UndoRegisteredArea<E> {
63 #[inline(always)]
64 pub fn push(&mut self, e: UndoEvent<E>) {
65 self.0.push(e);
66 }
67
68
69 #[inline(always)]
70 pub fn pop(&mut self) -> Option<E> {
71 self.0.pop().map(|e| e.inner)
72 }
73
74
75 #[inline(always)]
76 pub fn pop_if_has_latest(&mut self, counter: &UndoCounter) -> Option<E> {
77 let index = self.0.iter().position(|undo| **counter <= undo.no)?;
78 Some(self.0.remove(index).inner)
79 }
80}
81
82
83#[derive(Event)]
84pub(crate) struct DecrementCounterEvent;
85
86
87fn decrement_counter(
88 mut er: EventReader<DecrementCounterEvent>,
89 mut counter: ResMut<UndoCounter>,
90) {
91 for _ in er.iter() {
92 counter.decrement();
93 }
94}
95
96
97#[derive(Event)]
98pub(crate) struct CommitReservationsEvent(pub UndoCounter);
99
100fn reserve_reset_system(
101 mut er: EventReader<RequestCommitReservationsEvent>,
102 mut er2: EventReader<RequestCommitReservationsFromSchedulerEvent>,
103 mut ew: EventWriter<CommitReservationsEvent>,
104 mut counter: ResMut<UndoCounter>,
105 mut reserve_counter: ResMut<ReserveCounter>,
106) {
107 if er.iter().next().is_some() || er2.iter().next().is_some() {
108 ew.send(CommitReservationsEvent(*counter));
109 *counter += *reserve_counter;
110 reserve_counter.reset();
111 }
112}
113
114
115#[cfg(test)]
116mod tests {
117 use bevy::app::{App, Startup, Update};
118 use bevy::input::Input;
119 use bevy::prelude::{Commands, Component, Event, EventReader, KeyCode, Res};
120 use crate::counter::UndoCounter;
121 use crate::extension::AppUndoEx;
122 use crate::prelude::UndoRequester;
123 use crate::reserve::{ReserveCounter, UndoReservedArea, UndoReserveEvent};
124 use crate::undo_event::UndoScheduler;
125 use crate::{UndoPlugin, UndoRegisteredArea};
126
127 #[derive(Event, Clone, Default)]
128 struct UndoEvent;
129
130 #[derive(Component)]
131 struct OnUndo;
132
133
134 #[test]
135 fn once_register() {
136 let mut app = new_app();
137 app.add_systems(Startup, |mut s: UndoScheduler<UndoEvent>| {
138 s.register_default();
139 });
140 app.update();
141
142 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::R);
143 app.update();
144 app.update();
145
146 assert_eq!(app.world.query::<&OnUndo>().iter(&app.world).len(), 1);
147 }
148
149
150 #[test]
151 fn register_2times() {
152 let mut app = new_app();
153 app.add_systems(Startup, |mut s: UndoScheduler<UndoEvent>| {
154 s.register_default();
155 s.register_default();
156 });
157
158 app.update();
159
160 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::R);
161 app.update();
162
163 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::R);
164 app.update();
165
166 assert_eq!(app.world.query::<&OnUndo>().iter(&app.world).len(), 1);
167 app.update();
168
169 assert_eq!(app.world.query::<&OnUndo>().iter(&app.world).len(), 2);
170 }
171
172
173 #[test]
174 fn reserve_init_3times() {
175 let mut app = new_app();
176 app.add_systems(Startup, |mut s: UndoScheduler<UndoEvent>| {
177 s.reserve_default();
178 s.reserve_default();
179 s.reserve_default();
180 s.register_all_reserved();
181 });
182
183 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::R);
184 app.update();
185 app.update();
186
187 assert_eq!(app.world.query::<&OnUndo>().iter(&app.world).len(), 3);
188 }
189
190
191 #[test]
192 fn reserve_at_intervals() {
193 let mut app = new_app();
194 app.add_systems(Update, |mut s: UndoScheduler<UndoEvent>, key: Res<Input<KeyCode>>| {
195 if key.just_pressed(KeyCode::A) {
196 s.reserve_default();
197 } else if key.just_pressed(KeyCode::B) {
198 s.register_all_reserved();
199 }
200 });
201
202 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::A);
203 app.update();
204 assert_eq!(app.world.resource_mut::<UndoReservedArea<UndoEvent>>().0.len(), 1);
205
206 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::A);
207 app.update();
208 assert_eq!(app.world.resource_mut::<UndoReservedArea<UndoEvent>>().0.len(), 2);
209
210 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::A);
211 app.update();
212 assert_eq!(app.world.resource_mut::<UndoReservedArea<UndoEvent>>().0.len(), 3);
213
214 app.world.resource_mut::<Input<KeyCode>>().reset(KeyCode::A);
215 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::B);
216 app.update();
217 app.world.resource_mut::<Input<KeyCode>>().reset(KeyCode::B);
218 app.update();
219
220 assert_eq!(app.world.resource_mut::<UndoReservedArea<UndoEvent>>().0.len(), 0);
221 assert_eq!(app.world.resource_mut::<UndoRegisteredArea<UndoReserveEvent<UndoEvent>>>().0.len(), 3);
222
223 app.world.resource_mut::<Input<KeyCode>>().reset(KeyCode::B);
224 app.world.resource_mut::<Input<KeyCode>>().press(KeyCode::R);
225 app.update();
226
227 app.update();
228
229 assert_eq!(**app.world.resource_mut::<UndoCounter>(), 0);
230 assert_eq!(**app.world.resource_mut::<ReserveCounter>(), 0);
231 assert_eq!(app.world.resource_mut::<UndoRegisteredArea<UndoEvent>>().0.len(), 0);
232 assert_eq!(app.world.resource_mut::<UndoReservedArea<UndoEvent>>().0.len(), 0);
233 assert_eq!(app.world.query::<&OnUndo>().iter(&app.world).len(), 3);
234 }
235
236
237 fn undo(mut req: UndoRequester, key: Res<Input<KeyCode>>) {
238 if key.just_pressed(KeyCode::R) {
239 req.undo();
240 }
241 }
242
243 fn read_undo(
244 mut commands: Commands,
245 mut er: EventReader<UndoEvent>,
246 ) {
247 for _ in er.iter() {
248 commands.spawn(OnUndo);
249 }
250 }
251
252 fn new_app() -> App {
253 let mut app = App::new();
254 app.add_plugins(UndoPlugin);
255 app.init_resource::<Input<KeyCode>>();
256 app.add_undo_event::<UndoEvent>();
257 app.add_systems(Update, read_undo);
258 app.add_systems(Update, undo);
259
260 app
261 }
262}