Torque/Networking/ConnectionSequence

From TDN

Client Initiated Connection Sequence Overview

DISCLAIMER: While every effort has been made to detail out as much of the sequence as possible, slight omissions (both intentional and probably unintentional) exist. The purpose of the walkthrough is to provide a top level understanding of the complex process of connecting a client to a server, and to serve as a guide for adding functionality to the process if your project requires.

Execution stacks are indicated by bullet indents. Each indent denotes a new stack, and what processing happens within that call stack


Execution flow between client and server is indicated by CLIENT and SERVER headers to flow descriptions. In some cases, I’ve glossed over iterated loops (sending all datablocks for example) with an SYNCHED block. NOTE: This is slightly misleading, as client-server communication should never be considered as directly coupled; however, guaranteed order packets do allow for transmission of data in an ordered manner. The execution within these blocks is really sychronized by the START and ACK commandToClient/commandToServer script calls.


CLIENT


client/ui/JoinServerGui.cs--join()

  • creates connection client side
  • calls engine/game/gameConnection.cc--GameConnection::setConnectArgs()
  • calls ConsoleMethod:connect()
  • calls engine/sim/netConnection.cc--NetConnecton::connect()
    • calls engine/sim/netInterface.cc--NetInterface::startConnection()
    • calls engine/sim/netInterface.cc--NetInterface::addPendingConnection()
      • stores this connection information for later use (once validated)
    • calls engine/sim/netInterface.cc--NetInterface::sendConnectChallengeRequest()
      • initializes the packet
      • calls engine/core/bitStream.cc--BitStream::sendPacketStream()
        • through the platform layer, delivers the packet to the server


SERVER


  • server Receives the packet, posts Event
  • (synchronization break)

  • /engine/<platform>Net.cc--Net::Process()
  • dispatches the event by calling /engine/platform/gameInterface.cc--GameInterface::postEvent()
  • (synchronization break)

  • handler catches the event: /engine/platform/gameInterface.cc--GameInterface::processEvent()
  • event is a PacketReceiveEventType, dispatched to
    • engine/sim/netInterface.cc--NetInterface::processPacketReceiveEvent()
    • we're still dealing with a connection challenge, so it is dispatched to
      • engine/sim/netInterface.cc--NetInterface::handleConnectChallengeRequest()
      • inits the handshake checksum, creates a PendingConnection and stores, sends a connect challenge response back to client via
        • engine/core/bitStream.cc--BitStream::sendPacketStream()



CLIENT

  • /engine/<platform>Net--Net::Process()
  • receives the connect challenge response, dispatches to GameInterface::postEvent()
  • (synchronization break)

  • handler catches the event: /engine/platform/gameInterface.cc--GameInterface::processEvent()
  • event is a PacketReceiveEventType, dispatched to
    • engine/sim/netInterface.cc--NetInterface::processPacketReceiveEvent()
    • we're now dealing with a connection challenge response, handle with
      • engine/sim/netInterface.cc--NetInterface::handleConnectChallengeResponse()
      • process the handshake checksum server gave us, grab the PendingConnection we added previously, and send on to the server the actual connection request via
        • NetInterface::sendConnectRequest() via
          • BitStream::sendPacketStream


SERVER


  • server Receives the packet, posts Event
  • (synchronization break)

  • /engine/<platform>Net.cc--Net::Process()
    • dispatches the event, event is a ConnectRequest, handled by
      • /engine/sim/netInterface.cc--NetInterface::handleConnectRequest()
        • check to see if the connection is already "established"--if so, accept it right off
        • validate the handshake checksum
          • if invalid, ignore (send nothing to client)
          • if valid, place in (established) connection table
          • continue reading/processing the connection request for further game level validation, call
            • engine/game/gameConnection.cc--GameConnection::readConnectRequest()
              • validate Parent::readConnectRequest()
                • engine/sim/netConnection.cc--NetConnection::readConnectRequest()
                  • test classGroup and CRC
                    • validate protocol version
                    • test admin password, if sent. if sent and wrong, reject connection
                    • grab connect args (these are the ones originally set with setConnectArgs() on the client side) and dispatch to the appropriate server side scripted onConnectRequest()
                      • (Script Callback) common/server/clientConnection.cs--GameConnection::onConnectRequest()
                        • checks to see if server is full, if not allow, otherwise reject
                      • call engine/game/gameConnection.cc--GameConnection::onConnectionEstablished() with initiator == false
                        • configure server's version of the connection for scoping, events, tagged string handling
                          • call onConnect script with connectionArgs:
                            • (Script Callback) common/server/clientConnection.cs--GameConnection::onConnect()
                          • message client with Connection status/error
                        • (Script Callback) send mission info to client via common/server/missionInfo.cs--sendLoadInfoToClient()
                          • this is just the mission description
                        • if client's IP address is "local" (same as server), auto-set admin and super-admin status for client
                        • set default values on client's object
                        • Echo to console.log: “CADD <clientId> <ipaddress>”
                        • inform the connecting client client about all the other clients (no message)
                        • inform connecting client that they are connected with message
                        • inform ALL other clients that the new client has connected, with message
                        • if the mission is already running, start the load mission handler:
                          • common/server/missionDownload.cs--GameConnection::loadMission()


