# 世界數據存儲? ?
若問題歡迎討論,感謝閱讀!
* * *
官方教程([教程:global\_data \[面料百科\] (fabricmc.net)](https://fabricmc.net/wiki/tutorial:global_data))并沒講具體如何存儲數據數據,不過看了原版后也容易寫出,在此將教你保存世界數據(**注意,這些操作幾乎是在服務端完成的**)。
## PersistentState
游戲每個世界中的數據是通過`PersistentState`保存的,該世界存檔中保存的數據(\*.dat)可以在游戲目錄下`saves\[world]\data`文件夾看到,其中的文件都對應了一個`PersistentState`對象(如計分板的`ScoreboardState`類),這些類會實現“fromTag”和“toTag”方法來完成數據的解析與生成。接下來讓我們編寫自己的`PersistentState`類吧!
樣例:
```
public class ExampleState extends PersistentState {
? ?? ???private static final Logger LOGGER = LogManager.getLogger();
? ?? ???private ExampleObject object;
? ?? ???public ExampleState() {
? ?? ?? ?? ?? ? super("example");
? ?? ???}
? ?? ???public void setObject(ExampleObject object) {
? ?? ?? ?? ?? ? this.object = object;
? ?? ???}
? ?? ???public ExampleObject getObject() {
? ?? ?? ?? ?? ? return object;
? ?? ???}
? ?? ???public void fromTag(CompoundTag tag) {
? ?? ?? ?? ?? ? object.fromTag(tag.getCompound("ExampleData"));
? ?? ???}
? ?? ???public CompoundTag toTag(CompoundTag tag) {
? ?? ?? ?? ?? ? if(this.handler == null) {
? ?? ?? ?? ?? ?? ?? ?? ?LOGGER.warn("無法保存未創建的存儲對象。");
? ?? ?? ?? ?? ? } else {
? ?? ?? ?? ?? ?? ?? ?? ?tag.put("ExampleData", object.toTag());
? ?? ?? ?? ?? ? }
? ?? ?? ?? ?? ? return tag;
? ?? ???}
}
```
* 在對應功能的包下創建一個類并繼承 net.minecraft.world.PersistentState 類,類名為 記錄對象+“State”(如“ScoreboardState”);
* 添加構造器,調用父構造器傳入字符串 key,以小寫駝峰式命名(如“scoreboard”),該參數將用作數據文件的文件名;
* 添加被存儲對象成員,并實現“get”與“set”方法;(其實原版 ScoreboardState 里并無“get”方法,是通過服務器的成員方法獲取計分板的,這里只是為了獲取方便)
* 實現“public void fromTag(CompoundTag tag)”和“public CompoundTag toTag(CompoundTag tag)”方法,這是該類的核心方法,分別對應數據的讀入和寫出,一般會間接調用存儲對象的“fromTag”和“toTag”方法;
* 如果需要,可以創建一個 Logger 記錄錯誤。
完成后,存儲對象還需要注意下,為減少不必要的負擔,防止該對象未更新數據而再次保存,需要實現`void addUpdateListener(Runnable listener)`并加入一個`Synchronizer`監聽數據更改,稍等你將會用到它們,此外實現`void runUpdateListeners()`并在更新數據后調用。
樣例:
```
//存儲對象類
private Runnable[] updateListeners = new Runnable[0];
public void addUpdateListener(Runnable listener) {
? ?? ???this.updateListeners = Arrays.copyOf(this.updateListeners, this.updateListeners.length + );
? ?? ???this.updateListeners[this.updateListeners.length - 1] = listener;
}
protected void runUpdateListeners() {
? ?? ???Runnable[] var1 = this.updateListeners;
? ?? ???for(Runnable runnable : var1) {
? ?? ?? ?? ?? ? runnable.run();
? ?? ???}
}
```
```
public class ExampleSynchronizer implements Runnable {
? ?? ???private final PersistentState compound;
? ?? ???public ExampleSynchronizer(PersistentState compound) {
? ?? ?? ?? ?? ? this.compound = compound;
? ?? ???}
? ?? ???public void run() {
? ?? ?? ?? ?? ? this.compound.markDirty();
? ?? ???}
}
```
* `addUpdateListener()`:向存儲對象添加數據更新監聽器;
* `runUpdateListeners()`:數據更新后調用,將執行已添加的監聽器動作;
* `Synchronizer`:以 存儲對象+“Synchronizer” 命名,用來標記已更改的數據(原理:每個 PersistentState 都有`dirty`成員標記數據是否更改,在保存時會判斷該`dirty`是否為`ture`)。
接下來將教你將該 PersistentState 類加入到服務器中。
## PersistentStateManager
官方教程中講到數據是按維度(代碼中是`World`,使用`DimensionType`辨別維度)保存的,即每個維度都有自己的數據管理器——`PersistentStateManager`,我們可以使用`serverWorld.getPersistentStateManager()`獲得 ,其中使用一個 map 存儲著各個`PersistentState`,會在創建與保存時分別調用`PersistentState`的“fromTag”和“toTag”方法,我們可以在創建維度時使用`persistentStateManager.getOrCreate(Supplier factory, String id)`創建自己的`PersistentState`。接下來我們將使用 Mixin 向`MinecraftServer`中注入我們的代碼。
樣例:
```
@Mixin(MinecraftServer.class)
public abstract class MinecraftServerMixin {
? ?? ???private ExampleObject object;
? ?? ???@Inject(method = "(Ljava/io/File;Ljava/net/Proxy;Lcom/mojang/datafixers/DataFixer;Lnet/minecraft/server/command/CommandManager;Lcom/mojang/authlib/yggdrasil/YggdrasilAuthenticationService;Lcom/mojang/authlib/minecraft/MinecraftSessionService;Lcom/mojang/authlib/GameProfileRepository;Lnet/minecraft/util/UserCache;Lnet/minecraft/server/WorldGenerationProgressListenerFactory;Ljava/lang/String;)V", at = @At(value = "RETURN"))
? ?? ???private void initExampleObject(File gameDir, Proxy proxy, DataFixer dataFixer, CommandManager commandManager, YggdrasilAuthenticationService authService, MinecraftSessionService sessionService, GameProfileRepository gameProfileRepository, UserCache userCache, WorldGenerationProgressListenerFactory worldGenerationProgressListenerFactory, String levelName, CallbackInfo ci) {
? ?? ?? ?? ?? ? this.object = new ExampleObject();
? ?? ???}
? ?? ???private void initExampleObject(PersistentStateManager persistentStateManager) {
? ?? ?? ?? ?? ? ExampleState exampleState = persistentStateManager.getOrCreate(ExampleState::new, "example");
? ?? ?? ?? ?? ? exampleState.setObject(object);
? ?? ?? ?? ?? ? object.addUpdateListener(new ExampleSynchronizer(exampleState));
? ?? ???}
? ?? ???@Inject(method = "createWorlds(Lnet/minecraft/world/WorldSaveHandler;Lnet/minecraft/world/level/LevelProperties;Lnet/minecraft/world/level/LevelInfo;Lnet/minecraft/server/WorldGenerationProgressListener;)V", at = @At(value = "TAIL"), locals = LocalCapture.CAPTURE\_FAILHARD)
? ?? ???private void createTemperatureState(WorldSaveHandler worldSaveHandler, LevelProperties properties, LevelInfo levelInfo, WorldGenerationProgressListener worldGenerationProgressListener, CallbackInfo ci, ServerWorld serverWorld, ersistentStateManager persistentStateManager) {
? ?? ?? ?? ?? ? this.initExampleObject(persistentStateManager);
? ?? ???}
}
```
* 在服務器被創建時,初始化存儲對象;
* 在創建世界時,向某維度中的 persistentStateManager 創建你的 PersistentState,設置存儲對象并對存儲對象添加數據更新監聽器(Synchronizer),注意,這里注入時捕獲其中的局部變量 persistentStateManager 是主世界的,若要在其他維度創建,請使用 this.getWorld(dimensionType).getPersistentStateManager() 獲取對應維度的 persistentStateManager。
完成后,我們將學習如何在其它地方獲取存儲對象對其修改。
## 獲取存儲對象
向 persistentStateManager 添加 PersistentState 后,我們應如何獲取我們的存儲對象?前面說到為了方便獲取存儲對象,在我們的 PersistentState 類中加入的“get”方法來獲取存儲對象,而已創建的 PersistentState 對象可以通過 persistentStateManager 的 get(Supplier factory, String id) 方法獲取,因此可以寫出以下代碼:
```
MinecraftClient.getInstance().getServer().getWorld(DimensionType.OVERWORLD).getPersistentStateManager().get(ExampleState::new, "example").getObject()
```
* 通過 MinecraftClient 類的靜態方法 getInstance() 獲取客戶端的游戲實例;
* 使用 getServer() 方法獲取服務端對象;
* 使用 getWorld(DimensionType) 獲取對應維度,若已有 ServerWorld 對象此前步驟可以跳過;
* 使用 getPersistentStateManager() 獲取該維度的 persistentStateManager 并獲取我們創建的 PersistentState 對象;
* 通過 PersistentState 中的“get”方法獲取被存儲對象,即可對之修改。
* * *
完成后,趕快進入游戲測試吧!若成功你將在游戲目錄(或開發環境的“run”目錄)下`saves\[world]\data`文件夾看到你的數據文件,如果在 IDEA 安裝的 MinecraftDev 插件,你可以方便地查看或編寫這些數據文件。編寫不易,你的支持是我最大的動力!