cvars 0.1.0

Configuration variables for games
Documentation

CI Dependency status Discord Total lines Lines of comments

Cvars (console variables or configuration variables) are a way to store settings which the user might want to change at runtime without restarting. They are inspired by the idSoftware family of game engines but they can be useful outside games.

TL;DR: Set and get struct fields based on the field's name as a string.

Cvars aims to minimize boiletplate - there are no traits to implement manually and no setup code to call per cvar. There is also be no extra performance cost for keeping everything configurable even after you're done finding the best values - you can (and are meant to) keep things tweakable for your players to experiment themselves.

Usage

Your game's config is in a struct like this:

use cvars::SetGet;

#[derive(SetGet)]
pub struct Cvars {
    g_rocket_launcher_ammo_max: i32,
    g_rocket_launcher_damage: f32,
}

impl Cvars {
    pub fn new() -> Self {
        Self {
            g_rocket_launcher_ammo_max: 20,
            g_rocket_launcher_damage: 100.0,
        }
    }
}

The player wants to change a cvar and types g_rocket_launcher_damage 150 into the game's console or stdin. You get both the cvar name and new value as strings so you can't do cvars.g_rocket_launcher_damage = 150. You need to look up the correct field based on the string - this is what cvars does - it generates set_str (among other things). You call cvars.set_str("g_rocket_launcher_damage", "150"); which looks up the right field and parses the value into its type. From then on, rockets do 150 damage.

The important thing is that in the rest of your application, you can still access your cvars as regular struct fields - e.g. player.health -= cvars.g_rocket_launcher_damage;. This means you only need to use strings when the user (player or developer when debugging or testing a different balance) is reading or writing the values. The rest of your gamelogic is still statically typed and using a cvar in gamecode is just a field access without any overhead.

See examples/stdin.rs for a small runnable example.

For a real-world example, look at how RecWars uses cvars.

Enums

Cvar values can have any type which implements the FromStr and Display traits. If you want to use enums, it's best to derive these traits automatically via [strum](https://crates.io/crates/strum).

use strum_macros::{Display, EnumString};

use cvars::SetGet;

#[derive(Debug, Clone, SetGet)]
pub struct Cvars {
    pub cl_splitscreen: Splitscreen,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumString)]
#[strum(ascii_case_insensitive)]
pub enum Splitscreen {
    Vertical,
    Horizontal,
}

Tip: use #[strum(ascii_case_insensitive)] so players don't need to pay attention to capilatization when changing cvars - both "Vertical" and "vertical" will parse into Splitscreen::Vertical.

MSRV

The minimum supported Rust version is currently 1.54 because of #![doc = include_str!("README.md")]. It could be lowered to 1.36 or 1.31 if somebody was interested in using this lib but couldn't use latest Rust.

(Planned) Features

  • Derive macro SetGet to create settters and getters for cvars based on their name
    • Statically typed (set, get)
    • As string (set_str, get_string)
  • Function like cvars! macro to declare type and initial value on one line
  • Save config to and load it from files - useful if your game has multiple balance presets
  • Allow setters to validate the new value and reject it (e.g. make sure it's within a sane range).
  • Autocompletion for in-game consoles
  • Console for macroquad
  • Console for rg3d
  • Browser GUI for games without a console

Alternatives

  • tuna
    • Web GUI
    • Unclear if it supports enums
    • Uses hashmaps - overhead on every access
  • cvar
    • Uses a trait instead of a macro. The trait seems to need to be implemented manually so more boilerplate.
    • Has additional features (lists, actions) which cvars doesn't.
  • const-tweaker
    • Web GUI
    • Has soundness issues according to tuna's author
    • Uses hashmaps - overhead on every access
  • inline_tweak
    • Uses hashmaps - overhead on every access

License

AGPL-v3 or newer