持久化世界数据

持久化世界数据

Saved Data(简称 SD)系统用于保存世界的额外数据。

如果数据只属于某个方块实体、区块或实体,可以考虑改用数据附件。

SavedData

每个 SD 实现都必须继承 SavedData。可以像普通对象一样定义自己的字段和方法,但如果需要将数据或修改保存到磁盘,必须调用 setDirty。它会通知游戏存在需要写入的修改。如果不调用,数据只会在当前维度保持加载期间存在;对于附加到 MinecraftServer 的数据,则只在整个世界保持加载期间存在。

// For some saved data implementation
public class ExampleSavedData extends SavedData {

    public void foo() {
        // Change data in saved data
        // Call set dirty if data changes
        this.setDirty();
    }
}

SavedDataType

SavedData 本身只是对象,因此还需要关联标识符,并负责磁盘读写,这由 SavedDataType 处理。它接受数据标识符、数据不存在时使用的默认构造器,以及编码和解码数据的 codec。标识符会作为世界文件夹或维度文件夹内的路径:

  • 服务端数据:./<world_folder>/data/<identifier_namespace>/<identifier_path>.dat。
  • 单独维度的数据:./<world_folder>/dimensions/<level_namespace>/<level_path>/data/<identifier_namespace>/<identifier_path>.dat。

不存在的目录会自动创建,包括标识符路径中的目录。

提示 另有第四个参数 DataFixTypes。由于 NeoForge 不支持数据修复器,原版的各处调用已通过补丁允许传入 null。

SavedDataType 构造方法有两个版本。一个接受用于构造实例的普通 Supplier 和处理磁盘数据的普通 Codec;如果需要保存当前 ServerLevel 或世界种子,可以使用 NeoForge 添加的重载,两者均接受获得 ServerLevel 的 SavedDataType.Factory。

// For some saved data implementation
public class NoContextExampleSavedData extends SavedData {

    public static final SavedDataType<NoContextExampleSavedData> ID = new SavedDataType<>(
        // The identifier of the saved data
        // Used as the path within the `data` folder
        Identifier.fromNamespaceAndPath("examplemod", "example"),
        // The initial constructor
        NoContextExampleSavedData::new,
        // The codec used to serialize the data
        RecordCodecBuilder.create(instance -> instance.group(
            Codec.INT.fieldOf("val1").forGetter(sd -> sd.val1),
            BuiltInRegistries.BLOCK.byNameCodec().fieldOf("val2").forGetter(sd -> sd.val2)
        ).apply(instance, NoContextExampleSavedData::new))
    );

    // Initial constructor
    public NoContextExampleSavedData() {
        // ...
    }

    // Data constructor
    public NoContextExampleSavedData(int val1, Block val2) {
        // ...
    }

    public void foo() {
        // Change data in saved data
        // Call set dirty if data changes
        this.setDirty();
    }
}

// For some saved data implementation
public class ContextExampleSavedData extends SavedData {

    public static final SavedDataType<ContextExampleSavedData> ID = new SavedDataType<>(
        // The identifier of the saved data
        // Used as the path within the `data` folder
        Identifier.fromNamespaceAndPath("examplemod", "example"),
        // The initial constructor
        ContextExampleSavedData::new,
        // The codec used to serialize the data
        level -> RecordCodecBuilder.create(instance -> instance.group(
            RecordCodecBuilder.point(level),
            Codec.INT.fieldOf("val1").forGetter(sd -> sd.val1),
            BuiltInRegistries.BLOCK.byNameCodec().fieldOf("val2").forGetter(sd -> sd.val2)
        ).apply(instance, ContextExampleSavedData::new))
    );

    // Initial constructor
    public ContextExampleSavedData(ServerLevel level) {
        // ...
    }

    // Data constructor
    public ContextExampleSavedData(ServerLevel level, int val1, Block val2) {
        // ...
    }

    public void foo() {
        // Change data in saved data
        // Call set dirty if data changes
        this.setDirty();
    }
}

附加到世界

SavedData 都是在运行时动态加载或附加到维度或服务器的。因此,如果从未在某个维度或服务器中创建,它就不存在。

SavedData 通过 SavedDataStorage 创建和加载。可调用 ServerChunkCache#getDataStorage 或 ServerLevel#getDataStorage 获取存储,再将 SavedDataType 传给 SavedDataStorage#computeIfAbsent 获取或创建实例。如果已经存在就返回当前实例,否则创建新实例并加载可用数据。

// In some method with access to the SavedDataStorage
netherDataStorage.computeIfAbsent(ContextExampleSavedData.ID);

如果数据并不属于某个特定维度,应通过 MinecraftServer#getDataStorage 附加到 MinecraftServer。


来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。