GNE Tutorials

GNE website

Tutorials Home

Tutorials

  1. Creating exhello
  2. Techniques using PacketFeeder

Figures

  1. GNE Mid-Level API Connection Process

Installation Tutorials

  1. MSVC.NET
  2. MinGW32
  3. Linux/UNIX

Network Game Programming Techniques using PacketFeeder

Contents

  1. Introduction
  2. A First Technique
  3. The PacketFeeder
  4. PacketFeeder on the Client
  5. PacketFeeder on the Server

Introduction

I'm writing this tutorial to explain my part of my thoughts about how I envisioned a game being made with GNE. Several people have asked me questions about how to start the game, and I figured I should mention my thought process. In this tutorial I'll explain more about the sending of data between servers and clients, rather than the software design and structure to support this (I hope I can find time to write another tutorial for that part).

The scheme I will be fitting this example in is a client-server game style, but many of this can also apply to peer-to-peer, token ring, or other styles.

The PacketFeeder class was a change that was made with GNE 0.55, and I got the idea somewhat from a description of the Unreal Tournament networking code -- about how certain data is prioritized. I go into more detail about the PacketFeeder and this technique later.

Back to Contents

A First Technique

This technique is the one that I used in Itana and usually the first one I hear mentioned from people making their first game. The technique is to have the client update its position to the server every so many frames (when using a fixed frame-length system) or after so much time. When the packets reach the server the server will typically do little or no processing, and send the packets to the clients, or send some of the packets to the clients. The main point of the technique I am trying to make a note of is the fact that the packets being sent are being sent on a fixed cycle. This is a decent first scheme, but it has a disadvantage:

You assume some sort of minimum bandwidth requirement. If the client has more bandwidth, you are not using it (ie a LAN). If the client has less bandwidth, you might lose packets or get a backlog of packets depending on how you do the network code. This can be worked around quite a bit by asking the user if they are on a LAN or 56k modem or on a cable modem or etc, but it's not exact.

Back to Contents

The PacketFeeder

Typically you want to send as much data as you can throw out (or at least up to some practical limit). GNE can handle both of these cases through its bandwidth throttles and the PacketFeeder. The original way before GNE 0.55 to do the same thing the PacketFeeder does was to capture the onDoneWriting event and then send another packet. This process was slow because it happened only after the writing thread actually ran out of packets, and it required 2 context switches. The PacketFeeder is a much improved method. What it does is when the writing thread's queue of outgoing packets reaches some user-defined limit, it will run the PacketFeeder and give it a chance to run. The advantages to the PacketFeeder are:

Works right in the writer thread, so it is fast and efficient.
The writer thread calls the PacketFeeder before it runs out of packets (user-defined limit), giving the PacketFeeder chance to always keep the queue with some packets so GNE can combine packets to optimize bandwidth, and so GNE is always sending data.
The PacketFeeder can create packets right before they are sent, and the PacketFeeder will stop being called if packets are getting backlogged due to a slow connection, thus you always are sending the most current and up-to-date data as fast as possible.

At the writer thread is calling the PacketFeeder when data runs low, the speed at which you send packets is not at a fixed interval, but instead is metered between a user-specified bandwidth cap and the actual connection itself, thus the code will adapt to changing network conditions to take advantage of smooth times, and work around slow times, and the same basic process will work for a LAN or a modem, and additionally with the user-specified bandwidth caps you can share bandwidth equally and fairly between clients on a server.

Back to Contents

PacketFeeder on the Client

I will consider two types of clients. One of the types is suitable for a PacketFeeder and another is not:

A client in a strict rule-based game where the server is authoritative on the position and gameplay (most common). In this case the client wants to make sure the server hears all of its movements and keep the movements in the order the client made them. In this case the client wants to use reliable sending and let packets queue up in the outgoing queue, and not use the PacketFeeder
A client in a looser type of game where the server trusts the client, and the client is reporting its position constantly to the server. In this case loss is likely acceptable as the next packet will describe the position again. In this case a PacketFeeder will be helpful to get the best refreshing of the client on the server.

Because the first case is more common, the PacketFeeder is much more useful on the server, but I will describe how it might work on a client who is always updating his position.

Because the PacketFeeder runs from the writer thread, your logic code and the PacketFeeder will be running asyncronously. Thus you must share data and synchronize on that data with a mutex any time you access it. Because of these issues, it might be a good implementation idea to keep a second copy of the player's position, and use that as a shared data. I will call this structure currPlayerPos. Let's say that the structure looks something like this:

/**
 * Structure that holds the current player location (x, y) and speeds (dx, dy).
 */
struct PlayerPosition {
  float x;
  float y;
  float dx;
  float dy;
  Mutex sync;
};

PlayerPosition currPlayerPos;

