-
Notifications
You must be signed in to change notification settings - Fork 0
Holder Implementation
Every holder exposes one ConfigHolder API. Choose a mutable or read-only holder when building it,
then choose synchronous or asynchronous execution for each operation.
| Builder call | Behavior | Best fit |
|---|---|---|
create() |
Allows loads and updates | Regular runtime config |
readOnly().create() |
Refuses loads and updates | Config a mod reads but never changes |
ConfigHolder<MyModConfig> config = LiteConfig.holder(MyModConfig.class)
.modId("mymod")
.create();
config.update(values -> values.hudScale = 3); // runs on the calling thread
config.saveAsync(); // queues work on the config worker
ConfigHolder<MyModConfig> readOnly = LiteConfig.holder(MyModConfig.class)
.modId("mymod")
.readOnly()
.create();Execution: every lifecycle operation is serialized on the config's worker lane. load, save,
update, and updateAndSave wait for completion; their *Async counterparts return the queued
operation's CompletableFuture. Starting another config operation from a mutator or lifecycle hook
is rejected as NESTED_CONFIG_OPERATION; compose follow-up work after the current call or future.
Read-only: update and load are refused through the matching failure policy —
they throw under STRICT. Under FALLBACK, load logs and skips the operation, while updates
return a rejected UpdateResult. save still writes the current state. The config itself is not
frozen: it is shared with every other holder of the same class, so a value another holder changes
is visible through data() here too.
A config is registered once per config directory. Building a second holder for the same class hands back another handle onto that registration, so both agree on the values and both see each other's changes:
ConfigHolder<MyModConfig> hud = LiteConfig.holder(MyModConfig.class).modId("mymod").create();
ConfigHolder<MyModConfig> commands = LiteConfig.holder(MyModConfig.class).modId("mymod").create();
hud.updateAndSave(config -> config.hudScale = 3);
commands.data().hudScale; // 3The first build reads the file and resolves the options; a later build reuses that registration
rather than re-reading or reconfiguring it. Each holder can add its own listeners. Call close()
when a holder is no longer needed; this removes listeners registered through that holder and releases
the shared registration after its last holder closes. data() and metadata() remain readable on a
closed holder, while lifecycle and mutation operations are rejected.
Every holder exposes one ConfigHolder API. Choose a mutable or read-only holder when building it,
then choose synchronous or asynchronous execution for each operation.
| Builder call | Behavior | Best fit |
|---|---|---|
create() |
Allows loads and updates | Regular runtime config |
readOnly().create() |
Refuses loads and updates | Config a mod reads but never changes |
ConfigHolder<MyModConfig> config = LiteConfig.holder(MyModConfig.class)
.modId("mymod")
.create();
config.update(values -> values.hudScale = 3); // runs on the calling thread
config.saveAsync(); // queues work on the config worker
ConfigHolder<MyModConfig> readOnly = LiteConfig.holder(MyModConfig.class)
.modId("mymod")
.readOnly()
.create();Execution: every lifecycle operation is serialized on the config's worker lane. load, save,
update, and updateAndSave wait for completion; their *Async counterparts return the queued
operation's CompletableFuture. Starting another config operation from a mutator or lifecycle hook
is rejected as NESTED_CONFIG_OPERATION; compose follow-up work after the current call or future.
Read-only: update and load are refused through the matching failure policy —
they throw under STRICT. Under FALLBACK, load logs and skips the operation, while updates
return a rejected UpdateResult. save still writes the current state. The config itself is not
frozen: it is shared with every other holder of the same class, so a value another holder changes
is visible through data() here too.