Skip to content

Commit

Permalink
Schematic Caching
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
IThundxr committed Oct 10, 2024
1 parent aa15182 commit 5d2c8f2
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 24 deletions.
5 changes: 5 additions & 0 deletions src/main/java/com/simibubi/create/Create.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.Random;

import net.minecraftforge.fml.loading.FMLLoader;

import org.slf4j.Logger;

import com.google.gson.Gson;
Expand Down Expand Up @@ -152,6 +154,9 @@ public static void onCtor() {

// 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.simibubi.create.content.schematics;

public record SchematicFile(String playerName, String schematicName) {}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public SchematicItem(Properties 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) {
ItemStack blueprint = AllItems.SCHEMATIC.asStack();

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -24,21 +27,27 @@
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<>();

private Map<String, SchematicFile> sumToSchematic = new Object2ReferenceOpenHashMap<>();

public class SchematicUploadEntry {
public static class SchematicUploadEntry {
public Level world;
public BlockPos tablePos;
public OutputStream stream;
Expand All @@ -56,19 +65,42 @@ public SchematicUploadEntry(OutputStream stream, long totalBytes, Level world, B
}
}

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);

Expand All @@ -82,6 +114,7 @@ public void tick() {
for (String toRemove : deadEntries) {
this.cancelUpload(toRemove);
}

deadEntries.clear();
}

Expand Down Expand Up @@ -240,17 +273,22 @@ protected void cancelUpload(String playerSchematicId) {
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 {
Expand All @@ -259,6 +297,11 @@ public void handleFinishedUpload(ServerPlayer player, String schematic) {
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;
Expand All @@ -271,9 +314,7 @@ public void handleFinishedUpload(ServerPlayer player, String schematic) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
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 {

Expand Down Expand Up @@ -73,7 +75,11 @@ public void startNewUpload(String schematic) {

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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -9,6 +10,8 @@
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;
Expand All @@ -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;
}

Expand All @@ -45,8 +51,10 @@ public SchematicUploadPacket(FriendlyByteBuf buffer) {
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();
}
Expand All @@ -56,8 +64,10 @@ public void write(FriendlyByteBuf buffer) {
buffer.writeInt(code);
buffer.writeUtf(schematic);

if (code == BEGIN)
if (code == BEGIN) {
buffer.writeLong(size);
buffer.writeUtf(md5Hex);
}
if (code == WRITE)
buffer.writeByteArray(data);
}
Expand All @@ -69,9 +79,29 @@ public boolean handle(Context context) {
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);
Expand Down

0 comments on commit 5d2c8f2

Please sign in to comment.