Skip to content

Instantly share code, notes, and snippets.

@Vazkii
Last active March 8, 2021 20:37
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vazkii/13e0ce45577bbae49362 to your computer and use it in GitHub Desktop.
Save Vazkii/13e0ce45577bbae49362 to your computer and use it in GitHub Desktop.
Plug'n'Play Packet System
This is a super simple packet system.
You drop the Message class in your mod and make your messages extend it.
Then you put fields in your messages and they get transmitted across.
Transient, final and static fields are ignored.
The Message class must be able to access the fields so use the proper visibility.
You can override handleMessage to have stuff happen when the message arrives.
Tada! :D
Supports all primitives, String, NBTTagCompound, ItemStack and BlockPos.
More classes can be added, as you see right at the start of Message.java.
Requires Java 8.
/**
* This class was created by <Vazkii>. It's distributed as
* part of the Psi Mod. Get the Source Code in github:
* https://github.com/Vazkii/Psi
*
* Psi is Open Source and distributed under the
* CC-BY-NC-SA 3.0 License: https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB
*
* File Created @ [11/01/2016, 22:00:30 (GMT)]
*/
package vazkii.psi.common.network;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import org.apache.commons.lang3.tuple.Pair;
import io.netty.buffer.ByteBuf;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockPos;
import net.minecraftforge.fml.common.network.ByteBufUtils;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
public class Message<REQ extends Message> implements Serializable, IMessage, IMessageHandler<REQ, IMessage> {
private static final HashMap<Class, Pair<Reader, Writer>> handlers = new HashMap();
private static final HashMap<Class, Field[]> fieldCache = new HashMap();
static {
map(byte.class, Message::readByte, Message::writeByte);
map(short.class, Message::readShort, Message::writeShort);
map(int.class, Message::readInt, Message::writeInt);
map(long.class, Message::readLong, Message::writeLong);
map(float.class, Message::readFloat, Message::writeFloat);
map(double.class, Message::readDouble, Message::writeDouble);
map(boolean.class, Message::readBoolean, Message::writeBoolean);
map(char.class, Message::readChar, Message::writeChar);
map(String.class, Message::readString, Message::writeString);
map(NBTTagCompound.class, Message::readNBT, Message::writeNBT);
map(ItemStack.class, Message::readItemStack, Message::writeItemStack);
map(BlockPos.class, Message::readBlockPos, Message::writeBlockPos);
}
// The thing you override!
public IMessage handleMessage(MessageContext context) {
return null;
}
@Override
public final IMessage onMessage(REQ message, MessageContext context) {
return message.handleMessage(context);
}
@Override
public final void fromBytes(ByteBuf buf) {
try {
Class<?> clazz = getClass();
Field[] clFields = getClassFields(clazz);
for(Field f : clFields) {
Class<?> type = f.getType();
if(acceptField(f, type))
readField(f, type, buf);
}
} catch(Exception e) {
throw new RuntimeException("Error at reading packet " + this, e);
}
}
@Override
public final void toBytes(ByteBuf buf) {
try {
Class<?> clazz = getClass();
Field[] clFields = getClassFields(clazz);
for(Field f : clFields) {
Class<?> type = f.getType();
if(acceptField(f, type))
writeField(f, type, buf);
}
} catch(Exception e) {
throw new RuntimeException("Error at writing packet " + this, e);
}
}
private static Field[] getClassFields(Class<?> clazz) {
if(fieldCache.containsValue(clazz))
return fieldCache.get(clazz);
else {
Field[] fields = clazz.getFields();
Arrays.sort(fields, (Field f1, Field f2) -> {
return f1.getName().compareTo(f2.getName());
});
fieldCache.put(clazz, fields);
return fields;
}
}
private final void writeField(Field f, Class clazz, ByteBuf buf) throws IllegalArgumentException, IllegalAccessException {
Pair<Reader, Writer> handler = getHandler(clazz);
handler.getRight().write(f.get(this), buf);
}
private final void readField(Field f, Class clazz, ByteBuf buf) throws IllegalArgumentException, IllegalAccessException {
Pair<Reader, Writer> handler = getHandler(clazz);
f.set(this, handler.getLeft().read(buf));
}
private static Pair<Reader, Writer> getHandler(Class<?> clazz) {
Pair<Reader, Writer> pair = handlers.get(clazz);
if(pair == null)
throw new RuntimeException("No R/W handler for " + clazz);
return pair;
}
private static boolean acceptField(Field f, Class<?> type) {
int mods = f.getModifiers();
if(Modifier.isFinal(mods) || Modifier.isStatic(mods) || Modifier.isTransient(mods))
return false;
return handlers.containsKey(type);
}
private static <T extends Object>void map(Class<T> type, Reader<T> reader, Writer<T> writer) {
handlers.put(type, Pair.of(reader, writer));
}
private static byte readByte(ByteBuf buf) {
return buf.readByte();
}
private static void writeByte(byte b, ByteBuf buf) {
buf.writeByte(b);
}
private static short readShort(ByteBuf buf) {
return buf.readShort();
}
private static void writeShort(short s, ByteBuf buf) {
buf.writeShort(s);
}
private static int readInt(ByteBuf buf) {
return buf.readInt();
}
private static void writeInt(int i, ByteBuf buf) {
buf.writeInt(i);
}
private static long readLong(ByteBuf buf) {
return buf.readLong();
}
private static void writeLong(long l, ByteBuf buf) {
buf.writeLong(l);
}
private static float readFloat(ByteBuf buf) {
return buf.readFloat();
}
private static void writeFloat(float f, ByteBuf buf) {
buf.writeFloat(f);
}
private static double readDouble(ByteBuf buf) {
return buf.readDouble();
}
private static void writeDouble(double d, ByteBuf buf) {
buf.writeDouble(d);
}
private static boolean readBoolean(ByteBuf buf) {
return buf.readBoolean();
}
private static void writeBoolean(boolean b, ByteBuf buf) {
buf.writeBoolean(b);
}
private static char readChar(ByteBuf buf) {
return buf.readChar();
}
private static void writeChar(char c, ByteBuf buf) {
buf.writeChar(c);
}
private static String readString(ByteBuf buf) {
return ByteBufUtils.readUTF8String(buf);
}
private static void writeString(String s, ByteBuf buf) {
ByteBufUtils.writeUTF8String(buf, s);
}
private static NBTTagCompound readNBT(ByteBuf buf) {
return ByteBufUtils.readTag(buf);
}
private static void writeNBT(NBTTagCompound cmp, ByteBuf buf) {
ByteBufUtils.writeTag(buf, cmp);
}
private static ItemStack readItemStack(ByteBuf buf) {
return ByteBufUtils.readItemStack(buf);
}
private static void writeItemStack(ItemStack stack, ByteBuf buf) {
ByteBufUtils.writeItemStack(buf, stack);
}
private static BlockPos readBlockPos(ByteBuf buf) {
return BlockPos.fromLong(buf.readLong());
}
private static void writeBlockPos(BlockPos pos, ByteBuf buf) {
buf.writeLong(pos.toLong());
}
public static interface Writer<T extends Object> {
public void write(T t, ByteBuf buf);
}
public static interface Reader<T extends Object> {
public T read(ByteBuf buf);
}
}
/**
* This class was created by <Vazkii>. It's distributed as
* part of the Psi Mod. Get the Source Code in github:
* https://github.com/Vazkii/Psi
*
* Psi is Open Source and distributed under the
* CC-BY-NC-SA 3.0 License: https://creativecommons.org/licenses/by-nc-sa/3.0/deed.en_GB
*
* File Created @ [12/01/2016, 00:23:41 (GMT)]
*/
package vazkii.psi.common.network.message;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockPos;
import net.minecraftforge.fml.common.network.simpleimpl.IMessage;
import net.minecraftforge.fml.common.network.simpleimpl.MessageContext;
import vazkii.psi.common.network.Message;
public class TestMessage extends Message {
public byte b;
public short s;
public int i;
public long l;
public float f;
public double d;
public boolean bo;
public char c;
public String st;
public NBTTagCompound cmp;
public ItemStack stack;
public BlockPos pos;
public transient int trInt = 4;
public TestMessage() { }
public TestMessage(byte b, short s, int i, long l, float f, double d, boolean bo, char c, String st, NBTTagCompound cmp, ItemStack stack, BlockPos pos, int trInt) {
this.b = b;
this.s = s;
this.i = i;
this.l = l;
this.f = f;
this.d = d;
this.bo = bo;
this.c = c;
this.st = st;
this.cmp = cmp;
this.stack = stack;
this.pos = pos;
this.trInt = trInt;
}
@Override
public IMessage handleMessage(MessageContext context) {
System.out.println("byte is " + b);
System.out.println("short is " + s);
System.out.println("int is " + i);
System.out.println("long is " + l);
System.out.println("float is " + f);
System.out.println("double is " + d);
System.out.println("boolean is " + bo);
System.out.println("char is " + c);
System.out.println("String is " + st);
System.out.println("NBTTagCompount is " + cmp);
System.out.println("ItemStack is " + stack);
System.out.println("BlockPos is " + pos);
System.out.println("trInt is " + trInt);
return null;
}
}
@williewillus
Copy link

