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.
- Int / Long / Short / Byte / Double / Float
- Booleans
- Lists / Sets / Maps
- Enums
- Mathematical Expressions
- Colors
- Identifiers
- TagKey
- Ingredient
- Arbitrary Objects
- Choices of Non-Enum types
- Pairs of Values
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.
Heads up!
If you try to add a setting of an unsupported type, you will need to create custom validation for it, or it will not appear in config GUIs nor will it be serialized in the config files.
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.
Heads up!
The in-game widget for this will still be based on an int, so will be a slider or a number entry box. This example is illustrative only, as in this case a dedicated validation with a character selection box would probably be better.
//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.
Heads up!
A conditional setting can have additional conditions added with withCondition
. All conditions must pass for the setting to be "active"
// 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 listenersValidatedBoolean 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();