Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,188 @@
/*
* 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 "CryNetwork_precompiled.h"
/*!
* Basic marshalers and structures to support backward-compatibility with CrEngine's
* vanilla aspect serialization mechanisms are implemented in this file.
*
* Aspect serialization is implemented as behavior of EntityReplica, for simplicity.
* Most of the behavior is located in EntityReplica::UpdateAspects().
* Removal of the shim will also require removing those components of EntityReplica.
*
* Aspect profiles are also supported through EntityReplica, as are client-delegated
* aspects.
*/
#include "GridMateNetSerialize.h"
#include "../NetworkGridmateDebug.h"
namespace GridMate
{
namespace NetSerialize
{
//-----------------------------------------------------------------------------
uint32 HashBuffer(const char* buffer, size_t size)
{
if (size > 0)
{
// \todo - This is a very slow per-byte hash.
return AZStd::hash_range(&buffer[0], &buffer[size]);
}
return 0;
}
//-----------------------------------------------------------------------------
NetworkAspectType s_globallyDelegatableAspects = 0;
void SetDelegatableAspects(NetworkAspectType aspects)
{
s_globallyDelegatableAspects = aspects;
}
NetworkAspectType GetDelegatableAspectMask()
{
return s_globallyDelegatableAspects;
}
//-----------------------------------------------------------------------------
AspectSerializeState::AspectSerializeState()
: m_hash(0)
, m_writtenSizeBytes(0)
, m_serializeToken(0)
{
}
//-----------------------------------------------------------------------------
bool AspectSerializeState::operator == (const AspectSerializeState& rhs) const
{
return (GetHash() == rhs.GetHash() && m_serializeToken == rhs.m_serializeToken);
}
//-----------------------------------------------------------------------------
bool AspectSerializeState::UpdateHash(uint32 hash, uint32 bytesWritten)
{
bool changed = false;
if (hash != m_hash)
{
++m_serializeToken;
changed = true;
}
m_hash = hash;
m_writtenSizeBytes = bytesWritten;
return changed;
}
//-----------------------------------------------------------------------------
uint32 AspectSerializeState::GetHash() const
{
return m_hash;
}
//-----------------------------------------------------------------------------
AspectSerializeState::Marshaler::Marshaler()
: m_storage(nullptr)
, m_isEnabled(false)
, m_waitingForDispatch(false)
, m_debugName(nullptr)
, m_debugIndex(0)
{
}
//-----------------------------------------------------------------------------
bool AspectSerializeState::Marshaler::AllocateAspectSerializationBuffer(uint32 size)
{
m_storage.reset(size ? new AspectBuffer(size) : nullptr);
if (size)
{
GM_DEBUG_TRACE("Allocated buffer of size %u bytes for aspect buffer.", size);
}
return (m_storage->GetData() != nullptr);
}
//-----------------------------------------------------------------------------
void AspectSerializeState::Marshaler::DeallocateAspectSerializationBuffer()
{
m_storage.reset();
}
//-----------------------------------------------------------------------------
ReadBufferType AspectSerializeState::Marshaler::GetReadBuffer() const
{
if (m_storage)
{
return ReadBufferType(GridMate::EndianType::BigEndian, m_storage->GetData(), m_storage->GetSize());
}
else
{
return ReadBufferType(GridMate::EndianType::BigEndian, nullptr, 0);
}
}
//-----------------------------------------------------------------------------
WriteBufferType AspectSerializeState::Marshaler::GetWriteBuffer()
{
if (m_storage)
{
return WriteBufferType(GridMate::EndianType::BigEndian, m_storage->GetData(), m_storage->GetSize());
}
else
{
return WriteBufferType(GridMate::EndianType::BigEndian, nullptr, 0);
}
}
//-----------------------------------------------------------------------------
void AspectSerializeState::Marshaler::Marshal(GridMate::WriteBuffer& wb, const AspectSerializeState& s)
{
wb.Write(s.m_serializeToken);
const uint16 writtenSizeBytes = m_storage ? s.m_writtenSizeBytes : 0;
wb.Write(writtenSizeBytes);
GM_ASSERT_TRACE(writtenSizeBytes == 0 || writtenSizeBytes <= m_storage->GetSize(),
"Claims %u bytes written, but aspect buffer is only %u bytes in size.",
writtenSizeBytes, m_storage->GetSize());
if (writtenSizeBytes > 0)
{
wb.WriteRaw(m_storage->GetData(), writtenSizeBytes);
}
}
//-----------------------------------------------------------------------------
void AspectSerializeState::Marshaler::Unmarshal(AspectSerializeState& s, GridMate::ReadBuffer& rb)
{
rb.Read(s.m_serializeToken);
rb.Read(s.m_writtenSizeBytes);
if (s.m_writtenSizeBytes > 0)
{
if (s.m_writtenSizeBytes > GetStorageSize())
{
AllocateAspectSerializationBuffer(s.m_writtenSizeBytes);
}
GM_ASSERT_TRACE(m_storage.get() && m_storage->GetData(),
"NetSerializeUnmarshal: Buffer is not prepared for aspect.");
rb.ReadRaw(m_storage->GetData(), s.m_writtenSizeBytes);
}
}
} // namespace NetSerialize
} // namespace GridMate
@@ -0,0 +1,310 @@
/*
* 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.
*
*/
#ifndef INCLUDE_GRIDMATENETSERIALIZE_HEADER
#define INCLUDE_GRIDMATENETSERIALIZE_HEADER
#pragma once
#include "ProjectDefines.h"
#include "../NetworkGridMateCommon.h"
#include "../NetworkGridmateDebug.h"
#include "../NetworkGridmateMarshaling.h"
#include <GridMate/Serialize/DataMarshal.h>
#include <INetwork.h>
namespace GridMate
{
namespace NetSerialize
{
//! Callback for LegacySerializeProvider
using AcquireSerializeCallback = AZStd::function<void(ISerialize*)>;
/*
* This interface is to allow setting custom serializers for legacy aspects and rmis serialization
*/
class ILegacySerializeProvider : public AZ::EBusTraits
{
public:
// Called when need serializer for the legacy aspect, passing WriteBuffer to serialize aspect to and a callback that will return prepared serializer
virtual void AcquireSerializer(WriteBuffer& wb, AcquireSerializeCallback callback) = 0;
// Called when need deserializer for the legacy aspect, passing ReadBuffer to deserialize aspect to and a callback that will return prepared deserializer
virtual void AcquireDeserializer(ReadBuffer& rb, AcquireSerializeCallback callback) = 0;
};
enum
{
kNumAspectSlots = 26
};
void SetDelegatableAspects(NetworkAspectType aspects);
NetworkAspectType GetDelegatableAspectMask();
/*!
* Utility function for hashing an arbitrary byte buffer.
* This is currently used to detect changes in aspect data.
*/
uint32 HashBuffer(const char* buffer, size_t size);
/*!
* Wraps data and state management to facilitate the Aspect serialization model
* used by CryEngine.
* We pipe this data through the EntityReplica.
*/
class AspectSerializeState
{
public:
friend class AspectSerializeStateMarshaler;
/*!
* Marshaler for a given aspects serialization state.
*/
class Marshaler
{
public:
Marshaler();
//! An aspect is active if we unmarshaled a non-zero data requirement for it, per the server.
bool IsActive() const { return GetStorageSize() > 0; }
//! Is a dispatch-to-GameObject (via netSerialize()) pending?
bool IsWaitingForDispatch() const { return m_waitingForDispatch; }
//! New data has come in, mark for dispatch.
void MarkWaitingForDispatch() { m_waitingForDispatch = true; }
//! Clear pending dispatch.
void MarkDispatchComplete() { m_waitingForDispatch = false; }
//! Allocate space for this aspect to serialize data.
//! A size of zero is effectively ignored, and the aspect remains inactive.
bool AllocateAspectSerializationBuffer(uint32 size);
void DeallocateAspectSerializationBuffer();
uint32 GetStorageSize() const { return m_storage ? m_storage->GetSize() : 0; }
ReadBufferType GetReadBuffer() const;
WriteBufferType GetWriteBuffer();
void Marshal(GridMate::WriteBuffer& wb, const AspectSerializeState& s);
void Unmarshal(AspectSerializeState& s, GridMate::ReadBuffer& rb);
private:
bool m_waitingForDispatch; //! Set if the aspect was recently unmarshaled, so we know to
//! dispatch changes to the game.
bool m_isEnabled;
typedef ManagedFlexibleBuffer<256> AspectBuffer;
AspectBuffer::Ptr m_storage; //! Contents and size of the aspect buffer.
public:
const char* m_debugName;
size_t m_debugIndex;
};
AspectSerializeState();
uint32 GetWrittenSize() const { return m_writtenSizeBytes; }
//! Returns true if the data has changed and needs a resend (hash mismatch).
bool operator == (const AspectSerializeState& rhs) const;
//! Accessors for serialization buffer hash.
bool UpdateHash(uint32 hash, uint32 bytesWritten);
uint32 GetHash() const;
private:
uint32 m_hash; //! A hash of the serialization buffer's current contents.
uint16 m_writtenSizeBytes; //! Current size of data in the aspect's buffer.
uint8 m_serializeToken; //! Increments (wrapping okay) each time contents change,
//! so the remote side knows when to dispatch.
};
/*!
* Implementation of CryEngine serializer that marshals data into a GridMate write buffer.
* This is used when reading state from a game object, RMI param gathering, etc.
*/
class EntityNetSerializerCollectState
: public CSimpleSerializeImpl < false, eST_Network >
{
public:
EntityNetSerializerCollectState(GridMate::WriteBuffer& wb)
: m_wb(wb)
{
}
template <class T>
void Value(const char* name, T& value, uint32 policy)
{
(void)name;
(void)policy;
GridMate::Marshaler<T>().Marshal(m_wb, value);
}
void Value(const char* name, EntityId& value, uint32 policy)
{
(void)name;
EntityId serializedId = value;
switch (policy)
{
case 'eid':
{
// Entity Ids don't match across machines, so nodes need to convert
// back to the server's Id before sending.
serializedId = gEnv->pNetwork->LocalEntityIdToServerEntityId(value);
GM_ASSERT_TRACE(value == kInvalidEntityId || serializedId != kInvalidEntityId,
"Failed to map local entity Id %u to a server entity Id. "
"Make sure the entity whose Id is being serialized was spawned as a networked entity.",
value);
}
break;
}
GridMate::Marshaler<EntityId>().Marshal(m_wb, serializedId);
}
template <class T>
void Value(const char* name, T& value)
{
Value(name, value, 0);
}
void Value(const char* name, SSerializeString& value, uint32 policy)
{
(void)name;
(void)policy;
typedef ::string TempString;
TempString s = value.c_str();
GridMate::Marshaler<TempString>().Marshal(m_wb, value);
}
bool BeginGroup(const char* szName)
{
(void)szName;
return true;
}
bool BeginOptionalGroup(const char* szName, bool cond)
{
(void)szName;
GridMate::Marshaler<bool>().Marshal(m_wb, cond);
return cond;
}
void EndGroup()
{
}
uint32 CalculateHash() const
{
return HashBuffer(m_wb.Get(), m_wb.Size());
}
GridMate::WriteBuffer& m_wb;
};
/*!
* Implementation of CryEngine serializer that unmarshals data from a GridMate read buffer.
* This is used when writing state to a game objec, invoking RMIs, etc.
*/
class EntityNetSerializerDispatchState
: public CSimpleSerializeImpl < true, eST_Network >
{
public:
EntityNetSerializerDispatchState()
: m_rb(EndianType::BigEndian)
{
}
EntityNetSerializerDispatchState(const GridMate::ReadBuffer& rb)
: m_rb(rb)
{
}
template <class T>
void Value(const char* name, T& value, uint32 policy)
{
(void)name;
(void)policy;
GridMate::Marshaler<T>().Unmarshal(value, m_rb);
}
template <class T>
void Value(const char* name, T& value)
{
Value(name, value, 0);
}
void Value(const char* name, EntityId& value, uint32 policy)
{
(void)name;
GridMate::Marshaler<EntityId>().Unmarshal(value, m_rb);
switch (policy)
{
case 'eid':
{
// Entity Ids don't match across machines, so nodes need to convert
// back to the server's Id before sending.
EntityId mappedValue = gEnv->pNetwork->ServerEntityIdToLocalEntityId(value, true);
AZ_Warning("CryNetworkShim", value == kInvalidEntityId || mappedValue != kInvalidEntityId, "Failed to map server entity id 0x%x to local entity id", value);
value = mappedValue;
}
break;
}
}
void Value(const char* name, SSerializeString& value, uint32 policy)
{
(void)name;
(void)policy;
typedef ::string TempString;
TempString s = value.c_str();
CryStringMarshaler().Unmarshal(s, m_rb);
value = s;
}
bool BeginGroup(const char* szName)
{
(void)szName;
return true;
}
bool BeginOptionalGroup(const char* szName, bool cond)
{
(void)szName;
GridMate::Marshaler<bool>().Unmarshal(cond, m_rb);
return cond;
}
void EndGroup()
{
}
GridMate::ReadBuffer m_rb;
};
} // namespace NetSerialize
} // namespace GridMate
#endif // INCLUDE_GRIDMATENETSERIALIZE_HEADER
@@ -0,0 +1,120 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "GridMateNetSerializeAspectProfiles.h"
#include <GridMate/Serialize/CompressionMarshal.h>
namespace GridMate
{
namespace NetSerialize
{
//-----------------------------------------------------------------------------
EntityAspectProfiles::EntityAspectProfiles()
: m_profilesMask(0)
{
}
//-----------------------------------------------------------------------------
void EntityAspectProfiles::SetAspectProfile(size_t aspectIndex, AspectProfile profile)
{
GM_ASSERT_TRACE(aspectIndex < kNumAspectSlots, "Invalid aspect index: %u", aspectIndex);
m_aspectProfiles[ aspectIndex ] = profile;
if (profile != kUnsetAspectProfile)
{
m_profilesMask |= (1 << aspectIndex);
}
else
{
m_profilesMask &= ~(1 << aspectIndex);
}
}
//-----------------------------------------------------------------------------
AspectProfile EntityAspectProfiles::GetAspectProfile(size_t aspectIndex) const
{
GM_ASSERT_TRACE(aspectIndex < kNumAspectSlots, "Invalid aspect index: %u", aspectIndex);
return m_aspectProfiles[ aspectIndex ];
}
//-----------------------------------------------------------------------------
bool EntityAspectProfiles::operator == (const EntityAspectProfiles& other) const
{
for (size_t i = 0; i < NetSerialize::kNumAspectSlots; ++i)
{
if (m_aspectProfiles[ i ] != other.m_aspectProfiles[ i ])
{
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
void EntityAspectProfiles::Marshaler::SetChangeDelegate(ChangeDelegate changeDelegate)
{
m_changeDelegate = changeDelegate;
}
//-----------------------------------------------------------------------------
void EntityAspectProfiles::Marshaler::Marshal(GridMate::WriteBuffer& wb, const EntityAspectProfiles& s)
{
AZ::u32 profilesMask = s.m_profilesMask;
wb.Write(profilesMask, VlqU32Marshaler());
unsigned i = 0;
while (profilesMask)
{
if (profilesMask & 1)
{
m_profileMarshaler.Marshal(wb, s.m_aspectProfiles[i]);
}
profilesMask >>= 1;
++i;
}
}
//-----------------------------------------------------------------------------
void EntityAspectProfiles::Marshaler::Unmarshal(EntityAspectProfiles& s, GridMate::ReadBuffer& rb)
{
AZ::u32 profilesMask = 0;
if (rb.Read(profilesMask, VlqU32Marshaler()))
{
s.m_profilesMask = profilesMask;
for (size_t i = 0; i < NetSerialize::kNumAspectSlots; ++i, profilesMask >>= 1)
{
const AspectProfile oldValue = s.m_aspectProfiles[i];
if (profilesMask & 1)
{
m_profileMarshaler.Unmarshal(s.m_aspectProfiles[i], rb);
}
else
{
s.m_aspectProfiles[i] = kUnsetAspectProfile;
}
if (oldValue != s.m_aspectProfiles[i] && m_changeDelegate)
{
m_changeDelegate(i, oldValue, s.m_aspectProfiles[i]);
}
}
}
}
} // namespace NetSerialize
} // namespace GridMate
@@ -0,0 +1,72 @@
/*
* 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.
*
*/
#ifndef INCLUDE_GRIDMATENETSERIALIZEASPECTPROFILES_HEADER
#define INCLUDE_GRIDMATENETSERIALIZEASPECTPROFILES_HEADER
#pragma once
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Replica/DataSet.h>
#include "GridMateNetSerialize.h"
namespace GridMate
{
namespace NetSerialize
{
typedef uint8 AspectProfile;
static const AspectProfile kUnsetAspectProfile = (AspectProfile)(~0);
/*!
* Marshable list of aspect profiles.
*/
class EntityAspectProfiles
{
public:
EntityAspectProfiles();
void SetAspectProfile(size_t aspectIndex, AspectProfile profile);
AspectProfile GetAspectProfile(size_t aspectIndex) const;
bool operator == (const EntityAspectProfiles& other) const;
class Marshaler
{
public:
typedef AZStd::function<void(size_t /*aspectIndex*/,
AspectProfile /*oldProfile*/,
AspectProfile /*newProfile*/)> ChangeDelegate;
void Marshal(GridMate::WriteBuffer& wb, const EntityAspectProfiles& s);
void Unmarshal(EntityAspectProfiles& s, GridMate::ReadBuffer& rb);
void SetChangeDelegate(ChangeDelegate changeDelegate);
private:
ChangeDelegate m_changeDelegate;
GridMate::Marshaler<AspectProfile> m_profileMarshaler;
};
private:
AZ::u32 m_profilesMask;
AspectProfile m_aspectProfiles[ NetSerialize::kNumAspectSlots ] = { kUnsetAspectProfile };
};
typedef GridMate::DataSet<EntityAspectProfiles, EntityAspectProfiles::Marshaler>
SerializedEntityAspectProfiles;
} // namespace NetSerialize
} // namespace GridMate
#endif // INCLUDE_GRIDMATENETSERIALIZEASPECTPROFILES_HEADER
@@ -0,0 +1,49 @@
/*
* 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.
*
*/
ADD_ASPECT(eEA_Script, 0)
ADD_ASPECT(eEA_Physics, 1)
ADD_ASPECT(eEA_GameClientStatic, 2)
ADD_ASPECT(eEA_GameServerStatic, 3)
ADD_ASPECT(eEA_GameClientDynamic, 4)
ADD_ASPECT(eEA_GameServerDynamic, 5)
ADD_ASPECT(eEA_GameClientA, 6)
ADD_ASPECT(eEA_GameServerA, 7)
ADD_ASPECT(eEA_GameClientB, 8)
ADD_ASPECT(eEA_GameServerB, 9)
ADD_ASPECT(eEA_GameClientC, 10)
ADD_ASPECT(eEA_GameServerC, 11)
ADD_ASPECT(eEA_GameClientD, 12)
ADD_ASPECT(eEA_GameClientE, 13)
ADD_ASPECT(eEA_GameClientF, 14)
ADD_ASPECT(eEA_GameClientG, 15)
ADD_ASPECT(eEA_GameClientH, 16)
ADD_ASPECT(eEA_GameClientI, 17)
ADD_ASPECT(eEA_GameClientJ, 18)
ADD_ASPECT(eEA_GameServerD, 19)
ADD_ASPECT(eEA_GameClientK, 20)
ADD_ASPECT(eEA_Aspect29, 21)
ADD_ASPECT(eEA_Aspect30, 22)
ADD_ASPECT(eEA_Aspect31, 23)
ADD_ASPECT(eEA_GameClientO, 24)
ADD_ASPECT(eEA_GameClientP, 25)
// We currently don't have room for these due to replica data set limits.
// If a game requires more, the recommendation is just not use the shim,
// and use replica/replica chunks instead. That's the long term plan anyway,
// so it's unlikely we'll bother supporting more for the shim. If the need
// does arise, it can be done by devoting two separate replica chunks to
// the entity replica for handling aspects.
//eEA_GameServerE = 0x10000000u, // aspect 28
//eEA_GameClientL = 0x00800000u, // aspect 23
//eEA_GameClientM = 0x01000000u, // aspect 24
//eEA_GameClientN = 0x02000000u, // aspect 25
@@ -0,0 +1,483 @@
/*
* 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 "CryNetwork_precompiled.h"
/*!
* Between CryEngine and GameCore, we have three forms of RMIs supported through CryNetwork that
* the shim must maintain support for:
* - GameObjectExtension RMIs (aka Legacy CryEngine)
* - Actor system RMIs (GameCore components)
* - Script/Lua RMIs
*
* This shim does in fact support all three, albeit in an ad-hoc manner. There's no expectation
* for new features in the above systems, so the shim should not need to change.
* Moving forward (post-shim), we will be using replicas directly, with replica chunks owned
* by formal components, with all messages sent as native GridMate RPCs.
*
* All RMIs are packaged in buffers and RPC'd across. Legacy and Actor RMIs make use of static
* RMI representatives, aka "reps", to serialize and interpret parameter buffers.
* Script RMIs are handled through the CryEngine ScriptRMI system, which serializes to/from
* lua tables.
*
* All RMI buffers use a flexible structure that makes use of in-place storage, spilling over
* to heap-allocated space if the payload exceeds 128 bytes, as defined in GridMateRMI.h as
* kInvocationBufferBaseSize.
* Invocation wrappers that own this storage are allocated for each RMI invocation, however
* pooling is a relatively trivial option if we find the allocation count is too high.
*
* Note: All invocations coming from the game/engine are added to a queue, which maintains
* order across all RMI flavors. The root network layer (NetworkGridMate) is responsible for
* flushing the queue after each update.
*/
#include "../NetworkGridMate.h"
#include "../NetworkGridmateDebug.h"
#include "../Replicas/EntityReplica.h"
#include "GridMateRMI.h"
namespace GridMate
{
namespace RMI
{
//-----------------------------------------------------------------------------
uint32 s_actorRMIRepId = 0;
AZStd::vector<IActorRMIRep*, AZ::StdLegacyAllocator> s_actorRMIReps;
typedef std::tuple<EntityId,
LegacyInvocationWrapper::Ptr,
ActorInvocationWrapper::Ptr,
ScriptInvocationWrapper::Ptr> QueuedRMI;
using RMIQueue = AZStd::vector<QueuedRMI, AZ::StdLegacyAllocator>;
RMIQueue m_queuedRMIs;
void InvokeLegacyInternal(EntityId entityId, const LegacyInvocationWrapper::Ptr& invocation);
void InvokeActorInternal(EntityId entityId, const ActorInvocationWrapper::Ptr& invocation);
void InvokeScriptInternal(const ScriptInvocationWrapper::Ptr& invocation);
//-----------------------------------------------------------------------------
static void ValidateRMI([[maybe_unused]] ChannelId targetChannelFilter, WhereType where)
{
WhereType clientFlags = where & eRMI_ClientsMask;
if (clientFlags != 0)
{
CRY_ASSERT_MESSAGE((where & eRMI_ToServer) == 0, "You cannot have both client and server flags set for an RMI!");
CRY_ASSERT_MESSAGE((clientFlags & - clientFlags) == clientFlags, "Only one target client option can be set for an RMI!");
if ((clientFlags & (eRMI_ToClientChannel | eRMI_ToOtherClients | eRMI_ToOtherRemoteClients)) != 0)
{
CRY_ASSERT_MESSAGE(targetChannelFilter != kInvalidChannelId, "RMIs sent using eRMI_ToClientChannel, eRMI_ToOtherClients or eRMI_ToOtherRemoteClients require a valid channel id filter!");
}
}
}
//-----------------------------------------------------------------------------
void FlushQueue()
{
RMIQueue queuedRMIs(AZStd::move(m_queuedRMIs));
for (const QueuedRMI& rmi : queuedRMIs)
{
if (std::get<1>(rmi))
{
InvokeLegacyInternal(std::get<0>(rmi), std::get<1>(rmi));
}
else if (std::get<2>(rmi))
{
InvokeActorInternal(std::get<0>(rmi), std::get<2>(rmi));
}
else if (std::get<3>(rmi))
{
InvokeScriptInternal(std::get<3>(rmi));
}
}
}
//-----------------------------------------------------------------------------
void EmptyQueue()
{
m_queuedRMIs.clear();
}
//-----------------------------------------------------------------------------
inline bool ActorRMICompareId(IActorRMIRep* rep, uint32 id)
{
return rep->GetUniqueId() < id;
}
//-----------------------------------------------------------------------------
IActorRMIRep* FindActorRMIRep(uint32 repId)
{
auto foundAt = std::lower_bound(s_actorRMIReps.begin(), s_actorRMIReps.end(), repId, ActorRMICompareId);
if (foundAt != s_actorRMIReps.end() && (*foundAt)->GetUniqueId() == repId)
{
return *foundAt;
}
return nullptr;
}
//-----------------------------------------------------------------------------
ChannelId GetEntityOwnerChannelId([[maybe_unused]] EntityId entityId)
{
GM_DEBUG_TRACE("Cannot retrieve channelId for entity %u. Only actors have valid channel id.", entityId);
return kInvalidChannelId;
}
//-----------------------------------------------------------------------------
bool ShouldInvokeLocally(ChannelId sentFromChannelId, EntityId targetEntityId, ChannelId targetChannelFilter, WhereType whereMask)
{
const ChannelId localChannelId = Network::Get().GetLocalChannelId();
if (!!(whereMask & eRMI_ToServer))
{
if (gEnv->bServer)
{
return true;
}
}
if (!!(whereMask & eRMI_NoLocalCalls))
{
if (localChannelId == sentFromChannelId)
{
return false;
}
}
if (!!(whereMask & eRMI_ToOwningClient))
{
ChannelId ownerChannelId = GetEntityOwnerChannelId(targetEntityId);
if (gEnv->IsClient() && ownerChannelId == localChannelId)
{
return true;
}
}
if (!!(whereMask & eRMI_ToOtherClients))
{
if (gEnv->IsClient() && localChannelId != targetChannelFilter)
{
return true;
}
}
if (!!(whereMask & eRMI_ToAllClients))
{
if (gEnv->IsClient())
{
return true;
}
}
if (!!(whereMask & eRMI_ToRemoteClients))
{
if (localChannelId != sentFromChannelId)
{
return true;
}
}
if (!!(whereMask & eRMI_ToOtherRemoteClients))
{
if (localChannelId != sentFromChannelId && localChannelId != targetChannelFilter)
{
return true;
}
}
if (!!(whereMask & eRMI_ToClientChannel))
{
if (localChannelId == targetChannelFilter)
{
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
bool ShouldDispatch(ChannelId sentFromChannelId, [[maybe_unused]] EntityId targetEntityId, WhereType whereMask)
{
const ChannelId localChannelId = Network::Get().GetLocalChannelId();
if (!gEnv->bServer &&
localChannelId == sentFromChannelId &&
!!(whereMask & eRMI_ToServer))
{
return true;
}
return !!(whereMask & eRMI_ClientsMask);
}
//-----------------------------------------------------------------------------
void InvokeActor(EntityId entityId, uint8 actorExtensionId, ChannelId targetChannelFilter, IActorRMIRep& rep)
{
ValidateRMI(targetChannelFilter, rep.GetWhere());
enum
{
kRMIParamsMaxSize = 32 * 1024
};
char paramsStorage[ kRMIParamsMaxSize ];
WriteBufferType writeBuffer(EndianType::BigEndian, paramsStorage, sizeof(paramsStorage));
// Serialize params structure to a temporary GridMate buffer.
Network::Get().GetLegacySerializeProvider()->AcquireSerializer(writeBuffer, [&](ISerialize* serializer)
{
rep.SerializeParams(serializer);
});
GM_ASSERT_TRACE(writeBuffer.Size() < kRMIParamsMaxSize, "Overran params buffer.");
// Dispatch via Gridmate RPCs. This wrapper is ref-counted, and owns a copy
// of the params buffer.
ActorInvocationWrapper::Ptr invocation = new ActorInvocationWrapper(
Network::Get().GetLocalChannelId(),
actorExtensionId,
rep.GetUniqueId(),
targetChannelFilter,
rep.GetWhere(),
writeBuffer.Get(),
writeBuffer.Size());
QueuedRMI queuedRMI;
std::get<0>(queuedRMI) = entityId;
std::get<2>(queuedRMI) = invocation;
m_queuedRMIs.push_back(queuedRMI);
}
//-----------------------------------------------------------------------------
void LocalDispatchActor(const ActorInvocationWrapper::Ptr& invocation,
IActorRMIRep& rep,
EntityId entityId)
{
ReadBufferType readBuffer = invocation->m_paramsBuffer.GetReadBuffer();
Network::Get().GetLegacySerializeProvider()->AcquireDeserializer(readBuffer, [&](ISerialize* serializer)
{
rep.SerializeParams(serializer);
});
rep.Invoke(entityId, invocation->m_actorExtensionId);
}
//-----------------------------------------------------------------------------
void InvokeActorInternal(EntityId entityId, const ActorInvocationWrapper::Ptr& invocation)
{
IActorRMIRep* rep = FindActorRMIRep(invocation->m_repId);
GM_ASSERT_TRACE(rep, "Unable to locate RMI rep with id %u.", invocation->m_repId);
if (!rep)
{
return;
}
const uint8 actorExtensionId = invocation->m_actorExtensionId;
const ChannelId targetChannelFilter = invocation->m_targetChannelFilter;
const WhereType whereMask = rep->GetWhere();
GM_DEBUG_TRACE_LEVEL(2, "Invoking actor RMI %s for entity/extension %u/%u, where: 0x%u",
rep->GetDebugName(), entityId, actorExtensionId, whereMask);
const ChannelId localChannelId = Network::Get().GetLocalChannelId();
const bool dispatch = ShouldDispatch(localChannelId,
entityId,
whereMask);
const bool invokeLocally = ShouldInvokeLocally(localChannelId,
entityId,
targetChannelFilter,
whereMask);
// If the RMI only needs to execute on this machine, just invoke locally and bail.
if (!dispatch && invokeLocally)
{
if (gEnv->IsClient())
{
LocalDispatchActor(invocation, *rep, entityId);
}
GM_DEBUG_TRACE_LEVEL(3, "Locally handled actor RMI for entity/extension %u/%u, where: 0x%u",
entityId, actorExtensionId, whereMask);
return;
}
EntityReplica* replica = Network::Get().FindEntityReplica(entityId);
if (replica)
{
GM_DEBUG_TRACE_LEVEL(3, "Dispatching actor RMI %s for entity/extension %u/%u, where: 0x%u",
rep->GetDebugName(), entityId, actorExtensionId, whereMask);
EBUS_EVENT(NetworkSystemEventBus, ActorRMISent, entityId, *rep, invocation->m_paramsBuffer.GetSize());
if ((invocation->m_where & eRMI_ToServer) == eRMI_ToServer)
{
replica->RPCHandleActorServerRMI(invocation);
}
else
{
replica->RPCHandleActorClientRMI(invocation);
}
}
else
{
// Support offline invocation.
if (invokeLocally)
{
LocalDispatchActor(invocation, *rep, entityId);
}
}
}
//-----------------------------------------------------------------------------
bool HandleActor(EntityId entityId, ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
(void)rc;
IActorRMIRep* rep = FindActorRMIRep(invocation->m_repId);
GM_DEBUG_TRACE_LEVEL(2, "Handling actor RMI %s for entity/extension %u/%u, where: 0x%u",
rep->GetDebugName(), entityId, invocation->m_actorExtensionId, invocation->m_where);
if (rep)
{
const WhereType whereMask = invocation->m_where;
const ChannelId sentFromChannel = invocation->m_sentFromChannel;
const ChannelId targetChannelFilter = invocation->m_targetChannelFilter;
const bool dispatch = ShouldDispatch(sentFromChannel,
entityId,
whereMask);
const bool invokeLocally = ShouldInvokeLocally(sentFromChannel,
entityId,
targetChannelFilter,
whereMask);
if (invokeLocally)
{
LocalDispatchActor(invocation, *rep, entityId);
if (invocation->m_sentFromChannel != Network::Get().GetLocalChannelId())
{
EBUS_EVENT(NetworkSystemEventBus, ActorRMIReceived, entityId, *rep, invocation->m_paramsBuffer.GetSize());
}
GM_DEBUG_TRACE_LEVEL(3, "Dispatched to rep actor RMI %s for entity/extension %u/%u, where: 0x%u",
rep->GetDebugName(), entityId, invocation->m_actorExtensionId, invocation->m_where);
}
if (dispatch)
{
GM_DEBUG_TRACE_LEVEL(3, "Passing on to clients actor RMI %s for entity/extension %u/%u, where: 0x%u",
rep->GetDebugName(), entityId, invocation->m_actorExtensionId, invocation->m_where);
// This RMI is to be forwarded on to clients.
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
void InvokeLegacyInternal([[maybe_unused]] EntityId entityId, [[maybe_unused]] const LegacyInvocationWrapper::Ptr& invocation)
{
GM_DEBUG_TRACE("Cannot invoke queued RMI because game object for entity %u could not be found.", entityId);
}
//-----------------------------------------------------------------------------
bool HandleLegacy([[maybe_unused]] EntityId entityId, LegacyInvocationWrapper::Ptr invocation, [[maybe_unused]] const GridMate::RpcContext& rc)
{
GM_ASSERT_TRACE(0, "Failed to locate RMI rep with id %u for entity %u", invocation->m_repId, entityId);
return false;
}
//-----------------------------------------------------------------------------
void InvokeScript(ISerializable* serializable, bool isServerRMI, ChannelId toChannelId, ChannelId avoidChannelId)
{
// Serialize contents.
enum
{
kRMIDataMaxSize = 1024
};
char tempStorage[ kRMIDataMaxSize ];
WriteBufferType writeBuffer(EndianType::BigEndian, tempStorage, sizeof(tempStorage));
// Serialize params structure to a temporary GridMate buffer.
Network::Get().GetLegacySerializeProvider()->AcquireSerializer(writeBuffer, [&](ISerialize* serializer)
{
serializable->SerializeWith(serializer);
});
ScriptInvocationWrapper::Ptr invocation = new ScriptInvocationWrapper(
isServerRMI,
toChannelId,
avoidChannelId,
writeBuffer.Get(), writeBuffer.Size());
QueuedRMI queuedRMI;
std::get<3>(queuedRMI) = invocation;
m_queuedRMIs.push_back(queuedRMI);
}
//-----------------------------------------------------------------------------
bool HandleScript(ScriptInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
(void)invocation;
(void)rc;
return true;
}
//-----------------------------------------------------------------------------
void InvokeScriptInternal(const ScriptInvocationWrapper::Ptr& invocation)
{
EBUS_EVENT(NetworkSystemEventBus, ScriptRMISent, invocation->m_serializedData.GetSize());
// Support offline invocation.
HandleScript(invocation, GridMate::RpcContext());
}
//-----------------------------------------------------------------------------
void RegisterActorRMI(IActorRMIRep* rep)
{
GM_ASSERT_TRACE(0 == rep->GetUniqueId(), "Rep is already registered.");
if (0 == rep->GetUniqueId())
{
rep->SetUniqueId(++s_actorRMIRepId);
auto insertAt = std::lower_bound(s_actorRMIReps.begin(), s_actorRMIReps.end(), rep->GetUniqueId(), ActorRMICompareId);
if (insertAt == s_actorRMIReps.end() || (*insertAt)->GetUniqueId() != rep->GetUniqueId())
{
s_actorRMIReps.insert(insertAt, rep);
}
}
}
//-----------------------------------------------------------------------------
void UnregisterActorRMI(IActorRMIRep* rep)
{
GM_ASSERT_TRACE(0 != rep->GetUniqueId(), "Rep is not registered.");
auto removeAt = std::lower_bound(s_actorRMIReps.begin(), s_actorRMIReps.end(), rep->GetUniqueId(), ActorRMICompareId);
if (removeAt != s_actorRMIReps.end() && (*removeAt)->GetUniqueId() == rep->GetUniqueId())
{
s_actorRMIReps.erase(removeAt);
}
}
} // namespace RMI
} // namespace GridMate
@@ -0,0 +1,344 @@
/*
* 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.
*
*/
#ifndef INCLUDE_GRIDMATERMI_HEADER
#define INCLUDE_GRIDMATERMI_HEADER
#pragma once
#include <INetwork.h>
#include "../NetworkGridMateCommon.h"
#include "GridMateNetSerialize.h"
#include <GridMate/Replica/Replica.h>
enum ERMInvocation
{
eRMI_ToClientChannel = 0x01, // Send RMI from server to a specific client
eRMI_ToOwningClient = 0x04, // Send RMI from server to client that owns the actor
eRMI_ToOtherClients = 0x08, // Send RMI from server to all clients except the specified client
eRMI_ToOtherRemoteClients = 0x10, // Send RMI from server to all remote clients except the specified client
eRMI_ToAllClients = 0x20, // Send RMI from server to all clients
eRMI_ToServer = 0x100, // Send RMI from client to server
eRMI_NoLocalCalls = 0x1000, // For internal use only
// IMPORTANT: Using the RMI shim through GridMate, do not exceed 16 bits or flags will be lost in transit.
eRMI_ToRemoteClients = eRMI_NoLocalCalls | eRMI_ToAllClients, // Send RMI from server to all remote clients
// Mask aggregating all bits that require dispatching to non-server clients.
eRMI_ClientsMask = eRMI_ToAllClients | eRMI_ToOtherClients | eRMI_ToOtherRemoteClients | eRMI_ToOwningClient | eRMI_ToClientChannel,
};
namespace GridMate
{
namespace RMI
{
typedef uint16 WhereType;
enum
{
kInvocationBufferBaseSize = 128
};
/*!
* Flushes any RMIs invoked by the game this frame, by dispatching via RPCs.
*/
void FlushQueue();
/*!
* Empties pending RMI queue, does not dispatch.
*/
void EmptyQueue();
/*!
* Used to limit client RMIs to be callable from the host
*/
struct ClientRMITraits : public GridMate::RpcDefaultTraits
{
};
/*!
* Base helper template for wrapping CryEngine RMI invocations.
* This object is managed by a smart ptr to ensure the memory it owns
* is properly resource-managed while its queued in GridMate as an RPC.
*/
template<typename DerivedType>
class InvocationWrapperBase
: public _i_multithread_reference_target_t
{
public:
typedef _smart_ptr<DerivedType> Ptr;
WhereType m_where;
ChannelId m_sentFromChannel;
ChannelId m_targetChannelFilter; // contains channel to include or exclude, depending on WhereType
typedef FlexibleBuffer<kInvocationBufferBaseSize> ParamsBuffer;
ParamsBuffer m_paramsBuffer;
InvocationWrapperBase()
: m_where(0)
, m_sentFromChannel(kInvalidChannelId)
, m_targetChannelFilter(kInvalidChannelId)
{
}
InvocationWrapperBase(ChannelId sentFromChannel,
ChannelId targetChannelFilter,
WhereType where,
const char* paramsBuffer,
uint32 paramsBufferSize)
: m_where(where)
, m_sentFromChannel(sentFromChannel)
, m_targetChannelFilter(targetChannelFilter)
, m_paramsBuffer(paramsBuffer, paramsBufferSize)
{
}
virtual ~InvocationWrapperBase() {}
};
/*!
* Marshaler base class handling base class' members.
*/
template<typename DerivedType>
class InvocationWrapperMarshalerBase
{
public:
static bool RequiresFromChannel(WhereType where)
{
return !!(where & (eRMI_ToOtherRemoteClients | eRMI_NoLocalCalls));
}
static bool RequiresTargetChannelFilter(WhereType where)
{
return !!(where & (eRMI_ToClientChannel | eRMI_ToOtherClients | eRMI_ToOtherRemoteClients));
}
void Marshal(GridMate::WriteBuffer& wb, const typename DerivedType::Ptr& value)
{
wb.Write(value->m_where);
if (RequiresFromChannel(value->m_where))
{
wb.Write(value->m_sentFromChannel);
}
if (RequiresTargetChannelFilter(value->m_where))
{
wb.Write(value->m_targetChannelFilter);
}
typename DerivedType::ParamsBuffer::Marshaler().Marshal(wb, value->m_paramsBuffer);
}
void Unmarshal(typename DerivedType::Ptr& value, GridMate::ReadBuffer& rb)
{
DerivedType* invocation = new DerivedType();
rb.Read(invocation->m_where);
if (RequiresFromChannel(invocation->m_where))
{
rb.Read(invocation->m_sentFromChannel);
}
if (RequiresTargetChannelFilter(invocation->m_where))
{
rb.Read(invocation->m_targetChannelFilter);
}
typename DerivedType::ParamsBuffer::Marshaler().Unmarshal(invocation->m_paramsBuffer, rb);
value = std::move(invocation);
}
};
/*!
* Wrapper for legacy (GameObject / GameObjectExtension) RMIs.
*/
class LegacyInvocationWrapper
: public InvocationWrapperBase < LegacyInvocationWrapper >
{
public:
LegacyInvocationWrapper() {}
LegacyInvocationWrapper(ChannelId sentFromChannel,
uint32 repId,
ChannelId targetChannelFilter,
WhereType where,
const char* paramsBuffer,
uint32 paramsBufferSize)
: InvocationWrapperBase(sentFromChannel, targetChannelFilter, where, paramsBuffer, paramsBufferSize)
, m_repId(repId)
{
}
uint32 m_repId;
class Marshaler
: public InvocationWrapperMarshalerBase < LegacyInvocationWrapper >
{
public:
typedef InvocationWrapperMarshalerBase<LegacyInvocationWrapper> Super;
void Marshal(GridMate::WriteBuffer& wb, const LegacyInvocationWrapper::Ptr& value)
{
Super::Marshal(wb, value);
wb.Write(value->m_repId);
}
void Unmarshal(LegacyInvocationWrapper::Ptr& value, GridMate::ReadBuffer& rb)
{
Super::Unmarshal(value, rb);
rb.Read(value->m_repId);
}
};
};
/*!
* Wrapper for actor (GameCore / RPGSample) RMIs.
*/
class ActorInvocationWrapper
: public InvocationWrapperBase < ActorInvocationWrapper >
{
public:
ActorInvocationWrapper() {}
ActorInvocationWrapper(ChannelId sentFromChannel,
uint8 actorExtensionId,
uint32 repId,
ChannelId targetChannelFilter,
WhereType where,
const char* paramsBuffer,
uint32 paramsBufferSize)
: InvocationWrapperBase(sentFromChannel, targetChannelFilter, where, paramsBuffer, paramsBufferSize)
, m_repId(repId)
, m_actorExtensionId(actorExtensionId)
{
}
uint32 m_repId;
uint8 m_actorExtensionId;
class Marshaler
: public InvocationWrapperMarshalerBase < ActorInvocationWrapper >
{
public:
typedef InvocationWrapperMarshalerBase<ActorInvocationWrapper> Super;
void Marshal(GridMate::WriteBuffer& wb, const ActorInvocationWrapper::Ptr& value)
{
// \todo: Investigate reduction of repId size, or combining to reduce per-RMI overhead.
Super::Marshal(wb, value);
wb.Write(value->m_repId);
wb.Write(value->m_actorExtensionId);
}
void Unmarshal(ActorInvocationWrapper::Ptr& value, GridMate::ReadBuffer& rb)
{
Super::Unmarshal(value, rb);
rb.Read(value->m_repId);
rb.Read(value->m_actorExtensionId);
}
};
};
/*!
* Wrapper for lua script entity RMIs.
*/
class ScriptInvocationWrapper
: public _i_reference_target_t
{
public:
typedef _smart_ptr<ScriptInvocationWrapper> Ptr;
ScriptInvocationWrapper() {}
ScriptInvocationWrapper(bool isServerRMI,
ChannelId toChannelId,
ChannelId avoidChannelId,
const char* serializedData,
size_t serializedDataSize)
: m_toChannelId(toChannelId)
, m_avoidChannelId(avoidChannelId)
, m_isServerRMI(isServerRMI)
, m_serializedData(serializedData, serializedDataSize)
{
}
~ScriptInvocationWrapper() {}
ChannelId m_toChannelId;
ChannelId m_avoidChannelId;
bool m_isServerRMI;
typedef FlexibleBuffer<128> DataBuffer;
DataBuffer m_serializedData;
class Marshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const ScriptInvocationWrapper::Ptr& value)
{
wb.Write(value->m_toChannelId);
wb.Write(value->m_avoidChannelId);
DataBuffer::Marshaler().Marshal(wb, value->m_serializedData);
}
void Unmarshal(ScriptInvocationWrapper::Ptr& value, GridMate::ReadBuffer& rb)
{
ScriptInvocationWrapper* invocation = new ScriptInvocationWrapper();
rb.Read(invocation->m_toChannelId);
rb.Read(invocation->m_avoidChannelId);
DataBuffer::Marshaler().Unmarshal(invocation->m_serializedData, rb);
value = std::move(invocation);
}
};
};
//! Handles invocation for actor (GameCore) RMIs.
void InvokeActor(EntityId entityId, uint8 actorExtensionId, ChannelId targetChannelFilter, IActorRMIRep& rep);
//! Handles invocation for lua script entity RMIs.
void InvokeScript(ISerializable* serializable, bool isServerRMI, ChannelId toChannelId, ChannelId avoidChannelId);
//! Handles deciphering and dispatching of legacy GameObject RMIs.
bool HandleLegacy(EntityId entityId, LegacyInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
//! Handles deciphering and dispatching of actor (GameCore) RMIs.
bool HandleActor(EntityId entityId, ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
//! Handles deciphering and dispatching of lua script entity RMIs.
bool HandleScript(ScriptInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
//! Register/Unregister an actor RMI sink for dispatching upon receipt.
void RegisterActorRMI(IActorRMIRep* rep);
void UnregisterActorRMI(IActorRMIRep* rep);
IActorRMIRep* FindActorRMIRep(uint32 repId);
//! Convenience typedefs for RMI param serializers.
typedef NetSerialize::EntityNetSerializerCollectState RMIParamsSerializerStoreParams;
typedef NetSerialize::EntityNetSerializerDispatchState RMIParamsSerializerUnwindParams;
} // namespace RMI
} // namespace GridMate
#endif // INCLUDE_GRIDMATERMI_HEADER
@@ -0,0 +1,722 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "ILevelSystem.h"
#include "NetworkGridMate.h"
#include "NetworkGridmateDebug.h"
#include "Replicas/EntityReplica.h"
#include "Replicas/EntityScriptReplicaChunk.h"
#include "Compatibility/GridMateNetSerialize.h"
#include "Compatibility/GridMateRMI.h"
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/BasicHostChunkDescriptor.h>
#include "NetworkGridMateEntityEventBus.h"
#if defined(AZ_RESTRICTED_PLATFORM)
#undef AZ_RESTRICTED_SECTION
#define NETWORKGRIDMATE_CPP_SECTION_1 1
#define NETWORKGRIDMATE_CPP_SECTION_2 2
#endif
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION NETWORKGRIDMATE_CPP_SECTION_1
#include AZ_RESTRICTED_FILE(GridMate/NetworkGridMate_cpp)
#endif
namespace GridMate
{
//-----------------------------------------------------------------------------
Network* Network::s_instance = nullptr;
int Network::s_StatsIntervalMS = 1000; // 1 second by default.
int Network::s_DumpStatsEnabled = 0;
FILE* Network::s_DumpStatsFile = nullptr;
//-----------------------------------------------------------------------------
Network::Network()
: m_localChannelId(kOfflineChannelId)
, m_gridMate(nullptr)
, m_session(nullptr)
, m_levelLoadState(LevelLoadState_None)
, m_allowMinimalUpdate(false)
{
s_instance = this;
m_legacySerializeProvider = this;
m_postFrameTasks.reserve(32);
}
//-----------------------------------------------------------------------------
Network::~Network()
{
#if GRIDMATE_DEBUG_ENABLED
Debug::UnregisterCVars();
#endif
if (GetLevelSystem())
{
GetLevelSystem()->RemoveListener(this);
}
m_activeEntityReplicaMap.clear();
m_newProxyEntities.clear();
ShutdownGridMate();
if (s_DumpStatsFile)
{
fclose(s_DumpStatsFile);
s_DumpStatsFile = nullptr;
}
s_instance = nullptr;
}
//-----------------------------------------------------------------------------
bool Network::Init([[maybe_unused]] int ncpu)
{
#if GRIDMATE_DEBUG_ENABLED
Debug::RegisterCVars();
#endif
StartGridMate();
MarkAsLocalOnly();
return true;
}
//-----------------------------------------------------------------------------
Network& Network::Get()
{
GM_ASSERT_TRACE(s_instance, "Network interface has not yet been created.");
return *s_instance;
}
//-----------------------------------------------------------------------------
void Network::Release()
{
delete this;
}
//-----------------------------------------------------------------------------
bool Network::AllowEntityCreation() const
{
return true;
}
//-----------------------------------------------------------------------------
bool Network::IsInMinimalUpdate() const
{
return m_allowMinimalUpdate;
}
//-----------------------------------------------------------------------------
void Network::SyncWithGame(ENetworkGameSync syncType)
{
FUNCTION_PROFILER(GetISystem(), PROFILE_NETWORK);
switch (syncType)
{
case eNGS_FrameStart:
{
UpdateGridMate(syncType);
}
break;
case eNGS_FrameEnd:
{
FlushPostFrameTasks();
UpdateGridMate(syncType);
UpdateNetworkStatistics();
DebugDraw();
}
break;
/////////////////////////////////////////////////////////////////////////////////////////////
// Inherited from CryNetwork, this mechanism is required for safe updating during loading.
// During such time, the network is pumped via the NetworkStallerTicker thread, and this flag
// basically describes when it's safe for network messages to be distributed to the game.
case eNGS_AllowMinimalUpdate:
{
m_allowMinimalUpdate = true;
m_levelLoadState = LevelLoadState_Loading;
}
break;
case eNGS_DenyMinimalUpdate:
{
m_allowMinimalUpdate = false;
m_levelLoadState = LevelLoadState_Loaded;
}
break;
case eNGS_MinimalUpdateForLoading:
{
if (m_allowMinimalUpdate)
{
UpdateGridMate(syncType);
}
}
break;
/////////////////////////////////////////////////////////////////////////////////////////////
}
}
//-----------------------------------------------------------------------------
void Network::FlushPostFrameTasks()
{
BindNewEntitiesToNetwork();
for (const Task& task : m_postFrameTasks)
{
task();
}
RMI::FlushQueue();
m_postFrameTasks.clear();
}
//-----------------------------------------------------------------------------
void Network::UpdateGridMate(ENetworkGameSync syncType)
{
if (m_gridMate && m_mutexUpdatingGridMate.try_lock() )
{
FRAME_PROFILER("GridMate Update", GetISystem(), PROFILE_NETWORK);
GridMate::ReplicaManager* replicaManager = GetCurrentSession() ? GetCurrentSession()->GetReplicaMgr() : nullptr;
if (replicaManager)
{
switch (syncType)
{
case eNGS_MinimalUpdateForLoading:
case eNGS_FrameStart:
{
if (replicaManager)
{
replicaManager->Unmarshal();
replicaManager->UpdateFromReplicas();
}
// When called from the network stall ticker thread, marshalling should be performed as well.
if (syncType != eNGS_MinimalUpdateForLoading)
{
break;
}
}
case eNGS_FrameEnd:
{
if (replicaManager)
{
replicaManager->UpdateReplicas();
replicaManager->Marshal();
}
break;
}
default: break;
}
}
m_gridMate->Update();
m_mutexUpdatingGridMate.unlock();
}
}
//-----------------------------------------------------------------------------
ChannelId Network::GetChannelIdForSessionMember(GridMate::GridMember* member) const
{
return member ? ChannelId(member->GetIdCompact()) : kInvalidChannelId;
}
//-----------------------------------------------------------------------------
void Network::ChangedAspects(EntityId entityId, NetworkAspectType aspectBits)
{
if (aspectBits == 0)
{
return; // nothing to do
}
#ifndef _RELEASE
for (size_t i = NetSerialize::kNumAspectSlots; i < NUM_ASPECTS; ++i)
{
if (BIT64(i) & aspectBits)
{
GM_ASSERT_TRACE(0, "Any aspects >= %u can not be serialized through this layer, until support for > 32 data sets is enabled.", static_cast<uint32>(NetSerialize::kNumAspectSlots));
break;
}
}
#endif
EntityReplica* replica = FindEntityReplica(entityId);
if (replica)
{
if (replica->IsMaster() || replica->IsAspectDelegatedToThisClient())
{
NetworkAspectType oldDirtyAspects = replica->GetDirtyAspects();
replica->MarkAspectsDirty(aspectBits);
if (replica->IsAspectDelegatedToThisClient())
{
// Only add the task if these are the first aspects being dirtied.
if (oldDirtyAspects == 0)
{
m_postFrameTasks.push_back(
[=]()
{
EntityReplica* rep = FindEntityReplica(entityId);
if (rep)
{
rep->UploadClientDelegatedAspects();
}
}
);
}
}
}
}
else
{
GM_DEBUG_TRACE("Failed to mark aspects dirty because replica for "
"entity id %u could not be found.", entityId);
}
}
//-----------------------------------------------------------------------------
ChannelId Network::GetLocalChannelId() const
{
return m_localChannelId;
}
//-----------------------------------------------------------------------------
ChannelId Network::GetServerChannelId() const
{
if (m_session)
{
return GetChannelIdForSessionMember(m_session->GetHost());
}
return m_localChannelId;
}
//-----------------------------------------------------------------------------
EntityId Network::LocalEntityIdToServerEntityId(EntityId localId) const
{
if (!gEnv->bServer)
{
// \todo - Optimize. Keep a local->server id map locally. We already have server->local
// via m_activeEntityReplicaMap.
for (auto& replicaEntry : m_activeEntityReplicaMap)
{
if (replicaEntry.second->GetLocalEntityId() == localId)
{
return replicaEntry.first;
}
}
return kInvalidEntityId;
}
return localId;
}
//-----------------------------------------------------------------------------
EntityId Network::ServerEntityIdToLocalEntityId(EntityId serverId, bool allowForcedEstablishment /*= false*/) const
{
EntityId localId = kInvalidEntityId;
if (gEnv->bServer)
{
localId = serverId;
}
else
{
auto foundAt = m_activeEntityReplicaMap.find(serverId);
if (foundAt != m_activeEntityReplicaMap.end())
{
EntityReplicaPtr replica = foundAt->second;
localId = replica->GetLocalEntityId();
}
else if (allowForcedEstablishment)
{
AZ_Assert(AllowEntityCreation(), "Entity creation is not allowed during level loads! Forcing creation is going to cause problems!");
// If we're deserializing this entity Id via the 'eid' policy, but the local entity is not
// yet established, expedite establishment. This is to ensure we can properly map/decode
// the entity Id mid-serialization.
auto newProxy = m_newProxyEntities.find(serverId);
if (newProxy != m_newProxyEntities.end())
{
EntityReplicaPtr replica = newProxy->second;
localId = replica->HandleNewlyReceivedNow();
}
}
}
return localId;
}
//-----------------------------------------------------------------------------
void Network::InvokeActorRMI(EntityId entityId, uint8 actorExtensionId, ChannelId targetChannelFilter, IActorRMIRep& rep)
{
RMI::InvokeActor(entityId, actorExtensionId, targetChannelFilter, rep);
}
//-----------------------------------------------------------------------------
void Network::InvokeScriptRMI(ISerializable* serializable, bool isServerRMI, ChannelId toChannelId, ChannelId avoidChannelId)
{
RMI::InvokeScript(serializable, isServerRMI, toChannelId, avoidChannelId);
}
//-----------------------------------------------------------------------------
void Network::RegisterActorRMI(IActorRMIRep* rep)
{
RMI::RegisterActorRMI(rep);
}
//-----------------------------------------------------------------------------
void Network::UnregisterActorRMI(IActorRMIRep* rep)
{
RMI::UnregisterActorRMI(rep);
}
//-----------------------------------------------------------------------------
void Network::SetDelegatableAspectMask(NetworkAspectType aspectBits)
{
NetSerialize::SetDelegatableAspects(aspectBits);
}
//-----------------------------------------------------------------------------
void Network::SetObjectDelegatedAspectMask(EntityId entityId, NetworkAspectType aspects, bool set)
{
m_postFrameTasks.push_back(
[=]()
{
if (EntityReplica* entityReplica = FindEntityReplica(entityId))
{
NetworkAspectType mask = entityReplica->GetClientDelegatedAspectMask();
if (set)
{
mask |= aspects;
}
else
{
mask &= ~aspects;
}
entityReplica->SetClientDelegatedAspectMask(mask);
}
else
{
GM_DEBUG_TRACE("Failed to update aspect delegation mask because replica"
"for entity id %u could not be found.", entityId);
}
}
);
}
//-----------------------------------------------------------------------------
void Network::DelegateAuthorityToClient(EntityId entityId, ChannelId clientChannelId)
{
GridMate::EntityReplica* replica = FindEntityReplica(entityId);
if (replica)
{
replica->RPCDelegateAuthorityToOwner(clientChannelId);
}
}
void Network::ShutdownGridMate()
{
if (m_gridMate)
{
GM_DEBUG_TRACE("Shutting down GridMate network.");
m_postFrameTasks.clear();
RMI::EmptyQueue();
GridMateDestroy(m_gridMate);
m_gridMate = nullptr;
if (m_sessionEvents.IsConnected())
{
m_sessionEvents.Disconnect();
}
if (m_systemEvents.IsConnected())
{
m_systemEvents.Disconnect();
}
}
}
//-----------------------------------------------------------------------------
EntityReplica* Network::FindEntityReplica(EntityId id) const
{
if (!gEnv->bServer)
{
// Replicas are mapped by server-side entity Id, and we map back and forth
// to reconcile across server and clients.
// Upon deserializing via 'eid' policy, server-side Ids are converted back
// to local.
id = LocalEntityIdToServerEntityId(id);
}
auto replicaIter = m_activeEntityReplicaMap.find(id);
if (replicaIter != m_activeEntityReplicaMap.end())
{
return replicaIter->second.get();
}
return nullptr;
}
//-----------------------------------------------------------------------------
void Network::StartGridMate()
{
if (nullptr != m_gridMate)
{
return;
}
GridMateDesc desc;
m_gridMate = GridMateCreate(desc);
// Monitor session events.
GM_ASSERT_TRACE(!m_sessionEvents.IsConnected(), "Session events bus should not be connected yet.");
m_sessionEvents.Connect(m_gridMate);
// Monitor internal system events.
GM_ASSERT_TRACE(!m_systemEvents.IsConnected(), "System events bus should not be connected yet.");
m_systemEvents.Connect();
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(EntityReplica::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<EntityReplica, EntityReplica::EntityReplicaDesc>();
}
if (!ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(ReplicaChunkClassId(EntityScriptReplicaChunk::GetChunkName())))
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<EntityScriptReplicaChunk>();
}
#if defined(AZ_RESTRICTED_PLATFORM)
#define AZ_RESTRICTED_SECTION NETWORKGRIDMATE_CPP_SECTION_2
#include AZ_RESTRICTED_FILE(GridMate/NetworkGridMate_cpp)
#endif
}
//-----------------------------------------------------------------------------
void Network::OnLoadingComplete([[maybe_unused]] ILevel* level)
{
}
//-----------------------------------------------------------------------------
void Network::OnUnloadComplete([[maybe_unused]] ILevel* level)
{
m_levelLoadState = LevelLoadState_None;
m_activeEntityReplicaMap.clear();
m_newServerEntities.clear();
}
//-----------------------------------------------------------------------------
CTimeValue Network::GetSessionTime()
{
CTimeValue t = gEnv->pTimer->GetFrameStartTime();
if (m_session)
{
t.SetMilliSeconds(m_session->GetTime());
}
return t;
}
//-----------------------------------------------------------------------------
void Network::UpdateNetworkStatistics()
{
static float s_lastUpdate = 0.f;
const float time = gEnv->pTimer->GetCurrTime(ITimer::ETIMER_UI);
if (time >= s_lastUpdate + (s_StatsIntervalMS * 0.001f))
{
FUNCTION_PROFILER(GetISystem(), PROFILE_NETWORK);
s_lastUpdate = time;
if (m_session)
{
Carrier* carrier = m_session->GetCarrier();
for (unsigned int i = 0; i < m_session->GetNumberOfMembers(); ++i)
{
GridMember* member = m_session->GetMemberByIndex(i);
if (member == m_session->GetMyMember())
{
continue;
}
const ConnectionID connId = member->GetConnectionId();
if (connId != InvalidConnectionID)
{
TrafficControl::Statistics stats;
carrier->QueryStatistics(connId, &stats);
CarrierStatistics& memberStats =
m_statisticsPerChannel[ GetChannelIdForSessionMember(member) ];
memberStats.m_rtt = stats.m_rtt;
memberStats.m_packetLossRate = stats.m_packetLoss;
memberStats.m_totalReceivedBytes = stats.m_dataReceived;
memberStats.m_totalSentBytes = stats.m_dataSend;
memberStats.m_packetsLost = stats.m_packetLost;
memberStats.m_packetsReceived = stats.m_packetReceived;
memberStats.m_packetsSent = stats.m_packetSend;
}
}
}
#if GRIDMATE_DEBUG_ENABLED
if (s_DumpStatsEnabled > 0)
{
DumpNetworkStatistics();
m_gameStatistics = GameStatistics();
}
#endif // GRIDMATE_DEBUG_ENABLED
}
}
//-----------------------------------------------------------------------------
void Network::ClearNetworkStatistics()
{
m_gameStatistics = GameStatistics();
m_statisticsPerChannel.clear();
}
//-----------------------------------------------------------------------------
GameStatistics& Network::GetGameStatistics()
{
return m_gameStatistics;
}
//-----------------------------------------------------------------------------
CarrierStatistics Network::GetCarrierStatistics()
{
if (!m_statisticsPerChannel.empty())
{
return m_statisticsPerChannel.begin()->second;
}
return CarrierStatistics();
}
//-----------------------------------------------------------------------------
void Network::BindNewEntitiesToNetwork()
{
m_newServerEntities.clear();
for (EntityReplicaMap::iterator iterNewProxy = m_newProxyEntities.begin(); iterNewProxy != m_newProxyEntities.end(); )
{
EntityReplicaPtr entityChunk = iterNewProxy->second;
entityChunk->HandleNewlyReceivedNow();
if ((entityChunk->GetFlags() & EntityReplica::kFlag_NewlyReceived) == 0)
{
iterNewProxy = m_newProxyEntities.erase(iterNewProxy);
}
else
{
++iterNewProxy;
}
}
}
//-----------------------------------------------------------------------------
void Network::GetBandwidthStatistics(SBandwidthStats* const pStats)
{
pStats->m_numChannels = m_statisticsPerChannel.size();
if (!m_statisticsPerChannel.empty())
{
const auto& carrierStats = m_statisticsPerChannel.begin()->second;
pStats->m_1secAvg.m_totalPacketsDropped = carrierStats.m_packetsLost;
pStats->m_1secAvg.m_totalPacketsRecvd = carrierStats.m_packetsReceived;
pStats->m_1secAvg.m_totalPacketsSent = carrierStats.m_packetsSent;
pStats->m_1secAvg.m_totalBandwidthRecvd = carrierStats.m_totalReceivedBytes;
pStats->m_1secAvg.m_totalBandwidthSent = carrierStats.m_totalSentBytes;
}
}
//-----------------------------------------------------------------------------
void Network::GetPerformanceStatistics([[maybe_unused]] SNetworkPerformance* pSizer)
{
// Network Cpu stats.
}
//-----------------------------------------------------------------------------
void Network::GetProfilingStatistics(SNetworkProfilingStats* const pStats)
{
pStats->m_maxBoundObjects = uint(~0);
pStats->m_numBoundObjects = m_activeEntityReplicaMap.size();
// pStats->m_ProfileInfoStats Part of NET_PROFILE macros
}
//! Called when need serializer for the legacy aspect
void Network::AcquireSerializer(WriteBuffer& wb, NetSerialize::AcquireSerializeCallback callback)
{
NetSerialize::EntityNetSerializerCollectState serializerImpl(wb);
CSimpleSerialize<NetSerialize::EntityNetSerializerCollectState> serializer(serializerImpl);
callback(&serializer);
}
//! Called when need deserializer for the legacy aspect
void Network::AcquireDeserializer(ReadBuffer& rb, NetSerialize::AcquireSerializeCallback callback)
{
NetSerialize::EntityNetSerializerDispatchState serializerImpl(rb);
CSimpleSerialize<NetSerialize::EntityNetSerializerDispatchState> serializer(serializerImpl);
callback(&serializer);
}
//-----------------------------------------------------------------------------
void Network::MarkAsConnectedServer()
{
CryLog("Marked as hosting server.");
gEnv->bServer = true;
gEnv->bMultiplayer = true;
}
//-----------------------------------------------------------------------------
void Network::MarkAsConnectedClient()
{
CryLog("Marked as connected client.");
gEnv->bServer = false;
gEnv->bMultiplayer = true;
}
//-----------------------------------------------------------------------------
void Network::MarkAsLocalOnly()
{
CryLog("Marked as local only.");
gEnv->bServer = true;
gEnv->bMultiplayer = false;
}
} // namespace GridMate
@@ -0,0 +1,252 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATE_HEADER
#define INCLUDE_NETWORKGRIDMATE_HEADER
#pragma once
#include "NetworkGridMateCommon.h"
#include "NetworkGridMateSessionEvents.h"
#include "NetworkGridMateSystemEvents.h"
#include "NetworkGridMateProfiling.h"
#include "Replicas/EntityReplicaSpawnParams.h"
namespace GridMate
{
class SecureSocketDriver;
/*!
* Implementation of INetwork interface for GridMate-backed network.
*/
class Network
: public INetwork
, public ILevelSystemListener
, private NetSerialize::ILegacySerializeProvider
{
public:
friend class SessionEvents;
friend class NetworkSystemEvents;
static Network& Get();
Network();
virtual ~Network();
public:
typedef AZStd::unordered_map<EntityId, AZStd::intrusive_ptr<EntityReplica>> EntityReplicaMap;
public: // INetwork implementation.
IGridMate* GetGridMate() override { return m_gridMate; }
//! Helper for grabbing the channel id corresponding to a particular session member.
ChannelId GetChannelIdForSessionMember(GridMate::GridMember* member) const override;
//! Main module initialization, called by engine.
bool Init(int ncpu);
//! Legacy cleanup functions invoked by the engine.
void Release() override;
//! Main update routine invoked by the engine.
void SyncWithGame(ENetworkGameSync syncType) override;
//! Marks an aspect dirty. This will trigger a NetSerialize invocation, after which
//! we'll determine if a re-send is necessary.
void ChangedAspects(EntityId id, NetworkAspectType aspectBits) override;
//! Retrieve the local user's channel Id.
ChannelId GetLocalChannelId() const override;
//! Retrieve the channel Id of the server we're connected to.
//! If we are the server, we simply return our own channel Id.
ChannelId GetServerChannelId() const override;
//! Convert a local entity Id to the server side Id, since they can vary across
//! systems.
//! Before dispatching messages or events to the server or other clients, local
//! Ids should be converted to server Ids so they can be properly deciphered.
EntityId LocalEntityIdToServerEntityId(EntityId localId) const override;
//! Convert a server entity Id to a local entity Id so we can dispatch messages
//! to local objects.
EntityId ServerEntityIdToLocalEntityId(EntityId serverId, bool allowForcedEstablishment = false) const override;
//! Gets the synchronized network time as milliseconds since session creation time.
virtual CTimeValue GetSessionTime() override;
////////////////////////////////////////////////////////////////
//! Compatibility "Shim" interfaces.
//! Invoke a GameCore actor RMI through GridMate RPCs.
void InvokeActorRMI(EntityId entityId, uint8 actorExtensionId, ChannelId targetChannelFilter, IActorRMIRep& rep) override;
//! Invoke a lua script RMI through GridMate RPCs.
void InvokeScriptRMI(ISerializable* serializable, bool isServerRMI, ChannelId toChannelId = kInvalidChannelId, ChannelId avoidChannelId = kInvalidChannelId) override;
//! Registers an actor RMI rep; required for dispatching to the game upon receipt.
void RegisterActorRMI(IActorRMIRep* rep) override;
void UnregisterActorRMI(IActorRMIRep* rep) override;
//! Sets mask describing which aspects are globally delegatable.
void SetDelegatableAspectMask(NetworkAspectType aspectBits) override;
//! Sets mask on a given obejct describing which aspect that object has delegated to the controlling client.
void SetObjectDelegatedAspectMask(EntityId entityId, NetworkAspectType aspects, bool set) override;
//! Request authority for entityId be delegated to client at clientChannelId.
void DelegateAuthorityToClient(EntityId entityId, ChannelId clientChannelId) override;
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//! Currently unused INetwork APIs.
void GetMemoryStatistics(ICrySizer* pSizer) override { (void)pSizer; };
const char* GetHostName() override { return ""; }
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
//! Other INetwork API functions still applicable to GridMate.
void GetBandwidthStatistics(SBandwidthStats* const pStats) override;
void GetPerformanceStatistics(SNetworkPerformance* pSizer) override;
void GetProfilingStatistics(SNetworkProfilingStats* const pStats) override;
////////////////////////////////////////////////////////////////
bool IsInMinimalUpdate() const;
public: // GridMate-integration-specific APIs
//! Returns true if it's safe to spawn replicated entities at this time.
//! An example of a time during which this is not safe would be during a local level load.
bool AllowEntityCreation() const;
//! Returns the active client or server session.
GridSession* GetCurrentSession() { return m_session; }
//! Locate the replica for a service-side entity Id.
EntityReplica* FindEntityReplica(EntityId id) const;
//! Retrieves the global replica registration, mapped by server-side entity Id.
EntityReplicaMap& GetEntityReplicaMap() { return m_activeEntityReplicaMap; }
//! Retrieves new proxy registrations, mapped by server-side entity Id.
EntityReplicaMap& GetNewProxyEntityMap() { return m_newProxyEntities; }
////////////////////////////////////////////////////////////////
//! ILevelSystemListener callbacks.
void OnLoadingComplete(ILevel* level) override;
void OnUnloadComplete(ILevel* level) override;
////////////////////////////////////////////////////////////////
GameStatistics& GetGameStatistics();
CarrierStatistics GetCarrierStatistics();
//! Pumps GridMate instance.
void UpdateGridMate(ENetworkGameSync syncType);
//! Instantiates GridMate instance.
//! Create replicas for newly-spawned entities (server only).
void BindNewEntitiesToNetwork();
//! Execute deferred tasks.
void FlushPostFrameTasks();
//! Bandwidth statistics and profiling.
void UpdateNetworkStatistics();
void ClearNetworkStatistics();
void DumpNetworkStatistics();
void DebugDraw();
void SetLegacySerializeProvider(NetSerialize::ILegacySerializeProvider* provider) { m_legacySerializeProvider = provider; }
NetSerialize::ILegacySerializeProvider* GetLegacySerializeProvider() { return m_legacySerializeProvider; }
private:
// ILegacySerializeProvider implementation
void AcquireSerializer(WriteBuffer& wb, NetSerialize::AcquireSerializeCallback callback) override;
void AcquireDeserializer(ReadBuffer& rb, NetSerialize::AcquireSerializeCallback callback) override;
//! Instantiates GridMate
void StartGridMate();
//! Shuts down GridMate instance.
void ShutdownGridMate();
//! Sets globals and context flags appropriate for an active server hosting a session.
void MarkAsConnectedServer();
//! Sets globals and context flags for a client connected to a hosted session.
void MarkAsConnectedClient();
//! Sets globals and context flags for a single-player instance.
void MarkAsLocalOnly();
typedef std::map<ChannelId, CarrierStatistics> CarrierStatisticsMap;
typedef std__hash_map<EntityId, EntitySpawnParamsStorage> NewEntitiesMap;
//! Connection statistics for each outgoing channel.
CarrierStatisticsMap m_statisticsPerChannel;
//! Statistics for incoming/outgoing RMIs and aspects (global and per-entity).
GameStatistics m_gameStatistics;
//! The local "channel id", required by CryEngine to know which
//! client owns which actor.
ChannelId m_localChannelId;
//! Pointer to GridMate instance.
GridMate::IGridMate* m_gridMate;
//! Pointer to MP session
GridMate::GridSession* m_session;
//! Maintain a map of entity replicas per their server-side entity Id.
EntityReplicaMap m_activeEntityReplicaMap;
EntityReplicaMap m_newProxyEntities;
//! Stores a map of entities spawned this frame, so we can instantiate replicas
//! once it's safe to do so.
NewEntitiesMap m_newServerEntities;
//! EBus handlers for GridMate sessions.
SessionEvents m_sessionEvents;
//! EBus handlers for various system events.
NetworkSystemEvents m_systemEvents;
//! Used so areas of the network can be aware that we're loading a level.
enum LevelLoadState
{
LevelLoadState_None,
LevelLoadState_Loading,
LevelLoadState_Loaded
};
AZStd::atomic<LevelLoadState> m_levelLoadState;
//! Set if we're currently in a GridMate update.
AZStd::mutex m_mutexUpdatingGridMate;
//! Inherited from CryNetwork, this is sent by the NetworkStallTicker mechanism
//! to tell us it's unsafe to process minimal network updates (loading updates).
AZStd::atomic<bool> m_allowMinimalUpdate;
typedef AZStd::function<void()> Task;
std::vector<Task> m_postFrameTasks;
NetSerialize::ILegacySerializeProvider* m_legacySerializeProvider;
static Network* s_instance;
public:
// Profiler settings.
static int s_StatsIntervalMS;
static int s_DumpStatsEnabled;
static FILE* s_DumpStatsFile;
};
} // namespace GridMate
/// External systems expect the type 'CNetwork' in the global namespace.
typedef GridMate::Network CNetwork;
#endif // INCLUDE_NETWORKGRIDMATE_HEADER
@@ -0,0 +1,217 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATECOMMON_HEADER
#define INCLUDE_NETWORKGRIDMATECOMMON_HEADER
#pragma once
#include <I3DEngine.h>
#include <physinterface.h>
#include <ILevelSystem.h>
#include <SimpleSerialize.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Session/Session.h>
struct ILevelSystem;
namespace GridMate
{
class EntityReplica;
class IGridMate;
class GridSession;
class GridMember;
inline ILevelSystem* GetLevelSystem()
{
if (gEnv->pSystem)
{
return gEnv->pSystem->GetILevelSystem();
}
return nullptr;
}
/*!
* Buffer types for serialization.
*/
typedef GridMate::ReadBuffer ReadBufferType;
typedef GridMate::WriteBufferStaticInPlace WriteBufferType;
/*!
* Generic marshalable byte buffer.
* This is currently used to own memory used by RMIs and NetSerialize. To reduce allocations,
* the buffer attempts to use internal memory, allocating from the heap only as necessary.
* Many of the classes owning this structure allocate often, such as each RMI invocation.
* If this proves a problem, we can pool the invocation wrappers.
*/
template<size_t BaseSize, typename BufferSizeType = uint16>
class FlexibleBuffer
{
public:
typedef BufferSizeType SizeType;
FlexibleBuffer(const char* sourceBuffer = nullptr, SizeType sourceBufferSize = 0)
: m_buffer (nullptr)
, m_size (0)
{
Set(sourceBuffer, sourceBufferSize);
}
~FlexibleBuffer()
{
Free();
}
void Set(const char* sourceBuffer = nullptr, SizeType sourceBufferSize = 0)
{
Free();
if (0 != sourceBufferSize)
{
if (sourceBufferSize > BaseSize)
{
m_buffer = new char[ sourceBufferSize ];
}
else
{
m_buffer = m_baseBuffer;
}
if (sourceBuffer)
{
memcpy(m_buffer, sourceBuffer, sourceBufferSize);
}
m_size = sourceBufferSize;
}
else
{
m_buffer = nullptr;
m_size = 0;
}
}
void Free()
{
if (m_buffer != m_baseBuffer)
{
delete[] m_buffer;
}
m_buffer = nullptr;
m_size = 0;
}
ReadBufferType GetReadBuffer() const
{
return ReadBufferType(EndianType::BigEndian, m_buffer, m_size);
}
WriteBufferType GetWriteBuffer()
{
return WriteBufferType(m_buffer, m_size);
}
char* GetData() const { return m_buffer; }
SizeType GetSize() const { return m_size; }
class Marshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const FlexibleBuffer<BaseSize, BufferSizeType>& b)
{
wb.Write(b.m_size);
if (0 != b.m_size)
{
wb.WriteRaw(b.m_buffer, b.m_size);
}
}
void Unmarshal(FlexibleBuffer<BaseSize, BufferSizeType>& b, GridMate::ReadBuffer& rb)
{
b.Free();
rb.Read(b.m_size);
if (0 != b.m_size)
{
b.m_buffer = new char[ b.m_size ];
rb.ReadRaw(b.m_buffer, b.m_size);
}
}
};
char* m_buffer;
SizeType m_size;
char m_baseBuffer[BaseSize];
};
/*!
* Smart-ptr managed version of FlexibleBuffer.
*/
template<size_t BaseSize, typename BufferSizeType = uint16>
class ManagedFlexibleBuffer
: public _i_multithread_reference_target_t
, public FlexibleBuffer<BaseSize, BufferSizeType>
{
public:
typedef _smart_ptr<ManagedFlexibleBuffer<BaseSize> > Ptr;
typedef FlexibleBuffer<BaseSize> Parent;
ManagedFlexibleBuffer()
: Parent() {}
ManagedFlexibleBuffer(BufferSizeType sourceSize)
: Parent(nullptr, sourceSize) {}
ManagedFlexibleBuffer(const char* sourceBuffer, BufferSizeType sourceSize)
: Parent(sourceBuffer, sourceSize) {}
/// Marshaler to handle sending buffers via smart pointer.
class PtrMarshaler
{
public:
typedef ManagedFlexibleBuffer<BaseSize, BufferSizeType> BufferType;
void Marshal(GridMate::WriteBuffer& wb, const typename BufferType::Ptr& b)
{
const typename BufferType::SizeType size = b.get() ? b->m_size : 0;
wb.Write(size);
if (0 != size)
{
wb.WriteRaw(b->m_buffer, size);
}
}
void Unmarshal(typename BufferType::Ptr& b, GridMate::ReadBuffer& rb)
{
b = new BufferType();
rb.Read(b->m_size);
if (0 != b->m_size)
{
b->m_buffer = new char[ b->m_size ];
rb.ReadRaw(b->m_buffer, b->m_size);
}
}
};
};
} // namespace GridMate
#endif // INCLUDE_NETWORKGRIDMATECOMMON_HEADER
@@ -0,0 +1,49 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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.
*
*/
#ifndef CRYNETWORK_NETWORKGRIDMATEENTITYEVENTBUS
#define CRYNETWORK_NETWORKGRIDMATEENTITYEVENTBUS
#include <AzCore/EBus/EBus.h>
#include <GridMate/Replica/Replica.h>
namespace GridMate
{
/*
* This is a helper bus to bind/unbind legacy cryengine's entities with gridmate's replicas.
* Every networked cry-entity has an EntityReplica associated with it that provides legacy aspects and RMI support
* This bus is to help developers using crynetwork shim to add their custom chunks on the entity replica
* and to bind custom game obj extensions with those chunks.
*/
class NetworkGridMateEntityEvents : public AZ::EBusTraits
{
public:
// Called when new master entity replica is created for a given entityId
virtual void OnEntityBoundToNetwork(ReplicaPtr replica) { (void)replica; }
// Called when new proxy entity replica is received from the network for a given entityId
virtual void OnEntityBoundFromNetwork(ReplicaPtr replica) { (void)replica; }
// Called when entity replica is deactivated
virtual void OnEntityUnboundFromNetwork(ReplicaPtr replica) { (void)replica; }
// EBus settings
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef EntityId BusIdType; // This is bus callbacks per each entityId
};
// Actual bus
typedef AZ::EBus<NetworkGridMateEntityEvents> NetworkGridMateEntityEventBus;
}
#endif
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATEPROFILING_HEADER
#define INCLUDE_NETWORKGRIDMATEPROFILING_HEADER
#pragma once
#include "NetworkGridMateCommon.h"
namespace GridMate
{
struct CarrierStatistics
{
CarrierStatistics()
: m_rtt(0.f)
, m_packetLossRate(0.f)
, m_totalReceivedBytes(0)
, m_totalSentBytes(0)
, m_packetsLost(0)
, m_packetsReceived(0)
, m_packetsSent(0)
{
}
float m_rtt;
float m_packetLossRate;
uint32 m_totalReceivedBytes;
uint32 m_totalSentBytes;
uint32 m_packetsLost;
uint32 m_packetsReceived;
uint32 m_packetsSent;
};
struct GameStatistics
{
struct RMIStatistics
{
uint32 m_sendCount;
uint32 m_receiveCount;
uint32 m_totalSentBytes;
uint32 m_totalReceivedBytes;
RMIStatistics()
: m_sendCount(0)
, m_receiveCount(0)
, m_totalSentBytes(0)
, m_totalReceivedBytes(0)
{
}
};
struct AspectStatistics
{
uint32 m_sendCount;
uint32 m_receiveCount;
uint32 m_totalSentBytes;
uint32 m_totalReceivedBytes;
AspectStatistics()
: m_sendCount(0)
, m_receiveCount(0)
, m_totalSentBytes(0)
, m_totalReceivedBytes(0)
{
}
};
struct EntityStatistics
{
typedef std__hash_map<uint32, RMIStatistics> RMIInstanceMap;
RMIInstanceMap m_rmiActor;
RMIInstanceMap m_rmiLegacy;
AspectStatistics m_aspects[ NUM_ASPECTS ];
uint32 m_totalCostEstimate;
EntityStatistics()
: m_totalCostEstimate(0)
{
}
};
GameStatistics()
: m_aspectsSent(0)
, m_aspectsReceived(0)
, m_aspectSentBytes(0)
, m_aspectReceivedBytes(0)
{
}
uint32 m_aspectsSent;
uint32 m_aspectsReceived;
uint32 m_aspectSentBytes;
uint32 m_aspectReceivedBytes;
RMIStatistics m_rmiGlobalActor;
RMIStatistics m_rmiGlobalLegacy;
RMIStatistics m_rmiGlobalScript;
typedef std__hash_map<EntityId, EntityStatistics> EntityStatisticsMap;
EntityStatisticsMap m_entities;
};
} // namespace GridMate
#endif // INCLUDE_NETWORKGRIDMATEPROFILING_HEADER
@@ -0,0 +1,91 @@
/*
* 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 "CryNetwork_precompiled.h"
#include <I3DEngine.h>
#include "NetworkGridMate.h"
#include "NetworkGridmateDebug.h"
#include "NetworkGridMateSessionEvents.h"
#include "Replicas/EntityReplica.h"
#include <GridMate/Replica/ReplicaFunctions.h>
namespace GridMate
{
//-----------------------------------------------------------------------------
void SessionEvents::Connect(IGridMate* gridMate)
{
GridMate::SessionEventBus::Handler::BusConnect(gridMate);
AzFramework::NetBindingSystemEventsBus::Handler::BusConnect();
}
//-----------------------------------------------------------------------------
void SessionEvents::Disconnect()
{
AzFramework::NetBindingSystemEventsBus::Handler::BusDisconnect();
GridMate::SessionEventBus::Handler::BusDisconnect();
}
//-----------------------------------------------------------------------------
void SessionEvents::OnNetworkSessionCreated(GridMate::GridSession* session)
{
GM_DEBUG_TRACE("Session %s has been created.", session->GetId().c_str());
auto& net = Network::Get();
net.ClearNetworkStatistics();
net.m_localChannelId = net.GetChannelIdForSessionMember(session->GetMyMember());
net.m_session = session;
if (session->IsHost())
{
net.MarkAsConnectedServer();
}
else
{
net.MarkAsConnectedClient();
}
}
//-----------------------------------------------------------------------------
void SessionEvents::OnNetworkSessionDeactivated([[maybe_unused]] GridMate::GridSession* session)
{
GM_DEBUG_TRACE("Session %s has been deleted.", session->GetId().c_str());
auto& net = Network::Get();
net.MarkAsLocalOnly();
net.ClearNetworkStatistics();
net.m_localChannelId = kOfflineChannelId;
net.m_activeEntityReplicaMap.clear();
net.m_newProxyEntities.clear();
net.m_session = nullptr;
}
//-----------------------------------------------------------------------------
void SessionEvents::OnMemberLeaving(GridMate::GridSession* session, GridMate::GridMember* member)
{
auto& net = Network::Get();
if (session == net.m_session)
{
GM_ASSERT_TRACE(member, "NetworkGridMate::OnMemberLeaving(), departing member is null!");
const ChannelId departedChannelId = net.GetChannelIdForSessionMember(member);
GM_DEBUG_TRACE("Member for channel id %u has left the session.", departedChannelId);
if (gEnv->bServer && departedChannelId != kInvalidChannelId)
{
net.m_statisticsPerChannel.erase(departedChannelId);
}
}
}
} // namespace GridMate
@@ -0,0 +1,45 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATESESSIONEVENTS_HEADER
#define INCLUDE_NETWORKGRIDMATESESSIONEVENTS_HEADER
#pragma once
#include <GridMate/Session/Session.h> // Base class
#include <AzFramework/Network/NetBindingSystemBus.h>
namespace GridMate
{
/*!
* Acts as a sink for the session EBus.
*/
class SessionEvents
: public GridMate::SessionEventBus::Handler
, public AzFramework::NetBindingSystemEventsBus::Handler
{
public:
void Connect(IGridMate* gridMate);
void Disconnect();
bool IsConnected() const { return GridMate::SessionEventBus::Handler::BusIsConnected() && AzFramework::NetBindingSystemEventsBus::Handler::BusIsConnected(); }
void OnNetworkSessionCreated(GridMate::GridSession* session) override;
void OnNetworkSessionDeactivated(GridMate::GridSession* session) override;
///////////////////////////////////////////////////
// SessionEventBus
void OnMemberLeaving(GridSession* session, GridMember* member) override;
///////////////////////////////////////////////////
};
} // namespace GridMate
#endif // INCLUDE_NETWORKGRIDMATESESSIONEVENTS_HEADER
@@ -0,0 +1,148 @@
/*
* 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 "CryNetwork_precompiled.h"
#include <I3DEngine.h>
#include "NetworkGridMate.h"
#include "NetworkGridMateSystemEvents.h"
namespace GridMate
{
//-----------------------------------------------------------------------------
NetworkSystemEvents::NetworkSystemEvents()
: m_connected(false)
{
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::Connect()
{
GridMate::NetworkSystemEventBus::Handler::BusConnect();
m_connected = true;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::Disconnect()
{
GridMate::NetworkSystemEventBus::Handler::BusDisconnect();
m_connected = false;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::ActorRMISent(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalActor;
global.m_sendCount++;
global.m_totalSentBytes += paramsSize;
auto& entity = stats.m_entities[ entityId ];
auto& rmiEntry = entity.m_rmiActor[ rep.GetUniqueId() ];
rmiEntry.m_sendCount++;
rmiEntry.m_totalSentBytes += paramsSize;
entity.m_totalCostEstimate += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::ActorRMIReceived(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalActor;
global.m_receiveCount++;
global.m_totalReceivedBytes += paramsSize;
auto& entity = stats.m_entities[ entityId ];
auto& rmiEntry = entity.m_rmiActor[ rep.GetUniqueId() ];
rmiEntry.m_receiveCount++;
rmiEntry.m_totalReceivedBytes += paramsSize;
entity.m_totalCostEstimate += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::LegacyRMISent(EntityId entityId, const IRMIRep& rep, uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalLegacy;
global.m_sendCount++;
global.m_totalSentBytes += paramsSize;
auto& entity = stats.m_entities[ entityId ];
auto& rmiEntry = entity.m_rmiLegacy[ rep.GetUniqueId() ];
rmiEntry.m_sendCount++;
rmiEntry.m_totalSentBytes += paramsSize;
entity.m_totalCostEstimate += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::LegacyRMIReceived(EntityId entityId, const IRMIRep& rep, uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalLegacy;
global.m_receiveCount++;
global.m_totalReceivedBytes += paramsSize;
auto& entity = stats.m_entities[ entityId ];
auto& rmiEntry = entity.m_rmiLegacy[ rep.GetUniqueId() ];
rmiEntry.m_receiveCount++;
rmiEntry.m_totalReceivedBytes += paramsSize;
entity.m_totalCostEstimate += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::ScriptRMISent(uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalScript;
global.m_sendCount++;
global.m_totalSentBytes += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::ScriptRMIReceived(uint32 paramsSize)
{
auto& stats = Network::Get().GetGameStatistics();
auto& global = stats.m_rmiGlobalScript;
global.m_receiveCount++;
global.m_totalReceivedBytes += paramsSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::AspectSent(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize)
{
auto& stats = Network::Get().GetGameStatistics();
stats.m_aspectsSent++;
stats.m_aspectSentBytes += payloadSize;
auto& entity = stats.m_entities[ entityId ];
auto& aspectEntry = entity.m_aspects[ BitIndex(aspectBit) ];
aspectEntry.m_sendCount++;
aspectEntry.m_totalSentBytes += payloadSize;
entity.m_totalCostEstimate += payloadSize;
}
//-----------------------------------------------------------------------------
void NetworkSystemEvents::AspectReceived(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize)
{
auto& stats = Network::Get().GetGameStatistics();
stats.m_aspectsReceived++;
stats.m_aspectReceivedBytes += payloadSize;
auto& entity = stats.m_entities[ entityId ];
auto& aspectEntry = entity.m_aspects[ BitIndex(aspectBit) ];
aspectEntry.m_receiveCount++;
aspectEntry.m_totalReceivedBytes += payloadSize;
entity.m_totalCostEstimate += payloadSize;
}
} // namespace GridMate
@@ -0,0 +1,79 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATESYSTEMEVENTS_HEADER
#define INCLUDE_NETWORKGRIDMATESYSTEMEVENTS_HEADER
#pragma once
#include "Compatibility/GridMateRMI.h"
class IActorRMIRep;
namespace GridMate
{
class NetworkSystemCallbacks
: public AZ::EBusTraits
{
public:
virtual ~NetworkSystemCallbacks() {}
virtual void ActorRMISent(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize) { (void)entityId; (void)rep; (void)paramsSize; }
virtual void ActorRMIReceived(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize) { (void)entityId; (void)rep; (void)paramsSize; }
virtual void LegacyRMISent(EntityId entityId, const IRMIRep& rep, uint32 paramsSize) { (void)entityId; (void)rep; (void)paramsSize; }
virtual void LegacyRMIReceived(EntityId entityId, const IRMIRep& rep, uint32 paramsSize) { (void)entityId; (void)rep; (void)paramsSize; }
virtual void ScriptRMISent(uint32 paramsSize) { (void)paramsSize; }
virtual void ScriptRMIReceived(uint32 paramsSize) { (void)paramsSize; }
virtual void AspectSent(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize) { (void)entityId; (void)aspectBit; (void)payloadSize; }
virtual void AspectReceived(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize) { (void)entityId; (void)aspectBit; (void)payloadSize; }
};
typedef AZ::EBus<NetworkSystemCallbacks> NetworkSystemEventBus;
/*!
* Acts as a sink for the session EBus.
*/
class NetworkSystemEvents
: public GridMate::NetworkSystemEventBus::Handler
{
public:
NetworkSystemEvents();
void Connect();
void Disconnect();
bool IsConnected() const { return m_connected; }
public:
void ActorRMISent(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize) override;
void ActorRMIReceived(EntityId entityId, const IActorRMIRep& rep, uint32 paramsSize) override;
void LegacyRMISent(EntityId entityId, const IRMIRep& rep, uint32 paramsSize) override;
void LegacyRMIReceived(EntityId entityId, const IRMIRep& rep, uint32 paramsSize) override;
void ScriptRMISent(uint32 paramsSize) override;
void ScriptRMIReceived(uint32 paramsSize) override;
void AspectSent(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize) override;
void AspectReceived(EntityId entityId, ASPECT_TYPE aspectBit, uint32 payloadSize) override;
private:
bool m_connected;
};
} // namespace GridMate
#endif // INCLUDE_NETWORKGRIDMATESYSTEMEVENTS_HEADER
@@ -0,0 +1,772 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "NetworkGridMate.h"
#include "NetworkGridmateDebug.h"
#include "Replicas/EntityReplica.h"
#include <AzCore/PlatformIncl.h>
#include <CryPath.h>
#include <ILevelSystem.h>
#include <stdio.h>
#include <IRenderer.h>
#include <ITextModeConsole.h>
namespace
{
/**
* Helper for debug text printing, with colorization and formatting options.
*
* Example:
*
* DebugTextHelper text( gEnv->pRenderer, 100, 100 ); // At screen pos (100,100)
* text.SetAutoNewlined( true );
* text.SetMonospaced( true );
* text.SetColor( Col_Yellow );
* text.SetSize( 2.f );
* text.AddText( "This is a yellow title" );
* text.SetSize( 1.5f );
* text.AddText( "This is some detail." );
*/
class DebugTextHelper
{
public:
static const int kTextModeRowSize = 10;
static const int kTextModeColSize = 10;
static const int kTextModeColCount = 128; // WINDOWS_CONSOLE_WIDTH
static const int kTextModeRowCount = 48; // WINDOWS_CONSOLE_HEIGHT - 2
DebugTextHelper(IRenderer* r,
float x = 0.f, float y = 0.f,
float fontSize = 1.5f,
ColorF defaultColor = Col_White)
: m_pos(x, y, 0.f)
, m_defaultColor(defaultColor)
, m_fontSize(fontSize)
, m_flags(kFlag_AutoNewline | kFlag_Monospaced | kFlag_TextModeConsole)
, m_renderer(r)
{
}
~DebugTextHelper() {};
enum
{
kMaxLabelSize = 512
};
void AddText(const char* format, ...)
{
va_list argList;
char buffer[kMaxLabelSize];
va_start(argList, format);
const int len = vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, format, argList);
buffer[sizeof(buffer) - 1] = '\0';
va_end(argList);
AddText(m_defaultColor, buffer);
}
void AddText(ColorF color, const char* format, ...)
{
va_list argList;
char buffer[kMaxLabelSize];
va_start(argList, format);
const int len = vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, format, argList);
buffer[sizeof(buffer) - 1] = '\0';
va_end(argList);
const uint32 drawFlags = IsMonospaced() ?
(eDrawText_2D | eDrawText_800x600 | eDrawText_FixedSize | eDrawText_Monospace) :
(eDrawText_2D | eDrawText_800x600 | eDrawText_FixedSize);
m_renderer->Draw2dLabelWithFlags(m_pos.x, m_pos.y, m_fontSize, color, drawFlags, buffer);
if (m_flags & kFlag_TextModeConsole)
{
if (ITextModeConsole* textConsole = gEnv->pSystem->GetITextModeConsole())
{
const int posX = static_cast<int>(m_pos.x) / kTextModeColSize;
const int posY = static_cast<int>(m_pos.y) / kTextModeRowSize;
if (posX < kTextModeColCount && posY < kTextModeRowCount)
{
textConsole->PutText(posX, posY, buffer);
}
}
}
if (m_flags & kFlag_AutoNewline)
{
Newline();
}
}
void ClearLines(float startY, float height)
{
const int startRow = std::min(static_cast<int>(startY) / kTextModeRowSize, int(kTextModeRowCount));
const int numRows = std::min(static_cast<int>(height) / kTextModeRowSize, int(kTextModeRowCount));
// Text mode console requires wiping the frame buffer to maintain a reasonable
// quality display. The frame profiler doesn't do this, and as a result is
// sometimes completely unreadable. It's very slow to set characters in the
// console, so for now, we're wiping in scanline fashion.
if (ITextModeConsole* textConsole = gEnv->pSystem->GetITextModeConsole())
{
static char s_emptyLine[kTextModeColCount + 1] = { '\0' };
if (0 == s_emptyLine[0])
{
for (int col = 0; col < kTextModeColCount; ++col)
{
s_emptyLine[col] = ' ';
}
s_emptyLine[kTextModeColCount] = 0;
}
for (int row = startRow; row < numRows; ++row)
{
textConsole->PutText(0, row, s_emptyLine);
}
}
}
inline ColorF GetDefaultColor() const { return m_defaultColor; }
inline void SetDefaultColor(ColorF color) { m_defaultColor = color; }
inline Vec3 GetPosition() const { return m_pos; }
inline void SetPosition(const Vec3& pos) { m_pos = pos; }
inline float GetFontSize() const { return m_fontSize; }
inline void SetFontSize(float fontSize) { m_fontSize = fontSize; }
inline void Newline() { m_pos.y += m_fontSize * 10.f; }
inline bool IsAutoNewlined() const { return !!(m_flags & kFlag_AutoNewline); }
inline bool IsMonospaced() const { return !!(m_flags & kFlag_Monospaced); }
inline void SetAutoNewlined(bool set)
{
if (set)
{
m_flags |= kFlag_AutoNewline;
}
else
{
m_flags &= ~kFlag_AutoNewline;
}
}
inline void SetMonospaced(bool set)
{
if (set)
{
m_flags |= kFlag_Monospaced;
}
else
{
m_flags &= ~kFlag_Monospaced;
}
}
inline void SetTextModeConsole(bool set)
{
if (set)
{
m_flags |= kFlag_TextModeConsole;
}
else
{
m_flags &= ~kFlag_TextModeConsole;
}
}
private:
Vec3 m_pos;
ColorF m_defaultColor;
float m_fontSize;
enum Flags
{
kFlag_AutoNewline = (1 << 0), // Auto advance to next line after AddText().
kFlag_Monospaced = (1 << 1), // Print out using monospaced font.
kFlag_TextModeConsole = (1 << 2), // Write to "text mode console" (dedicated server).
};
uint32 m_flags;
IRenderer* m_renderer;
};
}
namespace GridMate
{
namespace Debug
{
const char* const GetAspectNameByBitIndex(size_t aspectIndex)
{
static const char* AspectNames[] =
{
#define ADD_ASPECT(x, y) #x,
#include "Compatibility/GridMateNetSerializeAspects.inl"
#undef ADD_ASPECT
};
STATIC_ASSERT(AZ_ARRAY_SIZE(AspectNames) <= NetSerialize::kNumAspectSlots,
"Too many Engine aspects for the replica.");
if (aspectIndex >= AZ_ARRAY_SIZE(AspectNames))
{
return "<invalid aspect index>";
}
return AspectNames[ aspectIndex ];
}
#if GRIDMATE_DEBUG_ENABLED
int s_DebugDraw = 0;
int s_TraceLevel = 0;
int s_EnableAsserts = 0;
struct TrackedDebugMsg
{
typedef CryFixedStringT<256> StringStorage;
TrackedDebugMsg(DebugMessageType type, const char* msg)
: m_type(type)
, m_string(msg)
{
#ifdef WIN32
time(&m_time);
#endif // WIN32
}
#ifdef WIN32
time_t m_time;
#endif // WIN32
DebugMessageType m_type;
StringStorage m_string;
};
AZStd::vector<TrackedDebugMsg, AZ::StdLegacyAllocator> s_trackedMessages;
CryCriticalSection s_debugLock;
void TrackMessage(DebugMessageType type, const char* msg)
{
enum
{
kMaxTrackedMessages = 20
};
while (s_trackedMessages.size() >= kMaxTrackedMessages)
{
s_trackedMessages.erase(s_trackedMessages.begin());
}
s_trackedMessages.push_back(TrackedDebugMsg(type, msg));
}
void DebugTrace(bool isAssertFailure, const char* format, ...)
{
enum
{
kMaxTraceMessageSize = 512
};
CryAutoLock<CryCriticalSection> lock(s_debugLock);
va_list argList;
char buffer[ kMaxTraceMessageSize ];
va_start(argList, format);
const int len = vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, format, argList);
buffer[ sizeof(buffer) - 1 ] = '\0';
va_end(argList);
if (isAssertFailure)
{
CryWarning(VALIDATOR_MODULE_NETWORK, VALIDATOR_ERROR, "<GridMate Assert> %s", buffer);
TrackMessage(DebugMessageType::kAssert, buffer);
if (s_EnableAsserts)
{
CryDebugBreak();
}
}
else
{
CryLog("<GridMate Trace> %s", buffer);
TrackMessage(DebugMessageType::kTrace, buffer);
}
}
//-----------------------------------------------------------------------------
static void CmdSetDebugDraw(IConsoleCmdArgs* args)
{
if (args->GetArgCount() > 1)
{
using namespace CryStringUtils;
char* items = const_cast<char*>(args->GetArg(1));
int value = 0;
const char* delims = "+";
char* nextToken = nullptr;
char* token = azstrtok(items, 0, delims, &nextToken);
while (token)
{
if (nullptr != stristr(token, "basic"))
{
value |= Debug::Basic;
}
if (nullptr != stristr(token, "trace"))
{
value |= Debug::Trace;
}
if (nullptr != stristr(token, "stat"))
{
value |= Debug::Stats;
}
if (nullptr != stristr(token, "rep"))
{
value |= Debug::Replicas;
}
if (nullptr != stristr(token, "act"))
{
value |= Debug::Actors;
}
if (nullptr != stristr(token, "detail"))
{
value |= Debug::EntityDetail;
}
if (nullptr != stristr(token, "full"))
{
value = Debug::Full;
break;
}
token = azstrtok(nullptr, 0, delims, &nextToken);
}
Debug::s_DebugDraw = value;
}
else
{
Debug::s_DebugDraw = Debug::Full;
}
}
//-----------------------------------------------------------------------------
static void OnDumpStatsChanged(ICVar* /*cvar*/)
{
if (Network::s_DumpStatsFile)
{
fclose(Network::s_DumpStatsFile);
Network::s_DumpStatsFile = nullptr;
}
ICVar* cvarFilename = gEnv->pConsole->GetCVar("gm_dumpstats_file");
if (Network::s_DumpStatsEnabled > 0 &&
cvarFilename && cvarFilename->GetString() && cvarFilename->GetString()[0])
{
const CryStringT<char> logFile = PathUtil::Make(
"@log@",
PathUtil::GetFile(cvarFilename->GetString()));
char resolvedPath[MAX_PATH] = { 0 };
gEnv->pFileIO->ResolvePath(logFile.c_str(), resolvedPath, MAX_PATH);
Network::s_DumpStatsFile = nullptr;
azfopen(&Network::s_DumpStatsFile, resolvedPath, "wt");
}
}
void RegisterCVars()
{
REGISTER_CVAR2("gm_debugdraw", &s_DebugDraw, s_DebugDraw, VF_DEV_ONLY, "GridMate debugging visualization level.");
REGISTER_CVAR2("gm_tracelevel", &s_TraceLevel, s_TraceLevel, VF_DEV_ONLY, "GridMate debugging trace verbosity level.");
REGISTER_CVAR2("gm_asserts", &s_EnableAsserts, s_EnableAsserts, VF_DEV_ONLY, "GridMate asserts.");
REGISTER_COMMAND("gm_setdebugdraw", CmdSetDebugDraw, VF_DEV_ONLY,
"Helper for setting up debug draw level: e.g. gm_setdebugdraw Basic+Stats+Trace."
"Options are Basic, Trace, Stats, Replicas, and Actors.");
// Profiling commands.
REGISTER_CVAR2_CB("gm_dumpstats", &Network::s_DumpStatsEnabled, Network::s_DumpStatsEnabled, VF_DEV_ONLY,
"Enable dumping of net profiling stats to file.", OnDumpStatsChanged);
REGISTER_STRING_CB("gm_dumpstats_file", "net_profile.log", VF_DEV_ONLY,
"Target file for net profiling stats.", OnDumpStatsChanged);
REGISTER_CVAR2("gm_stats_interval_msec", &Network::s_StatsIntervalMS, Network::s_StatsIntervalMS, VF_DEV_ONLY,
"Net profiling statistics will be gathered on this interval (in milliseconds). "
"If stats are being dumped to file, it will also occur on this interval.");
}
void UnregisterCVars()
{
UNREGISTER_CVAR("gm_stats_interval_msec");
UNREGISTER_CVAR("gm_dumpstats_file");
UNREGISTER_CVAR("gm_dumpstats");
if (gEnv->pConsole)
{
gEnv->pConsole->RemoveCommand("gm_setdebugdraw");
}
UNREGISTER_CVAR("gm_setdebugdraw");
UNREGISTER_CVAR("gm_asserts");
UNREGISTER_CVAR("gm_tracelevel");
UNREGISTER_CVAR("gm_debugdraw");
}
#endif // GRIDMATE_DEBUG_ENABLED
} // namespace Debug
//-----------------------------------------------------------------------------
void Network::DebugDraw()
{
#if GRIDMATE_DEBUG_ENABLED
using namespace Debug;
if (0 == Debug::s_DebugDraw)
{
return;
}
auto* levelSystem = GetLevelSystem();
static const float startX = 50.f;
static const float startY = 50.f;
static const float columnWidth = 500.f;
DebugTextHelper text(gEnv->pRenderer, startX, startY, 1.2f);
text.SetMonospaced(true);
text.ClearLines(startY, gEnv->pRenderer->GetHeight() - startY);
text.AddText(Col_Yellow, "=== GridMate ===");
text.Newline();
text.AddText(Col_Coral, "[Status]");
text.AddText(Col_White, "%-20s %s", "Is Server?", gEnv->bServer ? "yes" : "no");
text.AddText(Col_White, "%-20s %s", "Is Multiplayer?", gEnv->bMultiplayer ? "yes" : "no");
text.AddText(Col_White, "%-20s %u", "Local Channel", m_localChannelId);
string sessionStatusStr = "(none)";
if (m_session)
{
sessionStatusStr = "Multiplayer";
if (m_session->IsHost())
{
sessionStatusStr += " hosted";
}
else
{
sessionStatusStr += " joined";
}
}
text.AddText(Col_White, "%-20s %s", "Session Status", sessionStatusStr.c_str());
text.AddText(Col_White, "%-20s %s", "Current Level", (levelSystem && levelSystem->GetCurrentLevel()) ?
levelSystem->GetCurrentLevel()->GetLevelInfo()->GetName() : "(none)");
if (m_session)
{
text.Newline();
const char* sessionType = (m_session->IsHost()) ? "Server" : "Client";
text.AddText(Col_Coral, "[Session - %s]", sessionType);
if (!m_session->IsHost())
{
text.AddText(Col_White, "%-20s %u", "Server Channel",
GetServerChannelId());
}
text.AddText(Col_White, "%-20s %u", "Members", m_session->GetNumberOfMembers());
}
if (!!(s_DebugDraw & Stats) && !(s_DebugDraw & EntityDetail))
{
text.Newline();
text.AddText(Col_Coral, "[Overview (last %d msec)]", Network::s_StatsIntervalMS);
text.AddText(Col_LightBlue, "%-20s %-10s %-12s %-14s %-14s %-10s %-10s", "To Channel", "RTT", "Packet Loss", "Data Sent(kb)", "Data Recv(kb)", "Pack Sent", "Pack Recv");
for (const auto& stat : m_statisticsPerChannel)
{
text.AddText(Col_White, "%-20u %-10.2f %-12.2f %-14.2f %-14.2f %-10u %-10u",
stat.first, stat.second.m_rtt, stat.second.m_packetLossRate,
float( stat.second.m_totalSentBytes ) / 1024.f, float( stat.second.m_totalReceivedBytes ) / 1024.f,
stat.second.m_packetsSent, stat.second.m_packetsReceived);
}
const auto& stats = GetGameStatistics();
auto& rmiActor = stats.m_rmiGlobalActor;
auto& rmiLegacy = stats.m_rmiGlobalLegacy;
auto& rmiScript = stats.m_rmiGlobalScript;
text.Newline();
text.AddText(Col_Coral, "[Lifetime RMI]");
text.AddText(Col_LightBlue, "%-14s %-14s %-14s %-14s",
"Num Sent", "Num Received", "Total Sent(kb)", "Total Received(kb)");
text.AddText(Col_White, "%-14u %-14u %-14.2f %-14.2f",
rmiActor.m_sendCount + rmiLegacy.m_sendCount + rmiScript.m_sendCount,
rmiActor.m_receiveCount + rmiLegacy.m_receiveCount + rmiScript.m_receiveCount,
float( rmiActor.m_totalSentBytes + rmiLegacy.m_totalSentBytes + rmiScript.m_totalSentBytes ) / 1024.f,
float( rmiActor.m_totalReceivedBytes + rmiLegacy.m_totalReceivedBytes + rmiScript.m_totalReceivedBytes ) / 1024.f);
text.Newline();
text.AddText(Col_Coral, "[Lifetime Aspects]");
text.AddText(Col_LightBlue, "%-14s %-14s %-14s %-14s",
"Num Sent", "Num Received", "Total Sent(kb)", "Total Received(kb)");
text.AddText(Col_White, "%-14u %-14u %-14.2f %-14.2f",
stats.m_aspectsSent, stats.m_aspectsReceived,
float( stats.m_aspectSentBytes ) / 1024.f,
float( stats.m_aspectReceivedBytes ) / 1024.f);
}
if (!!(s_DebugDraw & Replicas) && !(s_DebugDraw & EntityDetail))
{
text.Newline();
text.AddText(Col_Coral, "[Entity Replicas By Type]");
text.AddText(Col_LightBlue, "%-20s %-10s", "Entity Class", "Count");
}
if (!!(s_DebugDraw & Actors) && !(s_DebugDraw & EntityDetail))
{
text.Newline();
text.AddText(Col_Coral, "[Game Actors]");
text.AddText(Col_LightBlue, "%-20s %-10s %-10s %-15s %-10s", "Name", "Channel", "Entity Id", "Client Actor?", "Player?");
}
if (!!(s_DebugDraw & Trace) && !(s_DebugDraw & EntityDetail))
{
text.Newline();
text.AddText(Col_Coral, "[Trace]");
for (auto iter = s_trackedMessages.rbegin(); iter != s_trackedMessages.rend(); ++iter)
{
const char* time = "";
#ifdef WIN32
char timeFriendly[ 128 ];
{
tm timeStruct;
localtime_s(&timeStruct, &iter->m_time);
strftime(timeFriendly, 20, "%H:%M:%S", &timeStruct);
time = timeFriendly;
}
#endif // WIN32
text.AddText(iter->m_type == DebugMessageType::kAssert ? Col_Red : Col_White,
"[%s] %s", time, iter->m_string.c_str());
}
}
#endif // GRIDMATE_DEBUG_ENABLED
}
//-----------------------------------------------------------------------------
void Network::DumpNetworkStatistics()
{
#if GRIDMATE_DEBUG_ENABLED
if (s_DumpStatsFile)
{
static uint32 s_actorRMIOverhead = 0;
static uint32 s_legacyRMIOverhead = 0;
static uint32 s_scriptRMIOverhead = 0;
static uint32 s_aspectOverhead = 0;
static bool s_overheadsComputed = false;
// Compute some overhead values.
{
char tempBuffer[2048];
GridMate::WriteBufferStaticInPlace buffer(EndianType::BigEndian, tempBuffer, sizeof(tempBuffer));
if (!s_overheadsComputed)
{
{
buffer.Clear();
GridMate::RMI::ActorInvocationWrapper::Ptr invocation = new GridMate::RMI::ActorInvocationWrapper();
GridMate::RMI::ActorInvocationWrapper::Marshaler().Marshal(buffer, invocation);
s_actorRMIOverhead = buffer.Size();
}
{
buffer.Clear();
GridMate::RMI::LegacyInvocationWrapper::Ptr invocation = new GridMate::RMI::LegacyInvocationWrapper();
GridMate::RMI::LegacyInvocationWrapper::Marshaler().Marshal(buffer, invocation);
s_legacyRMIOverhead = buffer.Size();
}
{
buffer.Clear();
GridMate::RMI::ScriptInvocationWrapper::Ptr invocation = new GridMate::RMI::ScriptInvocationWrapper();
GridMate::RMI::ScriptInvocationWrapper::Marshaler().Marshal(buffer, invocation);
s_scriptRMIOverhead = buffer.Size();
}
{
buffer.Clear();
GridMate::NetSerialize::AspectSerializeState aspect;
GridMate::NetSerialize::AspectSerializeState::Marshaler().Marshal(buffer, aspect);
s_aspectOverhead = buffer.Size();
}
s_overheadsComputed = true;
}
}
fprintf(s_DumpStatsFile, "Last %d msec\n", s_StatsIntervalMS);
static const char* s_unknown = "<unknown>";
//
// Global stats.
//
auto& stats = GetGameStatistics();
auto carrier = GetCarrierStatistics();
fprintf(s_DumpStatsFile,
"[Global]\n"
"Ping\tTotalBytesSent\tTotalBytesRecv\t"
"TotalPackSent\tTotalPackRecv\t"
"TotalRMIsSent\tTotalRMIsRecv\t"
"TotalRMIBytesSent\tTotalRMIBytesRecv\t"
"TotalAspectsSent\tTotalAspectsRecv\t"
"TotalAspectBytesSent\tTotalAspectBytesRecv\t"
"PacketsLost\tPackLossRate\t"
"ActorRMIOverheadBytes\tLegacyRMIOverheadBytes\tScriptRMIOverheadBytes\tAspectOverheadBytes\n");
fprintf(s_DumpStatsFile, "%.2f\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%u\t%.2f\t%u\t%u\t%u\t%u\n",
carrier.m_rtt,
carrier.m_totalSentBytes, carrier.m_totalReceivedBytes,
carrier.m_packetsSent, carrier.m_packetsReceived,
stats.m_rmiGlobalActor.m_sendCount + stats.m_rmiGlobalLegacy.m_sendCount + stats.m_rmiGlobalScript.m_sendCount,
stats.m_rmiGlobalActor.m_receiveCount + stats.m_rmiGlobalLegacy.m_receiveCount + stats.m_rmiGlobalScript.m_receiveCount,
stats.m_rmiGlobalActor.m_totalSentBytes + stats.m_rmiGlobalLegacy.m_totalSentBytes + stats.m_rmiGlobalScript.m_totalSentBytes,
stats.m_rmiGlobalActor.m_totalReceivedBytes + stats.m_rmiGlobalLegacy.m_totalReceivedBytes + stats.m_rmiGlobalScript.m_totalReceivedBytes,
stats.m_aspectsSent, stats.m_aspectsReceived,
stats.m_aspectSentBytes, stats.m_aspectReceivedBytes,
carrier.m_packetsLost, carrier.m_packetLossRate,
s_actorRMIOverhead, s_legacyRMIOverhead, s_scriptRMIOverhead, s_aspectOverhead);
//
// Per-entity detail.
//
fprintf(s_DumpStatsFile, "\n[Entity Detail]\n");
fprintf(s_DumpStatsFile, "Entity\tClass\tEventType\tSendCount\tRecvCount\tSentBytes\tRecvBytes\tOverheadBytes\tTotalBytes\n");
for (const auto& entityEntry : stats.m_entities)
{
const EntityId entityId = entityEntry.first;
const auto& entityStats = entityEntry.second;
bool hasTrafficData = false;
if (!entityStats.m_rmiActor.empty() || !entityStats.m_rmiLegacy.empty())
{
hasTrafficData = true;
}
if (!hasTrafficData)
{
for (size_t aspectIndex = 0; aspectIndex < AZ_ARRAY_SIZE(entityStats.m_aspects); ++aspectIndex)
{
const auto& aspectStats = entityStats.m_aspects[ aspectIndex ];
if (aspectStats.m_receiveCount + aspectStats.m_sendCount > 0)
{
hasTrafficData = true;
}
}
}
if (!hasTrafficData)
{
continue;
}
for (const auto& rmi : entityStats.m_rmiActor)
{
const uint32 rmiRepId = rmi.first;
const GameStatistics::RMIStatistics& rmiStats = rmi.second;
const IActorRMIRep* rep = RMI::FindActorRMIRep(rmiRepId);
const char* rmiName = rep ? rep->GetDebugName() : s_unknown;
const uint32 overhead = s_actorRMIOverhead * (rmiStats.m_sendCount + rmiStats.m_receiveCount);
fprintf(s_DumpStatsFile,
"\t\tRMI: %s\t%u\t%u\t%u\t%u\t%u\t%u\n",
rmiName,
rmiStats.m_sendCount, rmiStats.m_receiveCount,
rmiStats.m_totalSentBytes, rmiStats.m_totalReceivedBytes,
overhead,
overhead + rmiStats.m_totalSentBytes + rmiStats.m_totalReceivedBytes);
}
for (const auto& rmi : entityStats.m_rmiLegacy)
{
const uint32 rmiRepId = rmi.first;
const GameStatistics::RMIStatistics& rmiStats = rmi.second;
const uint32 overhead = s_actorRMIOverhead * (rmiStats.m_sendCount + rmiStats.m_receiveCount);
fprintf(s_DumpStatsFile,
"\t\tRMI: %s\t%u\t%u\t%u\t%u\t%u\t%u\n",
s_unknown,
rmiStats.m_sendCount, rmiStats.m_receiveCount,
rmiStats.m_totalSentBytes, rmiStats.m_totalReceivedBytes,
overhead,
overhead + rmiStats.m_totalSentBytes + rmiStats.m_totalReceivedBytes);
}
for (size_t aspectIndex = 0; aspectIndex < AZ_ARRAY_SIZE(entityStats.m_aspects); ++aspectIndex)
{
const auto& aspectStats = entityStats.m_aspects[ aspectIndex ];
if (aspectStats.m_receiveCount + aspectStats.m_sendCount > 0)
{
const uint32 overhead = s_aspectOverhead * (aspectStats.m_receiveCount + aspectStats.m_sendCount);
fprintf(s_DumpStatsFile,
"\t\tAspect: %s\t%u\t%u\t%u\t%u\t%u\t%u\n",
GridMate::Debug::GetAspectNameByBitIndex(aspectIndex),
aspectStats.m_sendCount, aspectStats.m_receiveCount,
aspectStats.m_totalSentBytes, aspectStats.m_totalReceivedBytes,
overhead,
overhead + aspectStats.m_totalSentBytes + aspectStats.m_totalReceivedBytes);
}
}
}
fprintf(s_DumpStatsFile, "\n");
}
#endif // GRIDMATE_DEBUG_ENABLED
}
} // namespace GridMate
@@ -0,0 +1,83 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATEDEBUG_HEADER
#define INCLUDE_NETWORKGRIDMATEDEBUG_HEADER
#pragma once
#ifndef _RELEASE
# define GRIDMATE_DEBUG_ENABLED 1
#else
# define GRIDMATE_DEBUG_ENABLED 0
#endif // !_RELEASE
//-----------------------------------------------------------------------------
namespace GridMate
{
namespace Debug
{
const char* const GetAspectNameByBitIndex(size_t aspectBit);
#if GRIDMATE_DEBUG_ENABLED
extern int s_DebugDraw; // Bound to gm_debugdraw cvar
extern int s_TraceLevel; // Bound to gm_tracelevel cvar
extern int s_EnableAsserts; // Bound to gm_asserts cvar
enum DebugDrawBits
{
Basic = BIT(0),
Trace = BIT(1),
Stats = BIT(2),
Replicas = BIT(3),
Actors = BIT(4),
EntityDetail = BIT(5),
Full = Basic | Trace | Stats | Replicas | Actors,
All = 0xffffffff,
};
enum class DebugMessageType
{
kTrace,
kAssert,
};
void RegisterCVars();
void UnregisterCVars();
void TrackMessage(DebugMessageType type, const char* msg);
void DebugTrace(bool isAssertFailure, const char* format, ...);
#endif // GRIDMATE_DEBUG_ENABLED
} // namespace Debug
} // namespace GridMate
#if GRIDMATE_DEBUG_ENABLED
#define GM_DEBUG_TRACE_LEVEL(level, ...) \
do { if (GridMate::Debug::s_TraceLevel >= level) {GridMate::Debug::DebugTrace(false, __VA_ARGS__); } \
} while (0);
#define GM_ASSERT_TRACE(c, ...) \
do { if (!(c)) {GridMate::Debug::DebugTrace(true, __VA_ARGS__); } \
} while (0);
#define GM_DEBUG_TRACE(...) GM_DEBUG_TRACE_LEVEL(1, __VA_ARGS__)
#else // !GRIDMATE_DEBUG_ENABLED
#define GM_DEBUG_TRACE_LEVEL(level, ...) {; }
#define GM_DEBUG_TRACE(...) {; }
#define GM_ASSERT_TRACE(c, ...) {; }
#endif // GRIDMATE_DEBUG_ENABLED
#endif // INCLUDE_NETWORKGRIDMATEDEBUG_HEADER
@@ -0,0 +1,225 @@
/*
* 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.
*
*/
#ifndef INCLUDE_NETWORKGRIDMATEMARSHALING_HEADER
#define INCLUDE_NETWORKGRIDMATEMARSHALING_HEADER
#pragma once
#include <Cry_Vector2.h>
#include <Cry_Vector3.h>
#include <Cry_Quat.h>
#include <TimeValue.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
struct ILevelInfo;
namespace GridMate
{
/*!
* Basic marshaller for CryEngine versions of string, stack_string, etc.
*/
template<typename T>
class CryStringMarshalerBase
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const T& str)
{
const uint16 size = static_cast<uint16>(str.length());
wb.Write(size);
wb.WriteRaw(str.c_str(), size);
}
AZ_FORCE_INLINE void Unmarshal(T& str, ReadBuffer& rb)
{
uint16 size = 0;
rb.Read(size);
str.resize(size);
char* dest = const_cast<char*>(str.c_str());
rb.ReadRaw(dest, size);
}
};
/*!
* Default marshaler for 2D vectors
*/
template<>
class Marshaler < Vec2 >
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const Vec2& v)
{
wb.Write(v.x);
wb.Write(v.y);
}
AZ_FORCE_INLINE void Unmarshal(Vec2& v, ReadBuffer& rb)
{
rb.Read(v.x);
rb.Read(v.y);
}
};
/*!
* Default marshaler for 3D vectors
*/
template<>
class Marshaler < Vec3 >
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const Vec3& v)
{
wb.Write(v.x);
wb.Write(v.y);
wb.Write(v.z);
}
AZ_FORCE_INLINE void Unmarshal(Vec3& v, ReadBuffer& rb)
{
rb.Read(v.x);
rb.Read(v.y);
rb.Read(v.z);
}
};
/*!
* Default marshaler for Angle-3s
*/
template<>
class Marshaler < Ang3 >
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const Ang3& v)
{
wb.Write(v.x);
wb.Write(v.y);
wb.Write(v.z);
}
AZ_FORCE_INLINE void Unmarshal(Ang3& v, ReadBuffer& rb)
{
rb.Read(v.x);
rb.Read(v.y);
rb.Read(v.z);
}
};
/*!
* Default marshaler for Quats
*/
template<>
class Marshaler < Quat >
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const Quat& v)
{
wb.Write(v.v.x);
wb.Write(v.v.y);
wb.Write(v.v.z);
wb.Write(v.w);
}
AZ_FORCE_INLINE void Unmarshal(Quat& v, ReadBuffer& rb)
{
rb.Read(v.v.x);
rb.Read(v.v.y);
rb.Read(v.v.z);
rb.Read(v.w);
}
};
/*!
* Default marshaler for time stamps.
*/
template<>
class Marshaler < CTimeValue >
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const CTimeValue& v)
{
int64 temp = v.GetValue();
wb.Write(temp);
}
AZ_FORCE_INLINE void Unmarshal(CTimeValue& v, ReadBuffer& rb)
{
int64 temp;
rb.Read(temp);
v.SetValue(temp);
}
};
/*!
* Default marshaler specializations for various engine string types.
*/
class CryStringMarshaler
: public CryStringMarshalerBase < CryStringT<char> >
{
};
template<size_t Size>
class CryFixedStringMarshaler
: public CryStringMarshalerBase < CryFixedStringT<Size> >
{
};
template<size_t Size>
class CryStackStringMarshaler
: public CryStringMarshalerBase < CryStackStringT<char, Size> >
{
};
/*!
* Unsupported marshaler. Right now this is just used for types that legacy engine defines
* require compile-time serialization handlers for, but we don't actually desire to use.
*/
template<typename T>
class UnsupportedMarshaler
{
public:
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const T& v)
{
(void)wb;
(void)v;
CRY_ASSERT_MESSAGE(0, "Marshaling not valid for this type");
}
AZ_FORCE_INLINE void Unmarshal(T& v, ReadBuffer& rb)
{
(void)v;
(void)rb;
CRY_ASSERT_MESSAGE(0, "Marshaling not valid for this type");
}
};
template<>
class Marshaler<SNetObjectID>
: public UnsupportedMarshaler < SNetObjectID >
{
};
template<>
class Marshaler<XmlNodeRef>
: public UnsupportedMarshaler < XmlNodeRef >
{
};
} // namespace GridMate (marshalers)
#endif // INCLUDE_NETWORKGRIDMATEMARSHALING_HEADER
@@ -0,0 +1,495 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "../NetworkGridMate.h"
#include "../NetworkGridmateDebug.h"
#include "../NetworkGridmateMarshaling.h"
#include "../NetworkGridMateEntityEventBus.h"
#include "EntityReplica.h"
#include "EntityScriptReplicaChunk.h"
#include <GridMate/Replica/ReplicaFunctions.h>
namespace GridMate
{
static const NetworkAspectType kAllEntityAspectBits = (1 << NetSerialize::kNumAspectSlots) - 1;
//-----------------------------------------------------------------------------
size_t EntityReplica::SerializedNetSerializeState::s_nextAspectIndex = 0;
//-----------------------------------------------------------------------------
EntityReplica::SerializedNetSerializeState::SerializedNetSerializeState()
: DataSet(Debug::GetAspectNameByBitIndex(s_nextAspectIndex))
{
m_aspectIndex = s_nextAspectIndex++;
}
//-----------------------------------------------------------------------------
void EntityReplica::SerializedNetSerializeState::DispatchChangedEvent([[maybe_unused]] const TimeContext& tc)
{
static_cast<EntityReplica*>(m_replicaChunk)->OnAspectChanged(m_aspectIndex);
}
//-----------------------------------------------------------------------------
EntityReplica::EntityReplica()
: m_gameDirtiedAspects(kAllEntityAspectBits)
, m_localEntityId(kInvalidEntityId)
, RPCHandleLegacyServerRMI("RPCHandleLegacyServerRMI")
, RPCHandleLegacyClientRMI("RPCHandleLegacyClientRMI")
, RPCHandleActorServerRMI("RPCHandleActorServerRMI")
, RPCHandleActorClientRMI("RPCHandleActorClientRMI")
, RPCUploadClientAspect("RPCUploadClientAspect")
, RPCDelegateAuthorityToOwner("RPCDelegateAuthorityToOwner")
, m_extraSpawnInfo("ExtraSpawnInfo")
, m_clientDelegatedAspects("ClientDelegatedAspects", 0)
, m_aspectProfiles("AspectProfiles")
, m_modifiedDataSets(0)
, m_scriptReplicaChunk(nullptr)
, m_masterAspectScratchBuffer(EndianType::BigEndian)
, m_isClientAspectAuthority(false)
, m_flags(0)
{
SerializedNetSerializeState::s_nextAspectIndex = 0;
}
//-----------------------------------------------------------------------------
EntityReplica::EntityReplica(const EntitySpawnParamsStorage& paramsStorage)
: m_gameDirtiedAspects(kAllEntityAspectBits)
, m_localEntityId(kInvalidEntityId)
, RPCHandleLegacyServerRMI("RPCHandleLegacyServerRMI")
, RPCHandleLegacyClientRMI("RPCHandleLegacyClientRMI")
, RPCHandleActorServerRMI("RPCHandleActorServerRMI")
, RPCHandleActorClientRMI("RPCHandleActorClientRMI")
, RPCUploadClientAspect("RPCUploadClientAspect")
, RPCDelegateAuthorityToOwner("RPCDelegateAuthorityToOwner")
, m_spawnParams(paramsStorage)
, m_extraSpawnInfo("ExtraSpawnInfo")
, m_clientDelegatedAspects("ClientDelegatedAspects", 0)
, m_aspectProfiles("AspectProfiles")
, m_modifiedDataSets(0)
, m_scriptReplicaChunk(nullptr)
, m_masterAspectScratchBuffer(EndianType::BigEndian)
, m_isClientAspectAuthority(false)
, m_flags(0)
{
SerializedNetSerializeState::s_nextAspectIndex = 0;
}
//-----------------------------------------------------------------------------
void EntityReplica::OnReplicaActivate([[maybe_unused]] const GridMate::ReplicaContext& rc)
{
m_scriptReplicaChunk = GetReplica()->FindReplicaChunk<EntityScriptReplicaChunk>().get();
Network& net = Network::Get();
GM_DEBUG_TRACE("EntityReplica::OnActivate - IsMaster:%s EntityId:%u EntityName:%s EntityClass:%s, Address:0x%p",
IsMaster() ? "yes" : "no",
m_spawnParams.m_id,
m_spawnParams.m_entityName.c_str(),
m_spawnParams.m_className.c_str(),
this);
#if GRIDMATE_DEBUG_ENABLED
for (size_t aspectIndex = 0; aspectIndex < NetSerialize::kNumAspectSlots; ++aspectIndex)
{
NetSerialize::AspectSerializeState::Marshaler& marshaler =
m_netSerializeState[ aspectIndex ].GetMarshaler();
marshaler.m_debugName = GridMate::Debug::GetAspectNameByBitIndex(aspectIndex);
marshaler.m_debugIndex = aspectIndex;
}
#endif // GRIDMATE_DEBUG_ENABLED
// Objects initially assume all globally-delegatable aspects are delegatable
// by the object.
m_clientDelegatedAspects.Set(eEA_All);
if (IsMaster())
{
NetSerialize::EntityAspectProfiles aspectProfiles;
for (size_t i = 0; i < NetSerialize::kNumAspectSlots; ++i)
{
aspectProfiles.SetAspectProfile(i, NetSerialize::kUnsetAspectProfile);
}
// Initialize aspect profiles
m_aspectProfiles.Set(aspectProfiles);
}
else
{
// Flag replica such that we can establish (create or link) the local entity
// associated this replica as soon as it's safe to do so.
net.GetNewProxyEntityMap()[m_spawnParams.m_id] = this;
m_flags |= kFlag_NewlyReceived;
SetupAspectCallbacks();
}
}
//-----------------------------------------------------------------------------
void EntityReplica::OnReplicaDeactivate([[maybe_unused]] const GridMate::ReplicaContext& rc)
{
GM_DEBUG_TRACE("EntityReplica::OnDeactivate - IsMaster:%s EntityId:%u EntityName:%s EntityClass:%s",
IsMaster() ? "yes" : "no",
m_spawnParams.m_id,
m_spawnParams.m_entityName.c_str(),
m_spawnParams.m_className.c_str());
EBUS_EVENT_ID(m_localEntityId, NetworkGridMateEntityEventBus, OnEntityUnboundFromNetwork, GetReplica());
// Remove knowledge of the now-dead replica.
Network::Get().GetNewProxyEntityMap().erase(m_spawnParams.m_id);
m_localEntityId = kInvalidEntityId;
}
//-----------------------------------------------------------------------------
bool EntityReplica::IsReplicaMigratable()
{
return false;
}
//-----------------------------------------------------------------------------
EntityId EntityReplica::HandleNewlyReceivedNow()
{
if (m_localEntityId == kInvalidEntityId)
{
HandleNewlyReceived();
}
return m_localEntityId;
}
//-----------------------------------------------------------------------------
void EntityReplica::HandleNewlyReceived()
{
if (m_spawnParams.m_id != kInvalidEntityId &&
m_localEntityId == kInvalidEntityId)
{
Network& net = Network::Get();
if (!net.AllowEntityCreation())
{
return;
}
bool isGameRules =
!!(m_spawnParams.m_paramsFlags & EntitySpawnParamsStorage::kParamsFlag_IsGameRules);
if (isGameRules)
{
GM_DEBUG_TRACE("Established game rules? %s", m_localEntityId != kInvalidEntityId ? "yes" : "no");
}
else
{
GM_DEBUG_TRACE_LEVEL(2, "Waiting for game rules...");
return;
}
// Flush pending RMIs.
if (kInvalidEntityId != m_localEntityId)
{
GM_DEBUG_TRACE("Flushing pending RMIs (%u / %u)",
m_pendingLegacyRMIs.size(), m_pendingActorRMIs.size());
for (const auto& rmi : m_pendingLegacyRMIs)
{
HandleLegacyClientRMI(rmi.first, rmi.second);
}
for (const auto& rmi : m_pendingActorRMIs)
{
HandleActorClientRMI(rmi.first, rmi.second);
}
m_pendingLegacyRMIs.clear();
m_pendingActorRMIs.clear();
}
}
m_flags &= ~kFlag_NewlyReceived;
}
//-----------------------------------------------------------------------------
void EntityReplica::UnbindLocalEntity()
{
m_localEntityId = kInvalidEntityId;
}
//-----------------------------------------------------------------------------
void EntityReplica::SetupAspectCallbacks()
{
using namespace NetSerialize;
for (size_t aspectIndex = 0; aspectIndex < NetSerialize::kNumAspectSlots; ++aspectIndex)
{
SerializedNetSerializeState& aspectState = m_netSerializeState[ aspectIndex ];
AspectSerializeState::Marshaler& marshaler = aspectState.GetMarshaler();
// Trigger initial dispatch of all aspects.
marshaler.MarkWaitingForDispatch();
}
// Setup client-side callback for aspect profile changes.
using namespace AZStd::placeholders;
auto aspectProfileCallback =
AZStd::bind(&EntityReplica::OnAspectProfileChanged, this, _1, _2, _3);
m_aspectProfiles.GetMarshaler().SetChangeDelegate(aspectProfileCallback);
}
//-----------------------------------------------------------------------------
bool EntityReplica::CommitAspectData(size_t aspectIndex, const char* newData, size_t newDataSize, uint32 hash)
{
GM_ASSERT_TRACE(newData, "Invalid data buffer.");
SerializedNetSerializeState& aspectState = m_netSerializeState[ aspectIndex ];
// Update outgoing storage for marshaling.
NetSerialize::AspectSerializeState::Marshaler& marshaler = aspectState.GetMarshaler();
if (marshaler.GetStorageSize() < newDataSize)
{
marshaler.AllocateAspectSerializationBuffer(newDataSize);
}
if (newDataSize > 0)
{
FRAME_PROFILER("AspectBufferCopy", GetISystem(), PROFILE_NETWORK);
WriteBufferType writeBuffer = marshaler.GetWriteBuffer();
writeBuffer.Clear();
writeBuffer.WriteRaw(newData, newDataSize);
}
// Store updated contents & hash. Any change will result in a downstream update.
NetSerialize::AspectSerializeState updatedState = aspectState.Get();
bool changed = false;
{
FRAME_PROFILER("AspectBufferHash", GetISystem(), PROFILE_NETWORK);
changed = updatedState.UpdateHash(hash, newDataSize);
}
{
FRAME_PROFILER("AspectUpdate", GetISystem(), PROFILE_NETWORK);
aspectState.Set(updatedState);
}
if (changed)
{
FRAME_PROFILER("AspectSentEvent", GetISystem(), PROFILE_NETWORK);
EBUS_EVENT(NetworkSystemEventBus, AspectSent, m_localEntityId, BIT(aspectIndex), newDataSize);
}
return changed;
}
//-----------------------------------------------------------------------------
bool EntityReplica::IsAspectDelegatedToThisClient(size_t aspectIndex) const
{
const NetworkAspectType engineAspectBit = BIT(aspectIndex);
return IsAspectDelegatedToThisClient() && // Authority over this entity has been delegated to this client.
!!(engineAspectBit & NetSerialize::GetDelegatableAspectMask()) && // This aspect supports client-delegation (globally).
!!(engineAspectBit & m_clientDelegatedAspects.Get()); // This aspect supports client-delegation (on this object).
}
//-----------------------------------------------------------------------------
bool EntityReplica::IsAspectDelegatedToThisClient() const
{
return m_isClientAspectAuthority; // Authority over this entity has been delegated to this client.
}
//-----------------------------------------------------------------------------
void EntityReplica::OnAspectChanged(size_t aspectIndex)
{
GM_ASSERT_TRACE(!IsMaster(), "We shouldn't have unmarshaled on master.");
SerializedNetSerializeState& aspectState = m_netSerializeState[ aspectIndex ];
aspectState.GetMarshaler().MarkWaitingForDispatch();
}
//-----------------------------------------------------------------------------
void EntityReplica::OnAspectProfileChanged([[maybe_unused]] size_t aspectIndex,
[[maybe_unused]] NetSerialize::AspectProfile oldProfile,
[[maybe_unused]] NetSerialize::AspectProfile newProfile)
{
}
//-----------------------------------------------------------------------------
bool EntityReplica::HandleLegacyServerRMI(RMI::LegacyInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
AZ_Assert(IsMaster(), "Legacy Server RMIs should only ever be processed on the server!");
if (IsMaster())
{
AZ_Assert(kInvalidEntityId != m_localEntityId, "local entity ids should be immediately available on the server!");
if (kInvalidEntityId != m_localEntityId)
{
RMI::HandleLegacy(m_localEntityId, invocation, rc);
}
}
return false;
}
//-----------------------------------------------------------------------------
bool EntityReplica::HandleLegacyClientRMI(RMI::LegacyInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
if (kInvalidEntityId != m_localEntityId)
{
return RMI::HandleLegacy(m_localEntityId, invocation, rc);
}
m_pendingLegacyRMIs.push_back(std::make_pair(invocation, rc));
return false;
}
//-----------------------------------------------------------------------------
bool EntityReplica::HandleActorServerRMI(RMI::ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
AZ_Assert(IsMaster(), "Legacy Server RMIs should only ever be processed on the server!");
if (IsMaster())
{
AZ_Assert(kInvalidEntityId != m_localEntityId, "local entity ids should be immediately available on the server!");
if (kInvalidEntityId != m_localEntityId)
{
RMI::HandleActor(m_localEntityId, invocation, rc);
}
}
return false;
}
//-----------------------------------------------------------------------------
bool EntityReplica::HandleActorClientRMI(RMI::ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc)
{
if (kInvalidEntityId != m_localEntityId)
{
return RMI::HandleActor(m_localEntityId, invocation, rc);
}
m_pendingActorRMIs.push_back(std::make_pair(invocation, rc));
return false;
}
//-----------------------------------------------------------------------------
bool EntityReplica::UploadClientAspect([[maybe_unused]] uint32 aspectIndex, AspectUploadBuffer::Ptr buffer, [[maybe_unused]] const GridMate::RpcContext& rc)
{
GM_ASSERT_TRACE(buffer.get(), "UploadClientAspect: Empty buffer received for client-delegated aspect.");
if (buffer.get())
{
const char* data = buffer->GetData();
size_t dataSize = buffer->GetSize();
ReadBufferType rb(EndianType::BigEndian, data, dataSize);
}
// No need to pass on - this only occurs on the server, and changes will be marshaled
// down through aspect states.
return false;
}
//-----------------------------------------------------------------------------
bool EntityReplica::DelegateAuthorityToOwner(ChannelId ownerChannelId, [[maybe_unused]] const GridMate::RpcContext& rc)
{
if (!IsMaster() && Network::Get().GetLocalChannelId() == ownerChannelId)
{
m_isClientAspectAuthority = true;
// Wipe hash values for client-delegated aspects.
for (size_t aspectIndex = 0; aspectIndex < NetSerialize::kNumAspectSlots; ++aspectIndex)
{
m_clientDelegatedAspectHashes[ aspectIndex ] = 0;
}
m_gameDirtiedAspects = 0;
}
return true;
}
//-----------------------------------------------------------------------------
const EntitySpawnParamsStorage& EntityReplica::GetSerializedSpawnParams() const
{
return m_spawnParams;
}
//-----------------------------------------------------------------------------
EntityId EntityReplica::GetLocalEntityId() const
{
return m_localEntityId;
}
//-----------------------------------------------------------------------------
void EntityReplica::MarkAspectsDirty(NetworkAspectType aspects)
{
m_gameDirtiedAspects |= aspects;
}
//-----------------------------------------------------------------------------
NetworkAspectType EntityReplica::GetDirtyAspects() const
{
return m_gameDirtiedAspects;
}
//-----------------------------------------------------------------------------
void EntityReplica::UploadClientDelegatedAspects()
{
m_gameDirtiedAspects = 0;
}
//-----------------------------------------------------------------------------
void EntityReplica::SetClientDelegatedAspectMask(NetworkAspectType aspects)
{
m_clientDelegatedAspects.Set(aspects);
}
//-----------------------------------------------------------------------------
NetworkAspectType EntityReplica::GetClientDelegatedAspectMask() const
{
return m_clientDelegatedAspects.Get();
}
//-----------------------------------------------------------------------------
NetSerialize::AspectProfile EntityReplica::GetAspectProfile(size_t aspectIndex) const
{
GM_ASSERT_TRACE(aspectIndex < NetSerialize::kNumAspectSlots, "Invalid aspect index: %u", aspectIndex);
return m_aspectProfiles.Get().GetAspectProfile(aspectIndex);
}
//-----------------------------------------------------------------------------
void EntityReplica::SetAspectProfile(size_t aspectIndex, NetSerialize::AspectProfile profile)
{
GM_ASSERT_TRACE(aspectIndex < NetSerialize::kNumAspectSlots, "Invalid aspect index: %u", aspectIndex);
if (GetAspectProfile(aspectIndex) != profile)
{
NetSerialize::EntityAspectProfiles aspectProfiles = m_aspectProfiles.Get();
aspectProfiles.SetAspectProfile(aspectIndex, profile);
m_aspectProfiles.Set(aspectProfiles);
}
}
//-----------------------------------------------------------------------------
AZ::u32 EntityReplica::CalculateDirtyDataSetMask(MarshalContext& mc)
{
if ((mc.m_marshalFlags & ReplicaMarshalFlags::ForceDirty))
{
return m_modifiedDataSets;
}
return ReplicaChunkBase::CalculateDirtyDataSetMask(mc);
}
//-----------------------------------------------------------------------------
void EntityReplica::OnDataSetChanged(const DataSetBase& dataSet)
{
// Keep track of which DataSets have been chagned, so when we initialize
// we only initialize the DataSets with data.
int index = GetDescriptor()->GetDataSetIndex(this,&dataSet);
m_modifiedDataSets |= (1 << index);
}
} // namespace GridMate
@@ -0,0 +1,304 @@
/*
* 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.
*
*/
#ifndef INCLUDE_GRIDMATEENTITYREPLICA_HEADER
#define INCLUDE_GRIDMATEENTITYREPLICA_HEADER
#pragma once
#include "../NetworkGridmateMarshaling.h"
#include "../Compatibility/GridMateRMI.h"
#include "../Compatibility/GridMateNetSerialize.h"
#include "../Compatibility/GridMateNetSerializeAspectProfiles.h"
#include "EntityReplicaSpawnParams.h"
#include <GridMate/Serialize/CompressionMarshal.h>
#include <GridMate/Replica/ReplicaFunctions.h>
enum EEntityAspects
{
eEA_All = NET_ASPECT_ALL,
#define ADD_ASPECT(x, y) x = BIT(y),
#include "../Compatibility/GridMateNetSerializeAspects.inl"
#undef ADD_ASPECT
};
namespace GridMate
{
class EntityScriptReplicaChunk;
/*!
* For replication of IEntity's.
*
* Upon being bound to the network, an EntityReplica is created on the server to ensure
* the entity is spawned identically on all machines.
*
* This replica also supports a GridMate-backed compatibility implementation of the
* CryEngine NetSerialize() model.
*/
class EntityReplica
: public GridMate::ReplicaChunk
{
friend class NetworkGridMate;
/*
* Special dataset customized to support aspects
* Allows the dataset to be part of an aspect array while still using descriptive
* debug names.
*/
using AspectDataSet = DataSet<NetSerialize::AspectSerializeState, NetSerialize::AspectSerializeState::Marshaler>;
class SerializedNetSerializeState : public AspectDataSet
{
public:
GM_CLASS_ALLOCATOR(SerializedNetSerializeState);
SerializedNetSerializeState();
void DispatchChangedEvent(const TimeContext& tc) override;
size_t m_aspectIndex;
PrepareDataResult PrepareData(EndianType endianType, AZ::u32 marshalFlags) override
{
return AspectDataSet::PrepareData(endianType, marshalFlags);
}
void SetDirty() override
{
AspectDataSet::SetDirty();
}
static size_t s_nextAspectIndex; // Reset to 0 at the end of EntityReplica construction
};
public:
/*
* Entity replica construction parameteres
*/
struct EntityReplicaCtorContext : public CtorContextBase
{
CtorDataSet<EntitySpawnParamsStorage, EntitySpawnParamsStorage::Marshaler> m_spawnParams;
};
/*
* Chunk descriptor
*/
class EntityReplicaDesc : public ReplicaChunkDescriptor
{
public:
EntityReplicaDesc()
: ReplicaChunkDescriptor(EntityReplica::GetChunkName(), sizeof(EntityReplica))
{
}
ReplicaChunkBase* CreateFromStream(UnmarshalContext& mc) override
{
ReplicaChunkBase* replicaChunk = nullptr;
AZ_Assert(!mc.m_rm->IsSyncHost(), "EntityReplica can only be owned by the host!");
if (!mc.m_rm->IsSyncHost())
{
EntityReplicaCtorContext ctorContext;
ctorContext.Unmarshal(*mc.m_iBuf);
replicaChunk = CreateReplicaChunk<EntityReplica>(ctorContext.m_spawnParams.Get());
}
else
{
DiscardCtorStream(mc);
}
return replicaChunk;
}
void DiscardCtorStream(UnmarshalContext& mc) override
{
EntityReplicaCtorContext ctorContext;
ctorContext.Unmarshal(*mc.m_iBuf);
}
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override
{
delete chunkInstance;
}
void MarshalCtorData(ReplicaChunkBase* chunkInstance, WriteBuffer& wb) override
{
EntityReplica* entityChunk = static_cast<EntityReplica*>(chunkInstance);
EntityReplicaCtorContext ctorContext;
ctorContext.m_spawnParams.Set(entityChunk->GetSerializedSpawnParams());
ctorContext.Marshal(wb);
}
};
GM_CLASS_ALLOCATOR(EntityReplica);
EntityReplica();
EntityReplica(const EntitySpawnParamsStorage& paramsStorage);
virtual ~EntityReplica() {};
//////////////////////////////////////////////////////////////////////
//! GridMate::ReplicaChunk overrides.
static const char* GetChunkName() { return "EntityReplicaChunk"; }
void UpdateChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override;
void UpdateFromChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
bool IsReplicaMigratable() override;
//////////////////////////////////////////////////////////////////////
//! Retrieve the entity's spawn params as serialized from the server.
const EntitySpawnParamsStorage& GetSerializedSpawnParams() const;
//! Returns the Id of the local entity for this replica.
EntityId GetLocalEntityId() const;
//! Unbinds from the local entity
void UnbindLocalEntity();
//! Handler for game code calls to ChangedNetworkState(). This tells the replica that we need to gather new
//! data for this aspect. Internally we keep a hash, so only actual changes will result in a re-send.
void MarkAspectsDirty(NetworkAspectType aspects);
//! Returns a bitmask of the current aspects that are marked dirty.
NetworkAspectType GetDirtyAspects() const;
//! Returns true if the local machine has client-aspect authority, and the specified
//! aspect is in fact delegated.
bool IsAspectDelegatedToThisClient(size_t aspectIndex) const;
//! Returns true if the local machine has client-aspect authority
bool IsAspectDelegatedToThisClient() const;
//! Upload client-delegated aspects to the server.
void UploadClientDelegatedAspects();
//! Marks aspects that are delegated to the controlling authority.
void SetClientDelegatedAspectMask(NetworkAspectType aspects);
//! Retrieves client-delegated aspects for this replica.
NetworkAspectType GetClientDelegatedAspectMask() const;
//! Retrieves the active profile for the specified aspect.
NetSerialize::AspectProfile GetAspectProfile(size_t aspectIndex) const;
//! Sets the active profile for the specified aspect.
void SetAspectProfile(size_t aspectIndex, NetSerialize::AspectProfile profile);
//! RPC for dispatching legacy-style CryEngine RMIs.
bool HandleLegacyServerRMI(RMI::LegacyInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<RMI::LegacyInvocationWrapper::Ptr, RMI::LegacyInvocationWrapper::Marshaler>>::BindInterface<EntityReplica, &EntityReplica::HandleLegacyServerRMI> RPCHandleLegacyServerRMI;
bool HandleLegacyClientRMI(RMI::LegacyInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<RMI::LegacyInvocationWrapper::Ptr, RMI::LegacyInvocationWrapper::Marshaler>>::BindInterface<EntityReplica, &EntityReplica::HandleLegacyClientRMI, RMI::ClientRMITraits> RPCHandleLegacyClientRMI;
//! RPC for dispatching GameCore Actor RMIs.
bool HandleActorServerRMI(RMI::ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<RMI::ActorInvocationWrapper::Ptr, RMI::ActorInvocationWrapper::Marshaler>>::BindInterface<EntityReplica, &EntityReplica::HandleActorServerRMI> RPCHandleActorServerRMI;
bool HandleActorClientRMI(RMI::ActorInvocationWrapper::Ptr invocation, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<RMI::ActorInvocationWrapper::Ptr, RMI::ActorInvocationWrapper::Marshaler>>::BindInterface<EntityReplica, &EntityReplica::HandleActorClientRMI, RMI::ClientRMITraits> RPCHandleActorClientRMI;
//! RPC for dispatching client-delegated aspect updates.
typedef ManagedFlexibleBuffer<256> AspectUploadBuffer;
bool UploadClientAspect(uint32 aspectIndex, AspectUploadBuffer::Ptr buffer, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<uint32, Marshaler<uint32>>, GridMate::RpcArg<AspectUploadBuffer::Ptr, AspectUploadBuffer::PtrMarshaler>>::BindInterface<EntityReplica, &EntityReplica::UploadClientAspect> RPCUploadClientAspect;
//! RPC for notifying clients of delegation.
bool DelegateAuthorityToOwner(ChannelId ownerChannelId, const GridMate::RpcContext& rc);
GridMate::Rpc<GridMate::RpcArg<ChannelId>>::BindInterface<EntityReplica, &EntityReplica::DelegateAuthorityToOwner> RPCDelegateAuthorityToOwner;
//! Forces expedited handling of a new replica. This addresses a specific case where
//! we need to establish the local entity during decoding of its master-side entity Id.
EntityId HandleNewlyReceivedNow();
enum EFlags
{
kFlag_None = 0,
kFlag_NewlyReceived = BIT(0), //! The replica was just activated.
};
uint32 GetFlags() const { return m_flags; }
protected:
AZ::u32 CalculateDirtyDataSetMask(MarshalContext& rc) override;
void OnDataSetChanged(const DataSetBase& dataset) override;
private:
//! Process a newly received replica. This includes establishing (either linking to or creating)
//! the machine-local entity associated with the replica.
void HandleNewlyReceived();
//! Registers callbacks for aspect changes.
void SetupAspectCallbacks();
//! Commits a new data image to the aspect and prepares for outgoing marshaling.
bool CommitAspectData(size_t aspectIndex, const char* newData, size_t newDataSize, uint32 hash);
//! Delegate for changed aspect notifications.
void OnAspectChanged(size_t aspectIndex);
//! Delegate for changed aspect profiles.
void OnAspectProfileChanged(size_t aspectIndex,
NetSerialize::AspectProfile oldProfile,
NetSerialize::AspectProfile newProfile);
//! Id of the *local* entity.
EntityId m_localEntityId;
//! Mask representing aspects that have been dirtied by the game (matters on master only).
NetworkAspectType m_gameDirtiedAspects;
//! Entity spawn parameters received from master.
EntitySpawnParamsStorage m_spawnParams;
//! Arbitrary spawn info gathered on the master.
SerializedEntityExtraSpawnInfo m_extraSpawnInfo;
//! Mask representing aspects the server has delegated tot he client.
DataSet<NetworkAspectType> m_clientDelegatedAspects;
//! Stored hashes to detect changes in client-delegated aspects (to prevent constant uploading).
uint32 m_clientDelegatedAspectHashes[ NetSerialize::kNumAspectSlots ];
//! Per-aspect profile (NetSerialize compatibility/shim).
NetSerialize::SerializedEntityAspectProfiles m_aspectProfiles;
//! Per-aspect serialization state (NetSerialize compatibility/shim).
SerializedNetSerializeState m_netSerializeState[ NetSerialize::kNumAspectSlots ];
AZ::u32 m_modifiedDataSets;
// Specific chunk for the script aspect to allow for independent updates of various parts of the aspect.
EntityScriptReplicaChunk* m_scriptReplicaChunk;
typedef std::vector<std::pair< RMI::LegacyInvocationWrapper::Ptr, RpcContext> > PendingLegacyRMIs;
typedef std::vector<std::pair< RMI::ActorInvocationWrapper::Ptr, RpcContext> > PendingActorRMIs;
//! RMIs are queued during the interval between receiving the replica, and having everything
//! we need to spawn the local entity.
PendingLegacyRMIs m_pendingLegacyRMIs;
PendingActorRMIs m_pendingActorRMIs;
WriteBufferDynamic m_masterAspectScratchBuffer;
//! Set if the server has designated us as the authority of client-delegated aspects.
bool m_isClientAspectAuthority;
//! Internal state flags. See EFlags above for details.
uint32 m_flags;
};
typedef AZStd::intrusive_ptr<EntityReplica> EntityReplicaPtr;
} // namespace GridMate
#endif // INCLUDE_GRIDMATEENTITYREPLICA_HEADER
@@ -0,0 +1,180 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "../NetworkGridmateMarshaling.h"
#include "../NetworkGridmateDebug.h"
#include "EntityReplicaSpawnParams.h"
#include "../Compatibility/GridMateRMI.h"
#include <GridMate/Serialize/CompressionMarshal.h>
namespace GridMate
{
static const unsigned int kMaxExtFlags = 8; // maximum number of extended flags supported
static const unsigned int kMaxStrLen = 255; // maximum length of strings(name, className, archetype) in spawn parameters
class CryNameMarshaler
{
public:
void Marshal(WriteBuffer& wb, const string& name)
{
AZ_Assert(name.size() <= kMaxStrLen, "String is too long");
wb.Write(static_cast<AZ::u8>(name.size()));
wb.WriteRaw(name.data(), name.size());
}
void Unmarshal(string& name, ReadBuffer& rb)
{
AZ::u8 len = 0;
rb.Read(len);
char str[kMaxStrLen];
rb.ReadRaw(str, len);
name.assign(str, str + len);
}
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
EntitySpawnParamsStorage::EntitySpawnParamsStorage()
: m_channelId(kInvalidChannelId)
, m_orientation(IDENTITY)
, m_paramsFlags(0)
{
}
EntitySpawnParamsStorage::EntitySpawnParamsStorage(const EntitySpawnParamsStorage& another)
: m_id(another.m_id)
, m_entityName(another.m_entityName)
, m_className(another.m_className)
, m_archetypeName(another.m_archetypeName)
, m_flags(another.m_flags)
, m_flagsExtended(another.m_flagsExtended)
, m_channelId(another.m_channelId)
, m_position(another.m_position)
, m_orientation(another.m_orientation)
, m_scale(another.m_scale)
, m_paramsFlags(another.m_paramsFlags)
{
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void EntitySpawnParamsStorage::Marshaler::Marshal(GridMate::WriteBuffer& wb, const EntitySpawnParamsStorage& s)
{
static_assert(kParamsFlag_Max <= BIT(4) + 1, "Using 4 bits for params flags");
static_assert(kMarshalFlag_Max <= BIT(4) + 1, "Using 4 bits for marshal flags");
auto flagsMarker = wb.InsertMarker<AZ::u8>();
AZ::u8 marshalFlags = 0;
wb.Write(s.m_id);
wb.Write(s.m_flags, VlqU32Marshaler());
wb.Write(s.m_flagsExtended);
wb.Write(s.m_channelId);
wb.Write(s.m_position);
if (s.m_orientation.IsUnit())
{
AZ::Quaternion ori(s.m_orientation.v[0], s.m_orientation.v[1], s.m_orientation.v[2], s.m_orientation.w);
wb.Write(ori, QuatCompNormMarshaler());
marshalFlags |= kMarshalFlag_OrientationNorm;
}
else
{
wb.Write(s.m_orientation);
}
if (!s.m_scale.IsEquivalent(Vec3(1.f, 1.f, 1.f)))
{
wb.Write(s.m_scale);
marshalFlags |= kMarshalFlag_HasScale;
}
wb.Write(s.m_entityName, CryNameMarshaler());
wb.Write(s.m_className, CryNameMarshaler());
if (!s.m_archetypeName.empty())
{
wb.Write(s.m_archetypeName, CryNameMarshaler());
marshalFlags |= kMarshalFlag_HasArchetype;
}
flagsMarker = (marshalFlags << 4) | (s.m_paramsFlags & 0xF); // hi 4 bits - marshal flags, low 4 bits - params flags
}
//-----------------------------------------------------------------------------
void EntitySpawnParamsStorage::Marshaler::Unmarshal(EntitySpawnParamsStorage& s, GridMate::ReadBuffer& rb)
{
AZ::u8 flags = 0;
rb.Read(flags);
AZ::u8 marshalFlags = flags >> 4; // hi 4 bits - marshal flags, low 4 bits - params flags
s.m_paramsFlags = flags & 0xF;
rb.Read(s.m_id);
rb.Read(s.m_flags, VlqU32Marshaler());
rb.Read(s.m_flagsExtended);
rb.Read(s.m_channelId);
rb.Read(s.m_position);
if (marshalFlags & kMarshalFlag_OrientationNorm)
{
AZ::Quaternion ori;
rb.Read(ori, QuatCompNormMarshaler());
s.m_orientation = Quat(ori.GetW(), ori.GetX(), ori.GetY(), ori.GetZ());
}
else
{
rb.Read(s.m_orientation);
}
s.m_scale = Vec3(1.f, 1.f, 1.f);
if (marshalFlags & kMarshalFlag_HasScale)
{
rb.Read(s.m_scale);
}
rb.Read(s.m_entityName, CryNameMarshaler());
rb.Read(s.m_className, CryNameMarshaler());
s.m_archetypeName.clear();
if (marshalFlags & kMarshalFlag_HasArchetype)
{
rb.Read(s.m_archetypeName, CryNameMarshaler());
}
}
//-----------------------------------------------------------------------------
void EntityExtraSpawnInfo::Marshaler::Marshal(GridMate::WriteBuffer& wb, const EntityExtraSpawnInfo::Ptr& v)
{
if (v)
{
EntityExtraSpawnInfo::DataBuffer::Marshaler().Marshal(wb, v->m_buffer);
}
else
{
EntityExtraSpawnInfo::DataBuffer::Marshaler().Marshal(wb, EntityExtraSpawnInfo::DataBuffer());
}
}
//-----------------------------------------------------------------------------
void EntityExtraSpawnInfo::Marshaler::Unmarshal(EntityExtraSpawnInfo::Ptr& v, GridMate::ReadBuffer& rb)
{
v = new EntityExtraSpawnInfo();
EntityExtraSpawnInfo::DataBuffer::Marshaler().Unmarshal(v->m_buffer, rb);
}
} // namespace GridMate
@@ -0,0 +1,123 @@
/*
* 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.
*
*/
#ifndef INCLUDE_GRIDMATEENTITYREPLICASPAWNPARAMS_HEADER
#define INCLUDE_GRIDMATEENTITYREPLICASPAWNPARAMS_HEADER
#pragma once
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Replica/DataSet.h>
#include "../NetworkGridMateCommon.h"
namespace GridMate
{
/*!
* Wrapper for entity spawn params, to ensure clients are able to spawn entities
* locally with correct parameters.
*/
class EntitySpawnParamsStorage
{
public:
enum ParamsFlags
{
kParamsFlag_None = 0,
kParamsFlag_IsGameRules = BIT(0), //! This entity represents the Game Rules instance.
kParamsFlag_IsLevelEntity = BIT(1), //! This entity is static, from the level.
kParamsFlag_CreatedThroughPool = BIT(2), //! Was the entity created through the entity pool?
kParamsFlag_Max
};
enum MarshalFlags
{
kMarshalFlag_HasGuid = BIT(0), //! This entity has optional GUID
kMarshalFlag_HasArchetype = BIT(1), //! This entity has archetype
kMarshalFlag_OrientationNorm = BIT(2), //! Is entity's orientation quaternion normalized
kMarshalFlag_HasScale = BIT(3), //! Entity is scaled
kMarshalFlag_Max
};
EntitySpawnParamsStorage();
EntitySpawnParamsStorage(const EntitySpawnParamsStorage& another);
EntityId m_id; //! The entity Id on the server.
string m_entityName; //! The name of the entity
string m_className; //! The entity's class name
string m_archetypeName; //! The entity's optional archetype name
AZ::u32 m_flags; //! Any entity flags
AZ::u8 m_flagsExtended; //! Extended flags
ChannelId m_channelId; //! Channel id (network owner) associated with the entity
Vec3 m_position; //! Starting position.
Quat m_orientation; //! Starting orientation.
Vec3 m_scale; //! Entity scale.
AZ::u8 m_paramsFlags; //! Internal flags (see above ParamsFlags enum).
bool IsCreatedThroughPool() const { return !!(m_paramsFlags & kParamsFlag_CreatedThroughPool); }
inline bool operator == (const EntitySpawnParamsStorage& rhs) const;
class Marshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const EntitySpawnParamsStorage& s);
void Unmarshal(EntitySpawnParamsStorage& s, GridMate::ReadBuffer& rb);
};
};
/*!
* When entities are created in CryEngine, they can optionally specify an arbitrary
* amount block of spawn data. We maintain support for this and marshal the data
* along with the replica.
*/
class EntityExtraSpawnInfo
: public _i_multithread_reference_target_t
{
public:
typedef _smart_ptr<EntityExtraSpawnInfo> Ptr;
EntityExtraSpawnInfo() {}
EntityExtraSpawnInfo(const char* sourceBuffer, uint32 sourceBufferSize)
: m_buffer(sourceBuffer, sourceBufferSize)
{
}
typedef FlexibleBuffer<1024> DataBuffer;
DataBuffer m_buffer;
struct Marshaler
{
void Marshal(GridMate::WriteBuffer& wb, const EntityExtraSpawnInfo::Ptr& v);
void Unmarshal(EntityExtraSpawnInfo::Ptr& v, GridMate::ReadBuffer& rb);
};
};
typedef GridMate::DataSet < EntityExtraSpawnInfo::Ptr, EntityExtraSpawnInfo::Marshaler >
SerializedEntityExtraSpawnInfo;
inline bool EntitySpawnParamsStorage::operator == (const EntitySpawnParamsStorage& rhs) const
{
return (m_id == rhs.m_id &&
m_flags == rhs.m_flags &&
m_flagsExtended == rhs.m_flagsExtended &&
m_channelId == rhs.m_channelId &&
m_entityName == rhs.m_entityName &&
m_className == rhs.m_className &&
m_archetypeName == rhs.m_archetypeName &&
m_paramsFlags == rhs.m_paramsFlags &&
m_position.IsEquivalent(rhs.m_position) &&
Quat::IsEquivalent(m_orientation, rhs.m_orientation));
}
} // namespace GridMate
#endif // INCLUDE_GRIDMATEENTITYREPLICASPAWNPARAMS_HEADER
@@ -0,0 +1,295 @@
/*
* 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 "CryNetwork_precompiled.h"
#include "EntityScriptReplicaChunk.h"
#include "EntityReplica.h"
#include "../NetworkGridMateSystemEvents.h"
#include "../NetworkGridmateMarshaling.h"
#include "../Compatibility/GridMateNetSerialize.h"
#include "../Compatibility/GridMateNetSerializeAspectProfiles.h"
namespace GridMate
{
////////////////////////
// EntityScriptDataSet
////////////////////////
const char* EntityScriptDataSet::GetDataSetName()
{
static int s_chunkIndex = 0;
static const char* s_nameArray[] = {
"DataSet1","DataSet2","DataSet3","DataSet4","DataSet5",
"DataSet6","DataSet7","DataSet8","DataSet9","DataSet10",
"DataSet11","DataSet12","DataSet13","DataSet14","DataSet15",
"DataSet16","DataSet17","DataSet18","DataSet19","DataSet20",
"DataSet21","DataSet22","DataSet23","DataSet24","DataSet25",
"DataSet26","DataSet27","DataSet28","DataSet29","DataSet30",
"DataSet31","DataSet32"
};
static_assert(EntityScriptReplicaChunk::k_maxScriptableDataSets <= AZ_ARRAY_SIZE(s_nameArray),"Insufficient number of names supplied to EntityScriptDataSet::GetDataSetName()");
if (s_chunkIndex > EntityScriptReplicaChunk::k_maxScriptableDataSets && EntityScriptReplicaChunk::k_maxScriptableDataSets >= 0)
{
s_chunkIndex = s_chunkIndex%EntityScriptReplicaChunk::k_maxScriptableDataSets;
}
return s_nameArray[s_chunkIndex++];
}
EntityScriptDataSet::EntityScriptDataSet()
: EntityScriptDataSetType(GetDataSetName())
, m_isEnabled(false)
{
/*
* ReplicaChunk has to declare all of its dataset at compile, however, scripts decide that at runtime.
* Thus, we create the maximum possible datasets and disable those that aren't being used.
*
* So a lot of time these datasets aren't used in scripts and thus they don't have any useful value in them.
* Marking them as default achieves not sending them on the network.
*/
MarkAsDefaultValue();
}
void EntityScriptDataSet::DispatchChangedEvent([[maybe_unused]] const TimeContext& tc)
{
SetIsEnabled(true);
static_cast<EntityScriptReplicaChunk*>(m_replicaChunk)->OnPropertyUpdate((*this));
}
void EntityScriptDataSet::SetIsEnabled(bool isEnabled)
{
m_isEnabled = isEnabled;
}
bool EntityScriptDataSet::IsEnabled() const
{
return m_isEnabled;
}
GridMate::PrepareDataResult EntityScriptDataSet::PrepareData(GridMate::EndianType endianType, AZ::u32 marshalFlags)
{
if (!IsEnabled())
{
return PrepareDataResult(false, false, false, false);
}
return EntityScriptDataSetType::PrepareData(endianType, marshalFlags);
}
void EntityScriptDataSet::SetDirty()
{
if (!IsEnabled())
{
return;
}
EntityScriptDataSetType::SetDirty();
}
/////////////////////////////
// EntityScriptReplicaChunk
/////////////////////////////
EntityScriptReplicaChunk::EntityScriptReplicaChunk()
: m_masterDataSetScratchBuffer(EndianType::BigEndian)
, m_serializerImpl(m_masterDataSetScratchBuffer)
, m_masterWriteSerializer(m_serializerImpl)
, m_localEntityId(kInvalidEntityId)
, m_enabledDataSetMask(0)
{
}
void EntityScriptReplicaChunk::OnReplicaActivate([[maybe_unused]] const GridMate::ReplicaContext& rc)
{
}
TSerialize EntityScriptReplicaChunk::FindSerializer(const char* name)
{
AZ_Error("EntityScriptReplicaChunk",!IsMarshaling(),"Trying to marshal two data sets at once.");
FRAME_PROFILER("StartMarshal",GetISystem(), PROFILE_NETWORK);
TSerialize retVal = nullptr;
if (!IsMarshaling() && name)
{
m_serializationTarget = name;
// Clear out our buffer and return the master serializer
m_masterDataSetScratchBuffer.Clear();
retVal = &m_masterWriteSerializer;
}
return retVal;
}
bool EntityScriptReplicaChunk::IsMarshaling() const
{
return !m_serializationTarget.empty();
}
bool EntityScriptReplicaChunk::CommitSerializer(const char* name, TSerialize serializer)
{
(void)name;
(void)serializer;
AZ_Error("EntityScriptReplicaChunk",IsMarshaling(),"Committing a serializer without finding it first.");
bool didMarshal = false;
if (IsMarshaling())
{
EntityScriptDataSet* scriptDataSet = FindDataSet(m_serializationTarget);
m_serializationTarget.clear();
AZ_Error("EntityScriptReplicaChunk",scriptDataSet,"Invalid SerializationTarget");
if (scriptDataSet)
{
didMarshal = true;
NetSerialize::AspectSerializeState::Marshaler& marshaler = scriptDataSet->GetMarshaler();
if (marshaler.GetStorageSize() < m_masterDataSetScratchBuffer.Size())
{
marshaler.AllocateAspectSerializationBuffer(m_masterDataSetScratchBuffer.Size());
}
if (m_masterDataSetScratchBuffer.Size() > 0)
{
FRAME_PROFILER("AspectBufferCopy",GetISystem(), PROFILE_NETWORK);
WriteBufferType writeBuffer = marshaler.GetWriteBuffer();
writeBuffer.Clear();
writeBuffer.WriteRaw(m_masterDataSetScratchBuffer.Get(),m_masterDataSetScratchBuffer.Size());
}
// Store update contents & hash. Any change willr esult in a downstream update.
NetSerialize::AspectSerializeState updatedState = scriptDataSet->Get();
bool changed = false;
{
FRAME_PROFILER("AspectBufferHash", GetISystem(), PROFILE_NETWORK);
changed = updatedState.UpdateHash(NetSerialize::HashBuffer(m_masterDataSetScratchBuffer.Get(), m_masterDataSetScratchBuffer.Size()), m_masterDataSetScratchBuffer.Size());
}
{
FRAME_PROFILER("AspectUpdate", GetISystem(), PROFILE_NETWORK);
scriptDataSet->Set(updatedState);
}
if (changed)
{
FRAME_PROFILER("AspectSentEvent", GetISystem(), PROFILE_NETWORK);
EBUS_EVENT(NetworkSystemEventBus, AspectSent, m_localEntityId, BIT(eEA_Script), m_masterDataSetScratchBuffer.Size());
}
}
}
return didMarshal;
}
void EntityScriptReplicaChunk::Synchronize()
{
// Only want to synchronize on the proxy(otherwise we might be overwriting
// good data inside of the script table that we actually want to pull out.
if (IsProxy())
{
for (int i=0; i < k_maxScriptableDataSets; ++i)
{
ReadBufferType rb = m_scriptDataSets[i].GetMarshaler().GetReadBuffer();
if (rb.Get())
{
m_enabledDataSetMask |= (1 << i);
OnPropertyUpdate(m_scriptDataSets[i]);
}
}
}
}
void EntityScriptReplicaChunk::OnPropertyUpdate(EntityScriptDataSet& dataSet)
{
ReadBufferType rb = dataSet.GetMarshaler().GetReadBuffer();
if (rb.Get())
{
{
FRAME_PROFILER(Debug::GetAspectNameByBitIndex(eEA_Script),GetISystem(),PROFILE_NETWORK);
EBUS_EVENT(NetworkSystemEventBus, AspectReceived, m_localEntityId, eEA_Script, rb.Size().GetSizeInBytesRoundUp());
}
}
}
void EntityScriptReplicaChunk::SetLocalEntityId(EntityId localEntityId)
{
m_localEntityId = localEntityId;
Synchronize();
}
EntityScriptDataSet* EntityScriptReplicaChunk::FindDataSet(const AZStd::string& valueName)
{
EntityScriptDataSet* retVal = nullptr;
DataSetIndexMapping::iterator indexIter = m_nameToIndex.find(valueName);
if (indexIter == m_nameToIndex.end())
{
for (int i = 0; i < k_maxScriptableDataSets; ++i)
{
if (!m_scriptDataSets[i].IsEnabled())
{
m_enabledDataSetMask |= (1 << i);
m_scriptDataSets[i].SetIsEnabled(true);
AZStd::pair<DataSetIndexMapping::iterator, bool> insertResult = m_nameToIndex.insert(DataSetIndexMapping::value_type(valueName,i));
indexIter = insertResult.first;
break;
}
}
}
if (indexIter != m_nameToIndex.end())
{
retVal = &m_scriptDataSets[indexIter->second];
}
return retVal;
}
void EntityScriptReplicaChunk::EnsureMapping(const AZStd::string& valueName, EntityScriptDataSet& dataSet)
{
if (!dataSet.IsEnabled())
{
DataSetIndexMapping::iterator nameIter = m_nameToIndex.find(valueName);
AZ_Error("EntityScriptReplicaChunk",nameIter == m_nameToIndex.end(),"Trying to create two script values with the same name.");
if (nameIter == m_nameToIndex.end())
{
for (int i=0; i < k_maxScriptableDataSets; ++i)
{
if (&m_scriptDataSets[i] == &dataSet)
{
m_nameToIndex.insert(DataSetIndexMapping::value_type(valueName,i));
break;
}
}
}
}
AZ_Error("EntityScriptReplicaChunk",m_nameToIndex.find(valueName) != m_nameToIndex.end() && &m_scriptDataSets[m_nameToIndex.find(valueName)->second] == &dataSet,"Given invalid DataSet for mapping to name");
}
AZ::u32 EntityScriptReplicaChunk::CalculateDirtyDataSetMask(MarshalContext& marshalContext)
{
return (m_enabledDataSetMask & GridMate::ReplicaChunk::CalculateDirtyDataSetMask(marshalContext));
}
}
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#ifndef CRYINCLUE_CRYSCRIPTSYSTEM_NETBINDING_H
#define CRYINCLUE_CRYSCRIPTSYSTEM_NETBINDING_H
#include "../NetworkGridmateMarshaling.h"
#include "../Compatibility/GridMateRMI.h"
#include "../Compatibility/GridMateNetSerialize.h"
#include "../Compatibility/GridMateNetSerializeAspectProfiles.h"
#include "Serialization/NetScriptSerialize.h"
#include "EntityReplicaSpawnParams.h"
namespace GridMate
{
typedef DataSet<NetSerialize::AspectSerializeState, NetSerialize::AspectSerializeState::Marshaler> EntityScriptDataSetType;
class EntityScriptDataSet
: public EntityScriptDataSetType
{
private:
static const char* GetDataSetName();
public:
GM_CLASS_ALLOCATOR(EntityScriptDataSet);
EntityScriptDataSet();
void DispatchChangedEvent(const TimeContext& tc) override;
void SetIsEnabled(bool isEnabled);
bool IsEnabled() const;
GridMate::PrepareDataResult PrepareData(GridMate::EndianType endianType, AZ::u32 marshalFlags);
void SetDirty();
private:
bool m_isEnabled;
};
class EntityScriptReplicaChunk
: public GridMate::ReplicaChunk
, public Serialization::INetScriptMarshaler
{
public:
static const int k_maxScriptableDataSets = GM_MAX_DATASETS_IN_CHUNK;
private:
friend class EntityScriptDataSet;
friend class EntityReplica;
typedef AZStd::unordered_map<AZStd::string, int> DataSetIndexMapping;
public:
GM_CLASS_ALLOCATOR(EntityScriptReplicaChunk);
EntityScriptReplicaChunk();
~EntityScriptReplicaChunk() = default;
//////////////////////////////////////////////////////////////////////
//! GridMate::ReplicaChunk overrides.
static const char* GetChunkName() { return "EntityScriptReplicaChunk"; }
void UpdateChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override { (void)rc; }
void UpdateFromChunk(const GridMate::ReplicaContext& rc) override { (void)rc; }
bool IsReplicaMigratable() override { return false; }
//////////////////////////////////////////////////////////////////////
TSerialize FindSerializer(const char* name) override;
bool CommitSerializer(const char* name, TSerialize serializer) override;
bool IsMarshaling() const;
int GetMaxServerProperties() const override { return k_maxScriptableDataSets; }
protected:
AZ::u32 CalculateDirtyDataSetMask(MarshalContext& marshalContext);
private:
void Synchronize();
void OnPropertyUpdate(EntityScriptDataSet& dataSet);
void SetLocalEntityId(EntityId localEntityId);
EntityScriptDataSet* FindDataSet(const AZStd::string& valueName);
void EnsureMapping(const AZStd::string& valueName, EntityScriptDataSet& dataSet);
EntityScriptDataSet m_scriptDataSets[k_maxScriptableDataSets];
DataSetIndexMapping m_nameToIndex;
AZStd::string m_serializationTarget;
GridMate::WriteBufferDynamic m_masterDataSetScratchBuffer;
NetSerialize::EntityNetSerializerCollectState m_serializerImpl;
CSimpleSerialize<NetSerialize::EntityNetSerializerCollectState> m_masterWriteSerializer;
EntityId m_localEntityId;
AZ::u32 m_enabledDataSetMask;
};
}
#endif