Skip to content

Commit

Permalink
Merge pull request #70 from gungun974/network
Browse files Browse the repository at this point in the history
Make the networking simpler and universal for everyone
  • Loading branch information
MartinSVK12 authored Feb 11, 2025
2 parents d1f3f55 + cbdea97 commit ef1b1f8
Show file tree
Hide file tree
Showing 9 changed files with 680 additions and 1 deletion.
23 changes: 23 additions & 0 deletions src/main/java/turniplabs/halplibe/helper/EnvironmentHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package turniplabs.halplibe.helper;

import net.minecraft.client.Minecraft;
import net.minecraft.core.Global;

public class EnvironmentHelper {
public static boolean isServerEnvironment() {
return Global.isServer;
}

public static boolean isSinglePlayer() {
if (Global.isServer) {
return false;
}

return !Minecraft.getMinecraft().isMultiplayerWorld();
}

public static boolean isClientWorld() {
return !isSinglePlayer() && !isServerEnvironment();
}

}
275 changes: 275 additions & 0 deletions src/main/java/turniplabs/halplibe/helper/network/NetworkHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
package turniplabs.halplibe.helper.network;

import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.Minecraft;
import net.minecraft.core.entity.player.Player;
import net.minecraft.core.net.packet.Packet;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.entity.player.PlayerServer;
import org.jetbrains.annotations.NotNull;
import turniplabs.halplibe.helper.EnvironmentHelper;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

