-
Notifications
You must be signed in to change notification settings - Fork 0
Synchronization
Sync sends selected server config values to connected clients.
@Config(name = "rules", sync = true)
public final class RulesConfig {
public int maxTeamSize = 4;
public boolean friendlyFire = false;
}The example syncs every persisted leaf in RulesConfig from server to client.
Use @Config(sync = true) when the whole config is server-owned. For a mixed config, opt in only
the properties clients need:
@Config(name = "rules")
public final class RulesConfig {
@Entry(sync = true)
public int maxTeamSize = 4;
public String localLogPath = "logs/audit";
}Nested objects follow the same rule. Marking a nested object or a containing config syncs its persisted leaves; marking a leaf syncs only that leaf.
holder.metadata().synced() exposes the flattened synced properties.
Sync is one-way and server-authoritative. Creating a holder loads that config and registers its synced values; configs without a holder are never loaded or added to the network registry.
When a client joins, the server sends the cached SHA-256 hash of each synced config it has loaded. The client compares those hashes locally and requests only mismatched config IDs. Requests are stateless, and the server answers with its current cached values. A holder created after login performs the same comparison; a holder created late on the server publishes a refreshed manifest. Accepted server updates and loads are broadcast later, but an unchanged hash produces no packet.
Received values replace the synced portion of holder.data() and are written to the client file on
the ordered config worker rather than the client game thread. The receive completion finishes only
after persistence, state publication, callbacks, and update listeners have completed. Values that
are not synced stay local. Disconnecting never restores an older client value.
| Limit | Maximum |
|---|---|
| Entries per packet | 64 |
Lite Config batches by entry count and leaves encoded packet-size enforcement to Minecraft's network layer. Large manifests and responses are split into ordered chunks. Response chunks are applied in arrival order on the config worker; the final chunk completes restart handling for the response. Packet bytes are immutable after construction.
Built-in leaf types include primitive values, strings, characters, enums, List<T>, Set<T>, and
Map<K, V>. Collection element, key, and value types may themselves be built-in collections or
types with registered stream codecs. Nested config objects are flattened into their synced leaves.
A custom leaf needs a codec registered during common initialization before any holder using that
type is created.
Prefer Minecraft's Codec<T> and StreamCodec<ByteBuf, T> when the value already has them.
Codec<T> handles JSON5, TOML, and copying. Adding StreamCodec<ByteBuf, T> enables sync.
This generic range value is saved as an object and synced with two variable-length integers:
public record IntRange(int minimum, int maximum) {
public static final Codec<IntRange> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.INT.fieldOf("minimum").forGetter(IntRange::minimum),
Codec.INT.fieldOf("maximum").forGetter(IntRange::maximum)
).apply(instance, IntRange::new));
public static final StreamCodec<ByteBuf, IntRange> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.VAR_INT, IntRange::minimum,
ByteBufCodecs.VAR_INT, IntRange::maximum,
IntRange::new
);
}
LiteConfig.codecs()
.registerCodec(IntRange.class, IntRange.CODEC)
.registerStreamCodec(IntRange.class, IntRange.STREAM_CODEC);
@Config(name = "limits", sync = true)
public final class LimitsConfig {
public IntRange playerCount = new IntRange(1, 12);
}The generated JSON tree is equivalent to:
{
"minimum": 1,
"maximum": 12
}File and network codecs are independent. Register only IntRange.CODEC with
registerCodec(IntRange.class, IntRange.CODEC) when Gson cannot persist or copy the value correctly.
Register only IntRange.STREAM_CODEC with
registerStreamCodec(IntRange.class, IntRange.STREAM_CODEC) when Gson already handles the value and
only synchronization needs custom encoding. Both methods return the registry, so they can be chained
when both representations are needed. The stream codec must be context-free; registry-aware stream
codecs cannot be used because synchronized snapshots are encoded once and cached without a player
or registry context.
A registration may also target an exact parameterized type, such as List<IntRange>, to replace
the built-in collection representation. Pass an explicit schema version as the second
registerStreamCodec argument when maintaining a custom wire protocol, and change it whenever
STREAM_CODEC changes incompatibly. The version participates in the handshake schema, including
when the registered type is nested inside a built-in list or map. A synced type without a built-in
or registered stream codec is rejected while the holder is registered.
LiteConfig.codecs().registerStreamCodec(IntRange.class, "2", IntRange.STREAM_CODEC);Each payload includes a schema fingerprint describing its synced paths, order, and wire types. The fingerprint participates in handshake hashes so clients can request values when their wire shape differs. Values rejected by config validation leave the current config unchanged.
Callbacks from holder operations and network persistence share a per-registration FIFO. Accepted transitions invoke callbacks in transition order, and callbacks for one registration never overlap.
When a changed synced entry is restart-only, Lite Config writes the server value to the client file but keeps the startup value in memory. The client is then disconnected with a message asking for a game restart. Reconnecting without restarting repeats the mismatch and disconnect; after restart, the file loads the server value and the hash matches.
Clients that do not support the optional payload are not sent it and can still connect normally.
Server values are ordinary durable state after they arrive. save() and updateAndSave(...) keep
those synced values unless a later server payload replaces them. Non-synced local changes apply and
persist normally. See Lifecycle Listeners for update notifications and Names and Paths for
file locations.