bevy_convars/
lib.rs

1#![deny(missing_docs)]
2//! Provides an implementation of ConVars (henceforth CVars), a form of global configuration for an application.
3//!
4//! Intended for full applications, not for libraries!
5//! If you're a library author, the easiest and best way to integrate is simply to make your library configurable, and allow the end user to create convars themselves.
6//!
7
8#![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/// Internal re-exports to avoid depending on the user's scope.
47#[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
59/// Core plugin for providing CVars.
60/// # Remarks
61/// Needs to be registered before any of the generated plugins to ensure [CVarManagement] is available.
62pub 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/// App resource that provides management information and functionality for CVars.
187#[derive(Default, Resource)]
188pub struct CVarManagement {
189    /// An index of all cvar resources and their type registrations.
190    pub(crate) resources: HashMap<ComponentId, TypeRegistration>,
191    /// An index of all CVars and their types.
192    pub(crate) tree: CVarTreeNode,
193}
194
195impl CVarManagement {
196    /// Register a CVar of the given type to the internal storage.
197    #[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    /// Gets a CVar's value through reflection.
211    /// # Remarks
212    /// This returns the inner value, not the cvar resource itself.
213    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    /// Gets a CVar's value mutably through reflection.
239    /// # Remarks
240    /// This returns the inner value, not the cvar resource itself.
241    /// A change-detection aware handle is returned.
242    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    /// Set a CVar to the given reflected value using reflection.
268    /// # Remarks
269    /// Use the WorldExtensions version if you can, it handles the invariants. This is harder to call than it looks due to needing mutable world.
270    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    /// Set a CVar to the given reflected value using reflection, without triggering change detection.
295    /// # Remarks
296    /// Use the WorldExtensions version if you can, it handles the invariants. This is harder to call than it looks due to needing mutable world.
297    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    /// Set a CVar to the given deserializable value using reflection.
322    /// # Remarks
323    /// Use the WorldExtensions version if you can, it handles the invariants. This is harder to call than it looks due to needing mutable world.
324    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    /// Set a CVar to the given deserializable value using reflection, without triggering change detection.
365    /// # Remarks
366    /// Use the WorldExtensions version if you can, it handles the invariants. This is harder to call than it looks due to needing mutable world.
367    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    /// Returns an iterator for all CVar type registrations.
408    pub fn iterate_cvar_types(&self) -> impl Iterator<Item = &TypeRegistration> {
409        self.resources.values()
410    }
411}
412
413/// Provides extensions to the world for CVars.
414pub trait WorldExtensions {
415    #[doc(hidden)]
416    fn as_world(&mut self) -> &mut World;
417
418    /// Set a CVar on the world through reflection, by deserializing the provided data into it.
419    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    /// Set a CVar on the world through reflection by deserializing the provided data into it, without triggering change detection.
432    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    /// Set a CVar on the world through reflection
445    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    /// Set a CVar on the world through reflection, without triggering change detection.
454    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    /// Set a CVar on the world using the provided override.
467    /// # Remarks
468    /// CVar overrides, by design, bypass change detection to look like the default value of the CVar.
469    #[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/// Internal function meant for the macros. Don't use this!
503/// Handles reporting CVar changes if LogCVarChanges is set.
504#[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}