public final class NetworkHandler
{
private static final List<Supplier<NetworkMessage>> messagesToRegisterForServer = new LinkedList<>(Collections.singletonList(
MessageIdsNetworkMessage::new
));

private static final Map<Short, BiConsumer<NetworkMessage.NetworkContext, UniversalPacket>> packetReaders = new HashMap<>();
private static final Map<Class<?>, Short> packetIds = new HashMap<>();

private NetworkHandler()
{
}

public static void setup()
{
Packet.addMapping (88, true, true, UniversalPacket.class );

register();
}

public static void register()
{
packetReaders.clear();
packetIds.clear();

for (Supplier<NetworkMessage> networkMessage : messagesToRegisterForServer) {
addNetworkMessage(networkMessage);
}
}

public static void receiveUniversalPacket(NetworkMessage.NetworkContext context, UniversalPacket buffer )
{
short type = buffer.readShort();

if (!packetReaders.containsKey(type)) {
return;
}

packetReaders.get( type )
.accept( context, buffer );
}

/**
* Register a NetworkMessage, and a thread-unsafe handler for it.
*
* @param factory The factory for this type of message.
*/
@SuppressWarnings({"unused"})
public static void registerNetworkMessage( Supplier<NetworkMessage> factory )
{
messagesToRegisterForServer.add(factory);
}

/**
* Register a NetworkMessage, and a thread-unsafe handler for it.
*
* @param <T> The type of the NetworkMessage to send.
* @param factory The factory for this type of message.
*/
@SuppressWarnings({"unused"})
public static <T extends NetworkMessage> void addNetworkMessage( Supplier<T> factory )
{
registerNetworkMessage((short) packetIds.size(), factory);
}

/**
* Register a NetworkMessage, and a thread-unsafe handler for it.
*
* @param <T> The type of the NetworkMessage to send.
* @param id The identifier for this message type
* @param factory The factory for this type of message.
*/
@SuppressWarnings({"unused"})
private static <T extends NetworkMessage> void registerNetworkMessage( short id, Supplier<T> factory )
{
registerNetworkMessage( id, getType( factory ), buf -> {
T instance = factory.get();
instance.decodeFromUniversalPacket( buf );
return instance;
} );
}

/**
* Register a NetworkMessage, and a thread-unsafe handler for it.
*
* @param <T> The type of the NetworkMessage to send.
* @param type The class of the type of message to send.
* @param id The identifier for this message type
* @param decoder The factory for this type of message.
*/
private static <T extends NetworkMessage> void registerNetworkMessage( short id, Class<T> type, Function<UniversalPacket, T> decoder )
{
packetIds.put( type, id );
packetReaders.put( id, ( context, buf ) -> {
T result = decoder.apply( buf );
result.handle(context);
} );
}

@SuppressWarnings( "unchecked" )
private static <T> Class<T> getType( Supplier<T> supplier )
{
return (Class<T>) supplier.get()
.getClass();
}

private static UniversalPacket encode(NetworkMessage message )
{
UniversalPacket buf = new UniversalPacket();
buf.writeShort( packetIds.get( message.getClass() ) );
message.encodeToUniversalPacket( buf );
return buf;
}

@Environment(EnvType.CLIENT)
private static void sendToPlayerLocal(NetworkMessage message)
{
message.handle(new NetworkMessage.NetworkContext(Minecraft.getMinecraft().thePlayer));
}

@Environment(EnvType.SERVER)
private static void sendToPlayerServer(Player player, NetworkMessage message)
{
((PlayerServer)player).playerNetServerHandler.sendPacket(encode(message));
}

@Environment(EnvType.SERVER)
public static void sendToPlayerMessagesConfiguration(Player player)
{
((PlayerServer)player).playerNetServerHandler.sendPacket(encode(new MessageIdsNetworkMessage(packetIds)));
}

/**
* Send a NetworkMessage to a specific Player from the server
* If we are in SinglePlayer this will skip encoding and directly call the message handle
*/
@SuppressWarnings({"unused"})
public static void sendToPlayer(Player player, NetworkMessage message )
{
if (!EnvironmentHelper.isServerEnvironment()){
sendToPlayerLocal(message);
return;
}
sendToPlayerServer(player, message);
}

/**
* Send a NetworkMessage to all Players from the server
* If we are in SinglePlayer this will skip encoding and directly call the message handle
*/
@SuppressWarnings({"unused"})
public static void sendToAllPlayers( NetworkMessage message )
{
if (!EnvironmentHelper.isServerEnvironment()){
sendToPlayerLocal(message);
return;
}
MinecraftServer.getInstance().playerList.sendPacketToAllPlayers(encode(message));
}

/**
* Send a NetworkMessage to the Server from the player
* If we are in SinglePlayer this will skip encoding and directly call the message handle
*/
@SuppressWarnings({"unused"})
@Environment( EnvType.CLIENT )
public static void sendToServer( NetworkMessage message )
{
if (EnvironmentHelper.isSinglePlayer()){
sendToPlayerLocal(message);
return;
}
Minecraft.getMinecraft().getSendQueue().addToSendQueue(encode(message));
}

/**
* Send a NetworkMessage to all Players around a block from the server
* If we are in SinglePlayer this will skip encoding and directly call the message handle
*/
@SuppressWarnings({"unused"})
public static void sendToAllAround(double x, double y, double z, double radius, int dimension, NetworkMessage message )
{
if (!EnvironmentHelper.isServerEnvironment()){
sendToPlayerLocal(message);
return;
}
MinecraftServer.getInstance().playerList.sendPacketToPlayersAroundPoint(x, y, z, radius, dimension, encode(message));
}

private static class MessageIdsNetworkMessage implements NetworkMessage{
Map<Class<?>, Short> packetIds;

public MessageIdsNetworkMessage() {}

public MessageIdsNetworkMessage(Map<Class<?>, Short> packetIds) {
this.packetIds = packetIds;
}

@Override
public void encodeToUniversalPacket(@NotNull UniversalPacket packet) {
packet.writeShort((short) packetIds.size());

for (Map.Entry<Class<?>, Short> entry : packetIds.entrySet()) {
packet.writeShort(entry.getValue());
packet.writeString(entry.getKey().getName());
}
}

@Override
public void decodeFromUniversalPacket(@NotNull UniversalPacket packet) {
this.packetIds = new HashMap<>();

final short size = packet.readShort();

try {
for (int i = 0; i < size; i++) {
final short id = packet.readShort();
final Class<?> messageClass = Class.forName(packet.readString());

this.packetIds.put(messageClass, id);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

@Override
public void handle(NetworkContext context) {
if (EnvironmentHelper.isServerEnvironment()) {
return;
}

try {
NetworkHandler.packetReaders.clear();
NetworkHandler.packetIds.clear();

for (Map.Entry<Class<?>, Short> entry : packetIds.entrySet()) {
Class<?> klass = entry.getKey();
if (NetworkMessage.class.isAssignableFrom(klass)) {
Supplier<NetworkMessage> supplier = () -> {
try {
return (NetworkMessage) klass.getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
};
NetworkHandler.registerNetworkMessage(entry.getValue(), supplier);
} else {
throw new IllegalArgumentException("Class " + klass.getName() + " does not extend NetworkMessage");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package turniplabs.halplibe.helper.network;

import net.minecraft.core.entity.player.Player;

import javax.annotation.Nonnull;

public interface NetworkMessage {
/**
* Encode the UniversalPacket into your NetworkMessage.
* This may be called on any thread, so this should be a pure operation.
*
* @param packet The packet to write data to.
*/
void encodeToUniversalPacket(@Nonnull UniversalPacket packet );

/**
* Decode the UniversalPacket into your NetworkMessage.
* This may be called on any thread, so this should be a pure operation.
*
* @param packet The packet to read data from.
*/
void decodeFromUniversalPacket(@Nonnull UniversalPacket packet );

/**
* Handle this {@link NetworkMessage}.
*
* @param context An intermediary representation of Packet handler common on both Client and Server environment.
*/
void handle(NetworkContext context);

class NetworkContext {
/**
* The player that send the NetworkPacket to the handle
*/
public Player player;

public NetworkContext(Player player) {
this.player = player;
}
}

}
Loading

0 comments on commit ef1b1f8

Please sign in to comment.