-
-
Save Vazkii/13e0ce45577bbae49362 to your computer and use it in GitHub Desktop.
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; | |
} | |
} |
Done!
Hey, so I saw Cpw's comment on twitter mentioning PacketBuffer and I did a thing.
https://gist.github.com/Torins/eaa90300fd47c70404be
@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
Yes, just register as normal.
@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
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
@Torins Aha! Much obliged. Completely disregarded doing that.
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.
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)?
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