-
Notifications
You must be signed in to change notification settings - Fork 39
Guide: network sync
The network model in the civ4 engine was written in the days when dialup connections still existed and it's optimized for minimal bandwidth usage. While bandwidth shouldn't be an issue anymore, it also reduce the effect of lag and it's still playable with lag far worse than would break other games.
The way to achieve this is to let all computers do all calculations. There is no host, but rather it runs more like a peer-to-peer system where each computer has an open connection to each of the other computers. Direct messages to a player are in fact direct. They will not pass through the host.
The tradeoff of this system is that the speed is set by the slowest computer. However far worse we have to make sure that all computers will do the same calculations. For instance there aren't any network activity during the AI turns. This means the AI code should be 100% predictable while at the same time appear random.
There are two "modes" the game can be in when it calls a function. It can be in sync, meaning all computers call it and it can be local, meaning only one computer calls it. This guide will tell about what to do and don't in each mode and how to move between modes.
This is where the game is most of the time and it covers most of the code. Each function is called with the same arguments on all computers and it's your job as a programmer to ensure that it will return the same on all computers and it calls the same functions on all. In most cases this is easy, but there are possible issues like randomness. Any call to system random will not provide the same number for all computers. Instead we need predictable randomness, which is provided by GC.getGameINLINE().getSorenRandNum(int range, log entry string). What it does is generating a random number based on a fixed random seed. Each time it's called, it will make a new random seed for itself based on the old one, meaning if you call it 3 times with the same arguments, all computers will get the same 3 numbers, though those 3 numbers aren't identical.
This is code, which is executed on one computer only. The key here is not what to do, but rather what not to do. You aren't allowed to alter the memory, which has a copy on the other computers, but you can read all data. For instance you can read the profession of a unit, but you can't change it.
You need to send data on the network. Let's start with an example. Say you have local code, which wants to set the numbers of bars in a city to iNumBars. The way to do this in synced code would be:
pCity->setBars(iNumBars);
However in order to get it done in sync on all computers, you need to call the network command and then it looks like:
gDLL->sendDoTask(pCity->getID(), TASK_SET_BARS, iNumBars, -1, false, false, false, false);
Note that it has to get all arguments, even if they aren't read.
gDLL->sendDoTask(pCity->getID(), TASK_SET_BARS, iNumBars);
// TASK_SET_BARS is an enum
// iNumBars is variable iData1
Now in CvCity::doTask, add this code:
case TASK_SET_BARS:
setBars(iData1);
break;
This will work because sendDoTask will tell the exe to call doTask in sync on all computers. In other words the local code requesting the change to bars in a city has managed to change the number of bars in the city on all computers and the game has stayed in sync.
To reduce sensitivity and bandwidth usage, only transmit data when needed. We can expand the local code by doing this:
if (pCity->getBars() != iNumBars)
{
gDLL->sendDoTask(pCity->getID(), TASK_SET_BARS, iNumBars, -1, false, false, false, false);
}
Now we prevent a case of useless data being transmitted.
The idea is to add a condition, which is only true for the local player, like:
getOwnerINLINE() == GC.getGameINLINE().getActivePlayer()
This is a tough question. Generally speaking, if it's GUI related, then it's local. If it's unrelated to the GUI, then it's in sync. Think opening the select profession popup window is local to the owner of the unit. The click in the window is local, but the change to the profession has to be in synced code.
There are a number of options available in CvDLLUtilityIFaceBase.h
A list of the generic ones. There are a number of special purpose ones as well, like sendUpdateCivics. This guide will only mention the most likely to get modded.
local | class | synced function | arguments |
---|---|---|---|
sendDoTask | CvCity | doTask | int iData1, int iData2, bool bOption, bool bAlt, bool bShift, bool bCtrl |
sendPlayerAction | CvPlayer | doAction | int iData1, int iData2, int iData3 |
sendDoCommand | CvUnit | doCommand | int iData1, int iData2, bool bAlt |
Other places to find network code are CvDiplomacy.pyand CvEventManager.py
See also proposal to add more network functions.