-
Notifications
You must be signed in to change notification settings - Fork 0
Reading and Writing
MyModConfig shared = holder.data(); // cheap, shared — treat as read-only
MyModConfig mine = holder.copy(); // deep copy you own, safe to mutate freelydata() returns the live published snapshot. It is cheap — no locking, no copying — and is the
right choice for hot paths. The returned object is shared with every concurrent reader, so mutating
it would corrupt the live state. Use copy() when several fields must come from one stable snapshot,
or when you need to edit values locally before applying them.
holder.update(config -> config.showHints = false); // publish in memory only
holder.updateAndSave(config -> config.hudScale = 3); // publish and write to diskThe lambda passed to update receives a private candidate copy, not the live state. The candidate
is validated before publication. If validation fails, the live state is never touched and the
failed update is reported on the returned UpdateResult. A mutator that throws is treated as a
defect and propagates regardless of the failure policy.
UpdateResult result = holder.updateAndSave(config -> config.hudScale = 99);
if (!result.accepted()) {
result.violations().forEach(v -> LOGGER.warn("{}: {}", v.id(), v.message()));
}Every holder also provides asynchronous variants that queue work on the shared config worker and
return CompletableFuture:
ConfigHolder<MyModConfig> holder = /* ... */;
holder.saveAsync();
holder.updateAndSaveAsync(config -> config.hudScale = 3).thenAccept(result -> { /* ... */ });All lifecycle operations for one config are serialized on its worker lane. Synchronous methods wait
for their queued work; asynchronous methods return its CompletableFuture. Starting another config
operation from a mutator or lifecycle hook is rejected as NESTED_CONFIG_OPERATION, so compose
follow-up work after the current call or future completes.