#include "ReplicaManager.h" #include "RakPeerInterface.h" #include "GetTime.h" #include "PacketEnumerations.h" #include "BitStream.h" #include "Replica.h" #include #include #include // For my debug printfs #ifdef _MSC_VER #pragma warning( push ) #endif int ReplicaManager::CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ) { if (key < data.replica) return -1; if (key==data.replica) return 0; return 1; } int ReplicaManager::RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ) { if (key < data.replica) return -1; if (key==data.replica) return 0; return 1; } int ReplicaManager::RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ) { if (key < data.replica) return -1; if (key==data.replica) return 0; return 1; } int ReplicaManager::ParticipantStructComp( const PlayerID &key, ReplicaManager::ParticipantStruct * const &data ) { if (key < data->playerId) return -1; if (key==data->playerId) return 0; return 1; } ReplicaManager::ReplicaManager() { _constructionCB=0; _sendDownloadCompleteCB=0; _receiveDownloadCompleteCB=0; rakPeer=0; sendChannel=0; autoParticipateNewConnections=false; defaultScope=false; autoConstructToNewParticipants=false; } ReplicaManager::~ReplicaManager() { Clear(); } void ReplicaManager::SetAutoParticipateNewConnections(bool autoAdd) { autoParticipateNewConnections=autoAdd; } void ReplicaManager::AddParticipant(PlayerID playerId) { assert(playerId!=UNASSIGNED_PLAYER_ID); // If this player is already in the list of participants, just return. ParticipantStruct *participantStruct; participantStruct=GetParticipantByPlayerID(playerId); if (participantStruct) return; // Create a new participant with this playerID participantStruct = new ParticipantStruct; participantStruct->playerId=playerId; // Signal that when done sending SendConstruction for each existing object, we call sendDownloadCompleteCB participantStruct->callDownloadCompleteCB=true; // Add the new participant to the list of participants participantList.Insert(playerId,participantStruct); if (autoConstructToNewParticipants) { // Signal that we need to call SendConstruction for each existing object to this participant unsigned i; CommandStruct replicaAndCommand; replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; for (i=0; i < replicatedObjects.Size(); i++) { replicaAndCommand.replica=replicatedObjects[i].replica; participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); } } } void ReplicaManager::RemoveParticipant(PlayerID playerId) { assert(playerId!=UNASSIGNED_PLAYER_ID); // Find this participant by playerId ParticipantStruct *participantStruct; participantStruct=GetParticipantByPlayerID(playerId); // If found, remove and free this participant structure if (participantStruct) { participantList.Remove(playerId); delete participantStruct; } } void ReplicaManager::Construct(Replica *replica, bool isCopy, PlayerID playerId, bool broadcast) { assert(replica); unsigned i; ParticipantStruct *participantStruct; CommandStruct replicaAndCommand; unsigned index; bool objectExists; replicaAndCommand.replica=replica; ReferencePointer(replica); for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && playerId!=participantStruct->playerId) || (broadcast==false && playerId==participantStruct->playerId)) { if (participantStruct->remoteObjectList.HasData(replica)==false) { index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) { #ifdef _DEBUG // Implicit is only used for objects that were not already registered. assert(isCopy==false); #endif participantStruct->commandList[index].command|=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit participantStruct->commandList[index].command&=0xFF ^ REPLICA_IMPLICIT_CONSTRUCTION; // Unset this bit } else { if (isCopy) replicaAndCommand.command=REPLICA_IMPLICIT_CONSTRUCTION; // Set this bit else replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); } } } } } void ReplicaManager::Destruct(Replica *replica, PlayerID playerId, bool broadcast) { assert(replica); bool objectExists; unsigned replicatedObjectsIndex; replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) return; // For each existing participant, send a packet telling them of this object destruction RakNet::BitStream outBitstream; unsigned i,tempIndex; bool replicaReferenced; ParticipantStruct *participantStruct; replicaReferenced=false; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && playerId!=participantStruct->playerId) || (broadcast==false && playerId==participantStruct->playerId)) { // Send the destruction packet immediately if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_DESTRUCTION)) { outBitstream.Reset(); outBitstream.Write((unsigned char)ID_REPLICA_MANAGER_DESTRUCTION); outBitstream.Write(replica->GetNetworkID()); replica->SendDestruction(&outBitstream, participantStruct->playerId); if (outBitstream.GetNumberOfBitsUsed()>0) { rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->playerId, false); } } // Remove any pending commands that reference this object, for this player tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) participantStruct->commandList.RemoveAtIndex(tempIndex); // Remove any remote object state tracking for this object, for this player tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); } else if (replicaReferenced==false) { // See if any commands or objects reference replica if (participantStruct->commandList.HasData(replica)) replicaReferenced=true; else if (participantStruct->remoteObjectList.HasData(replica)) replicaReferenced=true; } } // Remove replica from the list if no commands and no remote objects reference it if (replicaReferenced==false) replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); } void ReplicaManager::ReferencePointer(Replica *replica) { // Start tracking this object, if we are not already if (replicatedObjects.HasData(replica)==false) { RegisteredReplica replicaAndTime; replicaAndTime.replica=replica; replicaAndTime.lastDeserializeTrue=0; replicaAndTime.allowedInterfaces=REPLICA_SET_ALL; replicatedObjects.Insert(replica,replicaAndTime); } } void ReplicaManager::DereferencePointer(Replica *replica) { bool objectExists; unsigned replicatedObjectsIndex; unsigned tempIndex; replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) return; replicatedObjects.RemoveAtIndex(replicatedObjectsIndex); ParticipantStruct *participantStruct; unsigned i; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; // Remove any pending commands that reference this object for any player tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) participantStruct->commandList.RemoveAtIndex(tempIndex); // Remove any remote object state tracking for this object for any player tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); } } void ReplicaManager::SetScope(Replica *replica, bool inScope, PlayerID playerId, bool broadcast) { assert(replica); // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry // About the order of operations. if (replicatedObjects.HasData(replica)==false) ReferencePointer(replica); // For each player that we want, flag to call SendScopeChange if inScope is different from what they already have unsigned i; ParticipantStruct *participantStruct; bool objectExists; unsigned index; CommandStruct replicaAndCommand; if (inScope) replicaAndCommand.command=REPLICA_SCOPE_TRUE; else replicaAndCommand.command=REPLICA_SCOPE_FALSE; replicaAndCommand.replica=replica; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && playerId!=participantStruct->playerId) || (broadcast==false && playerId==participantStruct->playerId)) { // If there is already a pending command for this object, add to it. Otherwise, add a new pending command index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) { // Update a pending command if (inScope) { participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_FALSE; // Unset this bit participantStruct->commandList[index].command|=REPLICA_SCOPE_TRUE; // Set this bit } else { participantStruct->commandList[index].command&=0xFF ^ REPLICA_SCOPE_TRUE; // Unset this bit participantStruct->commandList[index].command|=REPLICA_SCOPE_FALSE; // Set this bit } } else { // Add a new command, since there are no pending commands for this object participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); } } } } void ReplicaManager::SignalSerializeNeeded(Replica *replica, PlayerID playerId, bool broadcast) { assert(replica); // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry // About the order of operations. if (replicatedObjects.HasData(replica)==false) ReferencePointer(replica); // For each player that we want, if this object exists on that system, flag to call Serialize // (this may not necessarily happen - it depends on if the object is inScope when Update actually processes it.) unsigned i; ParticipantStruct *participantStruct; bool objectExists; unsigned index; CommandStruct replicaAndCommand; replicaAndCommand.command=REPLICA_SERIALIZE; replicaAndCommand.replica=replica; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && playerId!=participantStruct->playerId) || (broadcast==false && playerId==participantStruct->playerId)) { // If there is already a pending command for this object, add to it. Otherwise, add a new pending command index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) { participantStruct->commandList[index].command|=REPLICA_SERIALIZE; // Set this bit } else { // Add a new command, since there are no pending commands for this object participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); } } } } void ReplicaManager::SetReceiveConstructionCB(ReplicaReturnResult (* constructionCB)(RakNet::BitStream *inBitStream, RakNetTime timestamp, NetworkID networkID, PlayerID senderId, ReplicaManager *caller)) { // Just overwrite the construction callback pointer _constructionCB=constructionCB; } void ReplicaManager::SetDownloadCompleteCB(ReplicaReturnResult (* sendDownloadCompleteCB)(RakNet::BitStream *outBitStream, RakNetTime currentTime, PlayerID senderId, ReplicaManager *caller), ReplicaReturnResult (* receiveDownloadCompleteCB)(RakNet::BitStream *inBitStream, PlayerID senderId, ReplicaManager *caller)) { // Just overwrite the send and receive download complete pointers. _sendDownloadCompleteCB=sendDownloadCompleteCB; _receiveDownloadCompleteCB=receiveDownloadCompleteCB; } void ReplicaManager::SetSendChannel(unsigned char channel) { // Change the send channel from the default of 0 sendChannel=channel; } void ReplicaManager::SetAutoConstructToNewParticipants(bool autoConstruct) { autoConstructToNewParticipants=autoConstruct; } void ReplicaManager::SetDefaultScope(bool scope) { defaultScope=scope; } void ReplicaManager::EnableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) { bool objectExists; unsigned replicatedObjectsIndex; replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) { // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry // About the order of operations. ReferencePointer(replica); replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); } replicatedObjects[replicatedObjectsIndex].allowedInterfaces|=interfaceFlags; } void ReplicaManager::DisableReplicaInterfaces(Replica *replica, unsigned char interfaceFlags) { bool objectExists; unsigned replicatedObjectsIndex; replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) { // Autoreference the pointer if necessary. This way the user can call functions on an object without having to worry // About the order of operations. ReferencePointer(replica); replicatedObjectsIndex = replicatedObjects.GetIndexFromKey(replica, &objectExists); } replicatedObjects[replicatedObjectsIndex].allowedInterfaces&= 0xFF ^ interfaceFlags; } bool ReplicaManager::IsConstructed(Replica *replica, PlayerID playerId) { ParticipantStruct *participantStruct = GetParticipantByPlayerID(playerId); if (participantStruct) { bool objectExists; participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); return objectExists; } return false; } bool ReplicaManager::IsInScope(Replica *replica, PlayerID playerId) { ParticipantStruct *participantStruct = GetParticipantByPlayerID(playerId); if (participantStruct) { bool objectExists; unsigned remoteObjectListIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) return participantStruct->remoteObjectList[remoteObjectListIndex].inScope; } return false; } unsigned ReplicaManager::GetReplicaCount(void) const { return replicatedObjects.Size(); } Replica *ReplicaManager::GetReplicaAtIndex(unsigned index) { return replicatedObjects[index].replica; } void ReplicaManager::Clear(void) { // Free all memory unsigned i; for (i=0; i < participantList.Size(); i++) delete participantList[i]; participantList.Clear(); replicatedObjects.Clear(); } void ReplicaManager::OnAttach(RakPeerInterface *peer) { rakPeer=peer; } 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; } } } #ifdef _MSC_VER #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter #endif void ReplicaManager::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId) { RemoveParticipant(playerId); } #ifdef _MSC_VER #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter #endif void ReplicaManager::OnDisconnect(RakPeerInterface *peer) { Clear(); } PluginReceiveResult ReplicaManager::OnReceive(RakPeerInterface *peer, Packet *packet) { unsigned char packetIdentifier; if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) { if ( packet->length > sizeof( unsigned char ) + sizeof( unsigned int ) ) packetIdentifier = ( unsigned char ) packet->data[ sizeof( unsigned char ) + sizeof( unsigned int ) ]; else return RR_STOP_PROCESSING_AND_DEALLOCATE; } else packetIdentifier = ( unsigned char ) packet->data[ 0 ]; switch (packetIdentifier) { case ID_NEW_INCOMING_CONNECTION: case ID_CONNECTION_REQUEST_ACCEPTED: if (autoParticipateNewConnections) AddParticipant(packet->playerId); return RR_CONTINUE_PROCESSING; case ID_DISCONNECTION_NOTIFICATION: case ID_CONNECTION_LOST: OnCloseConnection(peer, packet->playerId); return RR_CONTINUE_PROCESSING; case ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE: if (_receiveDownloadCompleteCB==0) { return RR_STOP_PROCESSING_AND_DEALLOCATE; } case ID_REPLICA_MANAGER_CONSTRUCTION: case ID_REPLICA_MANAGER_DESTRUCTION: case ID_REPLICA_MANAGER_SCOPE_CHANGE: case ID_REPLICA_MANAGER_SERIALIZE: { ParticipantStruct *participantStruct; bool hasNetworkId; ReceivedCommand receivedCommand; bool b=true; RakNet::BitStream inBitstream(packet->data, packet->length, false); // SetWriteOffset is used here to get around a design flaw, where I should have had the bitstream constructor take bits, rather than bytes // It sets the actual number of bits in the packet inBitstream.SetWriteOffset(packet->bitSize); receivedCommand.playerId=packet->playerId; receivedCommand.command=packetIdentifier; if ( ( unsigned char ) packet->data[ 0 ] == ID_TIMESTAMP ) { inBitstream.IgnoreBits(8); b=inBitstream.Read(receivedCommand.u1); } else receivedCommand.u1=0; inBitstream.IgnoreBits(8); // Ignore the packet id receivedCommand.networkID=UNASSIGNED_NETWORK_ID; if (packetIdentifier==ID_REPLICA_MANAGER_CONSTRUCTION) // ID_REPLICA_MANAGER_CONSTRUCTION has an optional networkID { b=inBitstream.Read(hasNetworkId); if (hasNetworkId) b=inBitstream.Read(receivedCommand.networkID); } else if (packetIdentifier!=ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) { b=inBitstream.Read(receivedCommand.networkID); // Other packets always have an networkID } if (b==false) { // Invalid packet #ifdef _DEBUG assert(0); #endif return RR_STOP_PROCESSING_AND_DEALLOCATE; } receivedCommand.userData=&inBitstream; participantStruct=GetParticipantByPlayerID(receivedCommand.playerId); if (participantStruct) { // .Size()>0 is because commands are always processed in order. If a command is delayed, no further commands are processed. // ProcessReceivedCommand(...)==false means that the use signaled to delay a command if (participantStruct->pendingCommands.Size()>0 || ProcessReceivedCommand(participantStruct, &receivedCommand)==REPLICA_PROCESS_LATER) { // Copy the data and add this to a queue that will call ProcessReceivedCommand again in Update. // Allocate and copy structure ReceivedCommand *rc = new ReceivedCommand; memcpy(rc, &receivedCommand, sizeof(ReceivedCommand)); // Allocate and copy inBitstream remaining data rc->userData = new RakNet::BitStream; rc->userData->Write(&inBitstream, inBitstream.GetNumberOfBitsUsed()); participantStruct->pendingCommands.Push(rc); } } return RR_STOP_PROCESSING_AND_DEALLOCATE; } } return RR_CONTINUE_PROCESSING; } ReplicaManager::ParticipantStruct* ReplicaManager::GetParticipantByPlayerID(const PlayerID playerId) const { bool objectExists; unsigned index; index = participantList.GetIndexFromKey(playerId, &objectExists); if (objectExists==false) return 0; return participantList[index]; } #ifdef _MSC_VER #pragma warning( disable : 4100 ) // warning C4100: : unreferenced formal parameter #pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized #endif ReplicaReturnResult ReplicaManager::ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand) { Replica *replica = (Replica*) NetworkIDGenerator::GET_BASE_OBJECT_FROM_ID(receivedCommand->networkID); bool objectExists; unsigned index; ReplicaReturnResult b; if (replica) { index = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) { // Two ways to handle this. One is to autoregister the object - but this is dangerous in that if you have a network object that is not // using the replica system it would be of the wrong type, read read invalid memory, and crash. // Construct(replica, playerId, broadcast, true); // The other way is to assert and do nothing. The disadvantage is now the user has to register the object in order to receive calls on it. #ifdef _DEBUG // If this assert hits, call ReferencePointer on your replica pointer after it is created. assert(0); #endif return REPLICA_CANCEL_PROCESS; } } if (receivedCommand->command==ID_REPLICA_MANAGER_SERIALIZE) { if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SERIALIZE)) { b=replica->Deserialize(receivedCommand->userData, receivedCommand->u1, replicatedObjects[index].lastDeserializeTrue, receivedCommand->playerId); if (b==REPLICA_PROCESSING_DONE) replicatedObjects[index].lastDeserializeTrue=RakNet::GetTime(); return b; } } else if (receivedCommand->command==ID_REPLICA_MANAGER_CONSTRUCTION) { // If networkID already exists on this system, ignore the packet if (replica==0) { #ifdef _DEBUG assert(_constructionCB); #endif // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB return _constructionCB(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, receivedCommand->playerId, this); } } else if (receivedCommand->command==ID_REPLICA_MANAGER_SCOPE_CHANGE) { if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_SCOPE_CHANGE)) { return replica->ReceiveScopeChange(receivedCommand->userData, receivedCommand->playerId); } } else if (receivedCommand->command==ID_REPLICA_MANAGER_DESTRUCTION) { if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_DESTRUCTION)) { return replica->ReceiveDestruction(receivedCommand->userData, receivedCommand->playerId); } } else if (receivedCommand->command==ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) { if (_receiveDownloadCompleteCB) { return _receiveDownloadCompleteCB(receivedCommand->userData, receivedCommand->playerId, this); } } return REPLICA_CANCEL_PROCESS; } ReplicaManager::ParticipantStruct::~ParticipantStruct() { ReceivedCommand *receivedCommand; while ( pendingCommands.Size() ) { receivedCommand=pendingCommands.Pop(); delete receivedCommand->userData; delete receivedCommand; } } #ifdef _MSC_VER #pragma warning( pop ) #endif