Now our goal is to update our position on the server as fast as possible. To simplify the code I will make the assumption that the logic loop runs fast enough and the connection slow enough that you can't send more than one player update per frame and thus the PacketFeeder won't read the same position twice and send a duplicate packet. (You could implement this by adding an "isNew" field in the structure).

Here is some pseudocode for the client logic loop:

logicLoop {
  update_game_objects;
  do_collisions_and_things;
  currPlayerPos.sync.acquire();
  currPlayerPos = player.getPosition();
  currPlayerPos.sync.release();
}

So what the client does is after every complete frame (when the game is in a well-defined state), it copies the last position into the currPlayerPos buffer. Now let's suppose our connection is as such so that every few frames or so a new packet is ready to be sent, and the PacketFeeder is called. Here is a (somewhat) pseudocode implementation of the PacketFeeder:

class OurFeeder : public PacketFeeder {
public:
  virtual ~OurFeeder() {}
  onLowPackets(PacketStream& ps) {
    currPlayerPos.sync.acquire();
    //Create a new Packet to send based on the player's position
    PosPacket packet( currPlayerPos );
    currPlayerPos.sync.release();
    ps.writePacket( packet, false );
  }
};

It's as simple as this. Now whenever bandwidth becomes available, the PacketFeeder's onLowPackets event will be triggered, and it will send out the most recent position data. In a real implementation, you have to be aware of bursts of sent data during which it might be possible for the feeder to run more than once between the logic loop updating the position. You could do thiss by using/setting "isNew" true in the logic and false in the feeder, and keep from stalling the feeder by using PacketStream::setFeederTimeout.

Back to Contents

PacketFeeder on the Server

On the server, the PacketFeeder becomes much more interesting as you are usually sending more than one type of data to the client, and there is lots more data to send. On the server to client connection, you usually have much more data you could send than you actually have, thus you must choose your data carefully in all but simple games.

You can use mostly the technique described in the client section. But instead of just sending some positions, you might want to choose 1 or more packets to send, and this is where the PacketFeeder really shines. I heard of this idea from a description of the Unreal Tournament system where data is prioritized based on arbitrary values and also on game state.

Some data is clearly more important than other data. Data about stuff happening directly to the client is the most important data (like getting hit by a missile). Data about enemies close to you is more important than data about enemies on the other side of the game world. Data about ambient features such as background NPCs in an RPG or birds flying in the sky of a racing game is much less important. So the importantce of data is determined by the type of data, and the distance the object is away from the client.

So let's say for example you have a very large set of that data to send out and you want to try to get as much out as you can. You want to send the most important stuff out, but you can't just completely ignore the less important stuff -- you just want to update it less often. So you might come up with some sort of simple percentage, though not based on distance for this example to keep it simple:

  • 50% of packets will be packets about the client itself, where it is and if it's taking damage.
  • 30% of packets will be about the other players.
  • 20% of packets for ambient objects and NPCs.

How might you implement such an algorithm? You will need to keep track of some state in the PacketFeeder so that it can pull in packets in a proper order, and create them at the time they are being sent. The PacketFeeder makes it easy to send out as many packets as possible, but choosing appropriate packets to send. This way nothing stays fixed and you always have up-to-date data:

class OurFeeder : public PacketFeeder {
public:
  int currState;
  OurFeeder() : currState(0) {}
  virtual ~OurFeeder();

  onLowPackets(PacketStream& ps) {
    if ( currState % 2 )
      ps.writePacket( getPacketDescribingClient(), false );
    else if ( currState == 3 || currState == 5 || currState == 9 )
      ps.writePacket( getPacketDescribingSomeOtherPlayer(), false );
    else
      ps.writePacket( getPacketDescribingSomethingElse(), false );
    if ( currState == 9 )
      currState = 0;
    ++currState;
  }
};

Yes I realize this is very shakey ;). I've yet to come up with some good algorithms myself as I've yet to use this technique in a complicated matter. PLEASE feel free to suggest algorithms -- but my point is that while the game logic is running, the logic need not be concerned AT ALL about the network code. The feeder will just pull out data based on its priorities in a completely independent fashion, and this makes things much easier to program. The one complexity though is the same with the client -- is it acceptable to pick up a clients position in the middle of a game logic loop? In most games I can envision, this would be mostly acceptable. If it is acceptable in your game, then you have the advantage of sending and receiving packets even during frame updates rather than just between them, giving you somewhat better network usage and decreasing latency, and much better CPU usage on multi-CPU machines (likely common only on dedicated servers). The larger the frame length and the slower the computer, the more the potential benefit, although these benefits are fairly small on machines common today. But as I said before the real benefit comes from separating your game logic from your networking code, and this makes it easier to transform a single player game into a multiplayer one by just adding a feeder on the server and client to peek into and modify the game data.

Back to Contents