1use bevy_ecs::{
4 change_detection::MaybeLocation,
5 component::Tick,
6 reflect::{AppTypeRegistry, ReflectResource},
7 world::{Ref, World},
8};
9use bevy_reflect::{Reflect, ReflectSerialize};
10use serde::Serialize;
11use toml_edit::{DocumentMut, Item, Table, ser::ValueSerializer};
12
13use crate::{
14 CVarError, CVarFlags, CVarManagement,
15 reflect::{CVarMeta, ReflectCVar},
16};
17
18#[cfg(test)]
19mod tests;
20
21pub struct CVarSaveContext(DocumentMut);
38
39impl CVarSaveContext {
40 pub fn blank() -> Self {
42 Self(DocumentMut::new())
43 }
44
45 pub fn from_document(doc: DocumentMut) -> Self {
47 Self(doc)
48 }
49
50 pub fn return_document(self) -> DocumentMut {
52 self.0
53 }
54
55 fn get_cvar_entry(&mut self, path: &str) -> Result<toml_edit::Entry<'_>, CVarError> {
56 let mut sections = path.split('.');
57 let section_count = sections.clone().count();
58 let leading_sections = sections.clone().take(section_count - 1);
59 let final_section = sections.next_back().unwrap();
60
61 let mut cur_table = self.0.as_table_mut();
62
63 for section in leading_sections {
64 cur_table = cur_table
65 .entry(section)
66 .or_insert(toml_edit::Item::Table(Table::new()))
67 .as_table_mut()
68 .ok_or(CVarError::MalformedConfigDuringWrite("Expected a table."))?;
69 }
70
71 Ok(cur_table.entry(final_section))
72 }
73
74 fn save_cvar_inner(&mut self, path: &str, value: &impl Serialize) -> Result<(), CVarError> {
76 let entry = self.get_cvar_entry(path)?;
77
78 *entry.or_insert(toml_edit::Item::None) =
79 Item::Value(value.serialize(ValueSerializer::new())?);
80
81 Ok(())
82 }
83
84 fn save_cvar_inner_erased(
85 &mut self,
86 path: &str,
87 value: &bevy_reflect::serde::Serializable,
88 ) -> Result<(), CVarError> {
89 let entry = self.get_cvar_entry(path)?;
90
91 *entry.or_insert(toml_edit::Item::None) =
92 Item::Value(value.serialize(ValueSerializer::new())?);
93
94 Ok(())
95 }
96
97 pub fn save_cvar<T: CVarMeta>(&mut self, cvar: &T) -> Result<(), CVarError>
101 where
102 T::Inner: Serialize,
103 {
104 self.save_cvar_inner(T::CVAR_PATH, &**cvar)
105 }
106
107 pub fn save_cvar_from_world<T: CVarMeta>(&mut self, world: &World) -> Result<(), CVarError>
111 where
112 T::Inner: Serialize,
113 {
114 self.save_cvar_inner(T::CVAR_PATH, &**world.resource::<T>())
115 }
116
117 pub fn save_world(&mut self, world: &World) -> Result<(), CVarError> {
121 let management: &CVarManagement = world.resource::<CVarManagement>();
122 let registry = world.resource::<AppTypeRegistry>().read();
123 let types = management.iterate_cvar_types();
124
125 for reg in types {
126 let cvar = reg.data::<ReflectCVar>().expect("Impossible.");
127
128 if !cvar.flags().contains(CVarFlags::SAVED) {
129 continue;
130 }
131
132 let Some(serialize) = registry.get_type_data::<ReflectSerialize>(cvar.inner_type())
133 else {
134 panic!(
135 "Can't save a saveable cvar due to lack of ReflectSerialize implementation. CVar in question is {}",
136 cvar.cvar_path()
137 );
138 };
139
140 let resource = reg.data::<ReflectResource>().expect("Impossible.");
141
142 let cvar_id = management.tree.get(cvar.cvar_path()).unwrap();
143
144 let change_data = world.get_resource_change_ticks_by_id(cvar_id).unwrap();
145
146 let caller = MaybeLocation::caller();
147
148 let res = resource.reflect(world)?;
149 let resource: Ref<dyn Reflect> = {
150 Ref::new(
153 res,
154 &change_data.added,
155 &change_data.changed,
156 Tick::new(0),
157 Tick::new(0),
158 caller.as_ref(),
159 )
160 };
161
162 if cvar.is_default_value(resource) {
163 continue;
164 }
165
166 self.save_cvar_inner_erased(
167 cvar.cvar_path(),
168 &serialize.get_serializable(
169 cvar.reflect_inner(res.as_partial_reflect())?
170 .try_as_reflect()
171 .unwrap(),
172 ),
173 )?;
174 }
175
176 Ok(())
177 }
178}
179
180#[allow(clippy::to_string_trait_impl)]
181impl ToString for CVarSaveContext {
182 fn to_string(&self) -> String {
183 self.0.to_string()
184 }
185}