Extract the networking wrapper to a separate package.

This commit is contained in:
Sollace 2021-06-26 12:40:35 +02:00
parent fdcb9508a8
commit 5b398c27c7
13 changed files with 227 additions and 116 deletions

View file

@ -11,6 +11,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.util.math.BlockPos;
@ -120,13 +121,17 @@ public class BlockDestructionManager {
MsgBlockDestruction msg = new MsgBlockDestruction(values);
if (Channel.toBuffer(msg).writerIndex() > 1048576) {
if (msg.toBuffer().writerIndex() > 1048576) {
throw new IllegalStateException("Payload may not be larger than 1048576 bytes. Here's what we were trying to send: ["
+ values.size() + "]\n"
+ Arrays.toString(values.values().stream().mapToInt(Integer::intValue).toArray()));
}
players.forEach(player -> Channel.SERVER_BLOCK_DESTRUCTION.send(player, msg));
players.forEach(player -> {
if (player instanceof ServerPlayerEntity) {
Channel.SERVER_BLOCK_DESTRUCTION.send((ServerPlayerEntity)player, msg);
}
});
}
}
}

View file

@ -160,7 +160,7 @@ public class Pony extends Living<PlayerEntity> implements Transmittable, Copieab
if (entity instanceof ServerPlayerEntity) {
MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(full, this);
Channel.SERVER_PLAYER_CAPABILITIES.send(entity, packet);
Channel.SERVER_PLAYER_CAPABILITIES.send((ServerPlayerEntity)entity, packet);
Channel.SERVER_OTHER_PLAYER_CAPABILITIES.send(entity.world, packet);
}
}

View file

