NIO, IO, Threads, and the Meaning of Life

I am currently working on a network bomberman game. It is currently very playable over LAN, but I am not sure how well it would do over the internet. The game supports 4 players, but I am thinking of expanding it to 16 or maybe even 32 players.

I am in the process of converting the code from blocking IO to nonblock NIO, which is getting rid of a few ugly threading issues (but of course it creates a few ugly NIO issues). I am using UDP for everything because:

  1. Bomberman is a pretty ‘twitchy’ game – it needs low latencies to be playable.
  2. NAT traversal by UDP so that NATed players can host games.

Threads & Networking
With the new NIO code the server is single threaded - including both game logic and networking. Should I split the gamelogic and networking into two threads? This would create more threading hassles and synchronization issues. Also, would it be more efficient to have more than one networking thread (but one Selector)?

ByteBuffer
I am creating a new ByteBuffer every time I want to send or receive data. This seems pretty inefficient, so I was thinking of creating a class to hold a fixed number of direct ByteBuffers to be used and reused. Should I use/reuse direct ByteBuffers for both receives and sends?

Reliability
I am running the same simulations on both the client and the server (50 itr/s). Updates happen every time a player presses or releases a key because that when the simulations would become out of sync. Although I do not need in-order delivery for game messages, I still need the updates to come eventually - it would be irritating for a player to be killed by a bomb that they never saw. I could:

  1. Have the client ACK every game update they receive and then retransmit updates that have not been ACKed within a time constraint.
  2. If the client receives update 5 but has not yet received update 4 it starts a countdown which then requests that the server retransmit packet 4.
    Any ideas on which method (or any other one) would be best?

Threads & The Client
Like the server, the client is single threaded, except that it will has a few AWT threads (I’d love to use LWJGL, but too many broken OpenGL drivers…) Again, should I use seperate threads - perhaps one for gamelogic, one for graphics, and one for networking? The gamelogic on the client will likely have to do calculations a few times, such as when it receives an update for timestep 4 but it is on timestep 10. It then has to go back to 4, update, then calculate from 4-10.

File IO
When reading a file on the server, such as a map, to send to the client, should I spawn a thread to read in the file to a buffer or byte[] and then notify the main thread that its ready to be sent to the client?

Non Blocking IO
Since non blocking DatagramChannels have a possibility of not sending a packet if the buffer is filled, whenever I want to send a packet I add it to a LinkedList and register the channel for writing. Later, deregister, I remove packets from the linkedlist until its empty or one does not send, in which case I add it back to the list and reregister. Is this an accepted way of doing NBIO?

Thanks for your help,
-Sam :slight_smile:

[quote]ByteBuffer
I am creating a new ByteBuffer every time I want to send or receive data. This seems pretty inefficient, so I was thinking of creating a class to hold a fixed number of direct ByteBuffers to be used and reused. Should I use/reuse direct ByteBuffers for both receives and sends?
[/quote]
Reusing the direct ByteBuffer is a good idee. They can be slow to create and destroy. There might also be an issue where direct buffers are not garbage collected in a timely fasion.

Third option (if you got enough bandwidth):
Send all messages since last ack. Your sending update 15 but the highest ack is 11. Then you send message 12, 13, 14 and 15 in the same packet. Always piggyback a ack on all packet to be as up to date as possible. Things can explode if you get too far behind. They you would have to pause the game until the client acks.

Quake 3 did something similar only it sent what had changed in the “state” since last ack. Since the same stuff usually changed there were not a great increase in data sent when you did not get an recent ack.

No need to switch to nio if you only use udp. You’ll only need 2 threads wich will not cause performance problems. They synchronization is easy. Implement a synchronized queue that you put/get from.

Not really. The docs imply there’s a big difference, but in most game cases it’s actually negligible; the last m-benchmarks I did showed that the allocation time was vanishingly small on linux for small buffers. Certainly for a game this simple you would have to work pretty hard at finding a server slow enough for you to notice the difference.

Do remember that indirect byte-buffers are lethally broken in 1.4.2_04 and above, including at least some builds of 1.5. Memory leaks that crash your VM in under a minute. Yes, someone really ought to get around to explaining to the person in charge of Sun’s JVM dev the meaning of a “regression test suite”.

Maybe Sun fixed this by now, it is certainly something most people would consider “critical”.

One shouldalways switch to NIO :stuck_out_tongue: ;D

If it’s a direct Buffer you’ll be buggered though as they are collected whimsically by the JVM.

Cas :slight_smile:

[quote]If it’s a direct Buffer you’ll be buggered though as they are collected whimsically by the JVM.

Cas :slight_smile:
[/quote]
Yeah, and I should have also explained that direct BB == indistinguishably same performance as indirect BB for cases like this (in fact, for most cases!) - so that if the indirect bug didn’t exist, you could safely create indirect’s on-demand and not care because you’d be losing negligible (if any! **) performance.

** - direct bb’s can be and often are slower than indirect ones. Welcome to the undocumented world of Sun’s NIO…

/me is dieing of flu. Still.

Thanks for your responses.
So, it looks like I am going to be using direct ByteBuffers and recylcling them because indirect ByteBuffers are broken and allocation of direct ByteBuffers causes a severe performance hit.

I read through all of the TCP v. UDP thread (whoa that was long!) which had some useful networking/protocol tips. I also read blahblahblahhs guide to nio (http://grexengine.com/sections/people/adam/adamsguidetonio.html) . I’m beginning to doubt I should use NIO… How much of the information in it is up to date?

Should I be using a seperate thread for processing the messages? What about for the calculations for each timestep?

Thanks for your help,
Sam