minecraft-src/net/minecraft/world/level/storage/DimensionDataStorage.java
2025-07-04 03:45:38 +03:00

258 lines
7.9 KiB
Java

package net.minecraft.world.level.storage;
import com.google.common.collect.Iterables;
import com.mojang.datafixers.DataFixer;
import com.mojang.logging.LogUtils;
import com.mojang.serialization.Codec;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.RegistryOps;
import net.minecraft.util.FastBufferedInputStream;
import net.minecraft.util.Mth;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.level.saveddata.SavedDataType;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
public class DimensionDataStorage implements AutoCloseable {
private static final Logger LOGGER = LogUtils.getLogger();
private final SavedData.Context context;
private final Map<SavedDataType<?>, Optional<SavedData>> cache = new HashMap();
private final DataFixer fixerUpper;
private final HolderLookup.Provider registries;
private final Path dataFolder;
private CompletableFuture<?> pendingWriteFuture = CompletableFuture.completedFuture(null);
public DimensionDataStorage(SavedData.Context context, Path dataFolder, DataFixer fixerUpper, HolderLookup.Provider registries) {
this.context = context;
this.fixerUpper = fixerUpper;
this.dataFolder = dataFolder;
this.registries = registries;
}
private Path getDataFile(String filename) {
return this.dataFolder.resolve(filename + ".dat");
}
public <T extends SavedData> T computeIfAbsent(SavedDataType<T> type) {
T savedData = this.get(type);
if (savedData != null) {
return savedData;
} else {
T savedData2 = (T)type.constructor().apply(this.context);
this.set(type, savedData2);
return savedData2;
}
}
@Nullable
public <T extends SavedData> T get(SavedDataType<T> type) {
Optional<SavedData> optional = (Optional<SavedData>)this.cache.get(type);
if (optional == null) {
optional = Optional.ofNullable(this.readSavedData(type));
this.cache.put(type, optional);
}
return (T)optional.orElse(null);
}
@Nullable
private <T extends SavedData> T readSavedData(SavedDataType<T> type) {
try {
Path path = this.getDataFile(type.id());
if (Files.exists(path, new LinkOption[0])) {
CompoundTag compoundTag = this.readTagFromDisk(type.id(), type.dataFixType(), SharedConstants.getCurrentVersion().getDataVersion().getVersion());
RegistryOps<Tag> registryOps = this.registries.createSerializationContext(NbtOps.INSTANCE);
return (T)((Codec)type.codec().apply(this.context))
.parse(registryOps, compoundTag.get("data"))
.resultOrPartial(string -> LOGGER.error("Failed to parse saved data for '{}': {}", type, string))
.orElse(null);
}
} catch (Exception var5) {
LOGGER.error("Error loading saved data: {}", type, var5);
}
return null;
}
public <T extends SavedData> void set(SavedDataType<T> type, T value) {
this.cache.put(type, Optional.of(value));
value.setDirty();
}
public CompoundTag readTagFromDisk(String filename, DataFixTypes dataFixType, int version) throws IOException {
InputStream inputStream = Files.newInputStream(this.getDataFile(filename));
CompoundTag var8;
try {
PushbackInputStream pushbackInputStream = new PushbackInputStream(new FastBufferedInputStream(inputStream), 2);
try {
CompoundTag compoundTag;
if (this.isGzip(pushbackInputStream)) {
compoundTag = NbtIo.readCompressed(pushbackInputStream, NbtAccounter.unlimitedHeap());
} else {
DataInputStream dataInputStream = new DataInputStream(pushbackInputStream);
try {
compoundTag = NbtIo.read(dataInputStream);
} catch (Throwable var13) {
try {
dataInputStream.close();
} catch (Throwable var12) {
var13.addSuppressed(var12);
}
throw var13;
}
dataInputStream.close();
}
int i = NbtUtils.getDataVersion(compoundTag, 1343);
var8 = dataFixType.update(this.fixerUpper, compoundTag, i, version);
} catch (Throwable var14) {
try {
pushbackInputStream.close();
} catch (Throwable var11) {
var14.addSuppressed(var11);
}
throw var14;
}
pushbackInputStream.close();
} catch (Throwable var15) {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable var10) {
var15.addSuppressed(var10);
}
}
throw var15;
}
if (inputStream != null) {
inputStream.close();
}
return var8;
}
private boolean isGzip(PushbackInputStream inputStream) throws IOException {
byte[] bs = new byte[2];
boolean bl = false;
int i = inputStream.read(bs, 0, 2);
if (i == 2) {
int j = (bs[1] & 255) << 8 | bs[0] & 255;
if (j == 35615) {
bl = true;
}
}
if (i != 0) {
inputStream.unread(bs, 0, i);
}
return bl;
}
public CompletableFuture<?> scheduleSave() {
Map<SavedDataType<?>, CompoundTag> map = this.collectDirtyTagsToSave();
if (map.isEmpty()) {
return CompletableFuture.completedFuture(null);
} else {
int i = Util.maxAllowedExecutorThreads();
int j = map.size();
if (j > i) {
this.pendingWriteFuture = this.pendingWriteFuture.thenCompose(object -> {
List<CompletableFuture<?>> list = new ArrayList(i);
int k = Mth.positiveCeilDiv(j, i);
for (List<Entry<SavedDataType<?>, CompoundTag>> list2 : Iterables.partition(map.entrySet(), k)) {
list.add(CompletableFuture.runAsync(() -> {
for (Entry<SavedDataType<?>, CompoundTag> entry : list2) {
this.tryWrite((SavedDataType<?>)entry.getKey(), (CompoundTag)entry.getValue());
}
}, Util.ioPool()));
}
return CompletableFuture.allOf((CompletableFuture[])list.toArray(CompletableFuture[]::new));
});
} else {
this.pendingWriteFuture = this.pendingWriteFuture
.thenCompose(
object -> CompletableFuture.allOf(
(CompletableFuture[])map.entrySet()
.stream()
.map(entry -> CompletableFuture.runAsync(() -> this.tryWrite((SavedDataType<?>)entry.getKey(), (CompoundTag)entry.getValue()), Util.ioPool()))
.toArray(CompletableFuture[]::new)
)
);
}
return this.pendingWriteFuture;
}
}
private Map<SavedDataType<?>, CompoundTag> collectDirtyTagsToSave() {
Map<SavedDataType<?>, CompoundTag> map = new Object2ObjectArrayMap<>();
RegistryOps<Tag> registryOps = this.registries.createSerializationContext(NbtOps.INSTANCE);
this.cache.forEach((savedDataType, optional) -> optional.filter(SavedData::isDirty).ifPresent(savedData -> {
map.put(savedDataType, this.encodeUnchecked(savedDataType, savedData, registryOps));
savedData.setDirty(false);
}));
return map;
}
private <T extends SavedData> CompoundTag encodeUnchecked(SavedDataType<T> type, SavedData data, RegistryOps<Tag> ops) {
Codec<T> codec = (Codec<T>)type.codec().apply(this.context);
CompoundTag compoundTag = new CompoundTag();
compoundTag.put("data", codec.encodeStart(ops, (T)data).getOrThrow());
NbtUtils.addCurrentDataVersion(compoundTag);
return compoundTag;
}
private void tryWrite(SavedDataType<?> type, CompoundTag tag) {
Path path = this.getDataFile(type.id());
try {
NbtIo.writeCompressed(tag, path);
} catch (IOException var5) {
LOGGER.error("Could not save data to {}", path.getFileName(), var5);
}
}
public void saveAndJoin() {
this.scheduleSave().join();
}
public void close() {
this.saveAndJoin();
}
}