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 java.util.Random;
import net.minecraftforge.fml.loading.FMLLoader;
import org.slf4j.Logger; import org.slf4j.Logger;
import com.google.gson.Gson; import com.google.gson.Gson;
@ -152,6 +154,9 @@ public class Create {
// FIXME: this is not thread-safe // FIXME: this is not thread-safe
Mods.CURIOS.executeIfInstalled(() -> () -> Curios.init(modEventBus, forgeEventBus)); Mods.CURIOS.executeIfInstalled(() -> () -> Curios.init(modEventBus, forgeEventBus));
if (FMLLoader.getDist().isDedicatedServer())
SCHEMATIC_RECEIVER.computeHashes();
} }
public static void init(final FMLCommonSetupEvent event) { 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); 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) { public static ItemStack create(HolderGetter<Block> lookup, String schematic, String owner) {
ItemStack blueprint = AllItems.SCHEMATIC.asStack(); ItemStack blueprint = AllItems.SCHEMATIC.asStack();

View file

@ -1,10 +1,13 @@
package com.simibubi.create.content.schematics; package com.simibubi.create.content.schematics;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; 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.AllConfigs;
import com.simibubi.create.infrastructure.config.CSchematics; import com.simibubi.create.infrastructure.config.CSchematics;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.Util;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.codec.digest.DigestUtils;
import org.jetbrains.annotations.Nullable;
public class ServerSchematicLoader { 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 Level world;
public BlockPos tablePos; public BlockPos tablePos;
public OutputStream stream; public OutputStream stream;
@ -56,19 +65,42 @@ public class ServerSchematicLoader {
} }
} }
public ServerSchematicLoader() {
activeUploads = new HashMap<>();
}
public String getSchematicPath() { public String getSchematicPath() {
return "schematics/uploaded"; return "schematics/uploaded";
} }
private final ObjectArrayList<String> deadEntries = ObjectArrayList.of(); 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() { public void tick() {
// Detect Timed out Uploads // Detect Timed out Uploads
int timeout = getConfig().schematicIdleTimeout.get(); int timeout = getConfig().schematicIdleTimeout.get();
for (String upload : activeUploads.keySet()) { for (String upload : activeUploads.keySet()) {
SchematicUploadEntry entry = activeUploads.get(upload); SchematicUploadEntry entry = activeUploads.get(upload);
@ -82,6 +114,7 @@ public class ServerSchematicLoader {
for (String toRemove : deadEntries) { for (String toRemove : deadEntries) {
this.cancelUpload(toRemove); this.cancelUpload(toRemove);
} }
deadEntries.clear(); deadEntries.clear();
} }
@ -240,17 +273,22 @@ public class ServerSchematicLoader {
table.finishUpload(); 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) { public SchematicTableBlockEntity getTable(Level world, BlockPos pos) {
BlockEntity be = world.getBlockEntity(pos); return world.getBlockEntity(pos) instanceof SchematicTableBlockEntity table ? table : null;
if (!(be instanceof SchematicTableBlockEntity))
return null;
SchematicTableBlockEntity table = (SchematicTableBlockEntity) be;
return table;
} }
public void handleFinishedUpload(ServerPlayer player, String schematic) { public void handleFinishedUpload(ServerPlayer player, String schematic) {
String playerSchematicId = player.getGameProfile() String playerName = player.getGameProfile().getName();
.getName() + "/" + schematic; String playerSchematicId = playerName + "/" + schematic;
if (activeUploads.containsKey(playerSchematicId)) { if (activeUploads.containsKey(playerSchematicId)) {
try { try {
@ -259,6 +297,11 @@ public class ServerSchematicLoader {
Level world = removed.world; Level world = removed.world;
BlockPos pos = removed.tablePos; 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); Create.LOGGER.info("New Schematic Uploaded: " + playerSchematicId);
if (pos == null) if (pos == null)
return; return;
@ -271,9 +314,7 @@ public class ServerSchematicLoader {
if (table == null) if (table == null)
return; return;
table.finishUpload(); table.finishUpload();
table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, player.getGameProfile() table.inventory.setStackInSlot(1, SchematicItem.create(world.holderLookup(Registries.BLOCK), schematic, playerName));
.getName()));
} catch (IOException e) { } catch (IOException e) {
Create.LOGGER.error("Exception Thrown when finishing Upload: " + playerSchematicId); Create.LOGGER.error("Exception Thrown when finishing Upload: " + playerSchematicId);
e.printStackTrace(); 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.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import org.apache.commons.codec.digest.DigestUtils;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class ClientSchematicLoader { public class ClientSchematicLoader {
@ -73,7 +75,11 @@ public class ClientSchematicLoader {
in = Files.newInputStream(path, StandardOpenOption.READ); in = Files.newInputStream(path, StandardOpenOption.READ);
activeUploads.put(schematic, in); 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) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View file

@ -1,6 +1,7 @@
package com.simibubi.create.content.schematics.packet; package com.simibubi.create.content.schematics.packet;
import com.simibubi.create.Create; import com.simibubi.create.Create;
import com.simibubi.create.content.schematics.SchematicFile;
import com.simibubi.create.content.schematics.table.SchematicTableMenu; import com.simibubi.create.content.schematics.table.SchematicTableMenu;
import com.simibubi.create.foundation.networking.SimplePacketBase; import com.simibubi.create.foundation.networking.SimplePacketBase;
@ -9,6 +10,8 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkEvent.Context; import net.minecraftforge.network.NetworkEvent.Context;
import java.io.File;
public class SchematicUploadPacket extends SimplePacketBase { public class SchematicUploadPacket extends SimplePacketBase {
public static final int BEGIN = 0; public static final int BEGIN = 0;
@ -20,14 +23,17 @@ public class SchematicUploadPacket extends SimplePacketBase {
private String schematic; private String schematic;
private byte[] data; private byte[] data;
private String md5Hex;
public SchematicUploadPacket(int code, String schematic) { public SchematicUploadPacket(int code, String schematic) {
this.code = code; this.code = code;
this.schematic = schematic; 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); SchematicUploadPacket pkt = new SchematicUploadPacket(BEGIN, schematic);
pkt.size = size; pkt.size = size;
pkt.md5Hex = md5Hex;
return pkt; return pkt;
} }
@ -45,8 +51,10 @@ public class SchematicUploadPacket extends SimplePacketBase {
code = buffer.readInt(); code = buffer.readInt();
schematic = buffer.readUtf(256); schematic = buffer.readUtf(256);
if (code == BEGIN) if (code == BEGIN) {
size = buffer.readLong(); size = buffer.readLong();
md5Hex = buffer.readUtf(32);
}
if (code == WRITE) if (code == WRITE)
data = buffer.readByteArray(); data = buffer.readByteArray();
} }
@ -56,8 +64,10 @@ public class SchematicUploadPacket extends SimplePacketBase {
buffer.writeInt(code); buffer.writeInt(code);
buffer.writeUtf(schematic); buffer.writeUtf(schematic);
if (code == BEGIN) if (code == BEGIN) {
buffer.writeLong(size); buffer.writeLong(size);
buffer.writeUtf(md5Hex);
}
if (code == WRITE) if (code == WRITE)
buffer.writeByteArray(data); buffer.writeByteArray(data);
} }
@ -69,8 +79,28 @@ public class SchematicUploadPacket extends SimplePacketBase {
if (player == null) if (player == null)
return; return;
if (code == BEGIN) { if (code == BEGIN) {
BlockPos pos = ((SchematicTableMenu) player.containerMenu).contentHolder boolean usedLocalFile = false;
.getBlockPos();
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); Create.SCHEMATIC_RECEIVER.handleNewUpload(player, schematic, size, pos);
} }
if (code == WRITE) if (code == WRITE)