mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-12-25 06:53:35 +00:00
1282 lines
46 KiB
C++
1282 lines
46 KiB
C++
|
#include "ReplicaManager.h"
|
||
|
#include "RakPeerInterface.h"
|
||
|
#include "GetTime.h"
|
||
|
#include "MessageIdentifiers.h"
|
||
|
#include "BitStream.h"
|
||
|
#include "Replica.h"
|
||
|
#include <memory.h>
|
||
|
#include <assert.h>
|
||
|
#include <stdio.h> // 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<unsigned int, RegisteredReplica, ReplicaManager::RegisteredReplicaRefOrderComp> 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 <variable name> 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<ReplicaManager::CommandStruct> &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
|