NOTE: this is a highly synchronized process between the server and client. However, since the client and server are in fact "de-synchronized" (only connected via the network), we have to use a series of phases and "start/ready to start"/"should be finished/am finished" NetEvents across the network to stay synchronized.

Raw notes from the script:

//--------------------------------------------------------------------------

// Loading Phases:

// Phase 1: Transmit Datablocks

// Transmit targets

// Phase 2: Transmit Ghost Objects

// Phase 3: Start Game

//

// The server invokes the client MissionStartPhase[1-3] function to request

// permission to start each phase. When a client is ready for a phase,

// it responds with MissionStartPhase[1-3]Ack.


NOTE: For readability purposes, I am going to drop down the indents back to the base. Keep in mind that all of this is still a child call stack of all of the above:


SERVER


  • common/server/scripts/missionDownload.cs--commandToClient(‘MissionStartPhase1’)
    • ECHO: "*** Sending mission load to client: <missionInfo>"


CLIENT


  • (Script NetEvent Handler): /common/client/missionDownload.cs--clientCmdMissionStartPhase1()
    • ECHO: "*** New Mission: <MissionName>"
    • ECHO: *** Phase 1: Download Datablocks & Targets"
    • (Script Function)starter.fps/client/scripts/missionDownload.cs--onMissionDownloadPhase1()
      • close MessageHud if open
      • initialize LoadingProgressGui with start percent and "LOADING DATABLOCKS"
      • commandToServer('MissionStartPhase1Ack')


