Kryonet: Best way to send / read simple TCP messages?

I’m pretty new to all of this, please bear with me:
For most cases in my game, I register and send specific classes via kryonet which contain specific values (Attack.class has caster, target, dmg).

But for a number of use cases, all info that’s needed could either be the class itself (StartFight.class) or it could be a String “Start Fight”.
What are the pros and cons for either approach? I’d either have a long list of

if (object instanceof SomeSingleUseObjectWithNoVariables)
else if (object instanceof SomeOTHERSingleUseObjectWithNoVariables)

or of

if (object.equals(“Start Fight”))
else if (object.equals(“Exit Game”))

and so on. Is there an even better approach?

Many thanks!

Create a filtered Listener for special kind of messages that runs your “reaction”-code:

import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;

public abstract class RunMethodListener<T> extends Listener {
	
	private final Class<T> clazz;
	
	public RunMethodListener(Class<T> clazz) {
		this.clazz = clazz;
	}
	
	@Override
	public void received(Connection c, Object o) {
		if (clazz.isInstance(o)) {
			run(c,clazz.cast(o));
		}
	}

	public abstract void run(Connection c, T received);
}

Usage:

		Server server = new Server();
		server.addListener(
		new RunMethodListener<String>(String.class) {
			@Override
			public void run(Connection c, String rec) {
				System.out.println(rec);
			}
		});

You can then create a Listener for every special object.

Thanks for the quick reply. I’m not at a computer so can’t check it out myself, but it’s not immediately clear to me in which way this will help me?
From the looks of it, it would help make my code look more tidy since I would have a separate listener for each object type (so I might use it anyway), but is that all?

Because in your example, we simply print rec, but that’s not what I need; I need to do x when rec is “start fight” but do y when rec is “exit game”. So in the end, wouldn’t it still look kinda like this?

Server server = new Server();
      server.addListener(
      new RunMethodListener<String>(String.class) {
         @Override
         public void run(Connection c, String rec) {
            if (rec.equals("start fight")){
            	// start fight
            } else if (rex.equals("exit game")){
            	// exit game
            }
         }
      });

because then it’s basically the same as my original option 2?

This was meant to improve the option number one (Sending objects, not strings).
At some point you have to differentiate your messages, there is not that much of a way around it.
The String was just an example, it will look more like this:


      server.addListener(
      new RunMethodListener<Attack>(Attack.class) {
         @Override
         public void run(Connection c, Attack att) {
             executeAttack(att);
         }
      });

      server.addListener(
      new RunMethodListener<ChatMessage>(ChatMessage.class) {
         @Override
         public void run(Connection c, ChatMessage cm) {
             String playerName = connectionToName(c);
             chat.printMessage(playerName, cm.messageText)
         }
      });

      server.addListener(
      new RunMethodListener<ExitGame>(ExitGame.class) {
         @Override
         public void run(Connection c, ExitGame eg) {
             String playerName = connectionToName(c);
             chat.printMessage(playerName, "Left the game!")
         }
      });

You can also put these in separate Classes to make it even tidier.
It helps you by automatically passing through the objects and selecting the right one, so you do not have to write tedious if-else-if-else salad.
Every Listener gets the incoming object but only one will work with it by calling run.

And before you ask, the performance hit should be unnoticeable, serializing and sending stuff is an order of magnitude more expensive than a few more checks to see which listener needs to work with the received object.

Great, thanks. So just to make sure, there is no reason not to have 50
kryo.register(SomeMessagingClass.class)
kryo.register(AnotherMessagingClass.class)
etc?
And then a 50 RunMethodListeners?

I’m still not 100% seeing the light but I’ll take your word for it, it’s just that I don’t see how writing 50 if / else statements for String checking is more (or less, that is) tedious than adding 50 RunMethodListeners.

So I guess the question I should have asked is “Is it better to differentiate functionally different messages by class or by their values” and you’re saying it’s better to differentiate by class, right?

That’s actually how I started and which seemed to make sense to me at that time, but having a bunch of these:

static public class NextPlayerTurn {
	public String name;
}

static public class RegisterName {
	public String name;
}

static public class EndFight {
	public String displayText;
}

To me seems kinda wasteful, since they are all the same but in name.

Wasteful in what kind of way?
If you don’t want to waste bytes in your network messages use different classes for different messages instead of using your own hacky system to differentiate between your message types.
KryoNet compresses the class id field, it’ll use only use as many bytes as needed => one byte if you stay below ~128 registered classes, the classes that are registered after that will use 2 bytes for their class id, it goes like that up to class ~32000.

So if you have a look at the per-message-overhead for using many different message-classes:

  • Only the id field: 1 byte, 2 bytes if you go batshit crazy

=> realistically 1 byte, i doubt that you need 128 message classes.

And the per-message-overhead for a string based system:

  • Id field: 1 byte
  • String length field: 1 byte
  • String characters: 1 byte per character

=> At least 3 bytes, but probably more.

This could mean 10 to 20 bytes overhead if you choose meaningful identifier strings like “start_game” or “attack”.
This adds overhead nearly as big as your payload!

Having many classes removes the need to send extra data with your stuff.

Big if-else-if-else constructs are ugly and not very maintainable.

Have a look at this:

server.addListener(new MovementMessageListener(game));
server.addListener(new AttackMessageListener(game));
server.addListener(new ChatMessageListener(chat));
server.addListener(new QuitGameMessageListener(game,chat));

Now the same with if-else.

if (object.beginsWith("Move")) {
	//Extract Data
	//MovementCode
} else if (object.beginsWith("Attack")) {
	//Extract Data
	//AttackCode
} else if (object.beginsWith("Chat")) {
	//Extract Data
	//Print Chat Message
} else if (object.equals("Exit Game")) {
	//handle Exit
}

:persecutioncomplex: That is some bloated piece of code.

Instead of being all over the place the code is neatly packed into different Classes.
This means no inter-dependencies and no f*cking around with 200 lines of if-else.

A thousand thanks for this thorough explanation, I have nothing to respond.