Skip to content

How to persist module state with DataFile

DataFile<T> wraps a YAML file with dirty-tracking and integrates with the auto-save loop (core.auto-save-interval, default 5m).


Steps

1. Create a class extending DataFile<T>

T is the in-memory state type (a record, POJO, or AtomicReference<X>).

package de.kreiscraft.modules.myfeature;
import de.kreiscraft.core.config.DataFile;
import java.nio.file.Path;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
public final class MyFeatureData extends DataFile<MyFeatureState> {
public MyFeatureData(Path dataFolder) {
// dataFolder is ctx.plugin().getDataFolder().toPath()
// Second arg is the path relative to the plugin data folder.
super(dataFolder, "data/my-feature.yml");
}
@Override
protected MyFeatureState empty() {
return new MyFeatureState(/* defaults */);
}
@Override
protected MyFeatureState load(ConfigurationNode node) throws SerializationException {
return node.get(MyFeatureState.class);
}
@Override
protected void save(MyFeatureState state, ConfigurationNode node) throws SerializationException {
node.set(MyFeatureState.class, state);
}
}

The state type must be @ConfigSerializable for the node.get()/node.set() calls above to work with Configurate. Example:

@ConfigSerializable
public record MyFeatureState(String lastValue, int counter) {}

See SpawnData.java for a real example that stores a nullable Location via AtomicReference<Location> and uses explicit node paths instead of a top-level record.

2. Load the file in enable()

@Override
public void enable(ModuleContext ctx) {
MyFeatureData data = new MyFeatureData(ctx.plugin().getDataFolder().toPath());
try {
data.load(); // reads file or calls empty() if the file doesn't exist yet
} catch (IOException e) {
throw new ModuleException("failed to load my-feature data", e);
}
// Register with ConfigService so AutoSaveTask flushes it periodically.
ctx.services().get(ConfigService.class).register(data);
// Pass data to services/listeners that need it.
ctx.services().publish(MyFeatureService.class, new DefaultMyFeatureService(data));
}

3. Read state

MyFeatureState state = data.get();

4. Mutate state

data.update(s -> s.setCounter(s.counter() + 1));

update() calls mutator.accept(state) and marks the file dirty. The next AutoSaveTask tick calls flushIfDirty(), which writes YAML only when dirty.

5. Auto-save

No action required. AutoSaveTask runs on the interval set by core.auto-save-interval and calls flushIfDirty() on every registered DataFile.

6. Immediate flush (optional)

Call data.flushIfDirty() in disable() only if you want to guarantee the file is written before the plugin unloads, rather than waiting for the next auto-save tick. For most modules this is not necessary — AutoSaveTask is stopped before modules are disabled.


Summary

Operation Call
Declare extends DataFile<T>, implement empty(), load(node), save(state, node)
Load on startup data.load() (throws IOException)
Register for auto-save ctx.services().get(ConfigService.class).register(data)
Read data.get()
Write (dirty-marks) data.update(state -> ...)
Force flush data.flushIfDirty()