1736 lines
66 KiB
C++
1736 lines
66 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <GridMate/Carrier/Carrier.h>
|
|
|
|
#include <GridMate/Replica/ReplicaMgr.h>
|
|
#include <GridMate/Replica/SystemReplicas.h>
|
|
#include <GridMate/Replica/ReplicaFunctions.h>
|
|
#include <GridMate/Replica/ReplicaStatus.h>
|
|
#include <GridMate/Replica/DataSet.h>
|
|
#include <GridMate/Replica/RemoteProcedureCall.h>
|
|
#include <GridMate/Replica/MigrationSequence.h>
|
|
#include <GridMate/Replica/Tasks/ReplicaMarshalTasks.h>
|
|
#include <GridMate/Replica/Tasks/ReplicaUpdateTasks.h>
|
|
#include <GridMate/Replica/ReplicaDrillerEvents.h>
|
|
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
|
|
#include <GridMate/Serialize/CompressionMarshal.h>
|
|
|
|
#include <AzCore/Debug/Profiler.h>
|
|
#include <AzCore/Math/MathUtils.h>
|
|
|
|
namespace GridMate
|
|
{
|
|
bool ReplicaManager::k_enableBackPressure = false;
|
|
|
|
namespace ReplicaDebug
|
|
{
|
|
bool g_SendDbgHeartbeat = false; // send heartbeats from this station
|
|
bool g_TrackDbgHeartbeat = false; // track heartbeats received on this station
|
|
int g_MaxTicksPerHeartbeat = 50; // max allowed ticks without heartbeats
|
|
};
|
|
//-----------------------------------------------------------------------------
|
|
template<typename iter_type>
|
|
iter_type find(iter_type first, iter_type last, ReplicaId repId)
|
|
{
|
|
for (; first != last; ++first)
|
|
{
|
|
if ((*first)->GetRepId() == repId)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return first;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ReplicaPeer
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaPeer::ReplicaPeer(ReplicaManager* manager, ConnectionID connId, RemotePeerMode mode)
|
|
: m_flags(0)
|
|
, m_peerId(InvalidReplicaPeerId)
|
|
, m_connId(connId)
|
|
, m_mode(mode)
|
|
, m_reliableOutBuffer(EndianType::IgnoreEndian)
|
|
, m_unreliableOutBuffer(EndianType::IgnoreEndian)
|
|
, m_zoneMask(ZoneMask_All)
|
|
, m_rm(manager)
|
|
, m_lastReceiveTicks(0)
|
|
, m_avgSendRateBurst(0.f)
|
|
, m_sentBytes(0)
|
|
, m_sendBytesAllowed(0)
|
|
{
|
|
AZ_Assert(m_rm, "No replica manager specified");
|
|
if (m_rm->IsInitialized())
|
|
{
|
|
SetEndianType(manager->GetGridMate()->GetDefaultEndianType());
|
|
}
|
|
ResetBuffer();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::SetEndianType(EndianType endianType)
|
|
{
|
|
m_reliableOutBuffer.SetEndianType(endianType);
|
|
m_unreliableOutBuffer.SetEndianType(endianType);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::Add(Replica* pObj)
|
|
{
|
|
auto mapIt = m_objectsMap.insert_key(pObj->GetRepId());
|
|
if (mapIt.second)
|
|
{
|
|
mapIt.first->second.m_replica = pObj;
|
|
m_objectsTimeSort.insert(mapIt.first->second);
|
|
pObj->m_upstreamHop = this;
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::Remove(Replica* pObj)
|
|
{
|
|
auto mapIt = m_objectsMap.find(pObj->GetRepId());
|
|
if (mapIt != m_objectsMap.end())
|
|
{
|
|
m_objectsTimeSort.erase(mapIt->second);
|
|
m_objectsMap.erase(mapIt);
|
|
|
|
if (IsOrphan() && m_objectsTimeSort.empty())
|
|
{
|
|
m_rm->OnPeerReadyToRemove(this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (pObj->m_upstreamHop == this)
|
|
{
|
|
pObj->m_upstreamHop = nullptr;
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::Accept()
|
|
{
|
|
m_flags |= PeerFlags::Peer_New | PeerFlags::Peer_Accepted;
|
|
m_rm->OnPeerAccepted(this);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::SendBuffer(Carrier* carrier, unsigned char commChannel, const AZ::u32 replicaManagerTimer)
|
|
{
|
|
AZ_Assert(carrier, "No available net layer!");
|
|
|
|
bool hasReliableData = m_reliableOutBuffer.Size() > m_reliableTimestamp.GetOffsetAfterMarker();
|
|
bool hasUnreliableData = m_unreliableOutBuffer.Size() > m_unreliableTimestamp.GetOffsetAfterMarker();
|
|
|
|
if (ReplicaDebug::g_SendDbgHeartbeat)
|
|
{
|
|
if (hasReliableData)
|
|
{
|
|
m_reliableOutBuffer.Write(Cmd_Heartbeat);
|
|
}
|
|
else
|
|
{
|
|
m_unreliableOutBuffer.Write(Cmd_Heartbeat);
|
|
hasUnreliableData = true;
|
|
}
|
|
}
|
|
|
|
if (hasReliableData)
|
|
{
|
|
m_reliableTimestamp = replicaManagerTimer;
|
|
#ifdef REPLICA_MSG_CRC_CHECK
|
|
m_reliableMsgCrc = AZ::Crc32(m_reliableOutBuffer.Get() + m_reliableMsgCrc.GetOffsetAfterMarker(), m_reliableOutBuffer.Size() - m_reliableMsgCrc.GetOffsetAfterMarker());
|
|
#endif
|
|
|
|
auto callback = AZStd::make_unique<PeerAckCallbacks>((m_reliableCallbacks));
|
|
carrier->SendWithCallback(m_reliableOutBuffer.Get(), static_cast<unsigned int>(m_reliableOutBuffer.Size()), AZStd::move(callback), GetConnectionId(), Carrier::SEND_RELIABLE, Carrier::PRIORITY_NORMAL, commChannel);
|
|
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnSend, GetId(), m_reliableOutBuffer.Get(), m_reliableOutBuffer.Size(), true);
|
|
}
|
|
|
|
if (hasUnreliableData)
|
|
{
|
|
m_unreliableTimestamp = replicaManagerTimer;
|
|
#ifdef REPLICA_MSG_CRC_CHECK
|
|
m_unreliableMsgCrc = AZ::Crc32(m_unreliableOutBuffer.Get() + m_unreliableMsgCrc.GetOffsetAfterMarker(), m_unreliableOutBuffer.Size() - m_unreliableMsgCrc.GetOffsetAfterMarker());
|
|
#endif
|
|
if (m_unreliableOutBuffer.Size() > carrier->GetMessageMTU())
|
|
{
|
|
AZ_TracePrintf("GridMate", "SendBuffer [0x%p]: Unreliable replica update exceeds MTU (size=%u, MTU=%u), forcing reliable for this send.\n", GetConnectionId(), static_cast<unsigned int>(m_unreliableOutBuffer.Size()), carrier->GetMessageMTU());
|
|
}
|
|
|
|
auto callback = AZStd::make_unique<PeerAckCallbacks>((m_unreliableCallbacks));
|
|
carrier->SendWithCallback(m_unreliableOutBuffer.Get(), static_cast<unsigned int>(m_unreliableOutBuffer.Size()), AZStd::move(callback), GetConnectionId(), Carrier::SEND_UNRELIABLE, Carrier::PRIORITY_NORMAL, commChannel);
|
|
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnSend, GetId(), m_unreliableOutBuffer.Get(), m_unreliableOutBuffer.Size(), false);
|
|
}
|
|
// prepare for next cycle
|
|
ResetBuffer();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaPeer::ResetBuffer()
|
|
{
|
|
m_reliableOutBuffer.Clear();
|
|
m_unreliableOutBuffer.Clear();
|
|
|
|
#ifdef REPLICA_MSG_CRC_CHECK
|
|
m_reliableMsgCrc = m_reliableOutBuffer.InsertMarker<AZ::Crc32>();
|
|
m_unreliableMsgCrc = m_unreliableOutBuffer.InsertMarker<AZ::Crc32>();
|
|
#endif
|
|
m_reliableTimestamp = m_reliableOutBuffer.InsertMarker<AZ::u32>();
|
|
m_unreliableTimestamp = m_unreliableOutBuffer.InsertMarker<AZ::u32>();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaPtr ReplicaPeer::GetReplica(ReplicaId repId)
|
|
{
|
|
auto i = m_objectsMap.find(repId);
|
|
if (i == m_objectsMap.end())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return i->second.m_replica;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// RepIdMgrClient
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RepIdMgrClient::AddBlock(RepIdSeed seed)
|
|
{
|
|
auto ret = m_idBlocks.insert_key(seed);
|
|
if (ret.second)
|
|
{
|
|
ret.first->second = static_cast<ReplicaId>(seed);
|
|
m_nAvailableIds += GM_REPIDS_PER_BLOCK;
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RepIdMgrClient::RemoveBlock(RepIdSeed seed)
|
|
{
|
|
auto entry = m_idBlocks.find(seed);
|
|
if (entry != m_idBlocks.end())
|
|
{
|
|
size_t idsRemoved = entry->first + GM_REPIDS_PER_BLOCK - entry->second;
|
|
m_idBlocks.erase(entry);
|
|
m_nAvailableIds -= idsRemoved;
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaId ReplicaManager::RepIdMgrClient::Alloc()
|
|
{
|
|
AZ_Assert(m_nAvailableIds > 0, "We ran out of available replica ids!");
|
|
for (auto it = m_idBlocks.begin(); it != m_idBlocks.end(); ++it)
|
|
{
|
|
if (it->second < it->first + GM_REPIDS_PER_BLOCK)
|
|
{
|
|
m_nAvailableIds--;
|
|
return it->second++;
|
|
}
|
|
}
|
|
AZ_Assert(false, "We are supposed to have available replica ids but we couldn't find any empty slots!");
|
|
return InvalidReplicaId;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RepIdMgrClient::Dealloc(ReplicaId id)
|
|
{
|
|
// We don't support reusing replica ids
|
|
(void) id;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// ReplicaManager
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaManager::ReplicaManager()
|
|
: m_flags(0)
|
|
, m_self(this, InvalidConnectionID, Mode_Client)
|
|
, m_marshalingTasks(&m_tasksAllocator)
|
|
, m_updateTasks(&m_tasksAllocator)
|
|
, m_peerUpdateTasks(&m_tasksAllocator)
|
|
, m_latchedCarrierTime(0)
|
|
, m_autoBroadcast(true)
|
|
{ }
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::Init(const ReplicaMgrDesc& desc)
|
|
{
|
|
AZ_Assert((m_flags & Rm_Initialized) == 0, "ReplicaManager already initialized.");
|
|
m_cfg = desc;
|
|
|
|
m_self.SetEndianType(GetGridMate()->GetDefaultEndianType());
|
|
|
|
AZ_Assert(m_cfg.m_carrier, "Carrier must be valid!");
|
|
if (m_cfg.m_targetSendTimeMS < m_cfg.m_carrier->GetMaxSendRate())
|
|
{
|
|
//AZ_TracePrintf("GridMate", "targetSendTimeMS is too high for underlying carrier!\n");
|
|
m_cfg.m_targetSendTimeMS = m_cfg.m_carrier->GetMaxSendRate();
|
|
}
|
|
m_lastCheckTime = AZStd::chrono::system_clock::now();
|
|
m_nextSendTime = m_lastCheckTime + AZStd::chrono::milliseconds(m_cfg.m_targetSendTimeMS);
|
|
m_receiveBuffer.resize(m_cfg.m_carrier->GetMessageMTU());
|
|
|
|
m_currentFrameTime.m_localTime = m_currentFrameTime.m_realTime = m_latchedCarrierTime = m_cfg.m_carrier->GetTime();
|
|
|
|
AZ_Assert(m_cfg.m_myPeerId, "myPeerId has to be a non-zero globally unique id, it is used to identify this peer on the network.");
|
|
m_self.m_peerId = m_cfg.m_myPeerId;
|
|
|
|
m_sessionInfo = CreateReplicaChunk<ReplicaInternal::SessionInfo>(this);
|
|
m_flags = Rm_Initialized;
|
|
|
|
if (IsUsingFixedTimeStep())
|
|
{
|
|
m_fixedTimeStep.SetTargetUpdateRate(m_cfg.m_targetFixedTimeStepsPerSecond);
|
|
}
|
|
|
|
{
|
|
AZ::PoolAllocator::Descriptor allocDesc;
|
|
|
|
#if !defined(AZ_DEBUG_BUILD)
|
|
allocDesc.m_allocationRecords = false;
|
|
allocDesc.m_stackRecordLevels = 0;
|
|
#endif
|
|
allocDesc.m_pageSize = 256;
|
|
allocDesc.m_maxAllocationSize = 64;
|
|
allocDesc.m_numStaticPages = 1024;
|
|
allocDesc.m_pageAllocator = &AZ::AllocatorInstance<GridMateAllocatorMP>::Get();
|
|
|
|
m_tasksAllocator.Create(allocDesc);
|
|
}
|
|
|
|
if ((m_cfg.m_roles & ReplicaMgrDesc::Role_SyncHost) != 0)
|
|
{
|
|
Promote();
|
|
}
|
|
|
|
CarrierEventBus::Handler::BusConnect(m_cfg.m_carrier->GetGridMate());
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::Shutdown()
|
|
{
|
|
AZ_Assert((m_flags & Rm_Processing) == 0, "Cannot shut down ReplicaManager while still processing!");
|
|
if (m_flags & Rm_Terminating)
|
|
{
|
|
//AZ_TracePrintf("GridMate", "ReplicaManager is already shutting down!\n");
|
|
return;
|
|
}
|
|
m_flags = Rm_Terminating;
|
|
|
|
for (auto& migration : m_activeMigrations)
|
|
{
|
|
delete migration.second;
|
|
}
|
|
m_activeMigrations.clear();
|
|
|
|
ReplicaContext rc(this, GetTime());
|
|
m_sessionInfo.reset();
|
|
|
|
// remove all peers
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
ReplicaPtr pObj;
|
|
|
|
for (ReplicaPeerList::iterator it = m_remotePeers.begin(); it != m_remotePeers.end(); ++it)
|
|
{
|
|
ReplicaPeer* peer = *it;
|
|
for (auto& replicaObject : peer->m_objectsTimeSort)
|
|
{
|
|
UnregisterReplica(replicaObject.m_replica, rc);
|
|
}
|
|
delete peer;
|
|
}
|
|
|
|
m_remotePeers.clear();
|
|
}
|
|
|
|
// remove self
|
|
for (auto& replicaObject : m_self.m_objectsTimeSort)
|
|
{
|
|
UnregisterReplica(replicaObject.m_replica, rc);
|
|
}
|
|
m_self.m_objectsTimeSort.clear();
|
|
m_self.m_objectsMap.clear();
|
|
|
|
CarrierEventBus::Handler::BusDisconnect(m_cfg.m_carrier->GetGridMate());
|
|
|
|
AZ_Assert(m_replicas.empty(), "There shouldn't be registered replicas left since we have cleared out all the peers including ourselves!");
|
|
m_cfg.m_carrier = nullptr; /// We assume the transport is dead.
|
|
|
|
m_marshalingTasks.Clear();
|
|
m_updateTasks.Clear();
|
|
m_peerUpdateTasks.Clear();
|
|
m_tasksAllocator.Destroy();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::Promote()
|
|
{
|
|
AZ_Assert(IsInitialized(), "ReplicaMgr has not been initialized!");
|
|
AZ_Assert(!HasValidHost(), "There is already a valid host on the network!");
|
|
|
|
m_self.MakeSyncHost(true);
|
|
|
|
//AZ_TracePrintf("GridMate", "Local peer id=0x%x promoted to sync host.\n", m_self.GetId());
|
|
|
|
// if we don't have sessionInfo then either we just got started as the host
|
|
// or we never fully connected to the host. Either way, init ourselves as host
|
|
if (!m_sessionInfo->GetReplica())
|
|
{
|
|
Replica* replica = Replica::CreateReplica("ReplicaSessionInfo");
|
|
replica->AttachReplicaChunk(m_sessionInfo);
|
|
replica->SetMigratable(true);
|
|
|
|
// register global session info
|
|
// this one is kind of special so don't go through AddPrimary()
|
|
ReplicaContext rc(this, GetTime(), &m_self);
|
|
replica->m_createTime = rc.m_realTime;
|
|
replica->SetRepId(RepId_SessionInfo);
|
|
m_sessionInfo->m_nextAvailableIdBlock.Set(Max_Reserved_Cmd_Or_Id);
|
|
|
|
// give ourselves a block of ids if we don't have any
|
|
if (m_localIdBlocks.Available() == 0)
|
|
{
|
|
m_localIdBlocks.AddBlock(ReserveIdBlock(m_self.GetId()));
|
|
}
|
|
|
|
m_self.Add(m_sessionInfo->GetReplica());
|
|
replica->InitReplica(this);
|
|
RegisterReplica(replica, true, rc);
|
|
}
|
|
else
|
|
{
|
|
// take over ownership of the session info
|
|
AZ_Assert(!m_sessionInfo->IsPrimary(), "We just became host but we were already the owner of sessionInfo!");
|
|
AZ_Assert(m_sessionInfo->m_pHostPeer->IsOrphan(), "We can't be promoted if we are still connected to the host!");
|
|
m_sessionInfo->m_pHostPeer->Remove(m_sessionInfo->GetReplica());
|
|
m_self.Add(m_sessionInfo->GetReplica());
|
|
OnReplicaMigrated(m_sessionInfo->GetReplica(), true, ReplicaContext(this, GetTime(), &m_self));
|
|
}
|
|
|
|
// initialize accepted peer list to currently known peers
|
|
vector<PeerId> acceptedPeers;
|
|
acceptedPeers.push_back(m_self.GetId());
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (ReplicaPeerList::iterator it = m_remotePeers.begin(); it != m_remotePeers.end(); ++it)
|
|
{
|
|
ReplicaPeer* peer = *it;
|
|
if (peer->GetId() && !peer->IsOrphan())
|
|
{
|
|
acceptedPeers.push_back(peer->GetId());
|
|
}
|
|
}
|
|
}
|
|
|
|
m_sessionInfo->m_acceptedPeers.Set(acceptedPeers);
|
|
|
|
// reconcile peers
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (ReplicaPeerList::iterator it = m_remotePeers.begin(); it != m_remotePeers.end(); ++it)
|
|
{
|
|
ReplicaPeer* peer = *it;
|
|
|
|
// Queue request to start migration process on orphan peers.
|
|
// At the very least we need to know their peer id, otherwise there
|
|
// is no way to synchonize migration state
|
|
if (peer->IsOrphan() && peer->GetId())
|
|
{
|
|
m_sessionInfo->RequestPeerMigration(peer->GetId());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RegisterUserContext(int key, void* data)
|
|
{
|
|
m_userContexts.insert(AZStd::make_pair(key, data));
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::UnregisterUserContext(int key)
|
|
{
|
|
m_userContexts.erase(key);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void* ReplicaManager::GetUserContext(int key)
|
|
{
|
|
UserContextMapType::iterator iter = m_userContexts.find(key);
|
|
if (iter != m_userContexts.end())
|
|
{
|
|
return iter->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
RepIdSeed ReplicaManager::ReserveIdBlock(PeerId requestor)
|
|
{
|
|
(void) requestor;
|
|
AZ_Assert(IsSyncHost(), "Cannot reserve id blocks on a non-synchost.");
|
|
RepIdSeed idBlock = m_sessionInfo->m_nextAvailableIdBlock.Get();
|
|
AZ_Assert(idBlock + GM_REPIDS_PER_BLOCK > idBlock, "Replica host ran out of id blocks!");
|
|
m_sessionInfo->m_nextAvailableIdBlock.Set(idBlock + GM_REPIDS_PER_BLOCK);
|
|
return idBlock;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
size_t ReplicaManager::ReleaseIdBlock(PeerId requestor)
|
|
{
|
|
(void) requestor;
|
|
AZ_Assert(IsSyncHost(), "Cannot reserve id blocks on a non-synchost.");
|
|
// We don't support reusing id blocks
|
|
return 0;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::AddPeer(ConnectionID connId, RemotePeerMode peerMode)
|
|
{
|
|
AZ_Assert(IsSyncHost() || peerMode == Mode_Peer, "Mode_Client can only be added to hosts!");
|
|
ReplicaPeer* pPeer = aznew ReplicaPeer(this, connId, peerMode);
|
|
|
|
m_mutexRemotePeers.lock();
|
|
m_remotePeers.push_back(pPeer);
|
|
m_mutexRemotePeers.unlock();
|
|
|
|
// immediate introduce ourselves to this peer (only if we are not the host)
|
|
if (!IsSyncHost())
|
|
{
|
|
SendGreetings(pPeer);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::DiscardOrphans(PeerId orphanId)
|
|
{
|
|
if (IsSyncHost())
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
|
|
for (ReplicaPeerList::iterator iPeer = m_remotePeers.begin(); iPeer != m_remotePeers.end(); ++iPeer)
|
|
{
|
|
ReplicaPeer* peer = *iPeer;
|
|
if (peer->GetId() == orphanId)
|
|
{
|
|
ReplicaContext rc(this, GetTime(), peer);
|
|
for (auto iObj = peer->m_objectsTimeSort.begin(); iObj != peer->m_objectsTimeSort.end(); ++iObj)
|
|
{
|
|
RemoveReplicaFromDownstream(iObj->m_replica, rc);
|
|
}
|
|
|
|
OnPeerReadyToRemove(peer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RemovePeer(ConnectionID connId)
|
|
{
|
|
// We are shutting down, don't do anything
|
|
if (m_flags & Rm_Terminating)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
|
|
for (ReplicaPeerList::iterator iPeer = m_remotePeers.begin(); iPeer != m_remotePeers.end(); ++iPeer)
|
|
{
|
|
ReplicaPeer* peer = *iPeer;
|
|
if (peer->GetConnectionId() == connId)
|
|
{
|
|
peer->m_connId = InvalidConnectionID;
|
|
|
|
// immediately remove all non-migratable replicas
|
|
// then orphan the peer and wait for the host to take further action
|
|
ReplicaContext rc(this, GetTime(), peer);
|
|
for (auto iObj = peer->m_objectsTimeSort.begin(); iObj != peer->m_objectsTimeSort.end(); )
|
|
{
|
|
ReplicaPtr pObj = iObj->m_replica;
|
|
if (pObj && !pObj->IsMigratable())
|
|
{
|
|
iObj = peer->m_objectsTimeSort.erase(iObj);
|
|
peer->m_objectsMap.erase(pObj->GetRepId());
|
|
RemoveReplicaFromDownstream(pObj, rc);
|
|
}
|
|
else
|
|
{
|
|
++iObj;
|
|
}
|
|
}
|
|
|
|
if (IsSyncHost() && peer->GetId())
|
|
{
|
|
// remove from the peer acceptance list
|
|
vector<PeerId> newList;
|
|
for (auto it = m_sessionInfo->m_acceptedPeers.Get().begin(); it != m_sessionInfo->m_acceptedPeers.Get().end(); ++it)
|
|
{
|
|
PeerId peerId = *it;
|
|
if (peerId != peer->GetId())
|
|
{
|
|
newList.push_back(peerId);
|
|
}
|
|
}
|
|
m_sessionInfo->m_acceptedPeers.Set(newList);
|
|
|
|
// stop waiting for any pending reports from this peer
|
|
// since it will never respond
|
|
for (auto it = m_sessionInfo->m_pendingPeerReports.begin(); it != m_sessionInfo->m_pendingPeerReports.end(); )
|
|
{
|
|
vector<PeerId>& pending = it->second;
|
|
for (auto it2 = pending.begin(); it2 != pending.end(); ++it2)
|
|
{
|
|
if (*it2 == peer->GetId())
|
|
{
|
|
pending.erase(it2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// removed all pending reports
|
|
if (pending.empty())
|
|
{
|
|
AZ_Assert(peer->GetId() != it->first, "Peer was waiting for pending report from itself");
|
|
it = m_sessionInfo->m_pendingPeerReports.erase(it);
|
|
OnPendingReportsReceived(it->first);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
// Request to start migration process for this peer
|
|
// At the very least we need to know their peer id, otherwise there
|
|
// is no way to synchonize migration state.
|
|
m_sessionInfo->RequestPeerMigration(peer->GetId());
|
|
}
|
|
|
|
// not waiting for reports for this peer -> can start migration
|
|
if (IsSyncHost() && !m_sessionInfo->HasPendingReports(peer->GetId()))
|
|
{
|
|
OnMigratePeer(peer);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaPeer* ReplicaManager::FindPeer(PeerId peerId)
|
|
{
|
|
if (peerId == GetLocalPeerId())
|
|
{
|
|
return &m_self;
|
|
}
|
|
|
|
// TODO: make peer list hash map
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
|
|
for (ReplicaPeer* peer : m_remotePeers)
|
|
{
|
|
if (peer->GetId() == peerId)
|
|
{
|
|
return peer;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
bool ReplicaManager::AcceptPeer(ReplicaPeer* peer)
|
|
{
|
|
// trivial case
|
|
if (peer->m_flags & PeerFlags::Peer_Accepted)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// if we have peer's id and he hasn't been accepted yet then see if we should accept him
|
|
if (peer->GetId())
|
|
{
|
|
// if we are the host then accept him right away
|
|
if (IsSyncHost())
|
|
{
|
|
// if we are the host this is when we send our greetings
|
|
// and add the peer to the accept list
|
|
SendGreetings(peer);
|
|
vector<PeerId> acceptedPeers = m_sessionInfo->m_acceptedPeers.Get();
|
|
acceptedPeers.push_back(peer->GetId());
|
|
m_sessionInfo->m_acceptedPeers.Set(acceptedPeers);
|
|
peer->Accept();
|
|
|
|
// else we need to see if the host told us to accept him
|
|
}
|
|
else
|
|
{
|
|
if ((m_sessionInfo && m_sessionInfo->IsInAcceptList(peer->GetId())) || peer->IsSyncHost())
|
|
{
|
|
peer->Accept();
|
|
}
|
|
}
|
|
}
|
|
return !!(peer->m_flags & PeerFlags::Peer_Accepted);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnPeerReplicaActivated(ReplicaInternal::PeerReplica::Ptr peerReplica)
|
|
{
|
|
m_peerReplicas.insert(AZStd::make_pair(peerReplica->m_peerId.Get(), peerReplica));
|
|
if (peerReplica->IsProxy())
|
|
{
|
|
EBUS_EVENT_ID(GetGridMate(), ReplicaMgrCallbackBus, OnNewPeer, peerReplica->m_peerId.Get(), this);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnPeerReplicaDeactivated(ReplicaInternal::PeerReplica::Ptr peerReplica)
|
|
{
|
|
if (peerReplica->IsProxy())
|
|
{
|
|
EBUS_EVENT_ID(GetGridMate(), ReplicaMgrCallbackBus, OnPeerRemoved, peerReplica->m_peerId.Get(), this);
|
|
}
|
|
m_peerReplicas.erase(peerReplica->m_peerId.Get());
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
TimeContext ReplicaManager::GetTime() const
|
|
{
|
|
// Return the time context cached at the beginning of the frame (during Unmarshal)
|
|
return m_currentFrameTime;
|
|
}
|
|
AZ::u32 ReplicaManager::GetTimeForNetworkTimestamp() const
|
|
{
|
|
if (IsUsingFixedTimeStep())
|
|
{
|
|
return static_cast<AZ::u32>(m_fixedTimeStep.GetCurrentTime());
|
|
}
|
|
else
|
|
{
|
|
return m_latchedCarrierTime;
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetSendTimeInterval(unsigned int sendTimeMs)
|
|
{
|
|
m_cfg.m_targetSendTimeMS = sendTimeMs;
|
|
m_nextSendTime = m_lastCheckTime + AZStd::chrono::milliseconds(m_cfg.m_targetSendTimeMS);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
unsigned int ReplicaManager::GetSendTimeInterval() const
|
|
{
|
|
return m_cfg.m_targetSendTimeMS;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetSendLimit(unsigned int sendLimitBytesPerSec)
|
|
{
|
|
m_cfg.m_targetSendLimitBytesPerSec = sendLimitBytesPerSec;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
unsigned int ReplicaManager::GetSendLimit() const
|
|
{
|
|
return m_cfg.m_targetSendLimitBytesPerSec;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetSendLimitBurstRange(float rangeSec)
|
|
{
|
|
m_cfg.m_targetSendLimitBurst = AZStd::GetMax(1.f, rangeSec);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
float ReplicaManager::GetSendLimitBurstRange() const
|
|
{
|
|
return m_cfg.m_targetSendLimitBurst;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetAutoBroadcast(bool isEnabled)
|
|
{
|
|
m_autoBroadcast = isEnabled;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetLocalLagAmt(unsigned int ms)
|
|
{
|
|
if (!IsSyncHost())
|
|
{
|
|
AZ_Error("ReplicaManager", false, "SetLocalLagAmt() can only be called on the replication host!");
|
|
}
|
|
else
|
|
{
|
|
m_sessionInfo->m_localLagAmt.Set(ms);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::UnregisterReplica(const ReplicaPtr& pObj, const ReplicaContext& rc)
|
|
{
|
|
pObj->ClearPendingRPCs();
|
|
pObj->Deactivate(rc);
|
|
|
|
if (pObj->IsDirty())
|
|
{
|
|
RemoveFromDirtyList(*pObj);
|
|
}
|
|
|
|
m_replicas.erase(pObj->GetRepId());
|
|
pObj->m_flags &= Replica::Rep_Traits;
|
|
pObj->SetRepId(Invalid_Cmd_Or_Id);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RemoveReplicaFromDownstream(const ReplicaPtr& pObj, const ReplicaContext& rc)
|
|
{
|
|
pObj->Deactivate(rc);
|
|
|
|
if (pObj->IsDirty())
|
|
{
|
|
RemoveFromDirtyList(*pObj);
|
|
}
|
|
|
|
m_replicas.erase(pObj->GetRepId());
|
|
|
|
// Flags are reset and id is cleared after this task executes
|
|
m_marshalingTasks.Add<ReplicaMarshalZombieTask>(pObj);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::MigrateReplica(ReplicaPtr replica, PeerId newOwnerId)
|
|
{
|
|
auto iter = m_activeMigrations.insert_key(replica->GetRepId());
|
|
if (iter.second)
|
|
{
|
|
iter.first->second = aznew ReplicaInternal::MigrationSequence(replica.get(), newOwnerId);
|
|
}
|
|
else
|
|
{
|
|
iter.first->second->ModifyNewOwner(newOwnerId);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::AnnounceReplicaMigrated(ReplicaId replicaId, PeerId newOwnerId)
|
|
{
|
|
auto it = m_peerReplicas.find(GetLocalPeerId());
|
|
if (it != m_peerReplicas.end())
|
|
{
|
|
it->second->OnReplicaMigrated(replicaId, newOwnerId);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnReplicaMigrated(ReplicaPtr replica, bool isOwner, const ReplicaContext& rc)
|
|
{
|
|
ChangeReplicaOwnership(replica, rc, isOwner);
|
|
|
|
if (isOwner)
|
|
{
|
|
ReplicaStatus::Ptr replicaStatus = AZStd::static_pointer_cast<ReplicaStatus>(replica->m_replicaStatus);
|
|
replicaStatus->SetUpstreamSuspended(false);
|
|
replica->m_flags |= Replica::Rep_ChangedOwner;
|
|
replicaStatus->m_ownerSeq.Set(replicaStatus->m_ownerSeq.Get() + 1);
|
|
OnReplicaChanged(replica);
|
|
}
|
|
else if (IsSyncHost())
|
|
{
|
|
replica->m_flags |= Replica::Rep_ChangedOwner;
|
|
OnReplicaChanged(replica);
|
|
}
|
|
|
|
UpdateReplicaTargets(replica);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::AckUpstreamSuspended(ReplicaId replicaId, PeerId sendTo, AZ::u32 requestTime)
|
|
{
|
|
auto it = m_peerReplicas.find(sendTo);
|
|
if (it != m_peerReplicas.end())
|
|
{
|
|
it->second->OnAckUpstreamSuspended(replicaId, GetLocalPeerId(), requestTime);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnAckUpstreamSuspended(ReplicaId replicaId, PeerId from, AZ::u32 requestTime)
|
|
{
|
|
auto iter = m_activeMigrations.find(replicaId);
|
|
if (iter != m_activeMigrations.end())
|
|
{
|
|
iter->second->OnReceivedAckUpstreamSuspended(from, requestTime);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::AckDownstream(ReplicaId replicaId, PeerId sendTo, AZ::u32 requestTime)
|
|
{
|
|
auto it = m_peerReplicas.find(sendTo);
|
|
if (it != m_peerReplicas.end())
|
|
{
|
|
it->second->OnAckDownstream(replicaId, GetLocalPeerId(), requestTime);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnAckDownstream(ReplicaId replicaId, PeerId from, AZ::u32 requestTime)
|
|
{
|
|
auto iter = m_activeMigrations.find(replicaId);
|
|
if (iter != m_activeMigrations.end())
|
|
{
|
|
iter->second->OnReceivedAckDownstream(from, requestTime);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::UpdateFromReplicas()
|
|
{
|
|
AZ_PROFILE_TIMER("GridMate", __FUNCTION__);
|
|
|
|
if (!IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
m_flags |= Rm_Processing;
|
|
m_latchedCarrierTime = m_cfg.m_carrier->GetTime();
|
|
|
|
if (IsUsingFixedTimeStep())
|
|
{
|
|
m_fixedTimeStep.UpdateFixedTimeStep();
|
|
}
|
|
m_updateTasks.Run(this);
|
|
m_peerUpdateTasks.Run(this);
|
|
|
|
m_flags &= ~Rm_Processing;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::UpdateReplicas()
|
|
{
|
|
AZ_PROFILE_TIMER("GridMate", __FUNCTION__);
|
|
|
|
if (!IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
m_flags |= Rm_Processing;
|
|
|
|
ReplicaContext rc(this, GetTime());
|
|
for (auto& replicaObject : m_self.m_objectsTimeSort)
|
|
{
|
|
if (replicaObject.m_replica->IsPrimary())
|
|
{
|
|
replicaObject.m_replica->UpdateReplica(rc);
|
|
}
|
|
}
|
|
|
|
{
|
|
for (auto itMigration = m_activeMigrations.begin(); itMigration != m_activeMigrations.end(); )
|
|
{
|
|
itMigration->second->Update();
|
|
if (itMigration->second->IsDone())
|
|
{
|
|
delete itMigration->second;
|
|
itMigration = m_activeMigrations.erase(itMigration);
|
|
}
|
|
else
|
|
{
|
|
++itMigration;
|
|
}
|
|
}
|
|
}
|
|
|
|
const unsigned int tombstoneExpirationMs = 5000;
|
|
unsigned int curNetTime = GetTime().m_realTime;
|
|
for (TombstoneRecords::iterator iTombstone = m_tombstones.begin(); iTombstone != m_tombstones.end(); )
|
|
{
|
|
if (iTombstone->second + tombstoneExpirationMs < curNetTime)
|
|
{
|
|
iTombstone = m_tombstones.erase(iTombstone);
|
|
}
|
|
else
|
|
{
|
|
++iTombstone;
|
|
}
|
|
}
|
|
|
|
m_flags &= ~Rm_Processing;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::Marshal()
|
|
{
|
|
AZ_PROFILE_TIMER("GridMate", __FUNCTION__);
|
|
|
|
if (!IsReady())
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZStd::chrono::system_clock::time_point now(AZStd::chrono::system_clock::now());
|
|
AZStd::chrono::milliseconds dt(now - m_lastCheckTime);
|
|
m_lastCheckTime = now;
|
|
|
|
if (now + dt < m_nextSendTime)
|
|
{
|
|
return; // we'll probably come back before the train leaves the station...
|
|
}
|
|
m_nextSendTime += AZStd::chrono::milliseconds(m_cfg.m_targetSendTimeMS);
|
|
|
|
m_flags |= Rm_Processing;
|
|
|
|
while (!m_dirtyReplicas.empty())
|
|
{
|
|
auto& replica = m_dirtyReplicas.front();
|
|
if (!replica.HasMarshalingTask())
|
|
{
|
|
m_marshalingTasks.Add<ReplicaMarshalTask>(&replica);
|
|
}
|
|
RemoveFromDirtyList(replica);
|
|
}
|
|
m_marshalingTasks.Run(this);
|
|
|
|
// send collected updates to peers
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (ReplicaPeerList::iterator iPeer = m_remotePeers.begin(); iPeer != m_remotePeers.end(); ++iPeer)
|
|
{
|
|
ReplicaPeer* pRemote = *iPeer;
|
|
if (!pRemote->IsOrphan())
|
|
{
|
|
pRemote->SendBuffer(m_cfg.m_carrier, m_cfg.m_commChannel, GetTimeForNetworkTimestamp());
|
|
}
|
|
pRemote->SetNew(false);
|
|
}
|
|
}
|
|
|
|
m_flags &= ~Rm_Processing;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaPtr ReplicaManager::FindReplica(ReplicaId replicaId)
|
|
{
|
|
ReplicaMap::iterator iObj = m_replicas.find(replicaId);
|
|
if (iObj != m_replicas.end())
|
|
{
|
|
AZ_Assert(iObj->second, "Detected NULL replica pointer in replica map! (id=0x%x)", replicaId);
|
|
return iObj->second;
|
|
}
|
|
return ReplicaPtr(NULL);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::_Unmarshal(ReadBuffer& rb, ReplicaPeer* pFrom)
|
|
{
|
|
AZ_Assert(pFrom, "Invalid peer pointer!");
|
|
|
|
UnmarshalContext mc(ReplicaContext(this, GetTime(), pFrom));
|
|
|
|
#ifdef REPLICA_MSG_CRC_CHECK
|
|
AZ::u32 msgCrc;
|
|
if (!rb.Read(msgCrc))
|
|
{
|
|
return;
|
|
}
|
|
AZ::u32 actualCrc = AZ::Crc32(rb.GetCurrent(), rb.Left());
|
|
(void) actualCrc;
|
|
AZ_Assert(msgCrc == actualCrc, "Replica message crc mismatch!");
|
|
#endif
|
|
if (!rb.Read(mc.m_timestamp))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The tail end of the buffer might have a few extra unused bits that no replica can fit into.
|
|
*/
|
|
while (!rb.IsEmptyIgnoreTrailingBits())
|
|
{
|
|
// This is used later to report the buffer information to driller.
|
|
const char* cmdBufferBegin = rb.GetCurrent();
|
|
|
|
CmdId cmdhdr;
|
|
if (!rb.Read(cmdhdr))
|
|
{
|
|
return;
|
|
}
|
|
|
|
mc.m_hasCtorData = false;
|
|
switch (cmdhdr)
|
|
{
|
|
case Cmd_Greetings:
|
|
{
|
|
AZ_Assert(!pFrom->GetId(), "We should only receive one greetings msg from each peer!");
|
|
if (pFrom->GetId())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PeerId newPeerId;
|
|
if (!rb.Read(newPeerId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (ReplicaPeer* peer : m_remotePeers)
|
|
{
|
|
if (peer->GetId() == newPeerId)
|
|
{
|
|
AZ_Assert(false, "Peer Id 0x%x is already claimed by another peer!", newPeerId);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
pFrom->m_peerId = newPeerId;
|
|
|
|
bool peerIsHost;
|
|
if (!rb.Read(peerIsHost))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (peerIsHost)
|
|
{
|
|
AZ_Assert(!IsSyncHost() && !HasValidHost(), "We already have a host!");
|
|
if (IsSyncHost() || HasValidHost())
|
|
{
|
|
return;
|
|
}
|
|
pFrom->MakeSyncHost(true);
|
|
|
|
RepIdSeed firstSeed;
|
|
if (!rb.Read(firstSeed))
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_localIdBlocks.AddBlock(firstSeed);
|
|
}
|
|
break;
|
|
}
|
|
case Cmd_Heartbeat:
|
|
pFrom->m_lastReceiveTicks = 0;
|
|
break;
|
|
case Cmd_NewOwner:
|
|
if (GetSecurityOptions().m_enableStrictSourceValidation && !pFrom->IsSyncHost())
|
|
{
|
|
AZ_Assert(false, "Cmd_NewOwner discarded due to ReplicationSecurityOptions::m_enableStrictSourceValidation! Discarding rest of message!");
|
|
return;
|
|
}
|
|
case Cmd_NewProxy:
|
|
{
|
|
mc.m_hasCtorData = true;
|
|
|
|
bool isSyncStage;
|
|
if (!rb.Read(isSyncStage))
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isMigratable;
|
|
if (!rb.Read(isMigratable))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ::u32 createTime;
|
|
if (!rb.Read(createTime))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ::u32 ownerSeq;
|
|
if (!rb.Read(ownerSeq))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReplicaId repId;
|
|
if (!rb.Read(repId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
PackedSize chunkSize;
|
|
if (!rb.Read(chunkSize))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReadBuffer replicaPayload = rb.ReadInnerBuffer(chunkSize);
|
|
if (replicaPayload.IsValid())
|
|
{
|
|
mc.m_iBuf = &replicaPayload;
|
|
mc.m_peer = pFrom;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find out if we already know about this replica
|
|
ReplicaPtr pReplica = FindReplica(repId);
|
|
if (pReplica)
|
|
{
|
|
if (AZStd::static_pointer_cast<ReplicaStatus>(pReplica->m_replicaStatus)->m_ownerSeq.Get() > ownerSeq)
|
|
{
|
|
//AZ_TracePrintf("Gridmate", "Received replica, id 0x%x, with old ownerSeq. Discarding %d bytes\n", (int)repId, (int)chunkSize);
|
|
break;
|
|
}
|
|
|
|
if (cmdhdr == Cmd_NewOwner)
|
|
{
|
|
// find who originally owned the replica and transfer to the new owner
|
|
if (pReplica->m_upstreamHop != pFrom)
|
|
{
|
|
if (pReplica->m_upstreamHop)
|
|
{
|
|
pReplica->m_upstreamHop->Remove(pReplica.get());
|
|
}
|
|
pFrom->Add(pReplica.get());
|
|
}
|
|
OnReplicaMigrated(pReplica, false, mc);
|
|
}
|
|
|
|
if (GetSecurityOptions().m_enableStrictSourceValidation && pFrom != pReplica->m_upstreamHop)
|
|
{
|
|
AZ_Assert(false, "Cmd_NewProxy discarded due to ReplicationSecurityOptions::m_enableStrictSourceValidation! Discarding rest of message!");
|
|
return;
|
|
}
|
|
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaBegin, pReplica.get(), cmdBufferBegin, rb.GetCurrent() - cmdBufferBegin);
|
|
pReplica->Unmarshal(mc);
|
|
OnReplicaUnmarshaled(pReplica);
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaEnd, pReplica.get());
|
|
}
|
|
else
|
|
{
|
|
// if failed to find replica or creating new proxy
|
|
pReplica = aznew Replica("");
|
|
pReplica->m_createTime = createTime;
|
|
pReplica->SetRepId(repId);
|
|
pFrom->Add(pReplica.get());
|
|
pReplica->InitReplica(this);
|
|
pReplica->SetSyncStage(isSyncStage);
|
|
pReplica->SetMigratable(isMigratable);
|
|
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaBegin, pReplica.get(), cmdBufferBegin, rb.GetCurrent() - cmdBufferBegin);
|
|
pReplica->Unmarshal(mc);
|
|
ReplicaContext rc(this, GetTime(), pFrom);
|
|
RegisterReplica(pReplica, false, rc);
|
|
OnReplicaUnmarshaled(pReplica);
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaEnd, pReplica.get());
|
|
}
|
|
break;
|
|
}
|
|
case Cmd_DestroyProxy:
|
|
{
|
|
ReplicaId repId;
|
|
if (!rb.Read(repId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (GetSecurityOptions().m_enableStrictSourceValidation && !pFrom->IsSyncHost())
|
|
{
|
|
ReplicaPtr pReplica = FindReplica(repId);
|
|
if (pReplica && pFrom != pReplica->m_upstreamHop)
|
|
{
|
|
AZ_Assert(false, "Cmd_DestroyProxy discarded due to ReplicationSecurityOptions::m_enableStrictSourceValidation! Discarding rest of message!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
OnDestroyProxy(repId);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if (cmdhdr < Cmd_Count)
|
|
{
|
|
AZ_Assert(0, "Received invalid ReplicaId or Cmd 0x%x!", cmdhdr);
|
|
return;
|
|
}
|
|
|
|
ReplicaId repId = static_cast<ReplicaId>(cmdhdr);
|
|
ReplicaPtr pObj = FindReplica(repId);
|
|
|
|
PackedSize chunkSize;
|
|
if (!rb.Read(chunkSize))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReadBuffer replicaPayload = rb.ReadInnerBuffer(chunkSize);
|
|
if (replicaPayload.IsValid())
|
|
{
|
|
mc.m_iBuf = &replicaPayload;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pObj)
|
|
{
|
|
// we don't know about this replica, maybe it has already been deleted
|
|
if (m_tombstones.find(repId) != m_tombstones.end())
|
|
{
|
|
AZ_TracePrintf("GridMate", "Received updates for tombstoned replica(id=0x%x).\n", repId);
|
|
}
|
|
else
|
|
{
|
|
AZ_TracePrintf("GridMate", "Received updates for unknown replica(id=0x%x). Maybe it has already been deleted?\n", repId);
|
|
}
|
|
|
|
break;
|
|
}
|
|
if (pObj->m_upstreamHop)
|
|
{
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaBegin, pObj.get(), cmdBufferBegin, rb.GetCurrent() - cmdBufferBegin);
|
|
mc.m_peer = pFrom;
|
|
pObj->Unmarshal(mc);
|
|
OnReplicaUnmarshaled(pObj);
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceiveReplicaEnd, pObj.get());
|
|
}
|
|
else
|
|
{
|
|
//AZ_TracePrintf("GridMate", "Skipping unmarshal for 0x%x because we can't identify the correct peer.\n", pObj->GetRepId());
|
|
}
|
|
|
|
break; // TODO: should we process incoming data for orphaned replicas?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::Unmarshal()
|
|
{
|
|
AZ_PROFILE_TIMER("GridMate", __FUNCTION__);
|
|
|
|
if (!IsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
unsigned int lagAmount = m_sessionInfo ? m_sessionInfo->m_localLagAmt.Get() : 0;
|
|
m_currentFrameTime.m_realTime = m_cfg.m_carrier->GetTime();
|
|
m_currentFrameTime.m_localTime = m_currentFrameTime.m_realTime < lagAmount ? m_currentFrameTime.m_realTime : m_currentFrameTime.m_realTime - lagAmount;
|
|
|
|
AZ_Assert(m_cfg.m_carrier, "No available net layer!");
|
|
m_flags |= Rm_Processing;
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (ReplicaPeerList::const_iterator iPeer = m_remotePeers.begin(); iPeer != m_remotePeers.end(); ++iPeer)
|
|
{
|
|
ReplicaPeer* peer = *iPeer;
|
|
AZ_Assert(peer, "Invalid peer pointer!");
|
|
|
|
ConnectionID conn = peer->GetConnectionId();
|
|
if (conn == InvalidConnectionID)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// we are only allowed to receive anything else if this peer has been approved by the host
|
|
for (bool keepReceiving = !peer->GetId() || AcceptPeer(peer); keepReceiving; keepReceiving = AcceptPeer(peer))
|
|
{
|
|
Carrier::ReceiveResult result = m_cfg.m_carrier->Receive(m_receiveBuffer.data(), static_cast<unsigned int>(m_receiveBuffer.size()), conn, m_cfg.m_commChannel);
|
|
if (result.m_state == Carrier::ReceiveResult::UNSUFFICIENT_BUFFER_SIZE)
|
|
{
|
|
m_receiveBuffer.resize(result.m_numBytes);
|
|
result = m_cfg.m_carrier->Receive(m_receiveBuffer.data(), static_cast<unsigned int>(m_receiveBuffer.size()), conn, m_cfg.m_commChannel);
|
|
|
|
AZ_Assert(result.m_state != Carrier::ReceiveResult::UNSUFFICIENT_BUFFER_SIZE, "Carrier::ReceiveResult::UNSUFFICIENT_BUFFER_SIZE detected!, result.m_numBytes = %u, buffer size = %u", result.m_numBytes, static_cast<unsigned int>(m_receiveBuffer.size()));
|
|
}
|
|
|
|
if (result.m_state == Carrier::ReceiveResult::NO_MESSAGE_TO_RECEIVE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ReadBuffer rb(GetGridMate()->GetDefaultEndianType(), m_receiveBuffer.data(), result.m_numBytes);
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReceive, peer->GetId(), rb.Get(), rb.Size().GetSizeInBytesRoundUp());
|
|
_Unmarshal(rb, peer);
|
|
AZ_Assert(rb.IsEmptyIgnoreTrailingBits(), "We did not process the whole message!");
|
|
}
|
|
|
|
if (ReplicaDebug::g_TrackDbgHeartbeat)
|
|
{
|
|
++peer->m_lastReceiveTicks;
|
|
if (peer->m_lastReceiveTicks > ReplicaDebug::g_MaxTicksPerHeartbeat)
|
|
{
|
|
m_cfg.m_carrier->DebugStatusReport(conn, m_cfg.m_commChannel);
|
|
AZ_Assert(0, "No updates for %d ticks!", ReplicaDebug::g_MaxTicksPerHeartbeat);
|
|
peer->m_lastReceiveTicks = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_flags &= ~Rm_Processing;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RegisterReplica(const ReplicaPtr& pReplica, bool isPrimary, ReplicaContext& rc)
|
|
{
|
|
AZ_Assert((pReplica->m_flags & ~Replica::Rep_Traits) == 0, "This replica is not clean, flags=0x%x, rpcs=%d!", pReplica->m_flags);
|
|
AZ_Assert(pReplica->GetRepId() != InvalidReplicaId, "You should set the replica ID before you register it!");
|
|
pReplica->SetPrimary(isPrimary);
|
|
pReplica->SetNew();
|
|
auto it = m_replicas.insert(AZStd::make_pair(pReplica->GetRepId(), pReplica)); // register with lookup table
|
|
(void)it;
|
|
AZ_Assert(it.second, "Inserting duplicate id into map");
|
|
pReplica->Activate(rc);
|
|
UpdateReplicaTargets(pReplica);
|
|
OnReplicaChanged(pReplica);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicaId ReplicaManager::AddPrimary(const ReplicaPtr& pPrimary)
|
|
{
|
|
AZ_Assert(IsReady(), "ReplicaManager is not ready!");
|
|
AZ_Assert(pPrimary, "Attempting to register NULL replica!");
|
|
ReplicaId newId = m_localIdBlocks.Alloc();
|
|
ReplicaContext rc(this, GetTime(), &m_self);
|
|
pPrimary->m_createTime = rc.m_realTime;
|
|
pPrimary->SetRepId(newId);
|
|
m_self.Add(pPrimary.get());
|
|
AZStd::static_pointer_cast<ReplicaStatus>(pPrimary->m_replicaStatus)->m_ownerSeq.Set(1);
|
|
pPrimary->InitReplica(this);
|
|
RegisterReplica(pPrimary, true, rc);
|
|
|
|
//AZ_TracePrintf("GridMate", "Peer 0x%x: Added replica primary 0x%x.\n",
|
|
// GetLocalPeerId(), pPrimary->GetRepId());
|
|
|
|
return newId;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
bool ReplicaManager::Destroy(Replica* requestor)
|
|
{
|
|
AZ_Assert(requestor, "Invalid requestor!");
|
|
auto iObj = m_self.m_objectsMap.find(requestor->GetRepId());
|
|
if (iObj != m_self.m_objectsMap.end())
|
|
{
|
|
ReplicaPtr pReplica = iObj->second.m_replica;
|
|
AZ_Assert(pReplica == requestor, "Replica pointer mismatch!");
|
|
ReplicaContext rc(this, GetTime(), requestor->m_upstreamHop);
|
|
RemoveReplicaFromDownstream(pReplica, rc);
|
|
m_tombstones.insert(AZStd::make_pair(requestor->GetRepId(), GetTime().m_realTime));
|
|
m_self.m_objectsTimeSort.erase(iObj->second);
|
|
m_self.m_objectsMap.erase(pReplica->GetRepId());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::GetReplicaContext(const Replica* requestor, ReplicaContext& rc)
|
|
{
|
|
AZ_Assert(requestor, "Invalid requestor!");
|
|
ReplicaPtr pReplica = FindReplica(requestor->GetRepId());
|
|
if (pReplica && pReplica == requestor)
|
|
{
|
|
rc = ReplicaContext(this, GetTime(), requestor->m_upstreamHop);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SendGreetings(ReplicaPeer* peer)
|
|
{
|
|
peer->GetReliableOutBuffer().Write(Cmd_Greetings);
|
|
peer->GetReliableOutBuffer().Write(GetLocalPeerId());
|
|
peer->GetReliableOutBuffer().Write(IsSyncHost());
|
|
if (IsSyncHost())
|
|
{
|
|
peer->GetReliableOutBuffer().Write(ReserveIdBlock(peer->m_peerId));
|
|
}
|
|
peer->SendBuffer(m_cfg.m_carrier, m_cfg.m_commChannel, GetTimeForNetworkTimestamp());
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
IGridMate* ReplicaManager::GetGridMate() const
|
|
{
|
|
AZ_Assert(m_cfg.m_carrier, "ReplicaManager has an invalid carrier!");
|
|
return m_cfg.m_carrier->GetGridMate();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::EnqueueUpdateTask(ReplicaPtr replica)
|
|
{
|
|
if (!replica->HasUpdateTask())
|
|
{
|
|
m_updateTasks.Add<ReplicaUpdateTask>(replica);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::UpdateReplicaTargets(ReplicaPtr replica)
|
|
{
|
|
if (ShouldBroadcastReplica(replica.get()))
|
|
{
|
|
replica->m_targets.clear();
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (auto target : m_remotePeers)
|
|
{
|
|
if (target->IsOrphan() || !(target->m_flags & PeerFlags::Peer_Accepted))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ReplicaPeer* source = replica->IsPrimary() ? &m_self : replica->m_upstreamHop;
|
|
|
|
if (replica->IsPrimary()
|
|
|| (IsSyncHost() && source->GetId() != target->GetId() && !(source->GetMode() == Mode_Peer && target->GetMode() == Mode_Peer)))
|
|
{
|
|
ReplicaTarget::AddReplicaTarget(target, replica.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Replica might've changed owner -> we need to update its targets accordingly
|
|
ReplicaPeer* source = replica->IsPrimary() ? &m_self : replica->m_upstreamHop;
|
|
|
|
for (auto it = replica->m_targets.begin(); it != replica->m_targets.end(); )
|
|
{
|
|
auto& target = *(it++);
|
|
|
|
if (replica->IsProxy()
|
|
&& (!IsSyncHost()
|
|
|| source->GetId() == target.GetPeer()->GetId() // target points to the owner (no need to send replica to its owner)
|
|
|| (source->GetMode() == Mode_Peer && target.GetPeer()->GetMode() == Mode_Peer))) // Clients should never forward proxies
|
|
{
|
|
target.Destroy();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnPeerAccepted(ReplicaPeer* peer)
|
|
{
|
|
AZ_Assert(peer, "OnPeerAccepted: Invalid peer");
|
|
|
|
for (auto& pObj : m_self.m_objectsTimeSort)
|
|
{
|
|
OnReplicaChanged(pObj.m_replica);
|
|
if (ShouldBroadcastReplica(pObj.m_replica.get()))
|
|
{
|
|
ReplicaTarget::AddReplicaTarget(peer, pObj.m_replica.get());
|
|
}
|
|
}
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
for (auto source : m_remotePeers)
|
|
{
|
|
if (!IsSyncHost() ||
|
|
source->GetId() == peer->GetId()
|
|
|| (source->GetMode() == Mode_Peer && peer->GetMode() == Mode_Peer))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (auto& pObj : source->m_objectsTimeSort)
|
|
{
|
|
OnReplicaChanged(pObj.m_replica);
|
|
if (ShouldBroadcastReplica(pObj.m_replica.get()))
|
|
{
|
|
ReplicaTarget::AddReplicaTarget(peer, pObj.m_replica.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnPeerReadyToRemove(ReplicaPeer* peer)
|
|
{
|
|
AZ_Assert(peer, "OnPeerReadyToRemove: Invalid peer");
|
|
|
|
peer->m_objectsTimeSort.clear();
|
|
peer->m_objectsMap.clear();
|
|
peer->m_targets.clear();
|
|
|
|
{
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
|
|
for (auto i = m_remotePeers.begin(); i != m_remotePeers.end(); ++i)
|
|
{
|
|
if (*i == peer)
|
|
{
|
|
m_remotePeers.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_peerUpdateTasks.Add<ReplicaDestroyPeerTask>(peer);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnRPCQueued(ReplicaPtr replica)
|
|
{
|
|
OnReplicaChanged(replica);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnReplicaChanged(ReplicaPtr replica)
|
|
{
|
|
AZ_Assert(replica, "OnReplicaChanged: Invalid replica");
|
|
if (!replica->IsDirty() && replica->IsActive())
|
|
{
|
|
m_dirtyReplicas.push_back(*replica);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnReplicaUnmarshaled(ReplicaPtr replica)
|
|
{
|
|
if (IsSyncHost())
|
|
{
|
|
OnReplicaChanged(replica);
|
|
}
|
|
|
|
EnqueueUpdateTask(replica);
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::RemoveFromDirtyList(Replica& replica)
|
|
{
|
|
m_dirtyReplicas.erase(replica);
|
|
replica.m_dirtyHook.m_next = replica.m_dirtyHook.m_prev = nullptr;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::CancelTasks(ReplicaPtr replica)
|
|
{
|
|
for (auto& i : replica->m_marshalingTasks)
|
|
{
|
|
i->Cancel();
|
|
}
|
|
|
|
for (auto& i : replica->m_updateTasks)
|
|
{
|
|
i->Cancel();
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnDestroyProxy(ReplicaId repId)
|
|
{
|
|
ReplicaPtr pObj = FindReplica(repId);
|
|
if (pObj)
|
|
{
|
|
if (pObj->m_upstreamHop->m_objectsMap.find(pObj->GetRepId()) == pObj->m_upstreamHop->m_objectsMap.end())
|
|
{
|
|
AZ_TracePrintf("GridMate", "Received non-authoritative request to destroy replica!");
|
|
return;
|
|
}
|
|
|
|
for (auto& i : pObj->m_updateTasks)
|
|
{
|
|
i->Cancel();
|
|
}
|
|
m_updateTasks.Add<ReplicaUpdateDestroyedProxyTask>(pObj);
|
|
pObj->m_upstreamHop->Remove(pObj.get());
|
|
}
|
|
m_tombstones.insert(AZStd::make_pair(repId, GetTime().m_realTime));
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnPendingReportsReceived(PeerId peerId) // this is only called for host, as others are not waiting for peer reports
|
|
{
|
|
// Got all pending reports for given peer
|
|
// Should either remove it or start migration
|
|
AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutexRemotePeers);
|
|
|
|
for (auto& p : m_remotePeers)
|
|
{
|
|
if (p->GetId() == peerId)
|
|
{
|
|
if (p->m_objectsTimeSort.empty())
|
|
{
|
|
OnPeerReadyToRemove(p);
|
|
}
|
|
else
|
|
{
|
|
OnMigratePeer(p);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnMigratePeer(ReplicaPeer* peer)
|
|
{
|
|
if (peer->m_objectsTimeSort.empty()) // peer has no replicas -> can remove immediately and bailout
|
|
{
|
|
OnPeerReadyToRemove(peer);
|
|
return;
|
|
}
|
|
|
|
for (auto& r : peer->m_objectsTimeSort)
|
|
{
|
|
if (!r.m_replica->HasUpdateTask())
|
|
{
|
|
m_updateTasks.Add<ReplicaUpdateTask>(r.m_replica);
|
|
}
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::ChangeReplicaOwnership(ReplicaPtr replica, const ReplicaContext& rc, bool isPrimary)
|
|
{
|
|
bool wasPrimary = replica->IsPrimary();
|
|
if (wasPrimary != isPrimary) // wasPrimary == isPrimary can happen when host's replica is moved to client, and client confirms with Cmd_NewOwner
|
|
{
|
|
replica->SetPrimary(isPrimary);
|
|
replica->OnChangeOwnership(rc);
|
|
EBUS_EVENT(Debug::ReplicaDrillerBus, OnReplicaChangeOwnership, replica.get(), wasPrimary);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::OnReplicaPriorityUpdated(Replica* replica)
|
|
{
|
|
for (auto task : replica->m_marshalingTasks)
|
|
{
|
|
m_marshalingTasks.UpdatePriority(task);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
void ReplicaManager::SetSecurityOptions(const ReplicationSecurityOptions& options)
|
|
{
|
|
m_securityOptions = options;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
ReplicationSecurityOptions ReplicaManager::GetSecurityOptions() const
|
|
{
|
|
return m_securityOptions;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
bool ReplicaManager::ShouldBroadcastReplica(Replica* replica) const
|
|
{
|
|
return m_autoBroadcast || replica->IsBroadcast();
|
|
}
|
|
void ReplicaManager::UpdateConnectionRate(AZ::u32 bytesPerSecond, ConnectionID id)
|
|
{
|
|
if (m_connByCongestionState.empty())
|
|
{
|
|
return; //No connections to update
|
|
}
|
|
bool updateRate = false;
|
|
AZ::u32 minRateBytesPerSecond = m_connByCongestionState.top().m_rate;
|
|
//const AZ::u32 old = minRateBytesPerSecond; //For debugging
|
|
|
|
auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id);
|
|
|
|
if ( connIt == m_connByCongestionState.get_container().end())
|
|
{
|
|
return; //Already disconnected
|
|
}
|
|
|
|
//If rate changed
|
|
if (connIt->m_rate != bytesPerSecond)
|
|
{
|
|
//Note this could invalidate the heap ordering, but since we only need a weak guarantee that
|
|
//the top is the lowest we can delay re-running make-heap until 1) a different connection
|
|
//takes the top/min spot or 2) a connection is removed/added (very rare)
|
|
connIt->m_rate = bytesPerSecond;
|
|
|
|
//If new min or old min increased, rebuild the heap and send an update
|
|
if (bytesPerSecond < minRateBytesPerSecond
|
|
|| (id == m_connByCongestionState.top().m_connection && bytesPerSecond > minRateBytesPerSecond))
|
|
{
|
|
updateRate = true;
|
|
minRateBytesPerSecond = bytesPerSecond;
|
|
AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end());
|
|
}
|
|
}
|
|
|
|
if (updateRate)
|
|
{
|
|
//AZ_DbgIf(minRateBytesPerSecond < old)
|
|
//{
|
|
// AZ_TracePrintf("GridMate", "%p updateConnection %p %u -> %u \n",
|
|
// this, id, old, minRateBytesPerSecond);
|
|
//}
|
|
|
|
SetSendLimit(minRateBytesPerSecond); //Respond to congestion control update; also disables bursting
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
} // namespace GridMate
|