示例#1
0
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;
		}
	}
}