From a6a361e5e1297ef19cfb1ec38db61a1411f052da Mon Sep 17 00:00:00 2001 From: shedaniel Date: Sat, 23 Oct 2021 18:27:28 +0800 Subject: [PATCH] Add support for PacketTransformer and a SplitPacketTransformer (#142) * Add support for PacketTransformer and a SplitPacketTransformer * Add testmod and make it work * Add experimental, Remove generics, they are pointless --- .../networking/NetworkManager.java | 47 ++-- .../transformers/PacketCollector.java | 49 ++++ .../networking/transformers/PacketSink.java | 56 +++++ .../transformers/PacketTransformer.java | 111 +++++++++ .../transformers/SinglePacketCollector.java | 51 +++++ .../transformers/SplitPacketTransformer.java | 210 ++++++++++++++++++ .../mixin/fabric/MixinFallingBlockEntity.java | 19 ++ .../networking/fabric/NetworkManagerImpl.java | 60 ++++- .../forge/ClientNetworkingManager.java | 5 +- .../networking/forge/NetworkManagerImpl.java | 50 ++++- .../java/dev/architectury/test/TestMod.java | 3 +- .../test/networking/TestModNet.java | 35 +++ 12 files changed, 661 insertions(+), 35 deletions(-) create mode 100644 common/src/main/java/dev/architectury/networking/transformers/PacketCollector.java create mode 100644 common/src/main/java/dev/architectury/networking/transformers/PacketSink.java create mode 100644 common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java create mode 100644 common/src/main/java/dev/architectury/networking/transformers/SinglePacketCollector.java create mode 100644 common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java diff --git a/common/src/main/java/dev/architectury/networking/NetworkManager.java b/common/src/main/java/dev/architectury/networking/NetworkManager.java index 480ced18..578f14c7 100644 --- a/common/src/main/java/dev/architectury/networking/NetworkManager.java +++ b/common/src/main/java/dev/architectury/networking/NetworkManager.java @@ -20,48 +20,67 @@ package dev.architectury.networking; import dev.architectury.injectables.annotations.ExpectPlatform; +import dev.architectury.networking.transformers.PacketCollector; +import dev.architectury.networking.transformers.PacketSink; +import dev.architectury.networking.transformers.PacketTransformer; +import dev.architectury.networking.transformers.SinglePacketCollector; import dev.architectury.utils.Env; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; -import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.Packet; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.ApiStatus; -import java.util.Objects; +import java.util.Collections; +import java.util.List; public final class NetworkManager { - @ExpectPlatform public static void registerReceiver(Side side, ResourceLocation id, NetworkReceiver receiver) { - throw new AssertionError(); + registerReceiver(side, id, Collections.emptyList(), receiver); } @ExpectPlatform + @ApiStatus.Experimental + public static void registerReceiver(Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { + throw new AssertionError(); + } + + @Deprecated + @ApiStatus.ScheduledForRemoval public static Packet toPacket(Side side, ResourceLocation id, FriendlyByteBuf buf) { + SinglePacketCollector sink = new SinglePacketCollector(null); + collectPackets(sink, side, id, buf); + return sink.getPacket(); + } + + @Deprecated + @ApiStatus.ScheduledForRemoval + public static List> toPackets(Side side, ResourceLocation id, FriendlyByteBuf buf) { + PacketCollector sink = new PacketCollector(null); + collectPackets(sink, side, id, buf); + return sink.collect(); + } + + @ExpectPlatform + public static void collectPackets(PacketSink sink, Side side, ResourceLocation id, FriendlyByteBuf buf) { throw new AssertionError(); } public static void sendToPlayer(ServerPlayer player, ResourceLocation id, FriendlyByteBuf buf) { - Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(toPacket(serverToClient(), id, buf)); + collectPackets(PacketSink.ofPlayer(player), serverToClient(), id, buf); } public static void sendToPlayers(Iterable players, ResourceLocation id, FriendlyByteBuf buf) { - var packet = toPacket(serverToClient(), id, buf); - for (var player : players) { - Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet); - } + collectPackets(PacketSink.ofPlayers(players), serverToClient(), id, buf); } @Environment(EnvType.CLIENT) public static void sendToServer(ResourceLocation id, FriendlyByteBuf buf) { - if (Minecraft.getInstance().getConnection() != null) { - Minecraft.getInstance().getConnection().send(toPacket(clientToServer(), id, buf)); - } else { - throw new IllegalStateException("Unable to send packet to the server while not in game!"); - } + collectPackets(PacketSink.client(), clientToServer(), id, buf); } @Environment(EnvType.CLIENT) diff --git a/common/src/main/java/dev/architectury/networking/transformers/PacketCollector.java b/common/src/main/java/dev/architectury/networking/transformers/PacketCollector.java new file mode 100644 index 00000000..186f2def --- /dev/null +++ b/common/src/main/java/dev/architectury/networking/transformers/PacketCollector.java @@ -0,0 +1,49 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.networking.transformers; + +import net.minecraft.network.protocol.Packet; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class PacketCollector implements PacketSink { + @Nullable + private final Consumer> consumer; + private final List> packets = new ArrayList<>(); + + public PacketCollector(@Nullable Consumer> consumer) { + this.consumer = consumer; + } + + @Override + public void accept(Packet packet) { + packets.add(packet); + if (this.consumer != null) { + this.consumer.accept(packet); + } + } + + public List> collect() { + return packets; + } +} diff --git a/common/src/main/java/dev/architectury/networking/transformers/PacketSink.java b/common/src/main/java/dev/architectury/networking/transformers/PacketSink.java new file mode 100644 index 00000000..e07c1e17 --- /dev/null +++ b/common/src/main/java/dev/architectury/networking/transformers/PacketSink.java @@ -0,0 +1,56 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.networking.transformers; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.Minecraft; +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.level.ServerPlayer; + +import java.util.Objects; + +@FunctionalInterface +public interface PacketSink { + static PacketSink ofPlayer(ServerPlayer player) { + return packet -> Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet); + } + + static PacketSink ofPlayers(Iterable players) { + return packet -> { + for (var player : players) { + Objects.requireNonNull(player, "Unable to send packet to a 'null' player!").connection.send(packet); + } + }; + } + + @Environment(EnvType.CLIENT) + static PacketSink client() { + return packet -> { + if (Minecraft.getInstance().getConnection() != null) { + Minecraft.getInstance().getConnection().send(packet); + } else { + throw new IllegalStateException("Unable to send packet to the server while not in game!"); + } + }; + } + + void accept(Packet packet); +} diff --git a/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java b/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java new file mode 100644 index 00000000..741b99f8 --- /dev/null +++ b/common/src/main/java/dev/architectury/networking/transformers/PacketTransformer.java @@ -0,0 +1,111 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.networking.transformers; + +import dev.architectury.networking.NetworkManager; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +@ApiStatus.Experimental +public interface PacketTransformer { + void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink); + + void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink); + + @FunctionalInterface + interface TransformationSink { + void accept(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf); + } + + static PacketTransformer none() { + return new PacketTransformer() { + @Override + public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) { + sink.accept(side, id, buf); + } + + @Override + public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) { + sink.accept(side, id, buf); + } + }; + } + + static PacketTransformer concat(Iterable transformers) { + if (transformers instanceof Collection && ((Collection) transformers).isEmpty()) { + return PacketTransformer.none(); + } else if (transformers instanceof Collection && ((Collection) transformers).size() == 1) { + return transformers.iterator().next(); + } + return new PacketTransformer() { + @Override + public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) { + traverse(side, id, buf, context, sink, true, 0); + } + + @Override + public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) { + traverse(side, id, buf, null, sink, false, 0); + } + + private void traverse(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, @Nullable NetworkManager.PacketContext context, TransformationSink outerSink, boolean inbound, int index) { + if (transformers instanceof List) { + if (((List) transformers).size() > index) { + PacketTransformer transformer = ((List) transformers).get(index); + TransformationSink sink = (side1, id1, buf1) -> { + traverse(side1, id1, buf1, context, outerSink, inbound, index + 1); + }; + if (inbound) { + transformer.inbound(side, id, buf, context, sink); + } else { + transformer.outbound(side, id, buf, sink); + } + } else { + outerSink.accept(side, id, buf); + } + } else { + Iterator iterator = transformers.iterator(); + for (int i = 0; i < index; i++) { + iterator.next(); + } + PacketTransformer transformer = iterator.hasNext() ? iterator.next() : PacketTransformer.none(); + TransformationSink sink = (side1, id1, buf1) -> { + if (iterator.hasNext()) { + traverse(side1, id1, buf1, context, outerSink, inbound, index + 1); + } else { + outerSink.accept(side1, id1, buf1); + } + }; + if (inbound) { + transformer.inbound(side, id, buf, context, sink); + } else { + transformer.outbound(side, id, buf, sink); + } + } + } + }; + } +} diff --git a/common/src/main/java/dev/architectury/networking/transformers/SinglePacketCollector.java b/common/src/main/java/dev/architectury/networking/transformers/SinglePacketCollector.java new file mode 100644 index 00000000..562f2b15 --- /dev/null +++ b/common/src/main/java/dev/architectury/networking/transformers/SinglePacketCollector.java @@ -0,0 +1,51 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.networking.transformers; + +import net.minecraft.network.protocol.Packet; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +public class SinglePacketCollector implements PacketSink { + @Nullable + private final Consumer> consumer; + private Packet packet; + + public SinglePacketCollector(@Nullable Consumer> consumer) { + this.consumer = consumer; + } + + @Override + public void accept(Packet packet) { + if (this.packet == null) { + this.packet = packet; + if (this.consumer != null) { + this.consumer.accept(packet); + } + } else { + throw new IllegalStateException("Already accepted one packet!"); + } + } + + public Packet getPacket() { + return packet; + } +} diff --git a/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java b/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java new file mode 100644 index 00000000..05d6e7eb --- /dev/null +++ b/common/src/main/java/dev/architectury/networking/transformers/SplitPacketTransformer.java @@ -0,0 +1,210 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.networking.transformers; + +import dev.architectury.event.events.client.ClientPlayerEvent; +import dev.architectury.event.events.common.PlayerEvent; +import dev.architectury.networking.NetworkManager; +import dev.architectury.utils.Env; +import dev.architectury.utils.EnvExecutor; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +@ApiStatus.Experimental +public class SplitPacketTransformer implements PacketTransformer { + private static final Logger LOGGER = LogManager.getLogger(SplitPacketTransformer.class); + private static final byte START = 0x0; + private static final byte PART = 0x1; + private static final byte END = 0x2; + private static final byte ONLY = 0x3; + + private static class PartKey { + private final NetworkManager.Side side; + @Nullable + private final UUID playerUUID; + + public PartKey(NetworkManager.Side side, @Nullable UUID playerUUID) { + this.side = side; + this.playerUUID = playerUUID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PartKey)) return false; + PartKey key = (PartKey) o; + return side == key.side && Objects.equals(playerUUID, key.playerUUID); + } + + @Override + public int hashCode() { + return Objects.hash(side, playerUUID); + } + + @Override + public String toString() { + return "PartKey{" + + "side=" + side + + ", playerUUID=" + playerUUID + + '}'; + } + } + + private static class PartData { + private final ResourceLocation id; + private final int partsExpected; + private final List parts; + + public PartData(ResourceLocation id, int partsExpected) { + this.id = id; + this.partsExpected = partsExpected; + this.parts = new ArrayList<>(); + } + } + + private final Map cache = Collections.synchronizedMap(new HashMap<>()); + + public SplitPacketTransformer() { + PlayerEvent.PLAYER_QUIT.register(player -> { + cache.keySet().removeIf(key -> Objects.equals(key.playerUUID, player.getUUID())); + }); + EnvExecutor.runInEnv(Env.CLIENT, () -> new Client()::init); + } + + private class Client { + @Environment(EnvType.CLIENT) + private void init() { + ClientPlayerEvent.CLIENT_PLAYER_QUIT.register(player -> { + cache.keySet().removeIf(key -> key.side == NetworkManager.Side.S2C); + }); + } + } + + @Override + public void inbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, NetworkManager.PacketContext context, TransformationSink sink) { + PartKey key = side == NetworkManager.Side.S2C ? new PartKey(side, null) : new PartKey(side, context.getPlayer().getUUID()); + PartData data; + switch (buf.readByte()) { + case START: + data = new PartData(id, buf.readInt()); + if (cache.put(key, data) != null) { + LOGGER.warn("Received invalid START packet for SplitPacketTransformer with packet id " + id + " for side " + side); + } + buf.retain(); + data.parts.add(buf); + break; + case PART: + if ((data = cache.get(key)) == null) { + LOGGER.warn("Received invalid PART packet for SplitPacketTransformer with packet id " + id + " for side " + side); + buf.release(); + } else if (!data.id.equals(id)) { + LOGGER.warn("Received invalid PART packet for SplitPacketTransformer with packet id " + id + " for side " + side + ", id in cache is " + data.id); + buf.release(); + for (FriendlyByteBuf part : data.parts) { + if (part != buf) { + part.release(); + } + } + cache.remove(key); + } else { + buf.retain(); + data.parts.add(buf); + } + break; + case END: + if ((data = cache.get(key)) == null) { + LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " for side " + side); + buf.release(); + } else if (!data.id.equals(id)) { + LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " for side " + side + ", id in cache is " + data.id); + buf.release(); + for (FriendlyByteBuf part : data.parts) { + if (part != buf) { + part.release(); + } + } + cache.remove(key); + } else { + buf.retain(); + data.parts.add(buf); + } + if (data.parts.size() != data.partsExpected) { + LOGGER.warn("Received invalid END packet for SplitPacketTransformer with packet id " + id + " for side " + side + " with size " + data.parts + ", parts expected is " + data.partsExpected); + for (FriendlyByteBuf part : data.parts) { + if (part != buf) { + part.release(); + } + } + } else { + FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(data.parts.toArray(new ByteBuf[0]))); + sink.accept(side, data.id, byteBuf); + byteBuf.release(); + } + cache.remove(key); + break; + case ONLY: + sink.accept(side, id, buf); + break; + default: + throw new IllegalStateException("Illegal split packet header!"); + } + } + + @Override + public void outbound(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf, TransformationSink sink) { + int maxSize = (side == NetworkManager.Side.C2S ? 32767 : 1048576) - 1 - 10; + if (buf.readableBytes() <= maxSize) { + ByteBuf stateBuf = Unpooled.buffer(1); + stateBuf.writeByte(ONLY); + FriendlyByteBuf packetBuffer = new FriendlyByteBuf(Unpooled.wrappedBuffer(stateBuf, buf)); + sink.accept(side, id, packetBuffer); + } else { + int partSize = maxSize - 4; + int parts = Math.round(buf.readableBytes() / (float) partSize); + for (int i = 0; i < parts; i++) { + FriendlyByteBuf packetBuffer = new FriendlyByteBuf(Unpooled.buffer()); + if (i == 0) { + packetBuffer.writeByte(START); + packetBuffer.writeInt(parts); + } else if (i == parts - 1) { + packetBuffer.writeByte(END); + } else { + packetBuffer.writeByte(PART); + } + int next = Math.min(buf.readableBytes(), partSize); + packetBuffer.writeBytes(buf.retainedSlice(buf.readerIndex(), next)); + buf.skipBytes(next); + sink.accept(side, id, packetBuffer); + } + + } + buf.release(); + } +} diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinFallingBlockEntity.java b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinFallingBlockEntity.java index 9996d401..feb5a027 100644 --- a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinFallingBlockEntity.java +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinFallingBlockEntity.java @@ -1,3 +1,22 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + package dev.architectury.mixin.fabric; import dev.architectury.event.events.common.BlockEvent; diff --git a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java index 82c364a4..89aaf5cd 100644 --- a/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java +++ b/fabric/src/main/java/dev/architectury/networking/fabric/NetworkManagerImpl.java @@ -21,6 +21,8 @@ package dev.architectury.networking.fabric; import dev.architectury.networking.NetworkManager; import dev.architectury.networking.NetworkManager.NetworkReceiver; +import dev.architectury.networking.transformers.PacketSink; +import dev.architectury.networking.transformers.PacketTransformer; import dev.architectury.utils.Env; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -34,22 +36,55 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class NetworkManagerImpl { - public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, NetworkReceiver receiver) { + private static final Map C2S_RECEIVER = new HashMap<>(); + private static final Map S2C_RECEIVER = new HashMap<>(); + private static final Map C2S_TRANSFORMERS = new HashMap<>(); + private static final Map S2C_TRANSFORMERS = new HashMap<>(); + + public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { if (side == NetworkManager.Side.C2S) { - registerC2SReceiver(id, receiver); + registerC2SReceiver(id, packetTransformers, receiver); } else if (side == NetworkManager.Side.S2C) { - registerS2CReceiver(id, receiver); + registerS2CReceiver(id, packetTransformers, receiver); } } - private static void registerC2SReceiver(ResourceLocation id, NetworkReceiver receiver) { - ServerSidePacketRegistry.INSTANCE.register(id, (packetContext, buf) -> receiver.receive(buf, to(packetContext))); + private static void registerC2SReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { + C2S_RECEIVER.put(id, receiver); + PacketTransformer transformer = PacketTransformer.concat(packetTransformers); + ServerSidePacketRegistry.INSTANCE.register(id, (packetContext, buf) -> { + NetworkManager.PacketContext context = to(packetContext); + transformer.inbound(NetworkManager.Side.C2S, id, buf, context, (side, id1, buf1) -> { + NetworkReceiver networkReceiver = side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1); + if (networkReceiver == null) { + throw new IllegalArgumentException("Network Receiver not found! " + id1); + } + networkReceiver.receive(buf1, context); + }); + }); + C2S_TRANSFORMERS.put(id, transformer); } @Environment(EnvType.CLIENT) - private static void registerS2CReceiver(ResourceLocation id, NetworkReceiver receiver) { - ClientSidePacketRegistry.INSTANCE.register(id, (packetContext, buf) -> receiver.receive(buf, to(packetContext))); + private static void registerS2CReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { + S2C_RECEIVER.put(id, receiver); + PacketTransformer transformer = PacketTransformer.concat(packetTransformers); + ClientSidePacketRegistry.INSTANCE.register(id, (packetContext, buf) -> { + NetworkManager.PacketContext context = to(packetContext); + transformer.inbound(NetworkManager.Side.S2C, id, buf, context, (side, id1, buf1) -> { + NetworkReceiver networkReceiver = side == NetworkManager.Side.C2S ? C2S_RECEIVER.get(id1) : S2C_RECEIVER.get(id1); + if (networkReceiver == null) { + throw new IllegalArgumentException("Network Receiver not found! " + id1); + } + networkReceiver.receive(buf1, context); + }); + }); + S2C_TRANSFORMERS.put(id, transformer); } private static NetworkManager.PacketContext to(PacketContext context) { @@ -71,6 +106,17 @@ public class NetworkManagerImpl { }; } + public static void collectPackets(PacketSink sink, NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf) { + PacketTransformer transformer = side == NetworkManager.Side.C2S ? C2S_TRANSFORMERS.get(id) : S2C_TRANSFORMERS.get(id); + if (transformer != null) { + transformer.outbound(side, id, buf, (side1, id1, buf1) -> { + sink.accept(toPacket(side1, id1, buf1)); + }); + } else { + sink.accept(toPacket(side, id, buf)); + } + } + public static Packet toPacket(NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf) { if (side == NetworkManager.Side.C2S) { return toC2SPacket(id, buf); diff --git a/forge/src/main/java/dev/architectury/networking/forge/ClientNetworkingManager.java b/forge/src/main/java/dev/architectury/networking/forge/ClientNetworkingManager.java index 59f3f661..edc61985 100644 --- a/forge/src/main/java/dev/architectury/networking/forge/ClientNetworkingManager.java +++ b/forge/src/main/java/dev/architectury/networking/forge/ClientNetworkingManager.java @@ -30,15 +30,16 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fmllegacy.network.NetworkEvent; +import java.util.Collections; import java.util.Set; @OnlyIn(Dist.CLIENT) public class ClientNetworkingManager { public static void initClient() { - NetworkManagerImpl.CHANNEL.addListener(NetworkManagerImpl.createPacketHandler(NetworkEvent.ServerCustomPayloadEvent.class, NetworkManagerImpl.S2C)); + NetworkManagerImpl.CHANNEL.addListener(NetworkManagerImpl.createPacketHandler(NetworkEvent.ServerCustomPayloadEvent.class, NetworkManagerImpl.S2C_TRANSFORMERS)); MinecraftForge.EVENT_BUS.register(ClientNetworkingManager.class); - NetworkManagerImpl.registerS2CReceiver(NetworkManagerImpl.SYNC_IDS, (buffer, context) -> { + NetworkManagerImpl.registerS2CReceiver(NetworkManagerImpl.SYNC_IDS, Collections.emptyList(), (buffer, context) -> { Set receivables = NetworkManagerImpl.serverReceivables; int size = buffer.readInt(); receivables.clear(); diff --git a/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java b/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java index 6198942d..e5f2b5d0 100644 --- a/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java +++ b/forge/src/main/java/dev/architectury/networking/forge/NetworkManagerImpl.java @@ -24,6 +24,8 @@ import com.google.common.collect.*; import dev.architectury.forge.ArchitecturyForge; import dev.architectury.networking.NetworkManager; import dev.architectury.networking.NetworkManager.NetworkReceiver; +import dev.architectury.networking.transformers.PacketSink; +import dev.architectury.networking.transformers.PacketTransformer; import dev.architectury.utils.Env; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; @@ -48,6 +50,7 @@ import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -55,11 +58,11 @@ import java.util.function.Consumer; @Mod.EventBusSubscriber(modid = ArchitecturyForge.MOD_ID) public class NetworkManagerImpl { - public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, NetworkReceiver receiver) { + public static void registerReceiver(NetworkManager.Side side, ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { if (side == NetworkManager.Side.C2S) { - registerC2SReceiver(id, receiver); + registerC2SReceiver(id, packetTransformers, receiver); } else if (side == NetworkManager.Side.S2C) { - registerS2CReceiver(id, receiver); + registerS2CReceiver(id, packetTransformers, receiver); } } @@ -70,21 +73,34 @@ public class NetworkManagerImpl { return (side == NetworkManager.Side.C2S ? NetworkDirection.PLAY_TO_SERVER : NetworkDirection.PLAY_TO_CLIENT).buildPacket(Pair.of(packetBuffer, 0), CHANNEL_ID).getThis(); } + public static void collectPackets(PacketSink sink, NetworkManager.Side side, ResourceLocation id, FriendlyByteBuf buf) { + PacketTransformer transformer = side == NetworkManager.Side.C2S ? C2S_TRANSFORMERS.get(id) : S2C_TRANSFORMERS.get(id); + if (transformer != null) { + transformer.outbound(side, id, buf, (side1, id1, buf1) -> { + sink.accept(toPacket(side1, id1, buf1)); + }); + } else { + sink.accept(toPacket(side, id, buf)); + } + } + private static final Logger LOGGER = LogManager.getLogger(); private static final ResourceLocation CHANNEL_ID = new ResourceLocation("architectury:network"); static final ResourceLocation SYNC_IDS = new ResourceLocation("architectury:sync_ids"); static final EventNetworkChannel CHANNEL = NetworkRegistry.newEventChannel(CHANNEL_ID, () -> "1", version -> true, version -> true); static final Map S2C = Maps.newHashMap(); static final Map C2S = Maps.newHashMap(); + static final Map S2C_TRANSFORMERS = Maps.newHashMap(); + static final Map C2S_TRANSFORMERS = Maps.newHashMap(); static final Set serverReceivables = Sets.newHashSet(); private static final Multimap clientReceivables = Multimaps.newMultimap(Maps.newHashMap(), Sets::newHashSet); static { - CHANNEL.addListener(createPacketHandler(NetworkEvent.ClientCustomPayloadEvent.class, C2S)); + CHANNEL.addListener(createPacketHandler(NetworkEvent.ClientCustomPayloadEvent.class, C2S_TRANSFORMERS)); DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ClientNetworkingManager::initClient); - registerC2SReceiver(SYNC_IDS, (buffer, context) -> { + registerC2SReceiver(SYNC_IDS, Collections.emptyList(), (buffer, context) -> { Set receivables = (Set) clientReceivables.get(context.getPlayer()); int size = buffer.readInt(); receivables.clear(); @@ -94,7 +110,7 @@ public class NetworkManagerImpl { }); } - static Consumer createPacketHandler(Class clazz, Map map) { + static Consumer createPacketHandler(Class clazz, Map map) { return event -> { if (event.getClass() != clazz) return; NetworkEvent.Context context = event.getSource().get(); @@ -102,10 +118,11 @@ public class NetworkManagerImpl { FriendlyByteBuf buffer = event.getPayload(); if (buffer == null) return; ResourceLocation type = buffer.readResourceLocation(); - NetworkReceiver receiver = map.get(type); + PacketTransformer transformer = map.get(type); - if (receiver != null) { - receiver.receive(buffer, new NetworkManager.PacketContext() { + if (transformer != null) { + NetworkManager.Side side = context.getDirection().getReceptionSide() == LogicalSide.CLIENT ? NetworkManager.Side.S2C : NetworkManager.Side.C2S; + NetworkManager.PacketContext packetContext = new NetworkManager.PacketContext() { @Override public Player getPlayer() { return getEnvironment() == Env.CLIENT ? getClientPlayer() : context.getSender(); @@ -124,6 +141,13 @@ public class NetworkManagerImpl { private Player getClientPlayer() { return DistExecutor.unsafeCallWhenOn(Dist.CLIENT, () -> ClientNetworkingManager::getClientPlayer); } + }; + transformer.inbound(side, type, buffer, packetContext, (side1, id1, buf1) -> { + NetworkReceiver networkReceiver = side == NetworkManager.Side.C2S ? C2S.get(id1) : S2C.get(id1); + if (networkReceiver == null) { + throw new IllegalArgumentException("Network Receiver not found! " + id1); + } + networkReceiver.receive(buf1, packetContext); }); } else { LOGGER.error("Unknown message ID: " + type); @@ -134,12 +158,16 @@ public class NetworkManagerImpl { } @OnlyIn(Dist.CLIENT) - public static void registerS2CReceiver(ResourceLocation id, NetworkReceiver receiver) { + public static void registerS2CReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { S2C.put(id, receiver); + PacketTransformer transformer = PacketTransformer.concat(packetTransformers); + S2C_TRANSFORMERS.put(id, transformer); } - public static void registerC2SReceiver(ResourceLocation id, NetworkReceiver receiver) { + public static void registerC2SReceiver(ResourceLocation id, List packetTransformers, NetworkReceiver receiver) { C2S.put(id, receiver); + PacketTransformer transformer = PacketTransformer.concat(packetTransformers); + C2S_TRANSFORMERS.put(id, transformer); } public static boolean canServerReceive(ResourceLocation id) { diff --git a/testmod-common/src/main/java/dev/architectury/test/TestMod.java b/testmod-common/src/main/java/dev/architectury/test/TestMod.java index 03ce539d..84d623de 100644 --- a/testmod-common/src/main/java/dev/architectury/test/TestMod.java +++ b/testmod-common/src/main/java/dev/architectury/test/TestMod.java @@ -27,9 +27,9 @@ import dev.architectury.test.debug.client.ClientOverlayMessageSink; import dev.architectury.test.entity.TestEntity; import dev.architectury.test.events.DebugEvents; import dev.architectury.test.gamerule.TestGameRules; +import dev.architectury.test.item.TestBlockInteractions; import dev.architectury.test.networking.TestModNet; import dev.architectury.test.particle.TestParticles; -import dev.architectury.test.item.TestBlockInteractions; import dev.architectury.test.registry.TestRegistries; import dev.architectury.test.registry.client.TestKeybinds; import dev.architectury.test.tags.TestTags; @@ -62,6 +62,7 @@ public class TestMod { @Environment(EnvType.CLIENT) public static void initializeClient() { TestKeybinds.initialize(); + TestModNet.initializeClient(); EntityRendererRegistry.register(TestEntity.TYPE, context -> new MinecartRenderer<>(context, ModelLayers.MINECART)); } diff --git a/testmod-common/src/main/java/dev/architectury/test/networking/TestModNet.java b/testmod-common/src/main/java/dev/architectury/test/networking/TestModNet.java index e9ee209f..c52677dd 100644 --- a/testmod-common/src/main/java/dev/architectury/test/networking/TestModNet.java +++ b/testmod-common/src/main/java/dev/architectury/test/networking/TestModNet.java @@ -19,9 +19,18 @@ package dev.architectury.test.networking; +import dev.architectury.event.events.client.ClientPlayerEvent; +import dev.architectury.networking.NetworkManager; import dev.architectury.networking.simple.MessageType; import dev.architectury.networking.simple.SimpleNetworkManager; +import dev.architectury.networking.transformers.SplitPacketTransformer; import dev.architectury.test.TestMod; +import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collections; public interface TestModNet { SimpleNetworkManager NET = SimpleNetworkManager.create(TestMod.MOD_ID); @@ -31,7 +40,33 @@ public interface TestModNet { // An example Server to Client message MessageType SYNC_DATA = NET.registerS2C("sync_data", SyncDataMessage::new); + ResourceLocation BIG_DATA = new ResourceLocation(TestMod.MOD_ID, "big_data"); + String BIG_STRING = StringUtils.repeat('a', 60000); static void initialize() { + NetworkManager.registerReceiver(NetworkManager.Side.C2S, BIG_DATA, Collections.singletonList(new SplitPacketTransformer()), (buf, context) -> { + String utf = buf.readUtf(Integer.MAX_VALUE / 4); + if (utf.equals(BIG_STRING)) { + TestMod.SINK.accept("Network Split Packets worked"); + } else { + throw new AssertionError(utf); + } + utf = buf.readUtf(Integer.MAX_VALUE / 4); + if (utf.equals(BIG_STRING)) { + TestMod.SINK.accept("Network Split Packets worked"); + } else { + throw new AssertionError(utf); + } + }); + } + + static void initializeClient() { + ClientPlayerEvent.CLIENT_PLAYER_JOIN.register(player -> { + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + buf.writeUtf(BIG_STRING, Integer.MAX_VALUE / 4); + // write twice + buf.writeUtf(BIG_STRING, Integer.MAX_VALUE / 4); + NetworkManager.sendToServer(BIG_DATA, buf); + }); } } \ No newline at end of file