Config
ll/api/Config.h Β· Common
Overview
The Config module provides template functions for loading and saving mod configuration files as JSON. It leverages the Reflection module for automatic serialization/deserialization and supports versioned configuration with automatic migration.
| Header |
Description |
ll/api/Config.h |
saveConfig, loadConfig, and related utilities |
Concepts
IsConfig
A config type must satisfy:
ll::reflection::Reflectable<T> β The type must be an aggregate (for reflection)
- Has an integral
version field
| C++ |
|---|
| namespace ll::config {
template <class T>
concept IsConfig = ll::reflection::Reflectable<T>
&& std::integral<std::remove_cvref_t<decltype((std::declval<T>().version))>>;
}
|
Key Functions
saveConfig
| C++ |
|---|
| template <IsConfig T, class J = nlohmann::ordered_json>
bool saveConfig(T const& config, std::filesystem::path const& path);
|
Serializes config to JSON and writes it to path. Returns true on success.
loadConfig
| C++ |
|---|
| template <IsConfig T, class J = nlohmann::ordered_json, class F = bool(T&, J&)>
bool loadConfig(T& config, std::filesystem::path const& path, F&& updater = defaultConfigUpdater<T, J>);
|
Loads a config from path into config. If the file does not exist, saves the current config as a default. Returns true if no rewrite was needed (version matched).
Behavior:
- If the file doesn't exist β saves default config, returns
false
- If the file exists but version differs β calls
updater, returns false
- If the file exists and version matches β deserializes directly, returns
true
defaultConfigUpdater
| C++ |
|---|
| template <class T, class J>
bool defaultConfigUpdater(T& config, J& data);
|
The default update strategy: merges the existing file content onto the default config using merge_patch, preserving user modifications while adding new fields.
Usage
Defining a Config Struct
| C++ |
|---|
| #include "ll/api/Config.h"
struct MyConfig {
int version = 1;
std::string greeting = "Hello";
int maxPlayers = 20;
bool enableFeatureX = false;
};
|
Note
The struct must be an aggregate type (no user-defined constructors, no private members, no virtual functions) for reflection to work.
Loading and Saving
| C++ |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include "ll/api/Config.h"
#include "ll/api/mod/Mod.h"
void loadMyConfig(ll::mod::Mod& mod) {
MyConfig config;
auto configPath = mod.getConfigDir() / "config.json";
bool upToDate = ll::config::loadConfig(config, configPath);
if (!upToDate) {
// Config was created or migrated, save the updated version
ll::config::saveConfig(config, configPath);
}
mod.getLogger().info("Greeting: {}", config.greeting);
mod.getLogger().info("Max Players: {}", config.maxPlayers);
}
|
Custom Updater
| C++ |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | #include "ll/api/Config.h"
struct MyConfigV2 {
int version = 2;
std::string greeting = "Hello";
int maxPlayers = 20;
bool enableFeatureX = false;
std::string newField = "default"; // Added in v2
};
bool myUpdater(MyConfigV2& config, nlohmann::ordered_json& data) {
int oldVersion = data.value("version", 1);
if (oldVersion == 1) {
// Migrate from v1: rename "greeting" to keep old value
if (data.contains("greeting")) {
config.greeting = data["greeting"];
}
}
data.erase("version");
auto patch = ll::reflection::serialize<nlohmann::ordered_json>(config);
patch.value().merge_patch(data);
data = *std::move(patch);
return true;
}
void loadConfigV2(ll::mod::Mod& mod) {
MyConfigV2 config;
ll::config::loadConfig(config, mod.getConfigDir() / "config.json", myUpdater);
}
|
Generated JSON Example
For the MyConfig struct above, the generated JSON looks like:
| JSON |
|---|
| {
"version": 1,
"greeting": "Hello",
"maxPlayers": 20,
"enableFeatureX": false
}
|
- Reflection β Provides
serialize/deserialize used by config
- Mod β
Mod::getConfigDir() returns the config directory