[Kryonet] Advantages to simple packets

Hey everyone!

I don’t know how Kryonet works under the hood, so I don’t know if this is wasting/double-doing something. That’s what I am trying to
find out.

If I make my packets simple, then I don’t need to register as many classes. I’m thinking that I can simplify until it’s just a byte[buffer_size],
and then handle those bytes appropiately when they come in. This way I can use Kryonet as low-level as any other library, but it’s way easier to set up, and I still enjoy how easy it is to work with.

Now, if I do that, will I be wasting resources? Is there any reason for me not to do this? How big is the overhead on a class, and am I avoiding any overhead when doing this?

Thank you for your insight.

Cheers :slight_smile:

If all you’re doing is transferring raw bytes, then you don’t need KryoNet, any TCP socket will do. Kryo’s serialization is quite efficient, so I suggest you just try it as you would ideally use it at a high level and see if it’s actually a problem.

Kryo does serialization very similar to what you would write by hand. Eg, take this class:


public class Moo {
   public int damage;
   public String name;
}

Kryo will write an int ID that represents the class using variable length encoding, so it will be 1 byte. Then it writes an int for “damage” using variable length encoding, so numbers closer to zero will be 1 byte, 2 bytes further away, etc. If you know the damage is always positive, you can tell Kryo and it will write the int more efficiently for positive numbers. Next it writes the name string. If the string is short and it detects ASCII, it will use a 7 bit encoding with the 8th bit to denote if there is another character. Otherwise it will write the number of characters, then the UTF8 characters. The code to do all this would be:


kryo.writeObject(output, moo);

You would be hard pressed to represent the information more efficiently by hand and certainly not with less code. Kryo uses code generation to access fields if they are public, protected, or default access, so doing that is recommended. Serialization speed is not going to be your bottleneck, but doing it by hand is faster. Kryo can still help when doing it by hand. There are two ways to do that, like this…


public class Moo implements KryoSerializable {
	public int damage;
	public String name;

	public void write (Kryo kryo, Output output) {
		output.writeInt(damage, true);
		output.writeString(name);
	}

	public void read (Kryo kryo, Input input) {
		damage = input.readInt(true);
		name = input.readString();
	}
}

Or like this:


kryo.register(Moo.class, new Serializer<Moo>() {
	public void write (Kryo kryo, Output output, Moo moo) {
		output.writeInt(moo.damage, true);
		output.writeString(moo.name);
	}

	public Moo read (Kryo kryo, Input input, Class<Moo> type) {
		Moo moo = new Moo();
		moo.damage = input.readInt(true);
		moo.name = input.readString();
		return moo;
	}
});

The parameter name for the true passed to write/readInt is named “optimizePositive”, which gets you the variable length encoding. Pass nothing for a 4 byte int. If you know the string is ASCII you can call writeAscii, otherwise writeString does the magic described above to detect ASCII or write UTF8. Either way, you always use readString.

If you have ArrayLists, Maps, or other objects, Kryo can do most or all of the work for you, without a speed penalty. I can post an example if you want.

It sounds like you are only passing objects over the network, but if you are putting data into a database, you might think about how your data will evolve. If you add a field, all the data in your database won’t deserialize. See that link for more. If hand rolling serialization, you can do this…


public class Moo implements KryoSerializable {
	public int damage;
	public String name;

	public void write (Kryo kryo, Output output) {
		output.writeInt(damage, true);
		output.writeString(name);
		output.writeBoolean(false);
	}

	public void read (Kryo kryo, Input input) {
		damage = input.readInt(true);
		name = input.readString();
		input.readBoolean();
	}
}

Now when you add a field later:


public class Moo implements KryoSerializable {
	public int damage;
	public String name;
	public float newField;

	public void write (Kryo kryo, Output output) {
		output.writeInt(damage, true);
		output.writeString(name);
		output.writeBoolean(true);
		output.writeFloat(newField);
		output.writeBoolean(false);
	}

	public void read (Kryo kryo, Input input) {
		damage = input.readInt(true);
		name = input.readString();
		if (input.readBoolean()) {
			newField = input.readFloat();
			input.readBoolean();
		}
	}
}

This costs 1 extra byte, but means you can additively evolve your classes without invalidating previously serialized bytes. Note you don’t need this for objects that are only going over the network, only for objects going to longer term storage (disk/prefs/db/etc).

Thank you very much for your thorough reply. I wasn’t even thinking about using Kryo for my database as well, but it’s certainly better than textfiles and a parser.

This answered all my questions, and I’m very happy. I did take note on one specific thing you said. In the examples, the packets are recognized by an instanceof statement. However, I really don’t like the idea of that. Instead now, I have a lookup array for handlers, and every packet carries an opcode byte that determines the index of the handler in the array.

However, you just said Kryo assigns an value to recognize packets aswell. That leads me to think that I’m (again) doing something that is already done for me. Is there any way I can access that value, when a packet comes in?

Sure


server.addListener(new Listener() {
	public void received (Connection connection, Object object) {
		int id = connection.getEndPoint().getKryo().getRegistration(object.getClass()).getId();
	}
});

You can do the same on the client. Instead of “connection.getEndPoint()” you can just use the server or client reference. The IDs come from when you register the class. They can be auto generated or specified explicitly:

int id = server.getKryo().register(SomeClass.class).getId();
server.getKryo().register(SomeClass.class, 12);

You could also do it differently. Your classes could all implement an interface that has a getter that returns an ID. The getter would return any value you want to identify the class. It wouldn’t use a field, so it wouldn’t be serialized, or if you want to use a field then mark it transient.