Commit c9f32be6 by Jonathan Thomas

Integrating compression & decompression

 when sending chunked lite JSON data to new player on server (i.e. someone reconnects or logins in).
parent 32828faa
Pipeline #11960 passed with stage
in 19 seconds
......@@ -6,6 +6,7 @@ import com.owlmaddie.ModInit;
import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.network.ModPackets;
import com.owlmaddie.utils.ClientEntityFinder;
import com.owlmaddie.utils.Decompression;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.MinecraftClient;
......@@ -17,7 +18,10 @@ import net.minecraft.entity.mob.MobEntity;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
......@@ -31,8 +35,9 @@ import java.util.stream.Collectors;
* back to the server.
*/
public class ClickHandler {
public static final Logger LOGGER = LoggerFactory.getLogger("mobgpt");
private static boolean wasClicked = false;
static HashMap<Integer, String> receivedChunks = new HashMap<>();
static HashMap<Integer, byte[]> receivedChunks = new HashMap<>();
public static void register() {
ClientTickEvents.END_CLIENT_TICK.register(client -> {
......@@ -79,7 +84,7 @@ public class ClickHandler {
ClientPlayNetworking.registerGlobalReceiver(ModInit.PACKET_S2C_LOGIN, (client, handler, buffer, responseSender) -> {
int sequenceNumber = buffer.readInt(); // Sequence number of the current packet
int totalPackets = buffer.readInt(); // Total number of packets for this data
String chunk = buffer.readString(); // Read the chunk from the current packet
byte[] chunk = buffer.readByteArray(); // Read the byte array chunk from the current packet
client.execute(() -> { // Make sure to run on the client thread
// Store the received chunk
......@@ -87,12 +92,20 @@ public class ClickHandler {
// Check if all chunks have been received
if (receivedChunks.size() == totalPackets) {
// Reconstruct the original chatDataJSON from chunks
StringBuilder chatDataJSONBuilder = new StringBuilder();
LOGGER.info("Reassemble chunks on client and decompress lite JSON data string");
// Combine all byte array chunks
ByteArrayOutputStream combined = new ByteArrayOutputStream();
for (int i = 0; i < totalPackets; i++) {
chatDataJSONBuilder.append(receivedChunks.get(i));
combined.write(receivedChunks.get(i), 0, receivedChunks.get(i).length);
}
// Decompress the combined byte array to get the original JSON string
String chatDataJSON = Decompression.decompressString(combined.toByteArray());
if (chatDataJSON == null) {
LOGGER.info("Error decompressing lite JSON string from bytes");
return;
}
String chatDataJSON = chatDataJSONBuilder.toString();
// Parse JSON and update client chat data
Gson GSON = new Gson();
......
package com.owlmaddie.utils;
import java.io.ByteArrayOutputStream;
import java.util.zip.Inflater;
/**
* The {@code Decompression} class is used to decompress a JSON byte array and return a string.
*/
public class Decompression {
public static String decompressString(byte[] data) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length)) {
Inflater inflater = new Inflater();
inflater.setInput(data);
byte[] buffer = new byte[1024];
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
inflater.end();
return outputStream.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
......@@ -4,6 +4,7 @@ import com.owlmaddie.chat.ChatDataManager;
import com.owlmaddie.goals.EntityBehaviorManager;
import com.owlmaddie.goals.GoalPriority;
import com.owlmaddie.goals.TalkPlayerGoal;
import com.owlmaddie.utils.Compression;
import com.owlmaddie.utils.RandomUtils;
import com.owlmaddie.utils.ServerEntityFinder;
import io.netty.buffer.Unpooled;
......@@ -21,6 +22,7 @@ import net.minecraft.util.Identifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;
......@@ -146,23 +148,31 @@ public class ModInit implements ModInitializer {
// Data is sent in chunks, to prevent exceeding the 32767 limit per String.
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
ServerPlayerEntity player = handler.player;
LOGGER.info("Server send login message packet to player: " + player.getName().getString());
LOGGER.info("Server send compressed, chunked login message packets to player: " + player.getName().getString());
// Get lite JSON data & compress to byte array
String chatDataJSON = ChatDataManager.getServerInstance().GetLightChatData();
int chunkSize = 32000; // Slightly below the limit to account for any additional data
byte[] compressedData = Compression.compressString(chatDataJSON);
if (compressedData == null) {
LOGGER.error("Failed to compress chat data.");
return;
}
// Calculate the number of required packets to send the entire JSON string
int totalPackets = (int) Math.ceil(chatDataJSON.length() / (double) chunkSize);
final int chunkSize = 32000; // Define chunk size
int totalPackets = (int) Math.ceil((double) compressedData.length / chunkSize);
// Loop through each chunk of bytes, and send bytes to player
for (int i = 0; i < totalPackets; i++) {
int start = i * chunkSize;
int end = Math.min(start + chunkSize, chatDataJSON.length());
String chunk = chatDataJSON.substring(start, end);
int end = Math.min(compressedData.length, start + chunkSize);
PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer());
buffer.writeInt(i); // Packet sequence number
buffer.writeInt(totalPackets); // Total number of packets
buffer.writeString(chunk);
// Write chunk as byte array
byte[] chunk = Arrays.copyOfRange(compressedData, start, end);
buffer.writeByteArray(chunk);
ServerPlayNetworking.send(player, PACKET_S2C_LOGIN, buffer);
}
......
package com.owlmaddie.utils;
import java.io.ByteArrayOutputStream;
import java.util.zip.Deflater;
/**
* The {@code Compression} class is used to compress a JSON string and return a byte array.
*/
public class Compression {
public static byte[] compressString(String data) {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length())) {
Deflater deflater = new Deflater();
deflater.setInput(data.getBytes());
deflater.finish();
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer); // Returns the generated code... index
outputStream.write(buffer, 0, count);
}
deflater.end();
return outputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment