Server Side Mission Loading Process Explained
by Killer7 · 11/13/2006 (4:34 pm) · 2 comments
This is my first resource and the first pass on it, so please don't mind a few errors and hard to understand descriptions. I would love to get some feedback from you guys to make this resource better and also for future resources. Thanks.
Ok so lets load our mission. As we all know we need to call the script function loadMission. This function is called on all types of servers, single player, multiplayer and dedicated. So the loading process for all of these is pretty much the same. Lets see what the process really is.
Call to loadMission
loadMission function takes the path to an *.mis file. The mis file describes a single mission which will be loaded on the server. We would not go on into details about what the mis file contains, for now its enough to know that is completely describes the mission contents including environment, buildings and objects. Anyways the loadMission function first calls the function buildLoadInfo. This function opens the mis file and reads the top level mission details like name and description. This information is read as a string in the script code and is then passed to the engine to process through the eval function.
Passing Mission Info to connected clients
After that we need to pass this information to all the connected clients. We will have connected clients in case we are restarting a mission but not in case we are creating a new server. To pass the new mission info to all the clients we send it to them one by one. by calling sendLoadInfoToClient. sendLoadInfoToClient function sends clients three messages MsgLoadInfo, MsgLoadDescripition and MsgLoadInfoDone. These are basically client side functions that can save and display the new mission info to the user. Once we have sent this info to the clients we proceed to the next phase of the loading process.
Execute the Mission File
To proceed to the next stage loadMission calls function loadMissionStage2. loadMissionStage2 executes the mission file (*.mis) through the engine function exec. When the mission file gets executed the engine creates the required 3D objects, initializes terrain data and many other things. The outcome of this is that the server now has a game that is ready to be run. All objects have been loaded and the world is ready. But still the process is only on the server side.
Passing More Info to the clients
We need to transfer all this information to the clients as well. To do this we call GameConnection::loadMission function for each connected clients. As the game connection for each client is seperate, this results in a seperate loading sequence for each client. Game connection on server side talks with the GameConnection on the client side passing a series of messages which are described next.
Phase 1
The server now sends a message to the clients to start phase 1. The client can now update its Gui to let the user know that mission is about to start. The client then sends a acknowledgement to the server that it is ready to proceed with the next phase of loading. Once the server recieves acknowldegment from the client, it sends in the datablocks through the engine function transmitDataBlocks. This function is part of the GameConnection class so each connected client will have its own connection and multiple clients can be at different stage of the loading process. When all the datablocks have been transferred to a client the engine calls script funciton GameConnection::onDataBlocksDone. This function starts the next phase of the loading process.
Phase 2
Just like the first phase the servers send a message to the client that the next phase of the loading process is about to begin. The client again returns the acknowledgement. Upon recieving the ack the server calls engine function transmitPaths and activateGhosting to transfer the rest of the game data to the client. All the starting game data should now be transferred to the client. The ghosting data is somewhat big and the process can take some time. As the ghosting process goes the engine can call three script functions, onGhostAlwaysObjectsReceived to show that ghosting process is complete, onGhostAlwaysFailed if the process failed to complete and clientWantsGhostAlwaysRetry for a retry. If our ghosting was successful we proceed to the next phase.
Phase 3
Just as in phase 1 and 2 server send in the message to start phase 3 and the client replies with an ack. On a successful ack the server now calls startMission and onClientEnterGame functions on the client object on the server side. These functions result in the client entering the game world. One by one all clients enter the game and start playing. What happens next is a long story and hence not a part of this resource.
New clients
But there is one important thing left. As the game is running new clients connect. As we know the GameConnection class handles all the connections and when a new client has successfully entered the game the GameConnection::onConnect function is called on the scripting side. This function first notifies all the connected clients that a new client has just entered the game. Then it calls loadMission on the GameConnection (see at the end of the function) and this results in the same sequence of processes as described above. So most of the loading process is the same for all clients, existing and the new ones.
Ok so lets load our mission. As we all know we need to call the script function loadMission. This function is called on all types of servers, single player, multiplayer and dedicated. So the loading process for all of these is pretty much the same. Lets see what the process really is.
Call to loadMission
loadMission function takes the path to an *.mis file. The mis file describes a single mission which will be loaded on the server. We would not go on into details about what the mis file contains, for now its enough to know that is completely describes the mission contents including environment, buildings and objects. Anyways the loadMission function first calls the function buildLoadInfo. This function opens the mis file and reads the top level mission details like name and description. This information is read as a string in the script code and is then passed to the engine to process through the eval function.
function loadMission( %missionName, %isFirstMission )
{
endMission();
echo("*** LOADING MISSION: " @ %missionName);
echo("*** Stage 1 load");
// Reset all of these
clearCenterPrintAll();
clearBottomPrintAll();
// increment the mission sequence (used for ghost sequencing)
$missionSequence++;
$missionRunning = false;
$Server::MissionFile = %missionName;
// Extract mission info from the mission file,
// including the display name and stuff to send
// to the client.
buildLoadInfo( %missionName );
// Download mission info to the clients
%count = ClientGroup.getCount();
for( %cl = 0; %cl < %count; %cl++ )
{
%client = ClientGroup.getObject( %cl );
if (!%client.isAIControlled())
sendLoadInfoToClient(%client);
}
// if this isn't the first mission, allow some time for the server
// to transmit information to the clients:
if( %isFirstMission || $Server::ServerType $= "SinglePlayer" )
loadMissionStage2();
else
schedule( $MissionLoadPause, ServerGroup, loadMissionStage2 );
}function buildLoadInfo( %mission ) {
clearLoadInfo();
%infoObject = "";
%file = new FileObject();
if ( %file.openForRead( %mission ) ) {
%inInfoBlock = false;
while ( !%file.isEOF() ) {
%line = %file.readLine();
%line = trim( %line );
if( %line $= "new ScriptObject(MissionInfo) {" )
%inInfoBlock = true;
else if( %inInfoBlock && %line $= "};" ) {
%inInfoBlock = false;
%infoObject = %infoObject @ %line;
break;
}
if( %inInfoBlock )
%infoObject = %infoObject @ %line @ " ";
}
%file.close();
}
// Will create the object "MissionInfo"
eval( %infoObject );
%file.delete();
}Passing Mission Info to connected clients
After that we need to pass this information to all the connected clients. We will have connected clients in case we are restarting a mission but not in case we are creating a new server. To pass the new mission info to all the clients we send it to them one by one. by calling sendLoadInfoToClient. sendLoadInfoToClient function sends clients three messages MsgLoadInfo, MsgLoadDescripition and MsgLoadInfoDone. These are basically client side functions that can save and display the new mission info to the user. Once we have sent this info to the clients we proceed to the next phase of the loading process.
function sendLoadInfoToClient( %client )
{
messageClient( %client, 'MsgLoadInfo', "", MissionInfo.name );
// Send Mission Description a line at a time
for( %i = 0; MissionInfo.desc[%i] !$= ""; %i++ )
messageClient( %client, 'MsgLoadDescripition', "", MissionInfo.desc[%i] );
messageClient( %client, 'MsgLoadInfoDone' );
}Execute the Mission File
To proceed to the next stage loadMission calls function loadMissionStage2. loadMissionStage2 executes the mission file (*.mis) through the engine function exec. When the mission file gets executed the engine creates the required 3D objects, initializes terrain data and many other things. The outcome of this is that the server now has a game that is ready to be run. All objects have been loaded and the world is ready. But still the process is only on the server side.
function loadMissionStage2()
{
// Create the mission group off the ServerGroup
echo("*** Stage 2 load");
$instantGroup = ServerGroup;
// Make sure the mission exists
%file = $Server::MissionFile;
if( !isFile( %file ) ) {
error( "Could not find mission " @ %file );
return;
}
// Calculate the mission CRC. The CRC is used by the clients
// to caching mission lighting.
$missionCRC = getFileCRC( %file );
// Exec the mission, objects are added to the ServerGroup
exec(%file);
// If there was a problem with the load, let's try another mission
if( !isObject(MissionGroup) ) {
error( "No 'MissionGroup' found in mission \"" @ $missionName @ "\"." );
schedule( 3000, ServerGroup, CycleMissions );
return;
}
// Mission cleanup group
new SimGroup( MissionCleanup );
$instantGroup = MissionCleanup;
// Construct MOD paths
pathOnMissionLoadDone();
// Mission loading done...
echo("*** Mission loaded");
// Start all the clients in the mission
$missionRunning = true;
for( %clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++ )
ClientGroup.getObject(%clientIndex).loadMission();
// Go ahead and launch the game
onMissionLoaded();
purgeResources();
}Passing More Info to the clients
We need to transfer all this information to the clients as well. To do this we call GameConnection::loadMission function for each connected clients. As the game connection for each client is seperate, this results in a seperate loading sequence for each client. Game connection on server side talks with the GameConnection on the client side passing a series of messages which are described next.
function GameConnection::loadMission(%this)
{
// Send over the information that will display the server info
// when we learn it got there, we'll send the data blocks
%this.currentPhase = 0;
if (%this.isAIControlled())
{
// Cut to the chase...
%this.onClientEnterGame();
}
else
{
commandToClient(%this, 'MissionStartPhase1', $missionSequence,
$Server::MissionFile, MissionGroup.musicTrack);
echo("*** Sending mission load to client: " @ $Server::MissionFile);
}
}Phase 1
The server now sends a message to the clients to start phase 1. The client can now update its Gui to let the user know that mission is about to start. The client then sends a acknowledgement to the server that it is ready to proceed with the next phase of loading. Once the server recieves acknowldegment from the client, it sends in the datablocks through the engine function transmitDataBlocks. This function is part of the GameConnection class so each connected client will have its own connection and multiple clients can be at different stage of the loading process. When all the datablocks have been transferred to a client the engine calls script funciton GameConnection::onDataBlocksDone. This function starts the next phase of the loading process.
function serverCmdMissionStartPhase1Ack(%client, %seq)
{
// Make sure to ignore calls from a previous mission load
if (%seq != $missionSequence || !$MissionRunning)
return;
if (%client.currentPhase != 0)
return;
%client.currentPhase = 1;
// Start with the CRC
%client.setMissionCRC( $missionCRC );
// Send over the datablocks...
// OnDataBlocksDone will get called when have confirmation
// that they've all been received.
%client.transmitDataBlocks($missionSequence);
}function GameConnection::onDataBlocksDone( %this, %missionSequence )
{
// Make sure to ignore calls from a previous mission load
if (%missionSequence != $missionSequence)
return;
if (%this.currentPhase != 1)
return;
%this.currentPhase = 1.5;
// On to the next phase
commandToClient(%this, 'MissionStartPhase2', $missionSequence, $Server::MissionFile);
}Phase 2
Just like the first phase the servers send a message to the client that the next phase of the loading process is about to begin. The client again returns the acknowledgement. Upon recieving the ack the server calls engine function transmitPaths and activateGhosting to transfer the rest of the game data to the client. All the starting game data should now be transferred to the client. The ghosting data is somewhat big and the process can take some time. As the ghosting process goes the engine can call three script functions, onGhostAlwaysObjectsReceived to show that ghosting process is complete, onGhostAlwaysFailed if the process failed to complete and clientWantsGhostAlwaysRetry for a retry. If our ghosting was successful we proceed to the next phase.
function serverCmdMissionStartPhase2Ack(%client, %seq)
{
// Make sure to ignore calls from a previous mission load
if (%seq != $missionSequence || !$MissionRunning)
return;
if (%client.currentPhase != 1.5)
return;
%client.currentPhase = 2;
// Update mod paths, this needs to get there before the objects.
%client.transmitPaths();
// Start ghosting objects to the client
%client.activateGhosting();
}function GameConnection::clientWantsGhostAlwaysRetry(%client)
{
if($MissionRunning)
%client.activateGhosting();
}
function GameConnection::onGhostAlwaysFailed(%client)
{
}
function GameConnection::onGhostAlwaysObjectsReceived(%client)
{
// Ready for next phase.
commandToClient(%client, 'MissionStartPhase3', $missionSequence, $Server::MissionFile);
}Phase 3
Just as in phase 1 and 2 server send in the message to start phase 3 and the client replies with an ack. On a successful ack the server now calls startMission and onClientEnterGame functions on the client object on the server side. These functions result in the client entering the game world. One by one all clients enter the game and start playing. What happens next is a long story and hence not a part of this resource.
function serverCmdMissionStartPhase3Ack(%client, %seq)
{
// Make sure to ignore calls from a previous mission load
if(%seq != $missionSequence || !$MissionRunning)
return;
if(%client.currentPhase != 2)
return;
%client.currentPhase = 3;
// Server is ready to drop into the game
%client.startMission();
%client.onClientEnterGame();
}New clients
But there is one important thing left. As the game is running new clients connect. As we know the GameConnection class handles all the connections and when a new client has successfully entered the game the GameConnection::onConnect function is called on the scripting side. This function first notifies all the connected clients that a new client has just entered the game. Then it calls loadMission on the GameConnection (see at the end of the function) and this results in the same sequence of processes as described above. So most of the loading process is the same for all clients, existing and the new ones.
function GameConnection::onConnect( %client, %name )
{
messageClient(%client,'MsgConnectionError',"",$Pref::Server::ConnectionError);
sendLoadInfoToClient( %client );
%client.guid = 0;
addToServerGuidList( %client.guid );
// Set admin status
if (%client.getAddress() $= "local")
{
%client.isAdmin = true;
%client.isSuperAdmin = true;
}
else
{
%client.isAdmin = false;
%client.isSuperAdmin = false;
}
// Save client preferences on the connection object for later use.
%client.gender = "Male";
%client.armor = "Light";
%client.race = "Human";
%client.skin = addTaggedString( "base" );
%client.setPlayerName(%name);
%client.score = 0;
$instantGroup = ServerGroup;
$instantGroup = MissionCleanup;
echo("CADD: " @ %client @ " " @ %client.getAddress());
if ($localClientDefined != 1)
{
$localClient = %client;
$localClientDefined = 1;
}
$latestClient = %client;
// Inform the client of all the other clients
%count = ClientGroup.getCount();
for (%cl = 0; %cl < %count; %cl++)
{
%other = ClientGroup.getObject(%cl);
if ((%other != %client))
{
// These should be "silent" versions of these messages...
messageClient(%client, 'MsgClientJoin', "",
%other.name,
%other,
%other.sendGuid,
%other.score,
%other.isAIControlled(),
%other.isAdmin,
%other.isSuperAdmin);
}
}
// Inform the client we've joined up
messageClient(%client,
'MsgClientJoin', '\c2Welcome to the Torque demo app %1.',
%client.name,
%client,
%client.sendGuid,
%client.score,
%client.isAiControlled(),
%client.isAdmin,
%client.isSuperAdmin);
// Inform all the other clients of the new guy
messageAllExcept(%client, -1, 'MsgClientJoin', '\c1%1 joined the game.',
%client.name,
%client,
%client.sendGuid,
%client.score,
%client.isAiControlled(),
%client.isAdmin,
%client.isSuperAdmin);
// If the mission is running, go ahead download it to the client
if ($missionRunning)
%client.loadMission();
$Server::PlayerCount++;
}
Associate Orion Elenzil
Real Life Plus