1#![deny(missing_docs)]
2#![cfg_attr(all(feature = "config_loader_asset", feature = "config_loader_fs"), doc = include_str!("examples.md"))]
9
10use bevy_app::App;
11use bevy_app::prelude::*;
12use bevy_ecs::component::ComponentId;
13use bevy_ecs::prelude::*;
14use bevy_platform::collections::HashMap;
15use bevy_reflect::{TypeRegistration, prelude::*};
16#[cfg(feature = "config_loader")]
17use builtin::ConfigLoaderCVarsPlugin;
18use builtin::CoreCVarsPlugin;
19use builtin::LogCVarChanges;
20#[cfg(feature = "parse_cvars")]
21use parse::CVarOverride;
22use reflect::CVarMeta;
23use serde::Deserializer;
24#[cfg(feature = "parse_cvars")]
25use serde::de::IntoDeserializer as _;
26
27pub mod defaults;
28mod error;
29mod macros;
30mod types;
31pub use error::*;
32pub use types::*;
33pub mod builtin;
34#[cfg(feature = "config_loader")]
35pub mod loader;
36#[cfg(feature = "parse_cvars")]
37pub mod parse;
38#[cfg(feature = "parse_cvars")]
39pub mod save;
40pub mod prelude;
41pub mod reflect;
42
43#[cfg(test)]
44mod tests;
45
46#[doc(hidden)]
48pub mod reexports {
49 pub use bevy_app;
50 pub use bevy_ecs;
51 pub use bevy_reflect;
52 pub mod jank {
53 pub use crate::reflect::ReflectCVar as ReflectCVar__MACRO_JANK;
54 pub use bevy_ecs::reflect::ReflectResource as ReflectResource__MACRO_JANK;
55 pub use bevy_reflect::prelude::ReflectDefault as ReflectDefault__MACRO_JANK;
56 }
57}
58
59pub struct CVarsPlugin;
63
64#[derive(Debug)]
65pub(crate) enum CVarTreeNode {
66 Leaf {
67 name: &'static str,
68 reg: ComponentId,
69 },
70 Branch {
71 descendants: HashMap<&'static str, CVarTreeNode>,
72 },
73}
74
75impl Default for CVarTreeNode {
76 fn default() -> Self {
77 CVarTreeNode::Branch {
78 descendants: Default::default(),
79 }
80 }
81}
82
83struct CVarTreeEditContext {
84 new_cvar: &'static str,
85}
86
87impl CVarTreeNode {
88 pub fn children(&self) -> Option<impl Iterator<Item = (&'_ &'static str, &'_ CVarTreeNode)>> {
89 match self {
90 CVarTreeNode::Leaf { name: _, reg: _ } => None,
91 CVarTreeNode::Branch { descendants } => Some(descendants.iter()),
92 }
93 }
94
95 pub fn is_leaf(&self) -> bool {
96 matches!(self, CVarTreeNode::Leaf { .. })
97 }
98
99 pub fn insert(&mut self, name: &'static str, id: ComponentId) {
100 let segments: Vec<&'static str> = name.split('.').collect();
101 let edit_ctx = CVarTreeEditContext { new_cvar: name };
102
103 let mut cur = self;
104 for (idx, segment) in segments.iter().enumerate() {
105 if idx == segments.len() - 1 {
106 let _ = cur.insert_leaf(segment, id, &edit_ctx);
107 return;
108 } else {
109 cur = cur.get_or_insert_branch(segment, &edit_ctx);
110 }
111 }
112 }
113
114 #[must_use]
115 fn get_or_insert_branch(
116 &mut self,
117 key: &'static str,
118 ctx: &CVarTreeEditContext,
119 ) -> &mut CVarTreeNode {
120 match self {
121 CVarTreeNode::Leaf { name, reg: _ } => panic!(
122 "Tried to insert branch {name} into a terminating node. A CVar cannot be both a value and table. CVar in question is {}",
123 ctx.new_cvar
124 ),
125 CVarTreeNode::Branch { descendants } => {
126 descendants.entry(key).or_insert(CVarTreeNode::Branch {
127 descendants: Default::default(),
128 })
129 }
130 }
131 }
132
133 #[must_use]
134 fn insert_leaf(
135 &mut self,
136 key: &'static str,
137 reg: ComponentId,
138 ctx: &CVarTreeEditContext,
139 ) -> &mut CVarTreeNode {
140 match self {
141 CVarTreeNode::Leaf { name, reg: _ } => {
142 panic!(
143 "Tried to insert leaf {name} into a terminating node. Is there a duplicate or overlap? CVar in question is {}",
144 ctx.new_cvar
145 )
146 }
147 CVarTreeNode::Branch { descendants } => {
148 assert!(
149 descendants
150 .insert(
151 key,
152 CVarTreeNode::Leaf {
153 name: ctx.new_cvar,
154 reg
155 }
156 )
157 .is_none(),
158 "Attempted to insert a duplicate CVar. CVar in question is {}",
159 ctx.new_cvar
160 );
161
162 descendants.get_mut(key).unwrap()
163 }
164 }
165 }
166
167 #[must_use]
168 pub fn get(&self, name: &str) -> Option<ComponentId> {
169 let mut cur = self;
170 for seg in name.split('.') {
171 let CVarTreeNode::Branch { descendants } = cur else {
172 return None;
173 };
174
175 cur = descendants.get(seg)?;
176 }
177
178 let CVarTreeNode::Leaf { name: _, reg } = cur else {
179 return None;
180 };
181
182 Some(*reg)
183 }
184}
185
186#[derive(Default, Resource)]
188pub struct CVarManagement {
189 pub(crate) resources: HashMap<ComponentId, TypeRegistration>,
191 pub(crate) tree: CVarTreeNode,
193}
194
195impl CVarManagement {
196 #[doc(hidden)]
198 pub fn register_cvar<T: Reflect + Resource + CVarMeta>(&mut self, app: &mut App) {
199 let registration = {
200 let registry = app.world().resource::<AppTypeRegistry>();
201 let registry = registry.read();
202 registry.get(::std::any::TypeId::of::<T>()).unwrap().clone()
203 };
204 let cid = app.world().resource_id::<T>().unwrap();
205
206 self.tree.insert(T::CVAR_PATH, cid);
207 self.resources.insert(cid, registration);
208 }
209
210 pub fn get_cvar_reflect<'a>(
214 &self,
215 world: &'a World,
216 cvar: &str,
217 ) -> Result<&'a dyn Reflect, CVarError> {
218 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
219
220 let ty_info = self.resources.get(&cid).ok_or(CVarError::UnknownCVar)?;
221
222 let reflect_res = ty_info
223 .data::<ReflectResource>()
224 .ok_or(CVarError::BadCVarType)?;
225 let reflect_cvar = ty_info
226 .data::<reflect::ReflectCVar>()
227 .ok_or(CVarError::BadCVarType)?;
228
229 let res = reflect_res.reflect(world)?;
230
231 reflect_cvar
232 .reflect_inner(res.as_partial_reflect())
233 .unwrap()
234 .try_as_reflect()
235 .ok_or(CVarError::BadCVarType)
236 }
237
238 pub fn get_cvar_reflect_mut<'a>(
243 &self,
244 world: &'a mut World,
245 cvar: &str,
246 ) -> Result<Mut<'a, dyn Reflect>, CVarError> {
247 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
248
249 let ty_info = self.resources.get(&cid).ok_or(CVarError::UnknownCVar)?;
250
251 let reflect_res = ty_info
252 .data::<ReflectResource>()
253 .ok_or(CVarError::BadCVarType)?;
254 let reflect_cvar = ty_info
255 .data::<reflect::ReflectCVar>()
256 .ok_or(CVarError::BadCVarType)?;
257
258 Ok(reflect_res.reflect_mut(world)?.map_unchanged(|x| {
259 reflect_cvar
260 .reflect_inner_mut(x.as_partial_reflect_mut())
261 .unwrap()
262 .try_as_reflect_mut()
263 .unwrap()
264 }))
265 }
266
267 pub fn set_cvar_reflect(
271 &self,
272 world: &mut World,
273 cvar: &str,
274 value: &dyn Reflect,
275 ) -> Result<(), CVarError> {
276 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
277
278 let ty_reg = self.resources.get(&cid).ok_or(CVarError::MissingCid)?;
279
280 let reflect_cvar = ty_reg.data::<reflect::ReflectCVar>().unwrap();
281
282 let reflect_res = ty_reg.data::<ReflectResource>().unwrap();
283
284 let cvar = reflect_res.reflect_mut(world)?;
285
286 reflect_cvar.reflect_apply(
287 cvar.into_inner().as_partial_reflect_mut(),
288 value.as_partial_reflect(),
289 )?;
290
291 Ok(())
292 }
293
294 pub fn set_cvar_reflect_no_change(
298 &self,
299 world: &mut World,
300 cvar: &str,
301 value: &dyn Reflect,
302 ) -> Result<(), CVarError> {
303 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
304
305 let ty_reg = self.resources.get(&cid).ok_or(CVarError::MissingCid)?;
306
307 let reflect_cvar = ty_reg.data::<reflect::ReflectCVar>().unwrap();
308
309 let reflect_res = ty_reg.data::<ReflectResource>().unwrap();
310
311 let mut cvar = reflect_res.reflect_mut(world)?;
312
313 reflect_cvar.reflect_apply(
314 cvar.bypass_change_detection().as_partial_reflect_mut(),
315 value.as_partial_reflect(),
316 )?;
317
318 Ok(())
319 }
320
321 pub fn set_cvar_deserialize<'w, 'a>(
325 &self,
326 world: &mut World,
327 cvar: &str,
328 value: impl Deserializer<'a>,
329 ) -> Result<(), CVarError> {
330 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
331
332 let ty_reg = self.resources.get(&cid).ok_or(CVarError::MissingCid)?;
333
334 let reflect_cvar = ty_reg.data::<reflect::ReflectCVar>().unwrap();
335
336 let value_patch = {
337 let field_0 = reflect_cvar.inner_type();
338
339 let registry = world.resource::<AppTypeRegistry>().read();
340
341 let deserializer = registry
342 .get(field_0)
343 .ok_or(CVarError::BadCVarType)?
344 .data::<ReflectDeserialize>()
345 .ok_or(CVarError::CannotDeserialize)?;
346
347 deserializer
348 .deserialize(value)
349 .map_err(|e| CVarError::FailedDeserialize(format!("{e:?}")))?
350 };
351
352 let reflect_res = ty_reg.data::<ReflectResource>().unwrap();
353
354 let cvar = reflect_res.reflect_mut(world)?;
355
356 reflect_cvar.reflect_apply(
357 cvar.into_inner().as_partial_reflect_mut(),
358 value_patch.as_partial_reflect(),
359 )?;
360
361 Ok(())
362 }
363
364 pub fn set_cvar_deserialize_no_change<'w, 'a>(
368 &self,
369 world: &mut World,
370 cvar: &str,
371 value: impl Deserializer<'a>,
372 ) -> Result<(), CVarError> {
373 let cid = self.tree.get(cvar).ok_or(CVarError::UnknownCVar)?;
374
375 let ty_reg = self.resources.get(&cid).ok_or(CVarError::MissingCid)?;
376
377 let reflect_cvar = ty_reg.data::<reflect::ReflectCVar>().unwrap();
378
379 let value_patch = {
380 let field_0 = reflect_cvar.inner_type();
381
382 let registry = world.resource::<AppTypeRegistry>().read();
383
384 let deserializer = registry
385 .get(field_0)
386 .ok_or(CVarError::CannotDeserialize)?
387 .data::<ReflectDeserialize>()
388 .ok_or(CVarError::CannotDeserialize)?;
389
390 deserializer
391 .deserialize(value)
392 .map_err(|e| CVarError::FailedDeserialize(format!("{e:?}")))?
393 };
394
395 let reflect_res = ty_reg.data::<ReflectResource>().unwrap();
396
397 let mut cvar = reflect_res.reflect_mut(world)?;
398
399 reflect_cvar.reflect_apply(
400 cvar.bypass_change_detection().as_partial_reflect_mut(),
401 value_patch.as_partial_reflect(),
402 )?;
403
404 Ok(())
405 }
406
407 pub fn iterate_cvar_types(&self) -> impl Iterator<Item = &TypeRegistration> {
409 self.resources.values()
410 }
411}
412
413pub trait WorldExtensions {
415 #[doc(hidden)]
416 fn as_world(&mut self) -> &mut World;
417
418 fn set_cvar_deserialize<'a>(
420 &mut self,
421 cvar: &str,
422 value: impl serde::Deserializer<'a>,
423 ) -> Result<(), CVarError> {
424 let cell = self.as_world();
425
426 cell.resource_scope::<CVarManagement, _>(|w, management| {
427 management.set_cvar_deserialize(w, cvar, value)
428 })
429 }
430
431 fn set_cvar_deserialize_no_change<'a>(
433 &mut self,
434 cvar: &str,
435 value: impl serde::Deserializer<'a>,
436 ) -> Result<(), CVarError> {
437 let cell = self.as_world();
438
439 cell.resource_scope::<CVarManagement, _>(|w, management| {
440 management.set_cvar_deserialize_no_change(w, cvar, value)
441 })
442 }
443
444 fn set_cvar_reflect(&mut self, cvar: &str, value: &dyn Reflect) -> Result<(), CVarError> {
446 let cell = self.as_world();
447
448 cell.resource_scope::<CVarManagement, _>(|w, management| {
449 management.set_cvar_reflect(w, cvar, value)
450 })
451 }
452
453 fn set_cvar_reflect_no_change(
455 &mut self,
456 cvar: &str,
457 value: &dyn Reflect,
458 ) -> Result<(), CVarError> {
459 let cell = self.as_world();
460
461 cell.resource_scope::<CVarManagement, _>(|w, management| {
462 management.set_cvar_reflect_no_change(w, cvar, value)
463 })
464 }
465
466 #[cfg(feature = "parse_cvars")]
470 fn set_cvar_with_override(&mut self, r#override: &CVarOverride) -> Result<(), CVarError> {
471 let cell = self.as_world();
472
473 cell.resource_scope::<CVarManagement, _>(|w, management| {
474 management.set_cvar_deserialize_no_change(
475 w,
476 &r#override.0,
477 r#override.1.clone().into_deserializer(),
478 )
479 })
480 }
481}
482
483impl WorldExtensions for World {
484 fn as_world(&mut self) -> &mut World {
485 self
486 }
487}
488
489impl Plugin for CVarsPlugin {
490 fn build(&self, app: &mut bevy_app::App) {
491 app.register_type::<CVarFlags>();
492
493 app.insert_resource::<CVarManagement>(CVarManagement::default());
494 app.add_plugins(CoreCVarsPlugin);
495 #[cfg(feature = "config_loader")]
496 {
497 app.add_plugins(ConfigLoaderCVarsPlugin);
498 }
499 }
500}
501
502#[doc(hidden)]
505pub fn cvar_modified_system<T: CVarMeta>(
506 r: bevy_ecs::prelude::Res<T>,
507 log_updates: Res<LogCVarChanges>,
508) {
509 use bevy_ecs::prelude::DetectChanges as _;
510
511 if **log_updates && r.is_changed() {
512 bevy_log::info!("CVar modified: {} = {:?}", T::CVAR_PATH, **r);
513 }
514
515 if !r.is_changed() {
516 return;
517 }
518
519 if !T::flags().contains(CVarFlags::RUNTIME) && !r.is_added() {
520 if T::flags().contains(CVarFlags::SAVED) {
521 bevy_log::warn!("Non-runtime CVar was modified! Change will not apply until restart.");
522 } else {
523 bevy_log::error!("Non-runtime, non-saved CVar was modified! This will have NO EFFECT.");
524 }
525 }
526}