bevy_undo2/
lib.rs

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/// Add undo-operations to an app.
26#[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}