diff --git a/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java b/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java index eedeba27..47ca87af 100644 --- a/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java +++ b/src/main/java/ru/bclib/api/dataexchange/BaseDataHandler.java @@ -55,6 +55,7 @@ public abstract class BaseDataHandler { @Environment(EnvType.CLIENT) abstract void sendToServer(Minecraft client); + protected boolean isBlocking() { return false; } @Override public String toString() { diff --git a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java index 540d9193..b1043fc3 100644 --- a/src/main/java/ru/bclib/api/dataexchange/DataHandler.java +++ b/src/main/java/ru/bclib/api/dataexchange/DataHandler.java @@ -56,7 +56,10 @@ public abstract class DataHandler extends BaseDataHandler { @Override void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) { deserializeIncomingData(buf, responseSender, true); - client.execute(() -> runOnGameThread(client, null, true)); + final Runnable runner = () -> runOnGameThread(client, null, true); + + if (isBlocking()) client.executeBlocking(runner); + else client.execute(runner); } @Override @@ -64,7 +67,10 @@ public abstract class DataHandler extends BaseDataHandler { super.receiveFromClient(server, player, handler, buf, responseSender); deserializeIncomingData(buf, responseSender, false); - server.execute(() -> runOnGameThread(null, server, false)); + final Runnable runner = () -> runOnGameThread(null, server, false); + + if (isBlocking()) server.executeBlocking(runner); + else server.execute(runner); } @Override @@ -156,7 +162,10 @@ public abstract class DataHandler extends BaseDataHandler { super.receiveFromClient(server, player, handler, buf, responseSender); deserializeIncomingDataOnServer(buf, responseSender); - server.execute(() -> runOnServerGameThread(server)); + final Runnable runner = () -> runOnServerGameThread(server); + + if (isBlocking()) server.executeBlocking(runner); + else server.execute(runner); } @Override @@ -220,7 +229,10 @@ public abstract class DataHandler extends BaseDataHandler { @Override final void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) { deserializeIncomingDataOnClient(buf, responseSender); - client.execute(() -> runOnClientGameThread(client)); + final Runnable runner = () -> runOnClientGameThread(client); + + if (isBlocking()) client.executeBlocking(runner); + else client.execute(runner); } @Override diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java index b3282596..e021c98b 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/Chunker.java @@ -1,9 +1,13 @@ package ru.bclib.api.dataexchange.handler.autosync; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.client.Minecraft; +import net.minecraft.util.ProgressListener; +import ru.bclib.gui.screens.ProgressScreen; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; @@ -23,8 +27,35 @@ import java.util.Map; import java.util.Objects; import java.util.UUID; +/** + * Used to seperate large data transfers into multiple smaller messages. + *

+ * {@link DataHandler} will automatically convert larger messages into Chunks on the Server + * and assemble the original message from those chunks on the client. + */ public class Chunker extends DataHandler.FromServer { - public static class PacketChunkReceiver { + private static ProgressScreen progressScreen; + + @Environment(EnvType.CLIENT) + public static void setProgressScreen(ProgressScreen scr){ + progressScreen = scr; + } + + @Environment(EnvType.CLIENT) + public static ProgressScreen getProgressScreen(){ + return progressScreen; + } + + @Environment(EnvType.CLIENT) + public static ProgressListener getProgressListener(){ + return progressScreen; + } + + /** + * Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the + * receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1) + */ + static class PacketChunkReceiver { @NotNull public final UUID uuid; public final int chunkCount; @@ -70,6 +101,10 @@ public class Chunker extends DataHandler.FromServer { } public boolean testFinished(){ + ProgressListener listener = getProgressListener(); + if (listener!=null){ + listener.progressStagePercentage((100*receivedCount)/chunkCount); + } if (incomingBuffer == null){ return true; } if (lastReadSerial>=chunkCount-1){ @@ -79,7 +114,7 @@ public class Chunker extends DataHandler.FromServer { return false; } - protected void addBuffer(FriendlyByteBuf input){ + private void addBuffer(FriendlyByteBuf input){ final int size = input.readableBytes(); final int cap = networkedBuf.capacity()-networkedBuf.writerIndex(); @@ -102,7 +137,10 @@ public class Chunker extends DataHandler.FromServer { Map incomingBuffer = new HashMap<>(); int lastReadSerial = -1; + int receivedCount = 0; public void processReceived(FriendlyByteBuf buf, int serialNo, int size){ + receivedCount++; + if (lastReadSerial == serialNo-1){ addBuffer(buf); lastReadSerial = serialNo; @@ -130,6 +168,10 @@ public class Chunker extends DataHandler.FromServer { } } + /** + * Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and + * send them as seperate messages to the {@link Chunker}-Channel + */ public static class PacketChunkSender { private final FriendlyByteBuf networkedBuf; public final UUID uuid; @@ -160,7 +202,9 @@ public class Chunker extends DataHandler.FromServer { } } - private static final int HEADER_SIZE = 1 + 16 + 4 + 4; //header = version + UUID + serialNo + size + //header = version + UUID + serialNo + size, see serializeDataOnServer + private static final int HEADER_SIZE = 1 + 16 + 4 + 4; + public static final int MAX_PACKET_SIZE = 1024*1024; private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE; @@ -188,17 +232,20 @@ public class Chunker extends DataHandler.FromServer { @Override protected void serializeDataOnServer(FriendlyByteBuf buf) { + //Sending Header. Make sure to change HEADER_SIZE if you change this! buf.writeByte(0); buf.writeLong(uuid.getMostSignificantBits()); buf.writeLong(uuid.getLeastSignificantBits()); buf.writeInt(serialNo); + //sending Payload if (serialNo == -1){ - //this is our header + //this is our header-Chunk that transports status information buf.writeInt(chunkCount); writeString(buf, origin.getNamespace()); writeString(buf, origin.getPath()); } else { + //this is an actual payload chunk buf.capacity(MAX_PACKET_SIZE); final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes()); buf.writeInt(size); diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java index 3f6a9c3b..d28395c0 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/HelloClient.java @@ -6,6 +6,7 @@ import net.fabricmc.fabric.api.networking.v1.PacketSender; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.resources.ResourceLocation; import ru.bclib.BCLib; import ru.bclib.api.dataexchange.DataExchangeAPI; @@ -14,6 +15,7 @@ import ru.bclib.api.dataexchange.DataHandlerDescriptor; import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.WithContentOverride; import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile; import ru.bclib.config.Configs; +import ru.bclib.gui.screens.ProgressScreen; import ru.bclib.gui.screens.SyncFilesScreen; import ru.bclib.gui.screens.WarnBCLibVersionMismatch; import ru.bclib.util.ModUtil; @@ -300,6 +302,10 @@ public class HelloClient extends DataHandler.FromServer { } } + @Override + protected boolean isBlocking() { + return true; + } @Environment(EnvType.CLIENT) @Override @@ -371,8 +377,6 @@ public class HelloClient extends DataHandler.FromServer { } client.setScreen(new SyncFilesScreen(modFiles, configFiles, singleFiles, folderFiles, filesToRemove.size(), (downloadMods, downloadConfigs, downloadFiles, removeFiles) -> { - Minecraft.getInstance() - .setScreen((Screen) null); if (downloadMods || downloadConfigs || downloadFiles) { BCLib.LOGGER.info("Updating local Files:"); List localChanges = new ArrayList<>(files.toArray().length); @@ -398,6 +402,9 @@ public class HelloClient extends DataHandler.FromServer { aid.relFile.delete(); }); } + + Minecraft.getInstance() + .setScreen(Chunker.getProgressScreen()); })); } @@ -422,6 +429,11 @@ public class HelloClient extends DataHandler.FromServer { private void requestFileDownloads(List files) { BCLib.LOGGER.info("Starting download of Files:" + files.size()); + + final ProgressScreen progress = new ProgressScreen(null, new TranslatableComponent("title.bclib.filesync.progress"), new TranslatableComponent("message.bclib.filesync.progress")); + progress.progressStart(new TranslatableComponent("message.bclib.filesync.progress.stage.empty")); + Chunker.setProgressScreen(progress); + DataExchangeAPI.send(new RequestFiles(files)); } } diff --git a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java index 64fa2492..6f494234 100644 --- a/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java +++ b/src/main/java/ru/bclib/api/dataexchange/handler/autosync/SendFiles.java @@ -179,7 +179,10 @@ public class SendFiles extends DataHandler.FromServer { if (!parentFile.exists()) { parentFile.mkdirs(); } - Files.write(path, data); + //TODO: Disabled for testing. MUST BE ENABLED IN PRODUCTION!!! + if (!BCLib.isDevEnvironment()) { + Files.write(path, data); + } if (removeAfter != null){ final String bakFileName = removeAfter.toFile().getName(); String collisionFreeName = bakFileName;