space_persistence/
lib.rs

1#![allow(clippy::type_complexity)]
2
3// This part of code is used for saving and loading settings and window state
4use bevy::{
5    prelude::*,
6    reflect::{
7        serde::{ReflectSerializer, UntypedReflectDeserializer},
8        GetTypeRegistration,
9    },
10    utils::HashMap,
11    window::WindowCloseRequested,
12};
13use ron::ser::PrettyConfig;
14use serde::de::DeserializeSeed;
15
16/// Plugin that enables persistance for marked entities
17pub struct PersistencePlugin;
18
19#[derive(SystemSet, Hash, PartialEq, Clone, Debug, Eq)]
20pub enum PersistenceSet {
21    EventReader,
22    ResourceProcess,
23    Collect,
24}
25
26impl Plugin for PersistencePlugin {
27    fn build(&self, app: &mut App) {
28        app.init_resource::<PersistenceRegistry>()
29            .init_resource::<PersistenceSettings>();
30
31        app.add_event::<PersistenceEvent>();
32        app.add_event::<PersistenceResourceBroadcastEvent>();
33
34        app.configure_sets(
35            Update,
36            (
37                PersistenceSet::EventReader,
38                PersistenceSet::ResourceProcess,
39                PersistenceSet::Collect,
40            )
41                .chain(),
42        );
43
44        app.add_systems(Startup, persistence_startup_load);
45        app.add_systems(PreUpdate, persistence_save_on_close);
46
47        app.add_systems(
48            Update,
49            persistence_start.in_set(PersistenceSet::EventReader),
50        );
51        app.add_systems(Update, persistence_end.in_set(PersistenceSet::Collect));
52
53        app.persistence_resource::<PersistenceSettings>();
54    }
55}
56
57fn persistence_save_on_close(
58    mut events: EventWriter<PersistenceEvent>,
59    settings: Res<PersistenceSettings>,
60    mut close_events: EventReader<WindowCloseRequested>,
61) {
62    if settings.save_on_close && close_events.read().next().is_some() {
63        events.send(PersistenceEvent::Save);
64    }
65}
66
67fn persistence_startup_load(
68    mut events: EventWriter<PersistenceEvent>,
69    settings: Res<PersistenceSettings>,
70) {
71    if settings.load_on_startup {
72        events.send(PersistenceEvent::Load);
73    }
74}
75
76fn persistence_start(
77    mut events: EventReader<PersistenceEvent>,
78    mut broadcast: EventWriter<PersistenceResourceBroadcastEvent>,
79    mut persistence: ResMut<PersistenceRegistry>,
80) {
81    for event in events.read() {
82        match event {
83            PersistenceEvent::Save => {
84                broadcast.send(PersistenceResourceBroadcastEvent::Pack);
85                persistence.mode = PersistenceMode::Saving;
86                persistence.save_counter = 0;
87            }
88            PersistenceEvent::Load => {
89                match &persistence.source {
90                    PersistenceDataSource::File(path) => {
91                        let Ok(file) = std::fs::File::open(path) else {
92                            warn!("Persistence file not found at path {}", path);
93                            continue;
94                        };
95                        let data: HashMap<String, String> = ron::de::from_reader(file).unwrap();
96                        persistence.data = data;
97                    }
98                    PersistenceDataSource::Memory => {
99                        //do nothing
100                    }
101                }
102
103                broadcast.send(PersistenceResourceBroadcastEvent::Unpack);
104                persistence.mode = PersistenceMode::Loading;
105                persistence.load_counter = 0;
106            }
107        }
108    }
109}
110
111fn persistence_end(mut persistence: ResMut<PersistenceRegistry>) {
112    let mode = persistence.mode.clone();
113    match mode {
114        PersistenceMode::Saving => {
115            persistence.mode = PersistenceMode::None;
116            if persistence.save_counter != persistence.target_count {
117                error!(
118                    "Persistence saving error: {} of {} resources were saved",
119                    persistence.save_counter, persistence.target_count
120                );
121            }
122
123            match &persistence.source {
124                PersistenceDataSource::File(path) => {
125                    let mut file = std::fs::File::create(path).unwrap();
126                    ron::ser::to_writer_pretty(
127                        &mut file,
128                        &persistence.data,
129                        PrettyConfig::default(),
130                    )
131                    .unwrap();
132                }
133                PersistenceDataSource::Memory => {
134                    //do nothing
135                }
136            }
137            {}
138        }
139        PersistenceMode::Loading => {
140            persistence.mode = PersistenceMode::None;
141            if persistence.load_counter != persistence.target_count {
142                error!(
143                    "Persistence loading error: {} of {} resources were loaded",
144                    persistence.load_counter, persistence.target_count
145                );
146            }
147        }
148        _ => {}
149    }
150}
151
152#[derive(Resource, Reflect)]
153#[reflect(Resource)]
154pub struct PersistenceSettings {
155    pub load_on_startup: bool,
156    pub save_on_close: bool,
157}
158
159impl Default for PersistenceSettings {
160    fn default() -> Self {
161        Self {
162            load_on_startup: true,
163            save_on_close: true,
164        }
165    }
166}
167
168#[derive(Default, Clone)]
169enum PersistenceMode {
170    Saving,
171    Loading,
172    #[default]
173    None,
174}
175
176/// ['PersistenceRegistry'] contains lambda functions for loading/unloading editor state
177/// At the moment of closing the window or starting the game mode,
178/// all necessary data is saved to a file/memory, and then restored when the editor mode is opened.
179/// When the restored resource is loaded, the ['PersistenceLoaded<T>'] event is generated
180///
181/// ['PersistenceLoaded<T>']: crate::editor::core::persistence::PersistenceLoaded
182#[derive(Resource, Default)]
183pub struct PersistenceRegistry {
184    source: PersistenceDataSource,
185    data: HashMap<String, String>,
186    load_counter: usize,
187    save_counter: usize,
188    target_count: usize,
189    mode: PersistenceMode,
190}
191
192#[derive(Event, Default)]
193pub struct PersistenceLoaded<T> {
194    _phantom: std::marker::PhantomData<T>,
195}
196
197#[derive(Event)]
198pub enum PersistenceEvent {
199    Save,
200    Load,
201}
202
203#[derive(Event)]
204enum PersistenceResourceBroadcastEvent {
205    Unpack,
206    Pack,
207}
208
209#[derive(Reflect, Clone)]
210#[reflect(Default)]
211pub enum PersistenceDataSource {
212    File(String),
213    Memory,
214}
215
216// Persistence file has moved, FIX PATH
217impl Default for PersistenceDataSource {
218    fn default() -> Self {
219        Self::File("editor.ron".to_string())
220    }
221}
222
223#[derive(Resource)]
224struct PersistenceLoadPipeline<T> {
225    pub load_fn: Box<dyn Fn(&mut T, T) + Send + Sync>,
226}
227
228impl<T> Default for PersistenceLoadPipeline<T> {
229    fn default() -> Self {
230        Self {
231            load_fn: Box::new(|dst, src| {
232                *dst = src;
233            }),
234        }
235    }
236}
237
238pub trait AppPersistenceExt {
239    fn persistence_resource<T: Default + Reflect + FromReflect + Resource + GetTypeRegistration>(
240        &mut self,
241    ) -> &mut Self;
242
243    fn persistence_resource_with_fn<
244        T: Default + Reflect + FromReflect + Resource + GetTypeRegistration,
245    >(
246        &mut self,
247        load_function: Box<dyn Fn(&mut T, T) + Send + Sync>,
248    ) -> &mut Self;
249}
250
251impl AppPersistenceExt for App {
252    fn persistence_resource<T: Default + Reflect + FromReflect + Resource + GetTypeRegistration>(
253        &mut self,
254    ) -> &mut Self {
255        self.world
256            .resource_mut::<PersistenceRegistry>()
257            .target_count += 1;
258
259        self.register_type::<T>();
260        self.add_event::<PersistenceLoaded<T>>();
261
262        self.init_resource::<PersistenceLoadPipeline<T>>();
263
264        self.add_systems(
265            Update,
266            persistence_resource_system::<T>.in_set(PersistenceSet::ResourceProcess),
267        );
268
269        self
270    }
271
272    fn persistence_resource_with_fn<
273        T: Default + Reflect + FromReflect + Resource + GetTypeRegistration,
274    >(
275        &mut self,
276        load_function: Box<dyn Fn(&mut T, T) + Send + Sync>,
277    ) -> &mut Self {
278        self.world
279            .resource_mut::<PersistenceRegistry>()
280            .target_count += 1;
281
282        self.register_type::<T>();
283        self.add_event::<PersistenceLoaded<T>>();
284
285        self.insert_resource(PersistenceLoadPipeline {
286            load_fn: load_function,
287        });
288
289        self.add_systems(
290            Update,
291            persistence_resource_system::<T>.in_set(PersistenceSet::ResourceProcess),
292        );
293
294        self
295    }
296}
297
298fn persistence_resource_system<
299    T: Default + Reflect + FromReflect + Resource + GetTypeRegistration,
300>(
301    mut events: EventReader<PersistenceResourceBroadcastEvent>,
302    mut persistence: ResMut<PersistenceRegistry>,
303    mut resource: ResMut<T>,
304    registry: Res<AppTypeRegistry>,
305    mut persistence_loaded: EventWriter<PersistenceLoaded<T>>,
306    pipeline: ResMut<PersistenceLoadPipeline<T>>,
307) {
308    for event in events.read() {
309        match event {
310            PersistenceResourceBroadcastEvent::Pack => {
311                let type_registry = registry.read();
312                let serializer = ReflectSerializer::new(resource.as_ref(), &type_registry);
313                let data = ron::to_string(&serializer).unwrap();
314                persistence.data.insert(
315                    T::get_type_registration()
316                        .type_info()
317                        .type_path()
318                        .to_string(),
319                    data,
320                );
321                persistence.save_counter += 1;
322            }
323            PersistenceResourceBroadcastEvent::Unpack => {
324                let Some(data) = persistence
325                    .data
326                    .get(T::get_type_registration().type_info().type_path())
327                else {
328                    warn!(
329                        "Persistence resource {} not found",
330                        T::get_type_registration().type_info().type_path()
331                    );
332                    continue;
333                };
334                let type_registry = registry.read();
335                let deserializer = UntypedReflectDeserializer::new(&type_registry);
336                let reflected_value = deserializer
337                    .deserialize(&mut ron::Deserializer::from_str(data).unwrap())
338                    .unwrap();
339
340                let Some(converted) = <T as FromReflect>::from_reflect(&*reflected_value) else {
341                    warn!(
342                        "Persistence resource {} could not be converted",
343                        T::get_type_registration().type_info().type_path()
344                    );
345                    continue;
346                };
347                (pipeline.load_fn)(resource.as_mut(), converted);
348                resource.set_changed();
349
350                persistence_loaded.send(PersistenceLoaded::<T>::default());
351                persistence.load_counter += 1;
352            }
353        }
354    }
355}