SERVER

  • (Script NetEvent Handler): /common/server/missionDownload.cs--serverCmdMissionStartPhase1Ack()
    • set transmission phase and missionCRC

    • At this point we are in an emulated "synchronized phase" between client and server. Server dumps datablocks down the connection, and client receives, instantiates, and data populates based on the standard ghost system.


      SERVER

      • start sending datablocks via
        • engine/game/gameConnection.cc--ConsoleMethod( GameConnection, transmitDataBlocks...)
          • iterate until done with datablocks:
            • check to see if we're done, if so, message client that we're done
            • post NetEvents for each of the remaining datablocks in the DataBlockGroup

      CLIENT

      • /engine/<platform>Net--Net::Process()
        • receives the NetEvent, dispatches to engine/platform/GameInterface::postEvent()

      (synchronization break)

      • handler catches the event: /engine/platform/gameInterface.cc--GameInterface::processEvent()
        • process each datablock as required


      Once the server has finished all of the iteration over the datablocks, we arrive at the following:


      SERVER

      • post NetEvent: DataBlocksDone


      CLIENT

      • once we're coordinated and synched with all datablocks received (asynch handling based on NULL object received and processed), post the following back to server:
        • NetEvent: DataBlocksDownloadDone


    SERVER


    • process the DataBlocksDownloadDone event, call GameConnection::onDataBlocksDone
      • (Script Callback): common/server/missionDownload.cs--GameConnection::onDataBlocksDone()
        • commandToClient('MissionStartPhase2');


    CLIENT


    • (Script NetEvent handler): catch MissionStartPhase2 event with common/client/missionDownload.cs--clientCmdMissionStartPhase2()
      • call (empty) starter.fps/client/scripts/missionDownload.cs--onPhase1Complete()
      • ECHO: "*** Phase 2: Download Ghost Objects"
    • prep for phase 2 with (Script Callback): starter.fps/client/scripts/missionDownload.cs--onMissionDownloadPhase2()
      • reset the LoadingProgressGui for LOADING OBJECTS
      • ack the start of phase 2
        • commandToServer('MissionStartPhase2Ack')



    SERVER

    • catch the ack with (Script NetEvent Handler) common/server/missionDownload.cs--serverCmdMissionStartPhase2Ack()
      • transmit mod Paths so client knows where to look
      • start ghosting by calling the Console Method activateGhosting() on the GameConnection object
        • calls engine/sim/netGhost.cc--NetConnection::activateGhosting()
        • send connectionMessage: GhostAlwaysStarting


        • SYNCHED (again, in that "not quite tied directly" manner over the network conenction)


        • iterate over all GhostAlwaysObjectEvent events
        • {

          SERVER

        • NetPost GhostAlwaysObjectEvent

        • CLIENT


        • NetProcess GhostAlwaysObjectEvent
        • }


          SERVER

      • send connectionMessage: GhostAlwaysDone

      • NOTE: We are glossing over most of the details of the actual reception, instantiation, sim registration, and data population of these objects


        CLIENT


      • handle connectionMessage: GhostAlwaysDone
        • engine/sim/netGhost.cc--NetConnection::handleConnectionMessage()
        • // ok, all the ghost always objects are now on the client... but!
          
          // it's possible that there were some file load errors...
          
          // if so, we need to indicate to the server to restart ghosting, after
          
          // we download all the files...
          
          coordinate downloading the files that were missing, and resending ghostAlways objects that previously failed
          


            SERVER
          • coordinate any ghostAlways objects that have to be resent

          • CLIENT


          • once done resynching ghostAlways objects (if needed), sendConnectionMessage(ReadyForNormalGhosts)

          • SERVER


          • catch ReadyForNormalGhosts message, execute GameConnection::onGhostAlwaysObjectsReceived
            • common/server/missionDownload.cs--GameConnection::onGhostAlwaysObjectsReceived()
            • commandToClient( 'MissionStartPhase3' )


            CLIENT


          • catch MissionStartPhase3 with (Script NetEvent Handler): common/client/missionDownload.cs--clientCmdMissionStartPhase3()
            • call (empty) (Script Callback):starter.fps/client/scripts/missionDownload.cs--onPhase2Complete()
            • start shape replication with call to engine/game/fx/fxShapeReplicator.cc--ConsoleMethod(StartClientReplication)
              • do shape replication
            • ECHO: "Client Replication Startup has Happened!"
            • start foliage replication with call to engine/game/fx/fxFoliageReplicator.cc--ConsoleFunction(StartFoliageReplication)
              • do foliage replication
              • ECHO: "fxFoliageReplicator - Client Foliage Replication Startup is complete."
            • ECHO: "*** Phase 3: Mission Lighting"
              • kick off mission lighting
              • ECHO: "Lighting mission...."
              • do mission lighting while updating progress gui (onMissionDownloadPhase3 called to init gui)
            • ECHO: "Mission lighting done"
            • (Script CallBack) (starter.fps/client/scripts/missionDownload.cs--onPhase3Complete()
              • cleanup from lighting mission
            • (Script Callback) starter.fps/client/scripts/missionDownload.cs--onMissionDownloadComplete()
              • empty function, with a script comment:
              • // Client will shortly be dropped into the game, so this is
                
                // good place for any last minute gui cleanup.
                
            • commandToServer('MissionStartPhase3Ack')



            SERVER

            • (Script NetEvent Handler) common/server/scripts/missionDownload.cs--serverCmdMissionStartPhase3Ack()
              • common/server/clientConnection--GameConnection::startMission()
                • commandToClient( 'MissionStart' )

                • CLIENT


                • (Script NetEvent Handler) common/client/scripts/mission.cs--clientCmdMissionStart()</b>
                  • empty, with this comment:
                  • // The client recieves a mission start right before
                    
                    // being dropped into the game.
                    


            SERVER


          • common/server/scripts/game.cs--GameConnection::onClientEnterGame() "virtual", reimplemented in:
            • starter.fps/server/scripts/game.cs--GameConnection::onClientEnterGame()
              • Synchronize Game Clocks between server (authoritative) and client
              • Create camera object (for alternate control object)
              • Determine a spawn point from the mission's indicated SpawnSpheres
              • Create the Avatar (Player class by default) and set as Control Object
              • do housekeeping (MissionCleanup Group)



              • We are now connected, and “in game”!