1#![allow(clippy::type_complexity)]
2
3use 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
16pub 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 }
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 }
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#[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
216impl 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}