Fzzy Config

Validation

One of Fzzy Configs most powerful features is the validation system that is applied to every setting either implicitly or explicitly by the creator.

Every setting is tightly controlled and fail-soft; no need to worry about catastrophic failure of a mod system if the config changes and the old file isn't valid. No need to worry if a user modifies the .toml directly.

Every read, write, and update is checked for errors and problems are either automatically corrected or a fallback is used.

Validation Options

Below is a (potentially non-exhaustive) list of types that Fzzy Config has validation tools for.

Validation Manipulation

ValidatedFields can be manipulated in a variety of ways to enhance their effectiveness in more niche situations

Validation Concepts

What is it?

"Validation" is a bit of a misnomer, as the toolset does much more than that. It's a label of convenience stemming from the base class ValidatedField. Validation tools handle settings in various ways. To borrow from the KDoc for Entry:

  • serialize contents
  • deserialize input
  • validate updates
  • correct errors
  • provide widgets
  • apply inputs
  • supply outputs
  • create instances
  • manages flags
  • accepts listeners

Providing Validation

Every setting that appears in a Config GUI is backed by validation. Even when you don't explicitly provide it, Fzzy Config will wrap supported types with basic validation in the background.

For advanced control of your settings, in the vein of Minecraft GameOption, define the validation for your setting using one of the options above, or make a custom implementation if you are feeling adventurous. This grants you the ability to:

  • Provide input restrictions
  • Suggest inputs to users
  • In some places define the widget used in-game
  • Attach listeners, conditions, and feature flags to the setting

Mapping

As of Fzzy Config 0.5.0, Validation can be mapped to another convertible type, much like Mojang's Codecs. The validation will be stored as if it were the underlying mapped-from type, enforce limitations using the underlying type, the in-game widgets will be based on the underlying validation, and so on, but in-code you can interact directly with the mapped-to type.

Example: Character

Fzzy Config doesn't have built in validation for Characters. With mapping, we can easily build our own.

//Starting with a ValidatedInt, which characters map easily to, we define validation that bounds the int to the valid character range
//Then using map, we map the int to and from a Char, just like Codec mapping.
//This provides a ValidatedField<Char>, so calling get() will provide a character!
ValidatedField<Character> validatedCharacter = new ValidatedInt(0, Character.MAX_VALUE, Character.MIN_VALUE).map(
i -> (char)i,
c -> Character.getNumericValue(c)
);

Conditional Settings

Any validation can be wrapped with conditions that define whether the setting is "active" or not. This allows you to create settings that are based on:

  • Whether a certain mod is loaded
  • The status of another config setting; ValidatedBoolean can be used directly as a condition input
  • Whether the game is multiplayer or not
  • Etc. Etc.
// conditions should supply live values. Validated fields are a convenient mechanism to do that. A plain boolean won't update in-GUI until changes are applied.
ValidatedBoolean validatedBooleanGate = new ValidatedBoolean();
//create a conditional validation with toCondition. Note that the type is no longer ValidatedInt directly.
ValidatedCondition<Int> validatedConditionInt = (new ValidatedInt(5, 100, 0)).toCondition(validatedBooleanGate, Text.literal("Gate must be true"), () -> 0);

Listeners

You can listen to changes made to any ValidatedField by supplying a consumer of that field.

Listening works on both server and client and will trigger on config load too, as applicable. The listeners apply any time the stored value is set by anything (with a couple exceptions, see example below)

//boolean with an attached listener that setups some system when the setting is flipped to true.
ValidatedBoolean listenerBoolean = (new ValidatedBoolean(false)).withListener(lb -> {
if (lb.get())
setupSomething
});
//the below example has two booleans that toggle each other on/off, providing a sort of "cross-linked" setting where only one can be active at a time.
//it's important to note the usage of validateAndSetFlagged here, which lets the partner setting be updated "quietly", otherwise you will deadlock and stack overflow
//QUIET = no listeners applied
//UPDATE = applies updates to the config manager system, syncing/saving/etc any changes made by the listeners
ValidatedBoolean aBool = (new ValidatedBoolean(false)).withListener(a -> {
if (a.get())
bBool.validateAndSetFlagged(false, EntryFlag.Flag.QUIET, EntryFlag.Flag.UPDATE);
});
ValidatedBoolean bBool = (new ValidatedBoolean(false)).withListener(b -> {
if (b.get())
aBool.validateAndSetFlagged(false, EntryFlag.Flag.QUIET, EntryFlag.Flag.UPDATE);
});

Codecs

Any ValidatedField instance can be used to generate a Codec of the type the validation represents. Importantly, that resulting Codec will be backed by all the validation present in the field.

//this codec will parse integers, clamping the allowed value to 0 to 100.
Codec<Int> intCodec = (new ValidatedInt(5, 100, 0)).codec();