-
Notifications
You must be signed in to change notification settings - Fork 0
Rubric Builder API
Not every config fits the annotated-POJO shape. When you need to define entries at runtime — from data files, a plugin registry, a scripted DSL — reach for ConfigSpec instead.
ConfigSpec is Rubric's programmatic model. It produces a Schema backed by a flat Map<String, Object> (dotted paths) rather than a POJO. Everything downstream — validators, migrations, sync, snapshots, the GUI — works identically. Only the definition style differs.
Prefer ConfigSpec when:
- The set of entries is only known at runtime (loaded from a data pack, from an addon registry, etc.).
- You're bridging an existing configuration format that isn't Java-first.
- You want dozens of similar entries generated in a loop.
Prefer annotations when the shape is fixed at compile time — it's terser, IDE-friendlier, and Rubric knows more about your intent (real field types, real defaults from field initializers).
import com.oliveryasuna.mc.rubric.api.Format;
import com.oliveryasuna.mc.rubric.schema.api.ConfigSpec;
import com.oliveryasuna.mc.rubric.schema.MapConfigModel;
import com.oliveryasuna.mc.rubric.schema.Schema;
// Build the spec: id + name + format + version + entries.
final ConfigSpec spec = ConfigSpec.builder("mymod", "config", Format.TOML, 1)
.define("enabled", true, b -> b.comment("Master toggle for the demo feature."))
.define("tickIntervalMillis", 200, b -> b
.comment("Tick interval — server-authoritative.")
.range(50, 5_000)
.syncScope(Sync.Scope.SERVER))
.define("quality", "medium", b -> b
.comment("Quality preset.")
.oneOf("low", "medium", "high"))
.build();
// Wrap into a schema-backed model.
final MapConfigModel model = new MapConfigModel(spec);
// Hand to a ConfigManager just like an annotated model.
final ConfigManager<Map<String, Object>> manager = new ConfigManager<>(
model,
RubricSerialization.defaultIO(),
Loaders.platform(),
new CodecRegistry(),
null);
manager.load();final Map<String, Object> state = manager.get();
final int tickInterval = (Integer)state.get("tickIntervalMillis");Since the state is a Map<String, Object>, you cast. A ConfigValue<V> typed view still works — dotted paths address the same entries.
You can register annotation-defined configs and builder-defined configs in the same runtime. Each is its own ConfigManager, each writes to its own file, and both use the same event bus, sync service, and GUI plumbing.
- The GUI shows entries with their dotted-key labels (e.g.
display.opacity), which read uglier than field-name labels. There's a follow-up to humanize these — see YACL improvements & gaps and Cloth improvements & gaps. - No nested categories from the builder in the current API — everything is flat under
spec.root(). Use dotted keys ("display.opacity") to group visually; the reader interprets them as nested when needed. - Custom validators, migrations, sync scopes, reload tiers, and widget overrides are all available on the builder — the builder chain mirrors the annotation set.
- Concepts for the vocabulary the builder chains produce.
-
Validation for wiring
.range(...),.oneOf(...), and custom validators. - Migration — builder-defined configs migrate just like annotated ones.
Rubric
Reference
Runtime
GUI
Help