#include "ReplicaManager.h" #include "RakPeerInterface.h" #include "GetTime.h" #include "MessageIdentifiers.h" #include "BitStream.h" #include "Replica.h" #include #include #include // For my debug printfs #include "RakAssert.h" #include "NetworkIDManager.h" #ifdef _MSC_VER #pragma warning( push ) #endif int ReplicaManager::CommandStructComp( Replica* const &key, const ReplicaManager::CommandStruct &data ) { if (key->GetSortPriority() < data.replica->GetSortPriority()) return -1; else if (key->GetSortPriority() > data.replica->GetSortPriority()) return 1; if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) return -1; if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) return 0; return 1; } int ReplicaManager::RegisteredReplicaComp( Replica* const &key, const ReplicaManager::RegisteredReplica &data ) { if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) return -1; if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) return 0; return 1; } int ReplicaManager::RegisteredReplicaRefOrderComp( const unsigned int &key, const ReplicaManager::RegisteredReplica &data ) { if (key < data.referenceOrder) return -1; if (key==data.referenceOrder) return 0; return 1; } int ReplicaManager::RemoteObjectComp( Replica* const &key, const ReplicaManager::RemoteObject &data ) { if (key->GetAllocationNumber() < data.replica->GetAllocationNumber()) return -1; if (key->GetAllocationNumber()==data.replica->GetAllocationNumber()) return 0; return 1; } int ReplicaManager::ParticipantStructComp( const SystemAddress &key, ReplicaManager::ParticipantStruct * const &data ) { if (key < data->systemAddress) return -1; if (key==data->systemAddress) return 0; return 1; } ReplicaManager::ReplicaManager() { _constructionCB=0; _sendDownloadCompleteCB=0; _receiveDownloadCompleteCB=0; rakPeer=0; sendChannel=0; autoParticipateNewConnections=false; defaultScope=false; autoConstructToNewParticipants=false; autoSerializeInScope=false; nextReferenceIndex=0; #ifdef _DEBUG inUpdate=false; #endif } ReplicaManager::~ReplicaManager() { Clear(); } void ReplicaManager::SetAutoParticipateNewConnections(bool autoAdd) { autoParticipateNewConnections=autoAdd; } bool ReplicaManager::AddParticipant(SystemAddress systemAddress) { assert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); // If this player is already in the list of participants, just return. ParticipantStruct *participantStruct; participantStruct=GetParticipantBySystemAddress(systemAddress); if (participantStruct) return false; // Create a new participant with this systemAddress participantStruct = new ParticipantStruct; participantStruct->systemAddress=systemAddress; // 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(systemAddress,participantStruct, true); /* 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; if (defaultScope==true) replicaAndCommand.command |= REPLICA_SCOPE_TRUE; replicaAndCommand.userFlags=0; // Auto replicate objects in the order they were originally referenced, rather than the random sorted order they are in now // This way dependencies are created first. DataStructures::OrderedList sortByRefList; for (i=0; i < replicatedObjects.Size(); i++) sortByRefList.Insert(replicatedObjects[i].referenceOrder, replicatedObjects[i]); for (i=0; i < sortByRefList.Size(); i++) { replicaAndCommand.replica=sortByRefList[i].replica; participantStruct->commandList.Insert(replicaAndCommand.replica,replicaAndCommand); } } */ 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; if (defaultScope==true) replicaAndCommand.command |= REPLICA_SCOPE_TRUE; replicaAndCommand.userFlags=0; for (i=0; i < replicatedObjects.Size(); i++) { replicaAndCommand.replica=replicatedObjects[i].replica; participantStruct->commandList.Insert(replicaAndCommand); } } return true; } bool ReplicaManager::RemoveParticipant(SystemAddress systemAddress) { assert(systemAddress!=UNASSIGNED_SYSTEM_ADDRESS); // Find this participant by systemAddress ParticipantStruct *participantStruct; participantStruct=GetParticipantBySystemAddress(systemAddress); // If found, remove and free this participant structure if (participantStruct) { participantList.Remove(systemAddress); delete participantStruct; return true; } return false; } void ReplicaManager::Construct(Replica *replica, bool isCopy, SystemAddress systemAddress, bool broadcast) { assert(replica); unsigned i; ParticipantStruct *participantStruct; CommandStruct replicaAndCommand; unsigned index; bool objectExists; replicaAndCommand.replica=replica; replicaAndCommand.userFlags=0; ReferencePointer(replica); for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || (broadcast==false && systemAddress==participantStruct->systemAddress)) { if (participantStruct->remoteObjectList.HasData(replica)==false) { index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); // 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 if (defaultScope==true && (participantStruct->commandList[index].command & REPLICA_SCOPE_FALSE) == 0) participantStruct->commandList[index].command |= REPLICA_SCOPE_TRUE; } else { if (isCopy) replicaAndCommand.command=REPLICA_IMPLICIT_CONSTRUCTION; // Set this bit else replicaAndCommand.command=REPLICA_EXPLICIT_CONSTRUCTION; // Set this bit if (defaultScope==true) replicaAndCommand.command |= REPLICA_SCOPE_TRUE; participantStruct->commandList.Insert(replicaAndCommand); } } } } // Update immediately, otherwise if we take action on this object the first frame, the action packets will arrive before the object is created Update(rakPeer); } void ReplicaManager::Destruct(Replica *replica, SystemAddress systemAddress, bool broadcast) { assert(replica); bool sendTimestamp; 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, userDataBitStream; unsigned i,tempIndex; bool replicaReferenced; ParticipantStruct *participantStruct; replicaReferenced=false; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || (broadcast==false && systemAddress==participantStruct->systemAddress)) { // Remove any remote object state tracking for this object, for this player tempIndex = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) { // Send the destruction packet immediately if (replica->GetNetworkID()!=UNASSIGNED_NETWORK_ID && (replicatedObjects[replicatedObjectsIndex].allowedInterfaces & REPLICA_SEND_DESTRUCTION)) { userDataBitStream.Reset(); userDataBitStream.Write((MessageID)ID_REPLICA_MANAGER_DESTRUCTION); userDataBitStream.Write(replica->GetNetworkID()); sendTimestamp=false; ReplicaReturnResult res = replica->SendDestruction(&userDataBitStream, participantStruct->systemAddress, &sendTimestamp); if (res==REPLICA_PROCESSING_DONE) { outBitstream.Reset(); if (sendTimestamp) { outBitstream.Write((MessageID)ID_TIMESTAMP); outBitstream.Write(RakNet::GetTime()); outBitstream.Write(&userDataBitStream); rakPeer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); } else rakPeer->Send(&userDataBitStream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); } } participantStruct->remoteObjectList.RemoveAtIndex(tempIndex); } // Remove any pending commands that reference this object, for this player tempIndex = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); // tempIndex = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); if (objectExists) participantStruct->commandList.RemoveAtIndex(tempIndex); } else if (replicaReferenced==false) { bool objectExists; GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); // See if any commands or objects reference replica //if (participantStruct->commandList.HasData(replica)) if (objectExists) 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; replicaAndTime.referenceOrder=nextReferenceIndex++; replicatedObjects.Insert(replica,replicaAndTime, true); /// Try setting the network ID manager if the user forgot if (replica->GetNetworkIDManager()==0) replica->SetNetworkIDManager(rakPeer->GetNetworkIDManager()); } } 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 = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); // 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, SystemAddress systemAddress, 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. 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; replicaAndCommand.userFlags=0; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || (broadcast==false && systemAddress==participantStruct->systemAddress)) { // If there is already a pending command for this object, add to it. Otherwise, add a new pending command index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); // 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); } } } } void ReplicaManager::SignalSerializeNeeded(Replica *replica, SystemAddress systemAddress, 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; replicaAndCommand.userFlags=0; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || (broadcast==false && systemAddress==participantStruct->systemAddress)) { // 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); index = GetCommandListReplicaIndex(participantStruct->commandList, 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); } } } } void ReplicaManager::SetReceiveConstructionCB(ReceiveConstructionInterface *ReceiveConstructionInterface) { // Just overwrite the construction callback pointer _constructionCB=ReceiveConstructionInterface; } void ReplicaManager::SetDownloadCompleteCB(SendDownloadCompleteInterface *sendDownloadComplete, ReceiveDownloadCompleteInterface *receiveDownloadComplete) { // Just overwrite the send and receive download complete pointers. _sendDownloadCompleteCB=sendDownloadComplete; _receiveDownloadCompleteCB=receiveDownloadComplete; } 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::SetAutoSerializeInScope(bool autoSerialize) { autoSerializeInScope=autoSerialize; } 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, SystemAddress systemAddress) { ParticipantStruct *participantStruct = GetParticipantBySystemAddress(systemAddress); if (participantStruct) { bool objectExists; participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); return objectExists; } return false; } bool ReplicaManager::IsInScope(Replica *replica, SystemAddress systemAddress) { ParticipantStruct *participantStruct = GetParticipantBySystemAddress(systemAddress); 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; } unsigned ReplicaManager::GetParticipantCount(void) const { return participantList.Size(); } SystemAddress ReplicaManager::GetParticipantAtIndex(unsigned index) { return participantList[index]->systemAddress; } bool ReplicaManager::HasParticipant(SystemAddress systemAddress) { return participantList.HasData(systemAddress); } void ReplicaManager::SignalSerializationFlags(Replica *replica, SystemAddress systemAddress, bool broadcast, bool set, unsigned int flags) { 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. ReferencePointer(replica); CommandStruct replicaAndCommand; replicaAndCommand.replica=replica; replicaAndCommand.userFlags=flags; replicaAndCommand.command=0; bool objectExists; unsigned i, index; ParticipantStruct *participantStruct; for (i=0; i < participantList.Size(); i++) { participantStruct=participantList[i]; if ((broadcast==true && systemAddress!=participantStruct->systemAddress) || (broadcast==false && systemAddress==participantStruct->systemAddress)) { // Set the flags in the object if the object exists index = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) { if (set) participantStruct->remoteObjectList[index].userFlags|=flags; // Set these user flags else participantStruct->remoteObjectList[index].userFlags&=~flags; // Unset these user flags } else { // The object is not yet created. Add to the pending command, or create a new command. // index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); if (objectExists) { if (set) participantStruct->commandList[index].userFlags|=flags; // Set these user flags else participantStruct->commandList[index].userFlags&=~flags; // Unset these user flags } else if (set) { // Add a new command, since there are no pending commands for this object participantStruct->commandList.Insert(replicaAndCommand); } } } } } unsigned int* ReplicaManager::AccessSerializationFlags(Replica *replica, SystemAddress systemAddress) { 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. ReferencePointer(replica); unsigned index; bool objectExists; ParticipantStruct *participantStruct; CommandStruct replicaAndCommand; replicaAndCommand.replica=replica; replicaAndCommand.userFlags=0; replicaAndCommand.command=0; participantStruct=GetParticipantBySystemAddress(systemAddress); if (participantStruct) { // Set the flags in the object if the object exists index = participantStruct->remoteObjectList.GetIndexFromKey(replica, &objectExists); if (objectExists) { return &(participantStruct->remoteObjectList[index].userFlags); } else { // index = participantStruct->commandList.GetIndexFromKey(replica, &objectExists); index = GetCommandListReplicaIndex(participantStruct->commandList, replica, &objectExists); if (objectExists) { return &(participantStruct->commandList[index].userFlags); } else { // Add a new command, since there are no pending commands for this object //index = participantStruct->commandList.Insert(replicaAndCommand); // return &(participantStruct->commandList[index].userFlags); return & (participantStruct->commandList[participantStruct->commandList.Size()-1].userFlags); } } } // No such participant return 0; } void ReplicaManager::Clear(void) { // Free all memory unsigned i; for (i=0; i < participantList.Size(); i++) delete participantList[i]; participantList.Clear(); replicatedObjects.Clear(); nextReferenceIndex=0; } void ReplicaManager::AssertReplicatedObjectsClear(void) { RakAssert(replicatedObjects.Size()==0); } void ReplicaManager::AssertParticipantsClear(void) { RakAssert(participantList.Size()==0); } void ReplicaManager::OnAttach(RakPeerInterface *peer) { rakPeer=peer; } void ReplicaManager::Update(RakPeerInterface *peer) { if (participantList.Size()==0) return; // Check for recursive calls, which is not supported and should not happen #ifdef _DEBUG RakAssert(inUpdate==false); inUpdate=true; #endif 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 int userFlags; unsigned char command; currentTime=0; // For each participant for (participantIndex=0; participantIndex < participantList.Size(); participantIndex++) { participantStruct = participantList[participantIndex]; // 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->SendDownloadComplete(&userDataBitstream, currentTime, participantStruct->systemAddress, 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((MessageID)ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, false); participantStruct->callDownloadCompleteCB=false; } else if (sendDLComplete==REPLICA_CANCEL_PROCESS) { participantStruct->callDownloadCompleteCB=false; } else { assert(sendDLComplete==REPLICA_PROCESS_LATER); // else REPLICA_PROCESS_LATER } } } // 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; // userFlags=participantStruct->commandList[commandListIndex].userFlags; replicatedObjectsIndex=replicatedObjects.GetIndexFromKey(replica, &objectExists); #ifdef _DEBUG assert(objectExists); #endif if (objectExists==false) continue; // 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->systemAddress, participantStruct->commandList[commandListIndex].userFlags, &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((MessageID)ID_TIMESTAMP); outBitstream.Write(currentTime); } outBitstream.Write((MessageID)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->systemAddress, 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.inScope=false; remoteObject.lastSendTime=0; remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; // 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, true); } else if (res==REPLICA_PROCESS_IMPLICIT) { // 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.inScope=false; remoteObject.lastSendTime=0; remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; // 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, true); } 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.inScope=false; remoteObject.lastSendTime=0; remoteObject.userFlags=participantStruct->commandList[commandListIndex].userFlags; // 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, true); } // 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(); sendTimestamp=false; res=replica->SendScopeChange(scopeTrue, &userDataBitstream, currentTime, participantStruct->systemAddress, &sendTimestamp); 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(); if (sendTimestamp) { outBitstream.Write((MessageID)ID_TIMESTAMP); outBitstream.Write(currentTime); } outBitstream.Write((MessageID)ID_REPLICA_MANAGER_SCOPE_CHANGE); outBitstream.Write(replica->GetNetworkID()); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, HIGH_PRIORITY, RELIABLE_ORDERED, sendChannel, participantStruct->systemAddress, 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 if (scopeTrue && autoSerializeInScope) 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 ((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->systemAddress, participantStruct->remoteObjectList[remoteObjectListIndex].userFlags); if (res==REPLICA_PROCESSING_DONE || res==REPLICA_PROCESS_AGAIN) { participantStruct->remoteObjectList[remoteObjectListIndex].lastSendTime=currentTime; outBitstream.Reset(); if (sendTimestamp) { outBitstream.Write((MessageID)ID_TIMESTAMP); outBitstream.Write(currentTime); } outBitstream.Write((MessageID)ID_REPLICA_MANAGER_SERIALIZE); outBitstream.Write(replica->GetNetworkID()); outBitstream.Write(&userDataBitstream, userDataBitstream.GetNumberOfBitsUsed()); peer->Send(&outBitstream, priority, reliability, sendChannel, participantStruct->systemAddress, 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=GetParticipantBySystemAddress(receivedCommand->systemAddress); 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 _DEBUG inUpdate=false; #endif } void ReplicaManager::OnCloseConnection(RakPeerInterface *peer, SystemAddress systemAddress) { (void) peer; RemoveParticipant(systemAddress); } void ReplicaManager::OnShutdown(RakPeerInterface *peer) { (void) 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->systemAddress); return RR_CONTINUE_PROCESSING; case ID_DISCONNECTION_NOTIFICATION: case ID_CONNECTION_LOST: OnCloseConnection(peer, packet->systemAddress); 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=false; 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.systemAddress=packet->systemAddress; 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=GetParticipantBySystemAddress(receivedCommand.systemAddress); 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::GetParticipantBySystemAddress(const SystemAddress systemAddress) const { bool objectExists; unsigned index; index = participantList.GetIndexFromKey(systemAddress, &objectExists); if (objectExists==false) return 0; return participantList[index]; } #ifdef _MSC_VER #pragma warning( disable : 4701 ) // warning C4701: local variable may be used without having been initialized #endif ReplicaReturnResult ReplicaManager::ProcessReceivedCommand(ParticipantStruct *participantStruct, ReceivedCommand *receivedCommand) { (void) participantStruct; // If this assert hits you didn't first call RakPeer::SetNetworkIDManager as required. RakAssert(rakPeer->GetNetworkIDManager()); if (rakPeer->GetNetworkIDManager()==0) return REPLICA_CANCEL_PROCESS; Replica *replica = (Replica*) rakPeer->GetNetworkIDManager()->GET_BASE_OBJECT_FROM_ID(receivedCommand->networkID); bool objectExists; unsigned index=0; ReplicaReturnResult b; if (replica) { index = replicatedObjects.GetIndexFromKey(replica, &objectExists); if (objectExists==false) { if (receivedCommand->command==ID_REPLICA_MANAGER_CONSTRUCTION) { // Object already exists with this ID, but call construction anyway #ifdef _DEBUG assert(_constructionCB); #endif // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB return _constructionCB->ReceiveConstruction(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, replica, receivedCommand->systemAddress, this); } else { // This networkID is already in use but ReferencePointer was never called on it. // RakAssert(0); 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->systemAddress); 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 #ifdef _DEBUG assert(_constructionCB); #endif // Call the registered callback. If it crashes, you forgot to register the callback in SetReceiveConstructionCB return _constructionCB->ReceiveConstruction(receivedCommand->userData, receivedCommand->u1, receivedCommand->networkID, replica, receivedCommand->systemAddress, 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->systemAddress, receivedCommand->u1); } } else if (receivedCommand->command==ID_REPLICA_MANAGER_DESTRUCTION) { if (replica && (replicatedObjects[index].allowedInterfaces & REPLICA_RECEIVE_DESTRUCTION)) { return replica->ReceiveDestruction(receivedCommand->userData, receivedCommand->systemAddress, receivedCommand->u1); } } else if (receivedCommand->command==ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE) { if (_receiveDownloadCompleteCB) { return _receiveDownloadCompleteCB->ReceiveDownloadComplete(receivedCommand->userData, receivedCommand->systemAddress, this); } } return REPLICA_CANCEL_PROCESS; } ReplicaManager::ParticipantStruct::~ParticipantStruct() { ReceivedCommand *receivedCommand; while ( pendingCommands.Size() ) { receivedCommand=pendingCommands.Pop(); delete receivedCommand->userData; delete receivedCommand; } } unsigned ReplicaManager::GetCommandListReplicaIndex(const DataStructures::List &commandList, Replica *replica, bool *objectExists) const { unsigned i; for (i=0; i < commandList.Size(); i++) { if (commandList[i].replica==replica) { *objectExists=true; return i; } } *objectExists=false; return 0; } #ifdef _MSC_VER #pragma warning( pop ) #endif