A little micro-optimization: BlockPos has a toLong and fromLong method to pack/unpack the coords into/out of a long, one 64-bit signed integer instead of three 32-bit signed integers. It's what vanilla uses to transfer blockpos over the network

@Vazkii
Copy link
Author

Vazkii commented Jan 12, 2016

Done!

@Torins
Copy link

Torins commented Jan 12, 2016

Hey, so I saw Cpw's comment on twitter mentioning PacketBuffer and I did a thing.
https://gist.github.com/Torins/eaa90300fd47c70404be

@parzivail
Copy link

@Vazkii Do you go about registering the message in the same way other messages are? SimpleNetworkWrapper.registerMessage is complaining that it doesn't accept the extended/implemented class as both a message and handler

@Vazkii
Copy link
Author

Vazkii commented Jan 12, 2016

Yes, just register as normal.

@parzivail
Copy link

@Vazkii I hate to be a bother, but if you have time, could you explain what i'm doing wrong here? https://gist.github.com/parzivail/7ac56e03f0806f24ca47

@Torins
Copy link

Torins commented Jan 12, 2016

I might be wrong, but shouldn't you do
public class MessageCreateBlasterBolt extends Message<MessageCreateBlasterBolt>
to make it work?

Edit: had to add ` for styling

@parzivail
Copy link

@Torins Aha! Much obliged. Completely disregarded doing that.

@WorldSEnder
Copy link

I have concerns acceptField should probably not check if the Field has a type that can be read but just for final, static and transient. Having other fields will simply lead to getHandler throw a RuntimeException as it is definitly an error on the programmers side.

On another note, I don't know why you'd want to sort the Fields as you do in getClassFields, it looks like a waste of CPU time.

@Da-Technomancer
Copy link

Am I using this class wrong, because in Eclipse it's giving a lot of warnings about rawtypes and SerialVersionUID that can be fixed with SupressWarnings, though the SupressWarnings aren't in the original class (also, when I try to send packets with this class it gives errors, but I don't really know what I'm doing with packets, so that could be unrelated)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment