Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schematic Caching #7046

Open
wants to merge 2 commits into
base: mc1.20.1/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,45 @@ 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 schematicMd5Hex = DigestUtils.md5Hex(stream);

sumToSchematic.computeIfAbsent(schematicMd5Hex, k -> {
String[] pathSplit = path.toString()
.replace("schematics/uploaded/", "")
.replace(".nbt", "")
.split("/");
String playerName = pathSplit[0];
String schematicName = pathSplit[1];

return 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 +117,7 @@ public void tick() {
for (String toRemove : deadEntries) {
this.cancelUpload(toRemove);
}

deadEntries.clear();
}

Expand Down Expand Up @@ -240,17 +276,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 +300,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 +317,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