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
+67
View File
@@ -0,0 +1,67 @@
#
# 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.
#
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
ly_add_target(
NAME CryNetwork.Static STATIC
NAMESPACE Legacy
FILES_CMAKE
crynetwork_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
${pal_dir}
BUILD_DEPENDENCIES
PUBLIC
Legacy::CryCommon
AZ::GridMate
)
ly_add_target(
NAME CryNetwork ${PAL_TRAIT_MONOLITHIC_DRIVEN_LIBRARY_TYPE}
NAMESPACE Legacy
FILES_CMAKE
crynetwork_shared_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
${pal_dir}
BUILD_DEPENDENCIES
PUBLIC
Legacy::CryNetwork.Static
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME CryNetwork.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Legacy
FILES_CMAKE
crynetwork_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
.
${pal_dir}
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Legacy::CryNetwork.Static
)
ly_add_googletest(
NAME Legacy::CryNetwork.Tests
)
endif()
+109
View File
@@ -0,0 +1,109 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : dll entry point
#include "CryNetwork_precompiled.h"
#include <INetwork.h>
#include <GridMate/NetworkGridMate.h>
#include <IEngineModule.h>
#include <CryExtension/ICryFactory.h>
#include <CryExtension/Impl/ClassWeaver.h>
#include <AzCore/Module/Environment.h>
//////////////////////////////////////////////////////////////////////////
struct CSystemEventListner_Network
: public ISystemEventListener
{
public:
virtual void OnSystemEvent(ESystemEvent event, [[maybe_unused]] UINT_PTR wparam, [[maybe_unused]] UINT_PTR lparam)
{
switch (event)
{
case ESYSTEM_EVENT_LEVEL_POST_UNLOAD:
{
STLALLOCATOR_CLEANUP;
break;
}
}
}
};
static CSystemEventListner_Network g_system_event_listener_network;
//////////////////////////////////////////////////////////////////////////
class CEngineModule_CryNetwork
: public IEngineModule
{
CRYINTERFACE_SIMPLE(IEngineModule)
CRYGENERATE_SINGLETONCLASS(CEngineModule_CryNetwork, "EngineModule_CryNetwork", 0x7dc5c3b8bb374063, 0xa29ac2d6dd718e0f)
//////////////////////////////////////////////////////////////////////////
virtual const char* GetName() const {
return "CryNetwork";
};
virtual const char* GetCategory() const { return "CryEngine"; };
//////////////////////////////////////////////////////////////////////////
virtual bool Initialize(SSystemGlobalEnvironment& env, [[maybe_unused]] const SSystemInitParams& initParams)
{
ISystem* pSystem = env.pSystem;
AZ::AllocatorInstance<GridMate::GridMateAllocator>::Create();
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Create();
CNetwork* pNetwork = new CNetwork;
int ncpu = env.pi.numCoresAvailableToProcess;
if (!pNetwork->Init(ncpu))
{
pNetwork->Release();
return false;
}
pSystem->GetISystemEventDispatcher()->RegisterListener(&g_system_event_listener_network);
env.pNetwork = pNetwork;
CryLogAlways("[Network Version]: "
#if defined(_RELEASE)
"RELEASE "
#elif defined(_PROFILE)
"PROFILE "
#else
"DEBUG "
#endif
);
return true;
}
};
CRYREGISTER_SINGLETON_CLASS(CEngineModule_CryNetwork)
CEngineModule_CryNetwork::CEngineModule_CryNetwork()
{
};
CEngineModule_CryNetwork::~CEngineModule_CryNetwork()
{
AZ::AllocatorInstance<GridMate::GridMateAllocatorMP>::Destroy();
AZ::AllocatorInstance<GridMate::GridMateAllocator>::Destroy();
};
#if !defined(AZ_MONOLITHIC_BUILD)
#include <CrtDebugStats.h>
#endif
+111
View File
@@ -0,0 +1,111 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Russian resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
#ifdef _WIN32
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // Russian resources
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// German (Germany) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU)
#ifdef _WIN32
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000904b0"
BEGIN
VALUE "CompanyName", "Amazon.com, Inc."
VALUE "FileVersion", "1, 0, 0, 1"
VALUE "LegalCopyright", "Portions of this file Copyright (c) Amazon.com, Inc. or its affiliates. All Rights Reserved. Original file Copyright (c) Crytek GMBH. Used under license by Amazon.com, Inc. and its affiliates."
VALUE "ProductName", "Lumberyard"
VALUE "ProductVersion", "1, 0, 0, 1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x9, 1200
END
END
#endif // German (Germany) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
@@ -0,0 +1,14 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "CryNetwork_precompiled.h"
@@ -0,0 +1,30 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#pragma once
#include <stdio.h>
#include <stdarg.h>
#include <algorithm>
#include <map>
#include <memory>
#include <vector>
#include <platform.h>
#include <INetwork.h>
#include <StlUtils.h>
#pragma intrinsic(memcpy)
@@ -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
@@ -0,0 +1,22 @@
/*
* 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 <AzTest/AzTest.h>
#include "GridMate/Compatibility/GridMateNetSerializeAspectProfiles.h"
TEST(GridMateAspectProfilesTest, InstantiationTest_FT)
{
GridMate::NetSerialize::EntityAspectProfiles profiles;
EXPECT_EQ(profiles.GetAspectProfile(0), GridMate::NetSerialize::kUnsetAspectProfile);
}
@@ -0,0 +1,14 @@
/*
* 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 <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
@@ -0,0 +1,41 @@
#
# 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.
#
set(FILES
resource.h
GridMate/NetworkGridMate.h
GridMate/NetworkGridMate.cpp
GridMate/NetworkGridMateEntityEventBus.h
GridMate/NetworkGridMateSessionEvents.h
GridMate/NetworkGridMateSessionEvents.cpp
GridMate/NetworkGridMateSystemEvents.cpp
GridMate/NetworkGridmateDebug.h
GridMate/NetworkGridMateCommon.h
GridMate/NetworkGridMateProfiling.h
GridMate/NetworkGridMateSystemEvents.h
GridMate/NetworkGridmateDebug.cpp
GridMate/NetworkGridmateMarshaling.h
GridMate/Compatibility/GridMateNetSerialize.cpp
GridMate/Compatibility/GridMateNetSerialize.h
GridMate/Compatibility/GridMateNetSerializeAspectProfiles.cpp
GridMate/Compatibility/GridMateNetSerializeAspectProfiles.h
GridMate/Compatibility/GridMateNetSerializeAspects.inl
GridMate/Compatibility/GridMateRMI.cpp
GridMate/Compatibility/GridMateRMI.h
GridMate/Replicas/EntityReplica.cpp
GridMate/Replicas/EntityReplica.h
GridMate/Replicas/EntityScriptReplicaChunk.cpp
GridMate/Replicas/EntityScriptReplicaChunk.h
GridMate/Replicas/EntityReplicaSpawnParams.cpp
GridMate/Replicas/EntityReplicaSpawnParams.h
CryNetwork_precompiled.h
CryNetwork_precompiled.cpp
)
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
CryNetwork.cpp
)
@@ -0,0 +1,16 @@
#
# 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.
#
set(FILES
Tests/GridMateAspectProfilesTest.cpp
Tests/test_Main.cpp
CryNetwork.cpp
)
+25
View File
@@ -0,0 +1,25 @@
/*
* 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.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#define VS_VERSION_INFO 1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif