Skip to content
ProstStuff edited this page Jul 9, 2026 · 6 revisions

Warning

Some part is still work in progress and only provide a brief and short explanation.

Utilitary config system utilizes Minecraft codec to serialize and deserialize values. In addition, the config saved an additional metadata value.

{
	"config": {}, // The metadata of the config
	"data": {}, // The main content / data of the config
}

Setup

Preparing A Config Class

The class of the config can be either record or regular class depending on preferences.

public record Config() {}
public class Config {
	public Config() {}
}

And that's pretty much it. No need for an interface to implements.

Creating Codec

Use RecordCodecBuilder.create() to create a config codec. Since the config class above is empty and can't serve as a good example, we're going to use Skinmatics 2.0.0 config as an example.

public record SkinmaticsConfig(
	String profile,
	int updateRange,
	boolean forceStrongerEmissiveGlow,
	GUI gui,
	PaperDoll paperDoll,
	LinkedHashMap<UUID, String> autoAssigns
) {
}
public static final Codec<SkinmaticsConfig> CODEC = RecordCodecBuilder.create(  
        inst -> inst.group(    
                Codec.STRING.fieldOf("profile").forGetter(SkinmaticsConfig::profile),  
                Codec.INT.fieldOf("updateRange").forGetter(SkinmaticsConfig::updateRange),  
                Codec.BOOL.fieldOf("forceStrongerEmissiveGlow").forGetter(SkinmaticsConfig::forceStrongerEmissiveGlow),  
                GUI.CODEC.fieldOf("gui").forGetter(SkinmaticsConfig::gui),  
                PaperDoll.CODEC.fieldOf("paperDoll").forGetter(SkinmaticsConfig::paperDoll),  
                Codec.unboundedMap(UUIDUtil.CODEC, Codec.STRING).xmap(LinkedHashMap::new, (map) -> map)  
                        .fieldOf("autoAssigns").forGetter(SkinmaticsConfig::autoAssigns)  
        ).apply(inst, SkinmaticsConfig::new)  
);

Tip

You can directly store it in ConfigCodec using ConfigCodec.of(<Codec>).

Creating Config Type

The last step is creating a config type. Be sure to register the config type before using it.

import dev.proststuff.utilitary.api.config.ConfigCodec;  
import dev.proststuff.utilitary.api.config.ConfigManager;  
import dev.proststuff.utilitary.api.config.ConfigType;
import dev.proststuff.utilitary.api.config.serialization.metadata.SimpleMetadataType;

public static final ConfigType<SkinmaticsConfig, SimpleMetadataType.Metadata, SimpleMetadataType> TYPE = ConfigManager.register(  
        new ConfigType<>(  
                Identifier.fromNamespaceAndPath("skinmatics", "config"),  // Config type id
                1,  // config version
                ConfigCodec.of(CODEC),  
                (_) -> new SkinmaticsConfig(...), // Default value  
                null,  // Migrators
                new SimpleMetadataType() // Metadata type
        )  
);

And all set! Now you only have to load and save the config.

Loading, Saving, and Deleting Config

Note

The file name of the config always use Simple Identifier. of("utilitary", "config") -> <config>/utilitary/config.json of("skinmatics", "config") -> <config>/skinmatics/config.json of("skinmatics", "profiles/steve") -> <config>/skinmatics/profiles/steve.json

Loading Config

Load config using ConfigManager.load(<FileName>, <ConfigType>).

ConfigManager.Result<Config, ConfigMetadata> result = ConfigManager.load(<FileName>, <ConfigType>);

Config config = result.config();
ConfigMetadata metadata = result.metadata();
ProfileManager.Status status = result.status();

Saving Config

Save config using ConfigManager.save(<FileName>, <ConfigType>, <Config>)

ProfileManager.Status status = ConfigManager.save(<FileName>, <ConfigType>, <Config>)

Deleting Config

Delete config using ConfigManager.delete(<FileName>)

boolean deleted = ConfigManager.delete(<FileName>)

Migrating Config Values

Once you made changes to the config, be sure to change the version value of your config and add migrations. For each version or change you made, add one migration to your migrators.

public static final ConfigType<UtilitaryConfig, SimpleMetadataType.Metadata, SimpleMetadataType> TYPE;  
  
static {  
    NavigableMap<Integer, ConfigType.Migration> migrations = new TreeMap<>();  
  
    migrations.put(1, (context) -> {  
        JsonObject object = context.data().getAsJsonObject();  
        object.add("debugPrinting", object.remove("debug"));  
        return object;  
    });  
  
    TYPE = new ConfigType<>(  
            Identifier.fromNamespaceAndPath(Utilitary.ID, "config_type"),  
            2,  
            CODEC,  
            (_) -> new UtilitaryConfig(false, false),  
            migrations,  
            new SimpleMetadataType()  
    );  
}

Config Metadata

Config metadata serves as a way to store constant values or be a clear separation between data and metadata values.

SimpleMetadataType

This is the only metadata type Utilitary provided by default. Only serializes the mandatory version number.

{
	"config": 1,
	"data": {...}
}
public class SimpleMetadataType implements ConfigMetadataType<SimpleMetadataType.Metadata> {  
    public static final Codec<Metadata> CODEC = Codec.INT.comapFlatMap(SimpleMetadataType::read, Metadata::version);  
  
    @Override  
    public @NonNull Codec<Metadata> codec() {  
        return CODEC;  
    }  
  
    @Override  
    public <T> SimpleMetadataType.@NonNull Metadata create(Context<T> context) {  
        return new Metadata(context.version());  
    }  
  
    @Override  
    public JsonElement migrate(JsonElement original) {  
        if (original.isJsonObject()) {
            return original.getAsJsonObject().get("version");  
        }
  
        return original;  
    }  
  
    public static DataResult<Metadata> read(Integer version) {  
        return DataResult.success(new Metadata(version));  
    }  
  
    public record Metadata(int version) implements ConfigMetadata {}  
}

Custom Config Metadata

Important

It's recommended to use "version" as the config version. This way migrating metadata won't be much of a headache.

We'll be using Skinmatics ProfileMetadataType as the example of this part.

Record Metadata

Prepare a metadata record class by implementing ConfigMetadata

import dev.proststuff.utilitary.api.config.serialization.metadata.ConfigMetadata;

public record Metadata(int version, String skinmatics, String utilitary) implements ConfigMetadata {}

[!TIPS] Its recommended to store your record inside of the metadata type of that record.

Preparing Codec

public static final Codec<Metadata> CODEC = RecordCodecBuilder.create(  
        inst -> inst.group(  
                Codec.INT.fieldOf("version").forGetter(Metadata::version),  
                Codec.STRING.fieldOf("skinmatics").forGetter(Metadata::skinmatics),  
                Codec.STRING.fieldOf("utilitary").forGetter(Metadata::utilitary)  
            ).apply(inst, Metadata::new)  
);

The Main Class

public class ProfileMetadataType implements ConfigMetadataType<ProfileMetadataType.Metadata> {
    @Override  
    public @NonNull Codec<Metadata> codec() {  
        return CODEC;  
    }  
  
    @Override  
    public @NonNull <T> Metadata create(Context<T> context) {
        return new Metadata(context.version(), SkinmaticsMod.getVersion(), SkinmaticsMod.getVersion(Utilitary.ID));  
    }  
}

Using It

Just plug it in the last argument of new ConfigType<>()

public static final ConfigType<Profile, ProfileMetadataType.Metadata, ProfileMetadataType> TYPE = ConfigManager.register(  
        new ConfigType<>(  
                SkinmaticsMod.of("profile"),  
                1,  
                ConfigCodec.of(CODEC),  
                (id) -> ProfileManager.load(ProfileUtils.fromSimpleIdentifier(id)),  
                null,  
                new ProfileMetadataType()  
        )  
);

End Result

public class ProfileMetadataType implements ConfigMetadataType<ProfileMetadataType.Metadata> {  
    public static final Codec<Metadata> CODEC = RecordCodecBuilder.create(  
            inst -> inst.group(  
                    Codec.INT.fieldOf("version").forGetter(Metadata::version),  
                    Codec.STRING.fieldOf("skinmatics").forGetter(Metadata::skinmatics),  
                    Codec.STRING.fieldOf("utilitary").forGetter(Metadata::utilitary)  
            ).apply(inst, Metadata::new)  
    );  
  
    @Override  
    public @NonNull Codec<Metadata> codec() {  
        return CODEC;  
    }  
  
    @Override  
    public @NonNull <T> Metadata create(Context<T> context) {
        return new Metadata(context.version(), SkinmaticsMod.getVersion(), SkinmaticsMod.getVersion(Utilitary.ID));  
    }  
  
    public record Metadata(int version, String skinmatics, String utilitary) implements ConfigMetadata {}  
}
{
	"config": {
		"version": 1,
		"skinmatics": "2.0.0",
		"utilitary": "1.0.0"  
	},
	"data": {...}
}

Syncing Configs

Current Utilitary version does not have their own config sync system. You have to create your own network payload and stream codec for this.

Clone this wiki locally