@ -1,114 +1,20 @@
package com.minelittlepony.unicopia.network;
import java.util.function.Function;
import com.minelittlepony.unicopia.util.network.S2CBroadcastPacketType;
import com.minelittlepony.unicopia.util.network.C2SPacketType;
import com.minelittlepony.unicopia.util.network.S2CPacketType;
import com.minelittlepony.unicopia.util.network.SimpleNetworking;
import com.google.common.base.Preconditions;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
public interface Channel {
SPacketType<MsgPlayerAbility<?>> CLIENT_PLAYER_ABILITY = clientToServer(new Identifier("unicopia", "player_ability"), MsgPlayerAbility::new);
SPacketType<MsgRequestCapabilities> CLIENT_REQUEST_CAPABILITIES = clientToServer(new Identifier("unicopia", "request_capabilities"), MsgRequestCapabilities::new);
S2CPacketType<MsgPlayerAbility<?>> CLIENT_PLAYER_ABILITY = SimpleNetworking.clientToServer(new Identifier("unicopia", "player_ability"), MsgPlayerAbility::new);
S2CPacketType<MsgRequestCapabilities> CLIENT_REQUEST_CAPABILITIES = SimpleNetworking.clientToServer(new Identifier("unicopia", "request_capabilities"), MsgRequestCapabilities::new);
S2CBroadcastPacketType<MsgOtherPlayerCapabilities> SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new);
CPacketType<MsgPlayerCapabilities> SERVER_PLAYER_CAPABILITIES = serverToClient(new Identifier("unicopia", "player_capabilities"), MsgPlayerCapabilities::new);
BPacketType<MsgOtherPlayerCapabilities> SERVER_OTHER_PLAYER_CAPABILITIES = serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new);
CPacketType<MsgSpawnProjectile> SERVER_SPAWN_PROJECTILE = serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new);
CPacketType<MsgBlockDestruction> SERVER_BLOCK_DESTRUCTION = serverToClient(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new);
C2SPacketType<MsgPlayerCapabilities> SERVER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(new Identifier("unicopia", "player_capabilities"), MsgPlayerCapabilities::new);
C2SPacketType<MsgSpawnProjectile> SERVER_SPAWN_PROJECTILE = SimpleNetworking.serverToClient(new Identifier("unicopia", "projectile_entity"), MsgSpawnProjectile::new);
C2SPacketType<MsgBlockDestruction> SERVER_BLOCK_DESTRUCTION = SimpleNetworking.serverToClient(new Identifier("unicopia", "block_destruction"), MsgBlockDestruction::new);
static void bootstrap() { }
static <T extends Packet> SPacketType<T> clientToServer(Identifier id, Function<PacketByteBuf, T> factory) {
ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buffer, responder) -> {
T packet = factory.apply(buffer);
server.execute(() -> packet.handle(player));
});
return () -> id;
}
static <T extends Packet> CPacketType<T> serverToClient(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
static <T extends Packet> BPacketType<T> serverToClients(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
/**
* A broadcast packet type. Sent by the server to all surrounding players.
*/
interface BPacketType<T extends Packet> {
Identifier getId();
default void send(World world, T packet) {
world.getPlayers().forEach(player -> {
if (player instanceof ServerPlayerEntity) {
ServerPlayNetworking.send((ServerPlayerEntity)player, getId(), toBuffer(packet));
}
});
}
}
/**
* A client packet type. Sent by the server to a specific player.
*/
interface CPacketType<T extends Packet> {
Identifier getId();
default void send(PlayerEntity recipient, T packet) {
ServerPlayNetworking.send(((ServerPlayerEntity)recipient), getId(), toBuffer(packet));
}
default net.minecraft.network.Packet<?> toPacket(T packet) {
return ServerPlayNetworking.createS2CPacket(getId(), toBuffer(packet));
}
}
/**
* A server packet type. Sent by the client to the server.
*/
interface SPacketType<T extends Packet> {
Identifier getId();
default void send(T packet) {
Preconditions.checkState(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT, "Client packet send called by the server");
ClientPlayNetworking.send(getId(), toBuffer(packet));
}
}
interface Packet {
void handle(PlayerEntity sender);
void toBuffer(PacketByteBuf buffer);
}
static PacketByteBuf toBuffer(Packet packet) {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
packet.toBuffer(buf);
return buf;
}
class ClientProxy {
static <T extends Packet> void register(Identifier id, Function<PacketByteBuf, T> factory) {
ClientPlayNetworking.registerGlobalReceiver(id, (client, ignore1, buffer, ignore2) -> {
T packet = factory.apply(buffer);
client.execute(() -> packet.handle(client.player));
});
}
}
}

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.client.ClientBlockDestructionManager;
import com.minelittlepony.unicopia.util.network.Packet;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@ -8,7 +9,7 @@ import net.minecraft.network.PacketByteBuf;
import net.minecraft.client.MinecraftClient;
import net.minecraft.entity.player.PlayerEntity;
public class MsgBlockDestruction implements Channel.Packet {
public class MsgBlockDestruction implements Packet<PlayerEntity> {
private final Long2ObjectMap<Integer> destructions;

View file

@ -3,13 +3,14 @@ package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.ability.Ability;
import com.minelittlepony.unicopia.ability.data.Hit;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import com.minelittlepony.unicopia.ability.Abilities;
import net.minecraft.util.Identifier;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
public class MsgPlayerAbility<T extends Hit> implements Channel.Packet {
public class MsgPlayerAbility<T extends Hit> implements Packet<ServerPlayerEntity> {
private final Ability<T> power;
@ -33,7 +34,7 @@ public class MsgPlayerAbility<T extends Hit> implements Channel.Packet {
}
@Override
public void handle(PlayerEntity sender) {
public void handle(ServerPlayerEntity sender) {
Pony player = Pony.of(sender);
if (player == null) {
return;

View file

@ -8,6 +8,7 @@ import java.util.UUID;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream;
@ -16,7 +17,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
public class MsgPlayerCapabilities implements Channel.Packet {
public class MsgPlayerCapabilities implements Packet<PlayerEntity> {
protected final UUID playerId;

View file

@ -3,12 +3,13 @@ package com.minelittlepony.unicopia.network;
import com.minelittlepony.unicopia.Race;
import com.minelittlepony.unicopia.WorldTribeManager;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.util.network.Packet;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
public class MsgRequestCapabilities implements Channel.Packet {
public class MsgRequestCapabilities implements Packet<ServerPlayerEntity> {
private final Race clientPreferredRace;
@ -26,7 +27,7 @@ public class MsgRequestCapabilities implements Channel.Packet {
}
@Override
public void handle(PlayerEntity sender) {
public void handle(ServerPlayerEntity sender) {
Pony player = Pony.of(sender);
Race worldDefaultRace = WorldTribeManager.forWorld((ServerWorld)player.getWorld()).getDefaultRace();

View file

@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.Optional;
import com.minelittlepony.unicopia.Owned;
import com.minelittlepony.unicopia.util.network.Packet;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@ -14,7 +15,7 @@ import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.EntitySpawnS2CPacket;
public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Channel.Packet {
public class MsgSpawnProjectile extends EntitySpawnS2CPacket implements Packet<PlayerEntity> {
MsgSpawnProjectile(PacketByteBuf buffer) {
try {

View file

@ -0,0 +1,21 @@
package com.minelittlepony.unicopia.util.network;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A client packet type. Sent by the server to a specific player.
*/
public interface C2SPacketType<T extends Packet<PlayerEntity>> {
Identifier getId();
default void send(ServerPlayerEntity recipient, T packet) {
ServerPlayNetworking.send(recipient, getId(), packet.toBuffer());
}
default net.minecraft.network.Packet<?> toPacket(T packet) {
return ServerPlayNetworking.createS2CPacket(getId(), packet.toBuffer());
}
}

View file

@ -0,0 +1,35 @@
package com.minelittlepony.unicopia.util.network;
import io.netty.buffer.Unpooled;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
/**
* Represents a message that can be either send from the client to the server or back.
*/
public interface Packet<P extends PlayerEntity> {
/**
* Called to handle this packet on the receiving end.
*
* @param sender The player who initially sent this packet.
*/
void handle(P sender);
/**
* Writes this packet to the supplied buffer prior to transmission.
*
* @param buffer The buffer to write to.
*/
void toBuffer(PacketByteBuf buffer);
/**
* Writes this packet to a new buffer.
*
* @return The resulting buffer for transmission
*/
default PacketByteBuf toBuffer() {
PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
toBuffer(buf);
return buf;
}
}

View file

@ -0,0 +1,22 @@
package com.minelittlepony.unicopia.util.network;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
/**
* A broadcast packet type. Sent by the server to all surrounding players.
*/
public interface S2CBroadcastPacketType<T extends Packet<PlayerEntity>> {
Identifier getId();
default void send(World world, T packet) {
world.getPlayers().forEach(player -> {
if (player instanceof ServerPlayerEntity) {
ServerPlayNetworking.send((ServerPlayerEntity)player, getId(), packet.toBuffer());
}
});
}
}

View file

@ -0,0 +1,21 @@
package com.minelittlepony.unicopia.util.network;
import com.google.common.base.Preconditions;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A server packet type. Sent by the client to the server.
*/
public interface S2CPacketType<T extends Packet<ServerPlayerEntity>> {
Identifier getId();
default void send(T packet) {
Preconditions.checkState(FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT, "Client packet send called by the server");
ClientPlayNetworking.send(getId(), packet.toBuffer());
}
}

View file

@ -0,0 +1,96 @@
package com.minelittlepony.unicopia.util.network;
import java.util.function.Function;
import net.fabricmc.api.EnvType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.Identifier;
/**
* A simplified, side-agnostic, and declaritive wrapper around {@link ServerPlayNetworking} and {@link ClientPlayNetworking}
* designed to bring networking in line with the declaritive/registered nature of other parts of Mojang's echosystem.
* <p>
* It is safe to call these methods from either the client or the server, so modders can implement a
* single static <code>PacketTypes</code> class with which they can easily send packets without worrying
* about the complexities of the network thread, which side to register a global receiver on,
* which method to use to send, or even whether their receiver is registered for a given player or not.
* <p>
* All of the above is handled in a black-box style by this class.
* <p>
* <ul>
* <li>Packets are automatically reigstered on the appropriate sides.</li>
* <li>Sending is done in the same way by calling `send` on your packet type.</li>
* <li>Your packet's <code>handle</code> method is executed on the main thread where it is safe to interact with the world.</li>
*/
public final class SimpleNetworking {
private SimpleNetworking() {throw new RuntimeException("new SimpleNetworking()");}
/**
* Registers a packet type for transmisison to the server.
* <p>
* The returned handle can be used by the client to send messages to the active minecraft server.
* <p>
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<ServerPlayerEntity>> S2CPacketType<T> clientToServer(Identifier id, Function<PacketByteBuf, T> factory) {
ServerPlayNetworking.registerGlobalReceiver(id, (server, player, handler, buffer, responder) -> {
T packet = factory.apply(buffer);
server.execute(() -> packet.handle(player));
});
return () -> id;
}
/**
* Registers a packet type for transmission to the client.
*
* The returned handle can be used by the server to send messages to a given recipient.
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<PlayerEntity>> C2SPacketType<T> serverToClient(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
/**
* Registers a packet type for transmission to all clients.
*
* The returned handle can be used by the server to broadcast a message to all connected clients in a given dimension.
*
* @param <T> The type of packet to implement
* @param id The message's unique used for serialization
* @param factory A constructor returning new instances of the packet type
*
* @return A registered PacketType
*/
public static <T extends Packet<PlayerEntity>> S2CBroadcastPacketType<T> serverToClients(Identifier id, Function<PacketByteBuf, T> factory) {
if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
ClientProxy.register(id, factory);
}
return () -> id;
}
// Fabric's APIs are not side-agnostic.
// We punt this to a separate class file to keep it from being eager-loaded on a server environment.
private static final class ClientProxy {
private ClientProxy() {throw new RuntimeException("new ClientProxy()");}
public static <T extends Packet<PlayerEntity>> void register(Identifier id, Function<PacketByteBuf, T> factory) {
ClientPlayNetworking.registerGlobalReceiver(id, (client, ignore1, buffer, ignore2) -> {
T packet = factory.apply(buffer);
client.execute(() -> packet.handle(client.player));
});
}
}
}