[color="#4B0082"] Be sure to read the license agreement before using the code.[/color]
[spoiler][font="Courier New"]"Code On The Cob"
Source Code License Agreement
Copyright (c) 1998 Chris Hargrove
The licenser, Chris Hargrove, is herein referred to as the "licenser".
This license agreement, the source code provided, the executable(s)
compilable thereof, and all other contents of the distributable archive
including the archive itself are herein referred to as "covered" by this
Reading, compiling, running, or otherwise using any item covered by this
license constitutes automatic acceptance of the rules described in this
license, as precedes and follows in the remainder of this file.
All items covered by this license agreement are the sole property of the
licenser, and are licensed as-is to the public for educational use only.
No items covered by this license may be distributed in any archive other
than that which is originally provided, and said archive may only be
distributed via mediums that require no cost for retrieval of said archive
other than those required by the medium itself and its direct providers
thereof. None of the items covered may be modified or duplicated in any
form, with the sole exception of modifications and duplications made by
licensees for educational use only. Said modifications and duplications
may only be made to source code covered by this license, and all said
modifications and duplications become the immediate and sole property of the
licenser. The licenser is not responsible for any damages, direct or
indirect, that may result from usage, proper or improper, of items covered
under this license.
Any violation of the above rules must be authorized via express written
consent of the licenser, or it may be punishable by civil legal action.
THE LICENSE PARAPHRASED:
I'm writing this code so you can read it and learn from it. If you
understand it and find some bits and pieces useful as-is, feel free to make
use of them. But don't go taking credit for that which is not yours, and
don't go trying to make a buck off of my effort directly without my
permission. I'm doing this for your personal growth, not for your wallet
or your ego. In general, don't screw me and I won't screw you. The spirit
of sharing information is why I volunteer my already limited time to write
this series. That same spirit is behind a lot of what I and many others have
learned, and what we're all still learning to this day. This license is
basically my way of saying that I don't want some random guy out there to
ruin it for myself and everyone else. Be ethical.
"A page of history is worth a pound of logic." - Oliver Wendell Holmes, Jr.
Today, we'll be taking another step into the realm of actor logic by starting our discussion of multiplayer networking. In the next article I'll introduce the first part of our networking implementation (such as the low-level Winsock code and other odds and ends) to the code base, but for today I'll be sticking purely with discussion, partially due to personal time constraints and partially due to a deliberate desire to get you guys thinking about all this well in advance of seeing what ends up in the code. The low-level code will go in first, but that's the easy stuff, comparatively speaking. All it needs to do is handle sending packets of data from somebody to somebody else.
As for determining exactly what data to send and who to send it to, that's another story entirely, and this is where networking code is really made or broken. We'll be handling this topic gradually over the course of the rest of the project (since fine tuning the data usually works that way), but I'll do what I can to talk about some fundamentals here in this article. Even if you never touch the COTC code base, the stuff in this article will likely still apply if you plan on supporting multiplayer networking in your own game project.
[size="5"]What's This Got To Do With Cocktail Parties?
I've heard it said that the rules of multiplayer programming are kinda like the rules of a cocktail party. 1) It doesn't matter who you're talking to, as long as people are talking. 2) You keep the conversation short and sweet, or people will lose interest. 3) When someone does lose interest, they still mutter a word or two on occasion to feign interest and keep the conversation going, and 4) All this work to keep up the conversation seems somewhat ludicrous considering that nobody is saying anything truly important.
If you plan on supporting multiplayer in your game, there are lot of rules and guidelines to follow. A lot of these guidelines seem like common sense from the outside, but once you get down to implementing things you sometimes have to watch yourself to make sure you try and stick with them. Some of the guidelines only carry the weight they do because of the bad experiences that result when they're not followed, hence the quote at the top of the article.
The first rule is undoubtedly the most important. If you're thinking of supporting multiplayer, plan for it early. Do not make the mistake that oh-so-many developers / publishers / etc. have made over the past several years by assuming that you can "toss in" multiplayer near the end of the project. It does not work that way. Attempting to throw multiplayer in at the last minute is virtually guaranteed to transform the last phase of your project from a "normal" crunch hell into a full-blown death march. Stop that nightmare before it happens, and plan ahead. Get your basic networking mechanics (the low-level code, the decision of peer-to-peer vs. client/server, and so forth) out of the way before you dive head-on into game logic. The reason is simple: game logic works differently when multiplayer is involved. If you're using something like peer-to-peer, synchronization issues end up taking a spotlight and you need quite a bit of code around the game logic stuff to make sure things stay in check across all the players' machines. If you're using client/server, the decisions regarding what differences should exist between server and client machines (structures, variables, and so forth) takes a front-row seat as well. And in all cases, the breakdown of exactly what data needs to go over the network and what doesn't is of major importance. Any way you look at it, multiplayer needs to be planned beforehand. Ignoring this rule is committing programmer hubris in the highest form. This also goes for your publisher, if you have one. If you're not planning on multiplayer, and if your publisher doesn't need multiplayer in the milestone spec at the beginning of the project, then you might not need to worry about any of this. But if you think, for an instant that they are going to want to "feature creep" multiplayer support in at some point during the project timeline, stop them before they do. The "monkeys in suits" may not understand how important a choice this is, but you should. Publishers and developers need to make a decision on multiplayer from the start, and commit to it. If your publisher won't commit to "yes we will have multiplayer" or "no we will not"... to the point that there's no question about this in anybody's mind... then prod them until they do. It's that important. I've known way too many projects which either failed completely, or at a minimum missed milestones by leaps and bounds, because of this mistake.
The rest of the rules are more related to implementation, such as the second one: know your data. Specifically, know what data really matters when it comes to networking. Game actors (or entities, choose whatever name you like) have a lot of stuff in them. Many multiplayer games have actor structures with well over a hundred structure members. The thing is, only a fraction of that data needs to be transmitted across the network, depending on the specifics of the actor. In order to be efficient, you have to know what data is relevant and how you can most optimally get that data to other machines.
That rule often coincides with rule number three: elimination is the best optimization. Keeping packet size at a minimum makes the difference between life and death in terms of network performance. You'll always have to compensate for lag, but the less you send over, the less you need to compensate for. Many people naively think that the best method of keeping data packets small is to take the incoming data and use a generic compression algorithm on it (RLE, LZW, whatever), thinking that it'll give them optimal network efficiency. I have one thing to say to that: stierscheisse. A one-size-fits-all compression routine will not work on actors which have data that is anything but one-size-fits-all. The best way to compress for multiplayer is to eliminate that which is unnecessary, specific to the data that's being sent. For example, say you have a dword in your actor structure that holds an ammo count. If you know that the ammo can never be more than 999 in your game, then why send more than 10 bits of the dword (a maximum of 1024) over the network? Doing an RLE on a 32-bit value isn't guaranteed to fit into 10 bits, but the raw value can if you take advantage of information like this. Same thing goes for floating-point values. If you know that three of your floats are components of a normalized vector (i.e. they can only be between [-1,1]), and you want full float precision, then you can bump the float into the range of +-[1,2] and safely ignore the entire exponent block for those floats (8 out of 32 bits each) during network transmission. That's a whole byte lopped off for each of those floats, with no loss of precision. If you ARE willing to lose precision, you can save even more. Once again, the key here is to know your data.
There's plenty of guidelines beyond these three, but many of them depend on certain implementation details (such as whether you use peer-to-peer or client/server, etc). At the core though, a lot of them have their grounds in one of these, so take them to heart. They seem obvious, but you'd be surprised how many people neglect them. For our codebase we'll be implementing a client/server model, so in addition to these three guidelines I'll be going over some things specific to client/server as time goes on.
As you guys know, our project doesn't have a rigid game chosen yet, and hence no rigid spec of what an actor should be for our case. From what I know of the people reading this series, though, almost all of you aren't nearly as concerned with the project's game itself as you are with how it's being created. I'm glad to see that, because it means the series is doing what it's supposed to do... get you guys thinking on how this stuff applies to you. Because of this, until the next article I want you folks who are planning on putting networking in your game (a good 75% of you are working on a game it seems) to take a bit of time now and think about how you're going to do it. Like I said, you have to plan early, so take some time to make a few choices before they bite you. Are you going to go client/server, peer-to-peer, or something more esoteric? What kind of stuff will your actors need in them? Which machine(s) will need to keep track of those actors? How small can you get the data you need to transmit over the network? Put some thought into this stuff now, if you haven't already. A lot of the data decisions depend on the game being made, so some of this is stuff you simply have to work out on your own. Beginning with the next article, and continuing throughout the rest of the project, we'll see how COTC's codebase deals with these issues.
Until next time,
- Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever.
Code on the Cob is (C) 1998 Chris Hargrove.
Reprinted with permission.