#include "ReplicaManager.h"
#include "RakPeerInterface.h"
#include "GetTime.h"
#include "PacketEnumerations.h"
#include "BitStream.h"
#include "Replica.h"
#include <memory.h>
#include <assert.h>
#include <stdio.h> // 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: <variable name> : unreferenced formal parameter
#endif
void ReplicaManager::OnCloseConnection(RakPeerInterface *peer, PlayerID playerId)
{
	RemoveParticipant(playerId);
}
#ifdef _MSC_VER
#pragma warning( disable : 4100 ) // warning C4100: <variable name> : 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: <variable name> : unreferenced formal parameter
#pragma warning( disable : 4701 ) // warning C4701: local variable <variable name> 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


syntax highlighted by Code2HTML, v. 0.9.1