on the Cob:
"A page of history is worth a pound of logic." - Oliver Wendell Holmes, Jr.
oday, 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 finetuning 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.
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
|Credits: Code on the Cob logo illustrated by and is © 1999 Dan Zalkus. Code on the Cob is © 1999 Chris Hargrove. All other content is © 1999 loonyboi productions. Unauthorized reproduction is strictly prohibited, so don't do it, or we'll chmod you unexecutable.|