void ReplicaManager::Update(RakPeerInterface *peer) { if (participantList.Size()==0) return; unsigned participantIndex, remoteObjectListIndex, replicatedObjectsIndex; ReplicaReturnResult res; bool sendTimestamp; ParticipantStruct *participantStruct; unsigned commandListIndex; RakNet::BitStream outBitstream, userDataBitstream; RakNetTime currentTime; bool objectExists; PacketPriority priority; PacketReliability reliability; ReceivedCommand *receivedCommand; Replica *replica; unsigned char command; currentTime=0; // For each participant for (participantIndex=0; participantIndex < participantList.Size(); participantIndex++) { participantStruct = participantList[participantIndex]; // For each CommandStruct to send for (commandListIndex=0; commandListIndex < participantStruct->commandList.Size(); commandListIndex++) { // Only call RakNet::GetTime() once because it's slow if (currentTime==0) currentTime=RakNet::GetTime(); replica=participantStruct->commandList[commandListIndex].replica; command=participantStruct->commandList[commandListIndex].command; replicatedObjectsIndex=replicatedObjects.GetIndexFromKey(replica, &objectExists); #ifdef _DEBUG assert(objectExists); #endif // If construction is set, call SendConstruction. The only precondition is that the object was not already created, // which was checked in ReplicaManager::Replicate if (command & REPLICA_EXPLICIT_CONSTRUCTION) { if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_CONSTRUCTION) { userDataBitstream.Reset(); sendTimestamp=false; res=replica->SendConstruction(currentTime, participantStruct->playerId, &userDataBitstream, &sendTimestamp); if (res==REPLICA_PROCESSING_DONE) { outBitstream.Reset(); // If SendConstruction returns true and writes to outBitStream, do this send. Clear the construction command. Then process the next command for this CommandStruct, if any. if (sendTimestamp) { outBitstream.Write((unsigned char)ID_TIMESTAMP); outBitstream.Write(currentTime); } outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_CONSTRUCTION); // It's required to send an NetworkID if available. // Problem: // A->B->C // | | // D->E // // A creates. // B->C->E->D->B will cycle forever. // Fix is to always include an networkID. Objects are not created if that object id already is present. if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID) { outBitstream.Write(true); outBitstream.Write(replica->GetNetworkID()); } else outBitstream.Write(false); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); // Turn off this bit participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_EXPLICIT_CONSTRUCTION; // Add the object to the participant's object list, indicating this object has been remotely created RemoteObject remoteObject; remoteObject.replica=replica; remoteObject.inScope=defaultScope; remoteObject.lastSendTime=0; // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject); } else if (res==REPLICA_PROCESS_LATER) { continue; } else // REPLICA_CANCEL_PROCESS { assert(res==REPLICA_CANCEL_PROCESS); participantStruct->commandList[commandListIndex].command=0; } } else { // Don't allow construction, or anything else for this object, as the construction send call is disallowed participantStruct->commandList[commandListIndex].command=0; } } else if (command & REPLICA_IMPLICIT_CONSTRUCTION) { // Turn off this bit participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; // Add the object to the participant's object list, indicating this object is assumed to be remotely created RemoteObject remoteObject; remoteObject.replica=replica; remoteObject.inScope=defaultScope; remoteObject.lastSendTime=0; // Create an entry for this object. We do this now, even if the user might refuse the SendConstruction override, // because that call may be delayed and other commands sent while that is pending. We always do the REPLICA_EXPLICIT_CONSTRUCTION call first. participantStruct->remoteObjectList.Insert(remoteObject.replica,remoteObject); } // Sends the download complete packet // If callDownloadCompleteCB is true then check all the remaining objects starting at commandListIndex // I scan every frame in case the callback returns false to delay, and after that time a new object is Replicated if (participantStruct->callDownloadCompleteCB) { bool anyHasConstruction; unsigned j; anyHasConstruction=false; for (j=0; j < participantStruct->commandList.Size(); j++) { if (participantStruct->commandList[j].command & REPLICA_EXPLICIT_CONSTRUCTION) { anyHasConstruction=true; break; } } // If none have REPLICA_EXPLICIT_CONSTRUCTION, send ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE and set callDownloadCompleteCB false if (anyHasConstruction==false) { ReplicaReturnResult sendDLComplete; userDataBitstream.Reset(); if (_sendDownloadCompleteCB) sendDLComplete=_sendDownloadCompleteCB(&userDataBitstream, currentTime, participantStruct->playerId, this); // If you return false, this will be called again next update else sendDLComplete=REPLICA_CANCEL_PROCESS; if (sendDLComplete==REPLICA_PROCESSING_DONE) { outBitstream.Reset(); outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); participantStruct->callDownloadCompleteCB=false; } else if (sendDLComplete==REPLICA_CANCEL_PROCESS) { participantStruct->callDownloadCompleteCB=false; } else { assert(sendDLComplete==REPLICA_PROCESS_LATER); // else REPLICA_PROCESS_LATER } } } // The remaining commands, SendScopeChange and Serialize, require the object the command references exists on the remote system, so check that remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) { command = participantStruct->commandList[commandListIndex].command; // Process SendScopeChange. if ((command & (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE))) { if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) continue; // Not set yet so call this later. if (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SCOPE_CHANGE) { bool scopeTrue = (command & REPLICA_SCOPE_TRUE)!=0; // Only send scope changes if the requested change is different from what they already have if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope!=scopeTrue) { userDataBitstream.Reset(); res=replica->SendScopeChange(scopeTrue, &userDataBitstream, currentTime, participantStruct->playerId); if (res==REPLICA_PROCESSING_DONE) { // If the user returns true and does write to outBitstream, do this send. Clear the scope change command. Then process the next command for this CommandStruct, if any. outBitstream.Reset(); outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_SCOPE_CHANGE); outBitstream.Write(replica->GetNetworkID()); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); // Set the scope for this object and system participantStruct->remoteObjectList[remoteObjectListIndex].inScope=scopeTrue; // If scope is true, turn on serialize, since you virtually always want to serialize when an object goes in scope participantStruct->commandList[commandListIndex].command |= REPLICA_SERIALIZE; // Turn off these bits - Call is processed participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); } else if (res==REPLICA_CANCEL_PROCESS) { // Turn off these bits - Call is canceled participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); } else { // If the user returns false and the scope is currently set to false, just continue with another CommandStruct. Don't process serialization until scoping is resolved first. if (scopeTrue==false) continue; // If the user returns false and the scope is currently set to false, process the next command for this CommandStruct, if any. } } } else { // Turn off these bits - Call is disallowed participantStruct->commandList[commandListIndex].command &= 0xFF ^ (REPLICA_SCOPE_TRUE | REPLICA_SCOPE_FALSE); // Set the scope - even if the actual send is disabled we might still be able to serialize. participantStruct->remoteObjectList[remoteObjectListIndex].inScope=(command & REPLICA_SCOPE_TRUE)!=0; } } command = participantStruct->commandList[commandListIndex].command; // Process Serialize if ((participantStruct->commandList[commandListIndex].command & REPLICA_SERIALIZE)) { if (replica->GetNetworkID()==UNASSIGNED_NETWORK_ID) continue; // Not set yet so call this later. // If scope is currently false for this object in the RemoteObject list, cancel this serialize as the scope changed before the serialization went out if (participantStruct->remoteObjectList[remoteObjectListIndex].inScope && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_SERIALIZE)) { do { userDataBitstream.Reset(); priority=HIGH_PRIORITY; reliability=RELIABLE_ORDERED; sendTimestamp=false; res=replica->Serialize(&sendTimestamp, &userDataBitstream, participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime, &priority, &reliability, currentTime, participantStruct->playerId); if (res==REPLICA_PROCESSING_DONE || res==REPLICA_PROCESS_AGAIN) { participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime=currentTime; outBitstream.Reset(); if (sendTimestamp) { outBitstream.Write((unsigned char)ID_TIMESTAMP); outBitstream.Write(currentTime); } outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_SERIALIZE); outBitstream.Write(replica->GetNetworkID()); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, priority, reliability, sendChannel, participantStruct->playerId, false); // Clear the serialize bit when done if (res==REPLICA_PROCESSING_DONE) participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; // else res==REPLICA_PROCESS_AGAIN so it will repeat the enclosing do {} while(); loop } else if (res==REPLICA_CANCEL_PROCESS) { // Clear the serialize bit participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; } else { // if the user returns REPLICA_PROCESS_LATER, just continue with another CommandStruct. assert(res==REPLICA_PROCESS_LATER); } } while(res==REPLICA_PROCESS_AGAIN); } else { // Cancel this serialize participantStruct->commandList[commandListIndex].command &= 0xFF ^ REPLICA_SERIALIZE; } } } } // Go through the command list and delete all cleared commands, from back to front. It is more efficient to do this than to delete them as we process commandListIndex=participantStruct->commandList.Size(); if (commandListIndex>0) { #ifdef _MSC_VER #pragma warning( disable : 4127 ) // warning C4127: conditional expression is constant #endif while (1) { if (participantStruct->commandList[commandListIndex-1].command==0) { // If this is the last item in the list, and it probably is, then it just changes a number rather than shifts the entire array participantStruct->commandList.RemoveAtIndex(commandListIndex-1); } if (--commandListIndex==0) break; } } // Now process queued receives while (participantStruct->pendingCommands.Size()) { receivedCommand=participantStruct->pendingCommands.Pop(); participantStruct=GetParticipantByPlayerID(receivedCommand->playerId); if (participantStruct) { res=ProcessReceivedCommand(participantStruct, receivedCommand); // Returning false means process this command again later if (res==REPLICA_PROCESS_LATER) { // Push the command back in the queue participantStruct->pendingCommands.PushAtHead(receivedCommand); // Stop processing, because all processing is in order break; } else { assert(res==REPLICA_CANCEL_PROCESS); } } // Done with this command, so delete it delete receivedCommand->userData; delete receivedCommand; } } }