Schematic Caching

Currently schematics are always reuploaded even if they already exist on the server, these changes make it so if they already exist on the server, a schematic pointing towards that is made instead of requiring a full reupload.
This commit is contained in:
IThundxr 2024-10-08 22:03:26 -04:00
parent aa15182005
commit 5d2c8f281c
Failed to generate hash of commit
6 changed files with 113 additions and 24 deletions

View file

@ -2,6 +2,8 @@ package com.simibubi.create;
import java.util.Random;
import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger;
import com.google.gson.Gson;
@ -152,6 +154,9 @@ public class Create {
// FIXME: this is not thread-safe
Mods.CURIOS.executeIfInstalled(() -> () -> Curios.init(modEventBus, forgeEventBus));
if (FMLLoader.getDist().isDedicatedServer())
SCHEMATIC_RECEIVER.computeHashes();
}
public static void init(final FMLCommonSetupEvent event) {

View file

@ -0,0 +1,3 @@
package com.simibubi.create.content.schematics;
public record SchematicFile(String playerName, String schematicName) {}

View file

@ -57,6 +57,10 @@ public class SchematicItem extends Item {
super(properties);
}
public static ItemStack create(HolderGetter<Block> lookup, SchematicFile schematicFile) {
return create(lookup, schematicFile.schematicName(), schematicFile.playerName());
}
public static ItemStack create(HolderGetter<Block> lookup, String schematic, String owner) {
ItemStack blueprint = AllItems.SCHEMATIC.asStack();

View file

@ -1,10 +1,13 @@
package com.simibubi.create.content.schematics;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
@ -24,21 +27,27 @@ import com.simibubi.create.foundation.utility.Lang;
import com.simibubi.create.infrastructure.config.AllConfigs;
import com.simibubi.create.infrastructure.config.CSchematics;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.codec.digest.DigestUtils;
import org.jetbrains.annotations.Nullable;
public class ServerSchematicLoader {
private Map<String, SchematicUploadEntry> activeUploads;
private Map<String, SchematicUploadEntry> activeUploads = new HashMap<>();
public class SchematicUploadEntry {
private Map<String, SchematicFile> sumToSchematic = new Object2ReferenceOpenHashMap<>();
public static class SchematicUploadEntry {
public Level world;
public BlockPos tablePos;
public OutputStream stream;
@ -56,19 +65,42 @@ public class ServerSchematicLoader {
}
}
public ServerSchematicLoader() {
activeUploads = new HashMap<>();
}
public String getSchematicPath() {
return "schematics/uploaded";
}
private final ObjectArrayList<String> deadEntries = ObjectArrayList.of();
@Nullable
public SchematicFile getSchematicFileFromSum(String sum) {
return sumToSchematic.get(sum);
}
public void computeHashes() {
Util.ioPool().submit(() -> {
try (Stream<Path> filePaths = Files.find(Path.of(getSchematicPath()), 2,
(filePath, attributes) -> filePath.toString().endsWith(".nbt"))) {
for (Path path : filePaths.toList()) {
try (InputStream stream = new FileInputStream(path.toFile())) {
String[] pathSplit = path.toString()
.replace("schematics/uploaded/", "")
.replace(".nbt", "")
.split("/");
String playerName = pathSplit[0];
String schematicName = pathSplit[1];
String schematicMd5Hex = DigestUtils.md5Hex(stream);
sumToSchematic.computeIfAbsent(schematicMd5Hex, k -> new SchematicFile(playerName, schematicName));
}
}
} catch (IOException ignored) {}
});
}
public void tick() {
// Detect Timed out Uploads
int timeout = getConfig().schematicIdleTimeout.get();
for (String upload : activeUploads.keySet()) {
SchematicUploadEntry entry = activeUploads.get(upload);
@ -82,6 +114,7 @@ public class ServerSchematicLoader {
for (String toRemove : deadEntries) {
this.cancelUpload(toRemove);
}
deadEntries.clear();
}
@ -240,17 +273,22 @@ public class ServerSchematicLoader {
table.finishUpload();
}
// Use when the schematic already exists on the server
public void useLocalFile(Level level, BlockPos pos, SchematicFile schematicFile) {
SchematicTableBlockEntity table = getTable(level, pos);
if (table != null) {
table.finishUpload();
table.inventory.setStackInSlot(1, SchematicItem.create(level.holderLookup(Registries.BLOCK), schematicFile));
}
}
public SchematicTableBlockEntity getTable(Level world, BlockPos pos) {
BlockEntity be = world.getBlockEntity(pos);
if (!(be instanceof SchematicTableBlockEntity))
return null;
SchematicTableBlockEntity table = (SchematicTableBlockEntity) be;
return table;
return world.getBlockEntity(pos) instanceof SchematicTableBlockEntity table ? table : null;
}
public void handleFinishedUpload(ServerPlayer player, String schematic) {
String playerSchematicId = player.getGameProfile()
.getName() + "/" + schematic;
String playerName = player.getGameProfile().getName();
String playerSchematicId = playerName + "/" + schematic;
if (activeUploads.containsKey(playerSchematicId)) {
try {
@ -259,6 +297,11 @@ public class ServerSchematicLoader {
Level world = removed.world;
BlockPos pos = removed.tablePos;
// It'll be fine:tm:
try (InputStream stream = Files.newInputStream(Path.of(playerSchematicId), StandardOpenOption.READ)) {
sumToSchematic.computeIfAbsent(DigestUtils.md5Hex(stream), k -> new SchematicFile(playerName, schematic));
} catch (IOException ignored) {}
Create.LOGGER.info("New Schematic Uploaded: " + playerSchematicId);
if (pos == null)
return;
@ -271,9 +314,7 @@ public class ServerSchematicLoader {
if (table == null)
return;
table.finishUpload();
table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, player.getGameProfile()
.getName()));
table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, playerName));
} catch (IOException e) {
Create.LOGGER.error("Exception Thrown when finishing Upload: " + playerSchematicId);
e.printStackTrace();

View file

@ -28,6 +28,8 @@ import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.commons.codec.digest.DigestUtils;
@OnlyIn(Dist.CLIENT)
public class ClientSchematicLoader {
@ -73,7 +75,11 @@ public class ClientSchematicLoader {
in = Files.newInputStream(path, StandardOpenOption.READ);
activeUploads.put(schematic, in);
AllPackets.getChannel().sendToServer(SchematicUploadPacket.begin(schematic, size));
try (InputStream stream = Files.newInputStream(path, StandardOpenOption.READ)) {
String md5 = DigestUtils.md5Hex(stream);
AllPackets.getChannel().sendToServer(SchematicUploadPacket.begin(schematic, size, md5));
}
} catch (IOException e) {
e.printStackTrace();
}

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.schematics.packet;
import com.simibubi.create.Create;
import com.simibubi.create.content.schematics.SchematicFile;
import com.simibubi.create.content.schematics.table.SchematicTableMenu;
import com.simibubi.create.foundation.networking.SimplePacketBase;
@ -9,6 +10,8 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent.Context;
import java.io.File;
public class SchematicUploadPacket extends SimplePacketBase {
public static final int BEGIN = 0;
@ -20,14 +23,17 @@ public class SchematicUploadPacket extends SimplePacketBase {
private String schematic;
private byte[] data;
private String md5Hex;
public SchematicUploadPacket(int code, String schematic) {
this.code = code;
this.schematic = schematic;
}
public static SchematicUploadPacket begin(String schematic, long size) {
public static SchematicUploadPacket begin(String schematic, long size, String md5Hex) {
SchematicUploadPacket pkt = new SchematicUploadPacket(BEGIN, schematic);
pkt.size = size;
pkt.md5Hex = md5Hex;
return pkt;
}
@ -45,8 +51,10 @@ public class SchematicUploadPacket extends SimplePacketBase {
code = buffer.readInt();
schematic = buffer.readUtf(256);
if (code == BEGIN)
if (code == BEGIN) {
size = buffer.readLong();
md5Hex = buffer.readUtf(32);
}
if (code == WRITE)
data = buffer.readByteArray();
}
@ -56,8 +64,10 @@ public class SchematicUploadPacket extends SimplePacketBase {
buffer.writeInt(code);
buffer.writeUtf(schematic);
if (code == BEGIN)
if (code == BEGIN) {
buffer.writeLong(size);
buffer.writeUtf(md5Hex);
}
if (code == WRITE)
buffer.writeByteArray(data);
}
@ -69,9 +79,29 @@ public class SchematicUploadPacket extends SimplePacketBase {
if (player == null)
return;
if (code == BEGIN) {
BlockPos pos = ((SchematicTableMenu) player.containerMenu).contentHolder
.getBlockPos();
Create.SCHEMATIC_RECEIVER.handleNewUpload(player, schematic, size, pos);
boolean usedLocalFile = false;
BlockPos pos = ((SchematicTableMenu) player.containerMenu).contentHolder.getBlockPos();
SchematicFile schematicFile = Create.SCHEMATIC_RECEIVER.getSchematicFileFromSum(md5Hex);
if (schematicFile != null) {
String filePath = String.format(
"%s/%s/%s",
Create.SCHEMATIC_RECEIVER.getSchematicPath(),
schematicFile.playerName(),
schematicFile.schematicName()
);
// Check if the file exists
if (new File(filePath).isFile()) {
Create.SCHEMATIC_RECEIVER.useLocalFile(player.level(), pos, schematicFile);
usedLocalFile = true;
}
}
if (!usedLocalFile)
Create.SCHEMATIC_RECEIVER.handleNewUpload(player, schematic, size, pos);
}
if (code == WRITE)
Create.SCHEMATIC_RECEIVER.handleWriteRequest(player, schematic, data);