Merge branch 'main' into non-uniform-scale-compatibility

This commit is contained in:
greerdv
2021-05-12 22:19:33 +01:00
419 changed files with 14774 additions and 42309 deletions
@@ -52,8 +52,6 @@
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/IO/RemoteStorageDrive.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Physics/Utils.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Platform/PlatformDefaults.h>
@@ -66,7 +64,6 @@
#include <AzFramework/TargetManagement/TargetManagementComponent.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Driller/RemoteDrillerInterface.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
@@ -197,7 +194,6 @@ namespace AzFramework
ApplicationRequests::Bus::Handler::BusConnect();
AZ::UserSettingsFileLocatorBus::Handler::BusConnect();
NetSystemRequestBus::Handler::BusConnect();
}
Application::~Application()
@@ -207,7 +203,6 @@ namespace AzFramework
Stop();
}
NetSystemRequestBus::Handler::BusDisconnect();
AZ::UserSettingsFileLocatorBus::Handler::BusDisconnect();
ApplicationRequests::Bus::Handler::BusDisconnect();
@@ -285,13 +280,6 @@ namespace AzFramework
m_pimpl.reset();
/* The following line of code is a temporary fix.
* GridMate's ReplicaChunkDescriptor is stored in a global environment variable 'm_globalDescriptorTable'
* which does not get cleared when Application shuts down. We need to un-reflect here to clear ReplicaChunkDescriptor
* so that ReplicaChunkDescriptor::m_vdt doesn't get flooded when we repeatedly instantiate Application in unit tests.
*/
AZ::ReflectionEnvironment::GetReflectionManager()->RemoveReflectContext<NetworkContext>();
// Free any memory owned by the command line container.
m_commandLine = CommandLine();
@@ -320,8 +308,6 @@ namespace AzFramework
azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
azrtti_typeid<AzFramework::FileTag::ExcludeFileComponent>(),
azrtti_typeid<AzFramework::NetBindingComponent>(),
azrtti_typeid<AzFramework::NetBindingSystemComponent>(),
azrtti_typeid<AzFramework::TransformComponent>(),
azrtti_typeid<AzFramework::SceneSystemComponent>(),
azrtti_typeid<AzFramework::AzFrameworkConfigurationSystemComponent>(),
@@ -457,9 +443,6 @@ namespace AzFramework
void Application::CreateReflectionManager()
{
ComponentApplication::CreateReflectionManager();
// Setup NetworkContext
AZ::ReflectionEnvironment::GetReflectionManager()->AddReflectContext<NetworkContext>();
}
////////////////////////////////////////////////////////////////////////////
@@ -479,19 +462,6 @@ namespace AzFramework
return uuid;
}
////////////////////////////////////////////////////////////////////////////
NetworkContext* Application::GetNetworkContext()
{
NetworkContext* result = nullptr;
if (auto reflectionManager = AZ::ReflectionEnvironment::GetReflectionManager())
{
result = reflectionManager->GetReflectContext<NetworkContext>();
}
return result;
}
void Application::ResolveEnginePath(AZStd::string& engineRelativePath) const
{
AZ::IO::FixedMaxPath fullPath = m_engineRoot / engineRelativePath;
@@ -21,7 +21,6 @@
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/fixed_string.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzFramework/API/ApplicationAPI.h>
@@ -49,7 +48,6 @@ namespace AzFramework
: public AZ::ComponentApplication
, public AZ::UserSettingsFileLocatorBus::Handler
, public ApplicationRequests::Bus::Handler
, public NetSystemRequestBus::Handler
{
public:
// Base class for platform specific implementations of the application.
@@ -138,11 +136,6 @@ namespace AzFramework
// Convenience function that should be called instead of the standard exit() function to ensure platform requirements are met.
static void Exit(int errorCode) { ApplicationRequests::Bus::Broadcast(&ApplicationRequests::TerminateOnError, errorCode); }
//////////////////////////////////////////////////////////////////////////
//! NetSystemEventBus::Handler
//////////////////////////////////////////////////////////////////////////
NetworkContext* GetNetworkContext() override;
protected:
/**
@@ -22,8 +22,6 @@
#include <AzFramework/Entity/GameEntityContextComponent.h>
#include <AzFramework/FileTag/FileTagComponent.h>
#include <AzFramework/Input/System/InputSystemComponent.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
#include <AzFramework/Render/GameIntersectorComponent.h>
#include <AzFramework/Scene/SceneSystemComponent.h>
#include <AzFramework/Script/ScriptComponent.h>
@@ -42,8 +40,6 @@ namespace AzFramework
AzFramework::AssetCatalogComponent::CreateDescriptor(),
AzFramework::CustomAssetTypeComponent::CreateDescriptor(),
AzFramework::FileTag::ExcludeFileComponent::CreateDescriptor(),
AzFramework::NetBindingComponent::CreateDescriptor(),
AzFramework::NetBindingSystemComponent::CreateDescriptor(),
AzFramework::TransformComponent::CreateDescriptor(),
AzFramework::NonUniformScaleComponent::CreateDescriptor(),
AzFramework::GameEntityContextComponent::CreateDescriptor(),
@@ -878,15 +878,15 @@ namespace AzFramework
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<TransformComponent, AZ::Component, NetBindable>()
serializeContext->ClassDeprecate("NetBindable", "{80206665-D429-4703-B42E-94434F82F381}");
serializeContext->Class<TransformComponent, AZ::Component>()
->Version(4, &TransformComponentVersionConverter)
->Field("Parent", &TransformComponent::m_parentId)
->Field("Transform", &TransformComponent::m_worldTM)
->Field("LocalTransform", &TransformComponent::m_localTM)
->Field("ParentActivationTransformMode", &TransformComponent::m_parentActivationTransformMode)
->Field("IsStatic", &TransformComponent::m_isStatic)
->Field("InterpolatePosition", &TransformComponent::m_interpolatePosition)
->Field("InterpolateRotation", &TransformComponent::m_interpolateRotation)
;
}
@@ -17,7 +17,6 @@
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/EBus/Event.h>
#include <AzFramework/Network/NetBindable.h>
namespace AzToolsFramework
{
@@ -41,10 +40,9 @@ namespace AzFramework
, public AZ::TransformBus::Handler
, public AZ::TransformNotificationBus::Handler
, private AZ::TransformHierarchyInformationBus::Handler
, public NetBindable
{
public:
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, NetBindable, AZ::TransformInterface);
AZ_COMPONENT(TransformComponent, AZ::TransformComponentTypeId, AZ::TransformInterface);
friend class AzToolsFramework::Components::TransformComponent;
@@ -218,11 +216,5 @@ namespace AzFramework
bool m_parentActive = false; ///< Keeps track of the state of the parent entity.
bool m_onNewParentKeepWorldTM = true; ///< If set, recompute localTM instead of worldTM when parent becomes active.
bool m_isStatic = false; ///< If true, the transform is static and doesn't move while entity is active.
//! @deprecated
//! @{
AZ::InterpolationMode m_interpolatePosition = AZ::InterpolationMode::NoInterpolation;
AZ::InterpolationMode m_interpolateRotation = AZ::InterpolationMode::NoInterpolation;
//! @}
};
} // namespace AZ
@@ -1,146 +0,0 @@
/*
* 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.
*
*/
#pragma once
#ifndef AZFRAMEWORK_NETWORK_DYNAMICSERIALIZABLEFIELDMARSHALER_H
#define AZFRAMEWORK_NETWORK_DYNAMICSERIALIZABLEFIELDMARSHALER_H
#include <AzCore/IO/ByteContainerStream.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Serialization/DynamicSerializableField.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
namespace GridMate
{
/**
* Marshaler for DynamicSerializableField, contains a template param for allocating the memory buffer that it's going to use to write to.
*/
template<size_t BufferSize>
class DynamicSerializableFieldMarshaler
{
public:
DynamicSerializableFieldMarshaler()
: m_serializeContext(nullptr)
{
EBUS_EVENT_RESULT(m_serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
}
// Mainly here for unit test purposes.
DynamicSerializableFieldMarshaler(AZ::SerializeContext* context)
: m_serializeContext(context)
{
}
AZ_FORCE_INLINE void Marshal(WriteBuffer& wb, const AZ::DynamicSerializableField& value) const
{
AZ_Error("DynamicSerializableFieldMarshaler", m_serializeContext, "Unknown SerializationContext. Aborting Marshal attempt.\n");
if (m_serializeContext)
{
Marshaler<AZ::u32> sizeMarshaler;
Marshaler<AZ::Uuid> uuidMarshaler;
AZStd::vector<AZ::u8> memoryBuffer(BufferSize);
// Start buffer in write mode.
AZ::IO::ByteContainerStream<decltype(memoryBuffer)> memoryStream(&memoryBuffer);
AZ::u32 bufferSize = 0;
if (m_serializeContext->FindClassData(value.m_typeId))
{
if (AZ::Utils::SaveObjectToStream(memoryStream, AZ::DataStream::StreamType::ST_BINARY, value.m_data, value.m_typeId, m_serializeContext))
{
bufferSize = static_cast<AZ::u32>(memoryStream.GetCurPos());
}
}
else
{
AZ_Error("DynamicSerializableFieldMarshaler", !value.IsValid(), "Could not save object to stream because type Id %s is not registered with the serializer.\n", value.m_typeId.ToString<AZStd::string>().c_str());
}
sizeMarshaler.Marshal(wb, bufferSize);
uuidMarshaler.Marshal(wb, value.m_typeId);
wb.WriteRaw(memoryBuffer.data(), bufferSize);
}
}
AZ_FORCE_INLINE void Unmarshal(AZ::DynamicSerializableField& value, ReadBuffer& rb) const
{
value.DestroyData(m_serializeContext);
AZ_Error("DynamicSerializableFieldMarshaler", m_serializeContext, "Unknown SerializationContext. Aborting Unmarshal attempt.\n");
if (m_serializeContext)
{
Marshaler<AZ::u32> sizeMarshaler;
AZ::u32 marshaledBufferSize = 0;
sizeMarshaler.Unmarshal(marshaledBufferSize, rb);
AZ_Assert(marshaledBufferSize <= BufferSize,"Trying to deserialize too much data for the allocated buffer size\n");
// Marshal out the TypeId so I can use it on the receiving end.
Marshaler<AZ::Uuid> uuidMarshaler;
uuidMarshaler.Unmarshal(value.m_typeId, rb);
if (marshaledBufferSize > 0)
{
// See if there's some nice way to use this.
// - Can't make this a member variable, since both these methods are const.
AZStd::vector<AZ::u8> memoryBuffer(marshaledBufferSize + 1);
if (rb.ReadRaw(memoryBuffer.data(), marshaledBufferSize))
{
// Start buffer in read mode.
AZ::IO::ByteContainerStream<decltype(memoryBuffer)> memoryStream(&memoryBuffer);
// we'll use a strict filter here, one that doesn't allow deserialization to automatically start loading assets, nor tolerates errors.
// this is becuase this is coming from a network interface and should always be error-free.
AZ::ObjectStream::FilterDescriptor filterToUse(&AZ::Data::AssetFilterNoAssetLoading, AZ::ObjectStream::FILTERFLAG_STRICT);
value.m_data = AZ::Utils::LoadObjectFromStream(memoryStream, m_serializeContext, &value.m_typeId, filterToUse);
}
}
}
}
private:
AZ::SerializeContext* m_serializeContext;
};
/**
* Specialized marshaler for AZ::DynamicSerializableField
* Mainly here to hook into the DataSet Marshaler auto detection logic, and provide a default buffer size for the actual marshaler
*/
template<>
class Marshaler<AZ::DynamicSerializableField>
: public DynamicSerializableFieldMarshaler<1024>
{
public:
Marshaler()
{
}
// Mainly here for unit test purposes.
Marshaler(AZ::SerializeContext* context)
: DynamicSerializableFieldMarshaler(context)
{
}
};
}
#endif
@@ -1,76 +0,0 @@
/*
* 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.
*
*/
#pragma once
#ifndef AZFRAMEWORK_NETWORK_ENTITYIDMARSHALER_H
#define AZFRAMEWORK_NETWORK_ENTITYIDMARSHALER_H
#include <AzCore/Component/EntityId.h>
#include <AzCore/Component/NamedEntityId.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
namespace GridMate
{
template<>
class Marshaler<AZ::EntityId>
{
public:
AZ_TYPE_INFO_LEGACY( Marshaler, "{23F4722F-D104-4E30-9342-43F4DDD1894D}", AZ::EntityId );
void Marshal(GridMate::WriteBuffer& wb, const AZ::EntityId& source) const
{
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Marshal(wb,static_cast<AZ::u64>(source));
}
void Unmarshal(AZ::EntityId& target, GridMate::ReadBuffer& rb) const
{
AZ::u64 id = 0;
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Unmarshal(id,rb);
target = AZ::EntityId(id);
}
};
template<>
class Marshaler<AZ::NamedEntityId>
{
public:
void Marshal(GridMate::WriteBuffer& wb, const AZ::NamedEntityId& source) const
{
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Marshal(wb, static_cast<AZ::u64>(source));
Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Marshal(wb, source.GetName());
}
void Unmarshal(AZ::NamedEntityId& target, GridMate::ReadBuffer& rb) const
{
AZ::u64 id = 0;
Marshaler<AZ::u64> idMarshaler;
idMarshaler.Unmarshal(id, rb);
AZStd::string name;
Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Unmarshal(name, rb);
target = AZ::NamedEntityId(AZ::EntityId(id), name);
}
};
}
#endif
@@ -1,187 +0,0 @@
/*
* 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 <AzFramework/Network/InterestManagerComponent.h>
#include <AzCore/Memory/AllocationRecords.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <GridMate/GridMate.h>
#include <GridMate/Replica/Interest/BitmaskInterestHandler.h>
#include <GridMate/Replica/Interest/InterestManager.h>
#include <GridMate/Replica/Interest/ProximityInterestHandler.h>
using namespace GridMate;
namespace AzFramework
{
void InterestManagerComponent::Reflect(AZ::ReflectContext* context)
{
if (context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<InterestManagerComponent, AZ::Component>()
->Version(1);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<InterestManagerComponent>(
"InterestManagerComponent", "Interest manager instance")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b));
}
}
// We need to register the chunk types for each handler here at reflect time
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(ProximityInterestChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::ProximityInterestChunk>();
}
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(BitmaskInterestChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<GridMate::BitmaskInterestChunk>();
}
}
}
void InterestManagerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("InterestManager", 0x79993873));
}
void InterestManagerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("InterestManager", 0x79993873));
}
InterestManagerComponent::InterestManagerComponent()
: m_im(nullptr)
, m_bitmaskHandler(nullptr)
, m_proximityHandler(nullptr)
, m_session(nullptr)
{
}
void InterestManagerComponent::Activate()
{
InterestManagerRequestsBus::Handler::BusConnect();
NetBindingSystemEventsBus::Handler::BusConnect();
AZ::SystemTickBus::Handler::BusConnect();
}
void InterestManagerComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
NetBindingSystemEventsBus::Handler::BusDisconnect();
InterestManagerRequestsBus::Handler::BusDisconnect();
ShutdownInterestManager();
}
void InterestManagerComponent::OnSystemTick()
{
if (m_im && m_im->IsReady())
{
m_im->Update();
}
}
InterestManager* InterestManagerComponent::GetInterestManager()
{
return m_im.get();
}
BitmaskInterestHandler* InterestManagerComponent::GetBitmaskInterest()
{
return m_bitmaskHandler.get();
}
ProximityInterestHandler* InterestManagerComponent::GetProximityInterest()
{
return m_proximityHandler.get();
}
void InterestManagerComponent::OnNetworkSessionActivated(GridSession* session)
{
AZ_Assert(m_session == nullptr, "Already bound to the session");
AZ_TracePrintf("AzFramework", "Interest manager hooked up to the session '%s'\n", session->GetId().c_str());
m_session = session;
m_session->GetReplicaMgr()->SetAutoBroadcast(false);
InitInterestManager();
}
void InterestManagerComponent::OnNetworkSessionDeactivated(GridSession* session)
{
if (m_session && m_session == session)
{
AZ_TracePrintf("AzFramework", "Interest manager disconnected from the session '%s'\n", session ? session->GetId().c_str() : "nullptr");
if (m_session->GetReplicaMgr())
{
m_session->GetReplicaMgr()->SetAutoBroadcast(true);
}
m_session = nullptr;
ShutdownInterestManager();
}
else
{
AZ_Warning("AzFramework", false, "Interest manager was never active for session '%s'\n", session ? session->GetId().c_str() : "nullptr");
}
}
void InterestManagerComponent::InitInterestManager()
{
AZ_Assert(m_im == nullptr, "Already initialized interest manager");
m_im = AZStd::make_unique<InterestManager>();
InterestManagerDesc desc;
desc.m_rm = m_session->GetReplicaMgr();
m_im->Init(desc);
m_bitmaskHandler = AZStd::make_unique<BitmaskInterestHandler>();
m_im->RegisterHandler(m_bitmaskHandler.get());
m_proximityHandler = AZStd::make_unique<ProximityInterestHandler>();
m_im->RegisterHandler(m_proximityHandler.get());
InterestManagerEventsBus::Broadcast(
&InterestManagerEventsBus::Events::OnInterestManagerActivate, m_im.get());
}
void InterestManagerComponent::ShutdownInterestManager()
{
if (m_im)
{
InterestManagerEventsBus::Broadcast(
&InterestManagerEventsBus::Events::OnInterestManagerDeactivate, m_im.get());
m_im->UnregisterHandler(m_bitmaskHandler.get());
m_im->UnregisterHandler(m_proximityHandler.get());
m_bitmaskHandler = nullptr;
m_proximityHandler = nullptr;
m_im = nullptr;
}
}
} // namespace AzFramework
@@ -1,120 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
#define AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <GridMate/Session/Session.h>
namespace GridMate
{
class InterestManager;
class GridSession;
class BitmaskInterestHandler;
class ProximityInterestHandler;
}
namespace AzFramework
{
class InterestManagerSystemRequests
: public AZ::EBusTraits
{
public:
virtual ~InterestManagerSystemRequests() {}
// Returns interest manager instance
virtual GridMate::InterestManager* GetInterestManager() = 0;
// Returns interest manager instance
virtual GridMate::BitmaskInterestHandler* GetBitmaskInterest() = 0;
// Returns interest manager instance
virtual GridMate::ProximityInterestHandler* GetProximityInterest() = 0;
};
// Interface Bus
using InterestManagerRequestsBus = AZ::EBus<InterestManagerSystemRequests>;
class InterestManagerEvents
: public AZ::EBusTraits
{
public:
virtual ~InterestManagerEvents() {}
// Called when interest manager is initialized and ready to use
virtual void OnInterestManagerActivate(GridMate::InterestManager* im) { (void)im; }
// Called when interest manager is deactivated
virtual void OnInterestManagerDeactivate(GridMate::InterestManager* im) { (void)im; }
};
// Interface Bus
using InterestManagerEventsBus = AZ::EBus<InterestManagerEvents>;
/**
* Interest manager component.
* When component is activated replicas will go through interest filtering before being sent to other peers
*/
class InterestManagerComponent
: public AZ::Component
, public AZ::SystemTickBus::Handler
, public InterestManagerRequestsBus::Handler
, public NetBindingSystemEventsBus::Handler
{
public:
AZ_COMPONENT(InterestManagerComponent, "{55371FA7-2942-4A3C-A3EA-27FF2C7DB6C5}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
InterestManagerComponent();
void Activate() override;
void Deactivate() override;
protected:
// AZ::SystemTickBus::Listener interface implementation
void OnSystemTick() override;
// InterestManagerSystemRequests implementation
GridMate::InterestManager* GetInterestManager() override;
GridMate::BitmaskInterestHandler* GetBitmaskInterest() override;
GridMate::ProximityInterestHandler* GetProximityInterest() override;
// SessionEventBus
void OnNetworkSessionActivated(GridMate::GridSession* session) override;
void OnNetworkSessionDeactivated(GridMate::GridSession* session) override;
void InitInterestManager();
void ShutdownInterestManager();
// Interest handlers
AZStd::unique_ptr<GridMate::InterestManager> m_im;
AZStd::unique_ptr<GridMate::BitmaskInterestHandler> m_bitmaskHandler;
AZStd::unique_ptr<GridMate::ProximityInterestHandler> m_proximityHandler;
GridMate::GridSession* m_session; ///< currently bound session
private:
InterestManagerComponent(const InterestManagerComponent&) = delete; //Cannot use default due to unique_ptr.
};
} // namesapce AzFramework
#endif // AZFRAMEWORK_NET_INTERESTMANAGER_COMPONENT_H
@@ -1,111 +0,0 @@
/*
* 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 <AzFramework/Network/NetBindable.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AzFramework
{
////////////////
// NetBindable
////////////////
NetBindable::NetBindable()
: m_isSyncEnabled(true)
{
}
NetBindable::~NetBindable()
{
if (m_chunk)
{
// NetBindable is a base class for handlers for replica chunks, so we have to clear the handler since this object is about to go away
m_chunk->SetHandler(nullptr);
m_chunk = nullptr;
}
}
GridMate::ReplicaChunkPtr NetBindable::GetNetworkBinding()
{
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
m_chunk = netContext->CreateReplicaChunk(azrtti_typeid(this));
netContext->Bind(this, m_chunk, NetworkContextBindMode::Authoritative);
return m_chunk;
}
return nullptr;
}
void NetBindable::SetNetworkBinding (GridMate::ReplicaChunkPtr chunk)
{
m_chunk = chunk;
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
netContext->Bind(this, m_chunk, NetworkContextBindMode::NonAuthoritative);
}
}
void NetBindable::UnbindFromNetwork()
{
if (m_chunk)
{
// NetworkContext-reflected chunks need access to the handler when they are being destroyed, so we won't null handler in here
m_chunk = nullptr;
}
}
void NetBindable::NetInit()
{
NetworkContext* netContext = nullptr;
NetSystemRequestBus::BroadcastResult(netContext, &NetSystemRequestBus::Events::GetNetworkContext);
AZ_Assert(netContext, "Cannot bind objects to the network with no NetworkContext");
if (netContext)
{
netContext->Bind(this, nullptr, NetworkContextBindMode::NonAuthoritative);
}
}
void NetBindable::Reflect(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NetBindable>()
->Field("m_isSyncEnabled", &NetBindable::m_isSyncEnabled);
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<NetBindable>(
"Network Bindable", "Network-bindable components are synchronized over the network.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Networking")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->DataElement(AZ::Edit::UIHandlers::Default, &NetBindable::m_isSyncEnabled, "Bind To network", "Enable binding to the network.");
}
}
}
}
@@ -1,799 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDABLE_H
#define AZFRAMEWORK_NET_BINDABLE_H
#include <AzCore/Component/EntityId.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/list.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <GridMate/Replica/ReplicaChunkInterface.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
/*
* Including common GridMate marshallers.
* Otherwise, users of NetBindable/NetworkContext have to find and include them themselves.
*/
#include <AzFramework/Network/EntityIdMarshaler.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UtilityMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
namespace AZ
{
class ReflectContext;
namespace Internal
{
template <class FieldType>
class AzFrameworkNetBindableFieldContainer;
}
}
namespace AzFramework
{
using GridMate::DataSetBase;
using GridMate::DataSet;
using GridMate::Marshaler;
using GridMate::BasicThrottle;
using GridMate::RpcBase;
using GridMate::TimeContext;
using GridMate::RpcContext;
using GridMate::RpcDefaultTraits;
enum class NetworkContextBindMode
{
Authoritative,
NonAuthoritative
};
/**
* Components that want to be synchronized over the network should implement NetBindable.
* The NetBindable interface is obtained via AZ_RTTI so components need to make sure to
* declare NetBindable as a base class in their AZ_RTTI declaration (or AZ_COMPONENT declaration),
* as well as to declare both AZ::Component and NetBindable as base classes in the reflection.
*
* For example, here is how to mark a component for network replication in its class declaration:
*
* class TestFieldComponent
* : public AZ::Component
* , public AzFramework::NetBindable
* {
* public:
* AZ_COMPONENT(TestFieldComponent, "{DD02A926-F6B3-4820-9587-62EED9EEBB3F}", NetBindable);
*
* static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
* {
* required.push_back(AZ_CRC("ReplicaChunkService"));
* }
*
* Note, you should declare a dependency on NetBindingComponent as it is done above with "ReplicaChunkService."
* NetBindingComponent is required for an entity to be considered for network replication and replicate your NetBindable-components.
*/
class NetBindable
: public GridMate::ReplicaChunkInterface
{
public:
AZ_RTTI(NetBindable, "{80206665-D429-4703-B42E-94434F82F381}");
NetBindable();
virtual ~NetBindable();
void NetInit();
//! Called during network binding on the master. The default implementation will use the
//! NetworkContext to create a chunk. User implementations should create and return a new binding.
virtual GridMate::ReplicaChunkPtr GetNetworkBinding();
//! Called during network binding on proxies.
virtual void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk);
//! Called when network is unbound. Implementations should release their references to the binding, if they held a reference.
virtual void UnbindFromNetwork();
static void Reflect(AZ::ReflectContext* reflection);
template <class DataType, typename MarshalerType = Marshaler<DataType>, typename ThrottlerType = BasicThrottle<DataType> >
class Field;
template <class DataType, class InterfaceType, void (InterfaceType::*)(const DataType&, const TimeContext&), typename MarshalerType = Marshaler<DataType>, typename ThrottlerType = BasicThrottle<DataType> >
class BoundField;
template <typename ... Args>
class Rpc;
inline bool IsSyncEnabled() const { return m_isSyncEnabled; }
//! Can be used to disabled net sync on a per component basis
inline void SetSyncEnabled(bool enabled) { m_isSyncEnabled = enabled; }
protected:
bool m_isSyncEnabled;
GridMate::ReplicaChunkPtr m_chunk = nullptr;
};
class NetBindableFieldBase
{
public:
virtual ~NetBindableFieldBase() = default;
virtual void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) = 0;
};
/**
* \brief NetBindable provides a simplified network interface to mark a member variable inside AZ::Component
* as a network field that will be replicated by GridMate.
*
* \tparam DataType data type of the field, can be either a common C++ type or a custom type
* \tparam MarshalerType optional, marshaler type that provides custom marshal and unmarshal logic, i.e. how to write @DataType to the network and back, see @GridMate::Marshaler
* \tparam ThrottlerType optional, throttler provides the ability to detect if a value is to be considered changed significantly enough for GridMate to replicate its state, see @GridMate::BasicThrottle
*
* Example:
*
* class TestFieldComponent : public AZ::Component , public AzFramework::NetBindable
* {
* public:
* Field<int> m_testInt;
*
* And it must be reflected to SerializeContext _and_ NetworkContext:
*
* void TestFieldComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestFieldComponent, AZ::Component, AzFramework::NetBindable>()
* ->Field("Test Int", &TestFieldComponent::m_testInt)
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestFieldComponent>()
* ->Field("Test Int", &TestFieldComponent::m_testInt);
* }
* }
*
* Then you can simply write to it as it was an integer:
*
* m_testInt = 3;
* // or
* m_testInt = *m_testInt + 1;
*/
template <class DataType, typename MarshalerType, typename ThrottlerType>
class NetBindable::Field
: public NetBindableFieldBase
{
friend class AZ::Internal::AzFrameworkNetBindableFieldContainer<NetBindable::Field<DataType, MarshalerType, ThrottlerType> >;
public:
using DataSetType = DataSet<DataType, MarshalerType, ThrottlerType>;
using ValueType = DataType;
explicit Field(const DataType& value = DataType())
: m_dataSet(nullptr)
, m_value(value)
{}
~Field() override = default;
/*
* Disabling copy and move constructors in order to allow for a common use of fields, for example:
* m_field = m_field + 1;
*/
Field (const Field& other) = delete;
Field (Field&& other) = delete;
Field& operator= (const Field& other) = delete;
Field& operator= (Field&& other) = delete;
const DataType& Get() const
{
return m_dataSet ? m_dataSet->Get() : m_value;
}
virtual operator const DataType&() const
{
return Get();
}
virtual const DataType& operator*() const
{
return Get();
}
virtual Field& operator=(const DataType& val)
{
if (m_dataSet)
{
m_dataSet->Set(val);
}
else
{
m_value = val;
}
return *this;
}
virtual Field& operator=(const DataType&& val)
{
if (m_dataSet)
{
m_dataSet->Set(AZStd::forward<const DataType>(val));
}
else
{
m_value = AZStd::move(val);
}
return *this;
}
void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) override
{
BindDataSet(static_cast<DataSetType*>(dataSet), mode);
}
static void ConstructDataSet(void* mem, const char* name)
{
new (mem) DataSetType(name, DataType(), MarshalerType(), ThrottlerType());
}
static void DestructDataSet(void* mem)
{
DataSetType* dataSet = reinterpret_cast<DataSetType*>(mem);
dataSet->~DataSetType();
}
protected:
template <class DST>
void BindDataSet(DST* dataSet, NetworkContextBindMode mode)
{
if (m_dataSet)
{
m_value = m_dataSet->Get();
}
m_dataSet = dataSet;
if (m_dataSet)
{
if (mode == NetworkContextBindMode::Authoritative)
{
/*
* If we are binding Field<> or BoundField<> on a component of an authoritative entity,
* then we want to bring over the value of the field in the component. This occurs during GetNetworkBinding().
*
* Whereas on a client's (non-authoritative entities and their components) dataSet already has the desired value
* and should not be overwritten here.
*/
m_dataSet->Set(AZStd::move(m_value));
}
m_value = DataType();
}
}
DataType* CacheValue()
{
if (m_dataSet)
{
m_value = m_dataSet->Get();
}
return &m_value;
}
const DataType& GetCachedValue() const
{
return m_value;
}
private:
DataSet<DataType, MarshalerType, ThrottlerType>* m_dataSet;
DataType m_value;
};
/**
* \brief An extension of @NetBindable::Field with an ability to invoke a callback whenever the value changes on both authoritative and non-authoritative components.
* Or in other terms, on both the server and clients (when GridMate is setup to run in server-authoritative mode).
*
* \tparam DataType data type, same as @NetBindable::Field
* \tparam InterfaceType Component type class that holds this @BoundField
* \tparam FuncPtr member function pointer to the callback to invoke when this value is updated on non-authoritative components.
* \tparam MarshalerType optional, same as @NetBindable::Field
* \tparam ThrottlerType optional, same as @NetBindable::Field
*
* Example:
*
* BoundField<int, TestBoundFieldComponent, &TestBoundFieldComponent::OnBoundFieldChanged> m_testInt;
*
* And it must be reflected to SerializeContext _and_ NetworkContext just like @NetBindable::Field
*
* void TestFieldComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestFieldComponent, AZ::Component, AzFramework::NetBindable>()
* ->Field("Test Int", &TestFieldComponent::m_testInt)
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestFieldComponent>()
* ->Field("Test Int", &TestFieldComponent::m_testInt);
* }
* }
*/
template <class DataType, class InterfaceType, void (InterfaceType::* FuncPtr)(const DataType&, const TimeContext&), typename MarshalerType, typename ThrottlerType>
class NetBindable::BoundField
: public NetBindable::Field<DataType, MarshalerType, ThrottlerType>
{
using BaseClass = NetBindable::Field<DataType, MarshalerType, ThrottlerType>;
friend class AZ::Internal::AzFrameworkNetBindableFieldContainer<NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> >;
public:
AZ_TYPE_INFO_LEGACY(BoundField, "{5151CEAF-6AC0-45D7-AEDF-8B6C46CE07B9}", DataType, InterfaceType, MarshalerType, ThrottlerType);
using DataSetType = typename DataSet<DataType, MarshalerType, ThrottlerType>::template BindInterface<InterfaceType, FuncPtr, GridMate::DataSetInvokeEverywhereTraits>;
explicit BoundField(const DataType& value = DataType())
: BaseClass(value)
{}
~BoundField() override = default;
/*
* Disabling copy and move constructors in order to allow for a common use of fields, for example:
* m_field = m_field + 1;
*/
BoundField (const BoundField& other) = delete;
BoundField (BoundField&& other) = delete;
BoundField& operator= (const BoundField& other) = delete;
BoundField& operator= (BoundField&& other) = delete;
operator DataType() const
{
return BaseClass::Get();
}
const DataType& operator*() const override
{
return BaseClass::Get();
}
BaseClass& operator=(const DataType& val) override
{
BaseClass::operator=(val);
return *this;
}
BaseClass& operator=(const DataType&& val) override
{
BaseClass::operator=(val);
return *this;
}
void Bind(DataSetBase* dataSet, NetworkContextBindMode mode) override
{
BaseClass::BindDataSet(static_cast<DataSetType*>(dataSet), mode);
}
static void ConstructDataSet(void* mem, const char* name)
{
new (mem) DataSetType(name, DataType(), MarshalerType(), ThrottlerType());
}
static void DestructDataSet(void* mem)
{
DataSetType* dataSet = reinterpret_cast<DataSetType*>(mem);
dataSet->~DataSetType();
}
};
class NetBindableRpcBase
{
public:
virtual ~NetBindableRpcBase() = default;
virtual void Bind(RpcBase* rpc) = 0;
virtual void Bind(NetBindable* handler) = 0;
};
/**
* \brief NetBindable::Rpc::Binder should be used for any RPC in a NetBindable that you want
* to be able to call remotely. If the object is not network bound, RPC
* calls will dispatch directly, as if the object was authoritative.
*
* \tparam Args any custom parameters for the remote procedure calls.
*
* Here is an example:
*
* // callback
* bool OnRpc(float value, const GridMate::RpcContext& rc);
*
* // Rpc declaration
* Rpc<float>::Binder<TestRPCComponent, &TestRPCComponent::OnRpc> m_testRpc;
*
* Rpc needs to be reflected in NetworkContext like this:
*
* void TestRPCComponent::Reflect(AZ::ReflectContext* context)
* {
* if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
* {
* serialize->Class<TestRPCComponent, AZ::Component, AzFramework::NetBindable>()
* ->Version(1);
* }
*
* if (AzFramework::NetworkContext* net = azrtti_cast<AzFramework::NetworkContext*>(context))
* {
* net->Class<TestRPCComponent>()
* ->RPC("Test RPC", &TestRPCComponent::m_testRpc);
* }
* }
*
* It can be invoked as if it was a method:
*
* m_testRpc(deltaTime);
*/
template <typename ... Args>
class NetBindable::Rpc
{
public:
/**
* \brief Binds rpc callback to a pointer to member function of AZ::Component derived from AzFramework::NetBindable
* See @NetBindable::Rpc
*/
template<class InterfaceType, bool (InterfaceType::* FuncPtr)(Args..., const RpcContext&), class Traits = RpcDefaultTraits>
class Binder
: public NetBindableRpcBase
{
friend class NetworkContext;
public:
using BindInterfaceType = typename GridMate::Rpc<GridMate::RpcArg<Args>...>::template BindInterface<InterfaceType, FuncPtr, Traits>;
Binder()
: m_rpc(nullptr)
, m_instance(nullptr)
{}
void Bind(RpcBase* rpc) override
{
m_rpc = static_cast<BindInterfaceType*>(rpc);
m_instance = nullptr;
}
void Bind(NetBindable* bindable) override
{
m_instance = static_cast<InterfaceType*>(bindable);
m_rpc = nullptr;
}
template <typename ... CallArgs>
void operator()(CallArgs&& ... args)
{
AZ_Assert(m_instance || m_rpc, "Cannot call an RPC without either a local instance or a network bound handler, did you forget to register with NetworkContext()?");
if (m_rpc) // connected to network
{
(*m_rpc)(AZStd::forward<CallArgs>(args) ...);
}
else if (m_instance) // local dispatch
{
(*m_instance.*FuncPtr)(AZStd::forward<CallArgs>(args) ..., RpcContext());
}
}
protected:
static void ConstructRpc(void* mem, const char* name)
{
new (mem) BindInterfaceType(name);
}
static void DestructRpc(void*) { }
private:
BindInterfaceType* m_rpc;
InterfaceType* m_instance;
};
Rpc() = delete;
};
} // namespace AzFramework
namespace AZ
{
AZ_TYPE_INFO_TEMPLATE_WITH_NAME(AzFramework::NetBindable::Field, "Field", "{00D56FA7-F8BD-402B-97FB-0E2599897056}", AZ_TYPE_INFO_CLASS, AZ_TYPE_INFO_TYPENAME, AZ_TYPE_INFO_TYPENAME);
namespace Internal
{
template <class FieldType>
class AzFrameworkNetBindableFieldContainer
: public SerializeContext::IDataContainer
{
using ValueType = typename FieldType::ValueType;
public:
AzFrameworkNetBindableFieldContainer()
{
m_classElement.m_name = GetDefaultElementName();
m_classElement.m_nameCrc = GetDefaultElementNameCrc();
m_classElement.m_dataSize = sizeof(ValueType);
m_classElement.m_offset = 0;
m_classElement.m_azRtti = GetRttiHelper<ValueType>();
m_classElement.m_flags = AZStd::is_pointer<ValueType>::value ? SerializeContext::ClassElement::FLG_POINTER : 0;
m_classElement.m_genericClassInfo = SerializeGenericTypeInfo<ValueType>::GetGenericInfo();
m_classElement.m_typeId = SerializeGenericTypeInfo<ValueType>::GetClassTypeId();
m_classElement.m_editData = nullptr;
}
/// Returns the element generic (offsets are mostly invalid 0xbad0ffe0, there are exceptions). Null if element with this name can't be found.
virtual const SerializeContext::ClassElement* GetElement(AZ::u32 elementNameCrc) const override
{
if (elementNameCrc == m_classElement.m_nameCrc)
{
return &m_classElement;
}
return nullptr;
}
bool GetElement(SerializeContext::ClassElement& classElement, const SerializeContext::DataElement& dataElement) const override
{
if (dataElement.m_nameCrc == m_classElement.m_nameCrc)
{
classElement = m_classElement;
return true;
}
return false;
}
/// Enumerate elements in the array
virtual void EnumElements(void* instance, const ElementCB& cb) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
// We can't mess with the internal storage of the dataset safely, so we copy it into
// the field's local value cache temporarily, then hand that to the callback
// This will modify the local value cache, but that shouldn't matter as it will never
// be used as long as a dataset is bound
// If this turns out to be a perf problem due to copies of complex types, then
// the easy solution is to get DataSets to expose a pointer to their underlying
// data storage, and then we can return a pointer to that and modify it directly
// if the field is bound to the network
ValueType* valPtr = field->CacheValue();
cb(valPtr, m_classElement.m_typeId, m_classElement.m_genericClassInfo ? m_classElement.m_genericClassInfo->GetClassData() : nullptr, &m_classElement);
// Ensure that the dataset is updated if changes happened
*field = *valPtr;
}
void EnumTypes(const ElementTypeCB& cb) override
{
cb(m_classElement.m_typeId, &m_classElement);
}
/// Return number of elements in the container.
virtual size_t Size(void*) const override
{
return 1;
}
/// Returns the capacity of the container. Returns 0 for objects without fixed capacity.
virtual size_t Capacity(void* instance) const override
{
(void)instance;
return 1;
}
/// Returns true if elements pointers don't change on add/remove. If false you MUST enumerate all elements.
virtual bool IsStableElements() const override { return true; }
/// Returns true if the container is fixed size, otherwise false.
virtual bool IsFixedSize() const override { return true; }
/// Returns if the container is fixed capacity, otherwise false
virtual bool IsFixedCapacity() const override { return true; }
/// Returns true if the container is a smart pointer.
virtual bool IsSmartPointer() const override { return true; }
/// Returns true if the container elements can be addressed by index, otherwise false.
virtual bool CanAccessElementsByIndex() const override { return false; }
/// Reserve element
virtual void* ReserveElement(void* instance, const SerializeContext::ClassElement*) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = ValueType();
return field->CacheValue(); // return the local value, should be accurate as the field will be unbound at serialization time
}
/// Get an element's address by its index (called before the element is loaded).
virtual void* GetElementByIndex(void*, const SerializeContext::ClassElement*, size_t) override
{
return nullptr;
}
/// Store element
virtual void StoreElement(void* instance, void*) override
{
// force store the value again, just in case the field is bound to a dataset
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = field->GetCachedValue();
}
/// Remove element in the container.
virtual bool RemoveElement(void* instance, const void*, SerializeContext*) override
{
FieldType* field = reinterpret_cast<FieldType*>(instance);
*field = ValueType();
return false; // you can't remove element from this container.
}
/// Remove elements (removed array of elements) regardless if the container is Stable or not (IsStableElements)
virtual size_t RemoveElements(void* instance, const void**, size_t, SerializeContext*) override
{
RemoveElement(instance, nullptr, nullptr);
return 0; // you can't remove elements from this container.
}
/// Clear elements in the instance.
virtual void ClearElements(void* instance, SerializeContext*) override
{
RemoveElement(instance, nullptr, nullptr);
}
SerializeContext::ClassElement m_classElement; ///< Generic class element covering as must as possible of the element (offset, and some other fields are invalid)
};
}
template <class DataType, typename MarshalerType, typename ThrottlerType>
struct SerializeGenericTypeInfo< AzFramework::NetBindable::Field<DataType, MarshalerType, ThrottlerType> >
{
typedef typename AzFramework::NetBindable::Field<DataType, MarshalerType, ThrottlerType> ContainerType;
class GenericClassNetBindableField
: public GenericClassInfo
{
public:
AZ_TYPE_INFO(GenericClassNetBindableField, "{C1D4DD97-5DD7-42ED-969C-7435F27F5D8C}");
GenericClassNetBindableField()
: m_classData{ SerializeContext::ClassData::Create<ContainerType>("AzFramework::NetBindable::Field", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, &m_containerStorage) }
{
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetTemplatedTypeId(size_t) override
{
return SerializeGenericTypeInfo<DataType>::GetClassTypeId();
}
const Uuid& GetSpecializedTypeId() const override
{
return azrtti_typeid<ContainerType>();
}
const Uuid& GetGenericTypeId() const override
{
return TYPEINFO_Uuid();
}
const Uuid& GetLegacySpecializedTypeId() const override
{
return AZ::AzTypeInfo<ContainerType>::template Uuid<AZ::PointerRemovedTypeIdTag>();
}
void Reflect(SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ContainerType>::CreateAny);
if (GenericClassInfo* containerGenericClassInfo = m_containerStorage.m_classElement.m_genericClassInfo)
{
containerGenericClassInfo->Reflect(serializeContext);
}
}
}
protected:
Internal::AzFrameworkNetBindableFieldContainer<ContainerType> m_containerStorage;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericClassNetBindableField;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ContainerType>();
}
static const Uuid& GetClassTypeId()
{
return GetGenericInfo()->GetClassData()->m_typeId;
}
};
template <class DataType, class InterfaceType, void (InterfaceType::* FuncPtr)(const DataType&, const AzFramework::TimeContext&), typename MarshalerType, typename ThrottlerType>
struct SerializeGenericTypeInfo< typename AzFramework::NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> >
{
typedef typename AzFramework::NetBindable::BoundField<DataType, InterfaceType, FuncPtr, MarshalerType, ThrottlerType> ContainerType;
class GenericClassNetBindableBoundField
: public GenericClassInfo
{
public:
AZ_TYPE_INFO(GenericClassNetBindableBoundField, "{EFD64FE7-9432-401A-B7A1-1767F4C5A7F0}");
GenericClassNetBindableBoundField()
: m_classData{ SerializeContext::ClassData::Create<ContainerType>("AzFramework::NetBindable::BoundField", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, &m_containerStorage) }
{
}
SerializeContext::ClassData* GetClassData() override
{
return &m_classData;
}
size_t GetNumTemplatedArguments() override
{
return 1;
}
const Uuid& GetTemplatedTypeId(size_t) override
{
return SerializeGenericTypeInfo<DataType>::GetClassTypeId();
}
const Uuid& GetSpecializedTypeId() const override
{
return azrtti_typeid<ContainerType>();
}
const Uuid& GetGenericTypeId() const override
{
return TYPEINFO_Uuid();
}
const Uuid& GetLegacySpecializedTypeId() const override
{
return AZ::AzTypeInfo<ContainerType>::template Uuid<AZ::PointerRemovedTypeIdTag>();
}
void Reflect(SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ContainerType>::CreateAny);
if (GenericClassInfo* containerGenericClassInfo = m_containerStorage.m_classElement.m_genericClassInfo)
{
containerGenericClassInfo->Reflect(serializeContext);
}
}
}
protected:
Internal::AzFrameworkNetBindableFieldContainer<ContainerType> m_containerStorage;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericClassNetBindableBoundField;
static ClassInfoType* GetGenericInfo()
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ContainerType>();
}
static const Uuid& GetClassTypeId()
{
return GetGenericInfo()->GetClassData()->m_typeId;
}
};
}
#endif // AZFRAMEWORK_NET_BINDABLE_H
#pragma once
@@ -1,287 +0,0 @@
/*
* 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 <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindable.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Network/NetBindingComponentChunk.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
namespace AzFramework
{
void NetBindingComponent::Reflect(AZ::ReflectContext* reflection)
{
NetBindable::Reflect(reflection);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<NetBindingComponent, AZ::Component>()
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<NetBindingComponent>(
"Network Binding", "The Network Binding component marks an entity as able to be replicated across the network")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Networking")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/NetBinding.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/NetBinding.png")
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-network-binding.html")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c));
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(reflection);
if (behaviorContext)
{
behaviorContext->EBus<NetBindingHandlerBus>("NetBindingHandlerBus")
->Event("IsEntityBoundToNetwork", &NetBindingHandlerBus::Events::IsEntityBoundToNetwork)
->Event("IsEntityAuthoritative", &NetBindingHandlerBus::Events::IsEntityAuthoritative)
// Desired, but currently unsupported events.
// Seems to be an unsupported type(AZ::u16)
//->Event("SetReplicaPriority", &NetBindingHandlerBus::Events::SetReplicaPriority)
//->Event("GetReplicaPriority", &NetBindingHandlerBus::Events::GetReplicaPriority)
;
}
// We also need to register the chunk type, and this would be a good time to do so.
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(NetBindingComponentChunk::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<AzFramework::NetBindingComponentChunk>();
}
}
NetBindingComponent::NetBindingComponent()
: m_isLevelSliceEntity(false)
{
}
void NetBindingComponent::Activate()
{
NetBindingHandlerBus::Handler::BusConnect(GetEntityId());
if (!IsEntityBoundToNetwork())
{
bool shouldBind = false;
NetBindingSystemBus::BroadcastResult( shouldBind, &NetBindingSystemBus::Events::ShouldBindToNetwork);
if (shouldBind)
{
BindToNetwork(nullptr);
}
else
{
/*
* This is the Editor path. We still need to call NetBindable::NetInit() in order
* to initialize NetworkContext Fields and RPCs, so that they behave as
* authoritative in game editor mode. Without this call RPCs callbacks won't invoke inside the Editor.
* For example:
*
* static void Reflect(...)
* {
* NetworkContext->Class<MyNetworkComponent>()->RPC("my rpc", &MyNetworkComponent::m_myRpc);
* }
* ...
* m_myRpc(); // <--- will not invoke the callback inside the Editor unless NetInit() is called below.
*/
for (Component* component : GetEntity()->GetComponents())
{
if (NetBindable* netBindable = azrtti_cast<NetBindable*>(component))
{
netBindable->NetInit();
}
}
}
}
}
void NetBindingComponent::Deactivate()
{
NetBindingHandlerBus::Handler::BusDisconnect();
if (IsEntityBoundToNetwork())
{
static_cast<NetBindingComponentChunk*>(m_chunk.get())->SetBinding(nullptr);
if (m_chunk->IsMaster())
{
m_chunk->GetReplica()->Destroy();
}
m_chunk = nullptr;
}
}
bool NetBindingComponent::IsEntityBoundToNetwork()
{
return m_chunk && m_chunk->GetReplica();
}
bool NetBindingComponent::IsEntityAuthoritative()
{
return !m_chunk || m_chunk->IsMaster();
}
void NetBindingComponent::BindToNetwork(GridMate::ReplicaPtr bindTo)
{
AZ_Assert(!IsEntityBoundToNetwork(), "We shouldn't be bound to the network if the network is just starting!");
if (bindTo)
{
NetBindingComponentChunkPtr bindingChunk = bindTo->FindReplicaChunk<NetBindingComponentChunk>();
AZ_Assert(bindingChunk, "Can't find NetBindingComponentChunk!");
m_chunk = bindingChunk;
bindingChunk->SetBinding(this);
GridMate::Replica* replica = bindingChunk->GetReplica();
size_t nChunks = replica->GetNumChunks();
size_t nBindings = bindingChunk->m_bindMap.Get().size();
AZ_Assert(nChunks == nBindings, "Number of chunks received is not the same as the size of the bind map!");
nBindings = AZ::GetMin(nBindings, nChunks);
for (size_t i = 0; i < nBindings; ++i)
{
AZ::ComponentId bindToId = bindingChunk->m_bindMap.Get()[i];
if (bindToId != AZ::InvalidComponentId)
{
AZ::Component* component = GetEntity()->FindComponent(bindToId);
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
AZ_Assert(netBindable, "Can't find net bindable component with id %llu to be bound to chunk type %s!", bindToId, replica->GetChunkByIndex(i)->GetDescriptor()->GetChunkName());
if (netBindable && netBindable->IsSyncEnabled())
{
netBindable->SetNetworkBinding(replica->GetChunkByIndex(i));
}
}
}
}
else
{
GridMate::ReplicaPtr replica = GridMate::Replica::CreateReplica(GetEntity()->GetName().c_str());
NetBindingComponentChunk* chunk = GridMate::CreateReplicaChunk<NetBindingComponentChunk>();
m_chunk = chunk;
chunk->SetBinding(this);
replica->AttachReplicaChunk(chunk);
chunk->m_bindMap.Modify([&](AZStd::vector<AZ::ComponentId>& bindMap)
{
// Mark the chunks already in the replica as non-components.
bindMap.resize(replica->GetNumChunks(), AZ::InvalidComponentId);
// Collect the bindings and add the to the replica
AZ::Entity* entity = GetEntity();
for (Component* component : entity->GetComponents())
{
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
if (netBindable && netBindable->IsSyncEnabled())
{
GridMate::ReplicaChunkPtr bindingChunk = netBindable->GetNetworkBinding();
if (bindingChunk)
{
bindMap.push_back(component->GetId());
replica->AttachReplicaChunk(bindingChunk);
}
}
}
return true;
});
// Add replica to session replica manager (may be deferred)
NetBindingSystemBus::Broadcast( &NetBindingSystemBus::Events::AddReplicaMaster, GetEntity(), replica);
}
}
void NetBindingComponent::UnbindFromNetwork()
{
if (m_chunk)
{
for (Component* component : GetEntity()->GetComponents())
{
NetBindable* netBindable = azrtti_cast<NetBindable*>(component);
if (netBindable && netBindable->IsSyncEnabled())
{
netBindable->UnbindFromNetwork();
}
}
NetBindingComponentChunkPtr chunk = static_cast<NetBindingComponentChunk*>(m_chunk.get());
chunk->SetBinding(nullptr);
m_chunk = nullptr;
if (chunk->IsProxy())
{
EntityContextId contextId = EntityContextId::CreateNull();
EntityIdContextQueryBus::EventResult( contextId, GetEntityId(), &EntityIdContextQueryBus::Events::GetOwningContextId);
if (contextId.IsNull())
{
delete GetEntity();
}
else if (!IsLevelSliceEntity())
{
NetBindingSystemBus::Broadcast( &NetBindingSystemBus::Events::UnbindGameEntity, GetEntityId(), m_sliceInstanceId);
}
}
}
}
void NetBindingComponent::MarkAsLevelSliceEntity()
{
AZ_Assert(!IsEntityBoundToNetwork(), "MarkAsLevelSliceEntity() has to be called before the entity is bound to the network!");
m_isLevelSliceEntity = true;
}
void NetBindingComponent::SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
m_sliceInstanceId = sliceInstanceId;
}
void NetBindingComponent::RequestEntityChangeOwnership(GridMate::PeerId peerId)
{
if (m_chunk && m_chunk->GetReplica())
{
m_chunk->GetReplica()->RequestChangeOwnership(peerId);
}
}
void NetBindingComponent::SetReplicaPriority(GridMate::ReplicaPriority replicaPriority)
{
if (m_chunk)
{
m_chunk->SetPriority(replicaPriority);
}
}
GridMate::ReplicaPriority NetBindingComponent::GetReplicaPriority() const
{
if (m_chunk && m_chunk->GetReplica())
{
return m_chunk->GetReplica()->GetPriority();
}
else
{
AZ_Error("NetBindingComponent",false,"Trying to gather ReplicaPriority without having a Replica.");
return GridMate::k_replicaPriorityLowest;
}
}
bool NetBindingComponent::IsLevelSliceEntity() const
{
return m_isLevelSliceEntity;
}
} // namespace AzFramework
@@ -1,85 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDING_COMPONENT_H
#define AZFRAMEWORK_NET_BINDING_COMPONENT_H
#include <AzCore/Component/Component.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
namespace AzFramework
{
/**
* NetBindingComponent enables network synchronization for the entity.
* It works in conjunction with NetBindingComponentChunk and NetBindingSystemComponent
* to perform network binding and notifies other components on the entity to bind
* their ReplicaChunks via the NetBindable interface.
*
* Entities bound to proxy replicas will be automatically destroyed when they are
* unbound from the network.
*/
class NetBindingComponent
: public AZ::Component
, public NetBindingHandlerBus::Handler
{
friend class NetBindingComponentChunk;
public:
AZ_COMPONENT(NetBindingComponent, "{E9CA5D63-ED2D-4B59-B3C4-EBCD4A0013E4}", NetBindingHandlerInterface);
NetBindingComponent();
protected:
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ReplicaChunkService", 0xf86b88a8));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ReplicaChunkService", 0xf86b88a8));
}
///////////////////////////////////////////////////////////////////////
// AZ::Component
static void Reflect(AZ::ReflectContext* reflection);
void Activate() override;
void Deactivate() override;
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
// NetBindingHandlerBus::Handler
void BindToNetwork(GridMate::ReplicaPtr bindTo) override;
void UnbindFromNetwork() override;
bool IsEntityBoundToNetwork() override;
bool IsEntityAuthoritative() override;
void MarkAsLevelSliceEntity() override;
void SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) override;
void RequestEntityChangeOwnership(GridMate::PeerId peerId = GridMate::InvalidReplicaPeerId) override;
void SetReplicaPriority(GridMate::ReplicaPriority replicaPriority) override;
GridMate::ReplicaPriority GetReplicaPriority() const override;
///////////////////////////////////////////////////////////////////////
//! Returns if the entity belongs to the level slice for binding purposes.
bool IsLevelSliceEntity() const;
//! Points to the NetBindingComponentChunk counterpart.
GridMate::ReplicaChunkPtr m_chunk;
bool m_isLevelSliceEntity;
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_COMPONENT_H
#pragma once
@@ -1,254 +0,0 @@
/*
* 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 <AzFramework/Network/NetBindingComponentChunk.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Network/NetBindingEventsBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Slice/SliceEntityBus.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/UuidMarshal.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/IO/ByteContainerStream.h>
namespace AzFramework
{
NetBindingComponentChunk::SpawnInfo::SpawnInfo()
: m_runtimeEntityId(AZ::EntityId::InvalidEntityId)
, m_owningContextId(UnspecifiedNetBindingContextSequence)
, m_staticEntityId(AZ::EntityId::InvalidEntityId)
, m_sliceInstanceId(UnspecifiedSliceInstanceId)
, m_sliceAssetId(UnspecifiedSliceInstanceId, 0)
{
}
bool NetBindingComponentChunk::SpawnInfo::operator==(const SpawnInfo& rhs)
{
return m_owningContextId == rhs.m_owningContextId
&& m_runtimeEntityId == rhs.m_runtimeEntityId
&& m_staticEntityId == rhs.m_staticEntityId
&& m_serializedState == rhs.m_serializedState
&& m_sliceAssetId == rhs.m_sliceAssetId;
}
bool NetBindingComponentChunk::SpawnInfo::ContainsSerializedState() const
{
return !m_serializedState.empty();
}
void NetBindingComponentChunk::SpawnInfo::Marshaler::Marshal(GridMate::WriteBuffer& wb, const SpawnInfo& data)
{
wb.Write(data.m_owningContextId, GridMate::VlqU32Marshaler());
wb.Write(data.m_runtimeEntityId);
bool useSerializedState = data.ContainsSerializedState();
wb.Write(useSerializedState);
if (useSerializedState)
{
wb.Write(data.m_serializedState);
}
else
{
wb.Write(data.m_sliceAssetId);
wb.Write(data.m_staticEntityId);
wb.Write(data.m_sliceInstanceId);
}
}
void NetBindingComponentChunk::SpawnInfo::Marshaler::Unmarshal(SpawnInfo& data, GridMate::ReadBuffer& rb)
{
rb.Read(data.m_owningContextId, GridMate::VlqU32Marshaler());
rb.Read(data.m_runtimeEntityId);
bool hasSerializedState = false;
rb.Read(hasSerializedState);
if (hasSerializedState)
{
rb.Read(data.m_serializedState);
}
else
{
rb.Read(data.m_sliceAssetId);
rb.Read(data.m_staticEntityId);
rb.Read(data.m_sliceInstanceId);
}
}
NetBindingComponentChunk::NetBindingComponentChunk()
: m_bindingComponent(nullptr)
, m_spawnInfo("SpawnInfo")
, m_bindMap("ComponentBindMap")
{
m_spawnInfo.SetMaxIdleTime(0.f);
m_bindMap.SetMaxIdleTime(0.f);
}
void NetBindingComponentChunk::OnReplicaActivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
if (IsMaster())
{
// Get and store entity spawn data
AZ_Assert(m_bindingComponent, "Entity binding is invalid!");
m_spawnInfo.Modify([&](SpawnInfo& spawnInfo)
{
spawnInfo.m_runtimeEntityId = static_cast<AZ::u64>(m_bindingComponent->GetEntity()->GetId());
bool isProceduralEntity = true;
AZ::SliceComponent::SliceInstanceAddress sliceInfo;
EntityContextId contextId = EntityContextId::CreateNull();
const AZ::EntityId bindingComponentEntityId = m_bindingComponent->GetEntityId();
EntityIdContextQueryBus::EventResult(contextId, bindingComponentEntityId,
&EntityIdContextQueryBus::Events::GetOwningContextId);
if (!contextId.IsNull())
{
EBUS_EVENT_RESULT(spawnInfo.m_owningContextId, NetBindingSystemBus, GetCurrentContextSequence);
SliceEntityRequestBus::EventResult(sliceInfo, bindingComponentEntityId,
&SliceEntityRequestBus::Events::GetOwningSlice);
bool isDynamicSliceEntity = sliceInfo.IsValid();
isProceduralEntity = !m_bindingComponent->IsLevelSliceEntity() && !isDynamicSliceEntity;
}
if (isProceduralEntity)
{
// write cloning info
AZ::SerializeContext* sc = nullptr;
EBUS_EVENT_RESULT(sc, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(sc, "Can't find SerializeContext!");
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8>> spawnDataStream(&spawnInfo.m_serializedState);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&spawnDataStream, *sc, AZ::DataStream::ST_BINARY);
objStream->WriteClass(m_bindingComponent->GetEntity());
objStream->Finalize();
}
else
{
// write slice info
if (sliceInfo.IsValid())
{
AZ::Data::AssetId sliceAssetId = sliceInfo.GetReference()->GetSliceAsset().GetId();
spawnInfo.m_sliceAssetId = AZStd::make_pair(sliceAssetId.m_guid, sliceAssetId.m_subId);
}
if (sliceInfo.GetInstance())
{
spawnInfo.m_sliceInstanceId = sliceInfo.GetInstance()->GetId();
}
AZ::EntityId staticEntityId;
EBUS_EVENT_RESULT(staticEntityId, NetBindingSystemBus, GetStaticIdFromEntityId, m_bindingComponent->GetEntity()->GetId());
spawnInfo.m_staticEntityId = static_cast<AZ::u64>(staticEntityId);
}
return true;
});
}
else
{
AZ::EntityId runtimeEntityId(m_spawnInfo.Get().m_runtimeEntityId);
NetBindingContextSequence owningContextId = m_spawnInfo.Get().m_owningContextId;
//TODO Move to Filter Hook
// Reject and cancel sessions with duplicate MachineIds?
// Reject and cancel sessions with duplicate entity ID creation requests?
//Check MachineId collision
bool collision = AZ::Entity::GetProcessSignature() == (m_spawnInfo.Get().m_runtimeEntityId & 0xFFFFFFFF);
AZ_Error("GridMate", !collision, "Replica received with duplicate Entity Machine IDs. Ignoring");
if (!collision)
{
//Check EntityID collision
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, runtimeEntityId);
/*
* Only false if no machine ID collision and no entity ID collision
* And the entity is already active, it's possible the entity already exists in deactivated state as a cache mechanism
*/
collision = (entity != nullptr) && (entity->GetState() == AZ::Entity::State::Active);
}
/**
* Special case - static entities should not count as duplicates.
* Static entities are loaded with the level and will be bounded here.
*/
if (collision)
{
AZ::EntityId staticEntityId;
EBUS_EVENT_RESULT(staticEntityId, NetBindingSystemBus, GetStaticIdFromEntityId, runtimeEntityId);
if (staticEntityId == runtimeEntityId)
{
collision = false;
}
}
if (!collision) //Ignore duplicate runtime entity IDs
{
if (m_spawnInfo.Get().ContainsSerializedState())
{
// Spawn the entity from stream input data
AZ::IO::MemoryStream spawnData(m_spawnInfo.Get().m_serializedState.data(), m_spawnInfo.Get().m_serializedState.size());
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromStream, spawnData, runtimeEntityId, GetReplicaId(), owningContextId);
}
else
{
NetBindingSliceContext spawnContext;
spawnContext.m_contextSequence = owningContextId;
spawnContext.m_sliceAssetId = AZ::Data::AssetId(m_spawnInfo.Get().m_sliceAssetId.first, m_spawnInfo.Get().m_sliceAssetId.second);
spawnContext.m_runtimeEntityId = runtimeEntityId;
spawnContext.m_staticEntityId = AZ::EntityId(m_spawnInfo.Get().m_staticEntityId);
spawnContext.m_sliceInstanceId = m_spawnInfo.Get().m_sliceInstanceId;
EBUS_EVENT(NetBindingSystemBus, SpawnEntityFromSlice, GetReplicaId(), spawnContext);
}
}
else //Fail early to prevent unnecessary spawning of duplicate entity IDs
{
//Misconfiguration or potential cheating/DoS?
AZ_Warning("NetBinding", false, "Received duplicate Entity ID %llu. Ignoring.", runtimeEntityId);
}
}
}
void NetBindingComponentChunk::OnReplicaDeactivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
if (m_bindingComponent)
{
m_bindingComponent->UnbindFromNetwork();
}
}
bool NetBindingComponentChunk::AcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc)
{
bool result = true;
if (m_bindingComponent)
{
EBUS_EVENT_ID_RESULT(result, m_bindingComponent->GetEntityId(), NetBindingEventsBus, OnEntityAcceptChangeOwnership, requestor, rc);
}
return result;
}
void NetBindingComponentChunk::OnReplicaChangeOwnership(const GridMate::ReplicaContext& rc)
{
if (m_bindingComponent)
{
EBUS_EVENT_ID(m_bindingComponent->GetEntityId(), NetBindingEventsBus, OnEntityChangeOwnership, rc);
}
}
} // namespace AzFramework
@@ -1,112 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#define AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#include <AzCore/Component/ComponentBus.h>
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
#include <AzFramework/Network/NetBindingSystemImpl.h>
namespace AzFramework
{
class NetBindingComponent;
class NetBindingComponentChunkDescriptor;
/**
* NetBindingComponentChunk is the counterpart of NetBindingComponent on the network side.
* It contains entity spawn data. It is created by NetBindingComponent during network
* binding on the master and initiates entity creation and binding on the proxy side.
*/
class NetBindingComponentChunk
: public GridMate::ReplicaChunk
{
friend NetBindingComponent;
friend NetBindingComponentChunkDescriptor;
public:
AZ_CLASS_ALLOCATOR(NetBindingComponentChunk, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingComponentChunk"; }
NetBindingComponentChunk();
void SetBinding(NetBindingComponent* bindingComponent) { m_bindingComponent = bindingComponent; }
NetBindingComponent* GetBinding() const { return m_bindingComponent; }
protected:
///////////////////////////////////////////////////////////////////////
// ReplicaChunk
bool IsReplicaMigratable() override { return true; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override;
bool AcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc) override;
void OnReplicaChangeOwnership(const GridMate::ReplicaContext& rc) override;
///////////////////////////////////////////////////////////////////////
NetBindingComponent* m_bindingComponent;
class SpawnInfo
{
public:
class Marshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const SpawnInfo& data);
void Unmarshal(SpawnInfo& data, GridMate::ReadBuffer& rb);
};
class Throttle
{
public:
//! Always return true because SpawnInfo never changes
bool WithinThreshold(const SpawnInfo&) const { return true; }
void UpdateBaseline(const SpawnInfo& baseline) { (void)baseline; }
};
SpawnInfo();
bool operator==(const SpawnInfo& rhs);
bool ContainsSerializedState() const;
/**
* \brief Same as m_staticEntityId on authoritative entity with master replica
*/
AZ::u64 m_runtimeEntityId;
NetBindingContextSequence m_owningContextId;
AZStd::vector<AZ::u8> m_serializedState;
/**
* \brief EntityId of authoritative entity with master replica
*/
AZ::u64 m_staticEntityId;
AZStd::pair<AZ::Uuid, AZ::u32> m_sliceAssetId;
/**
* \brief uniquely identifies the slice instance that this entity is being replicated from
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
GridMate::DataSet<SpawnInfo, SpawnInfo::Marshaler, SpawnInfo::Throttle> m_spawnInfo;
GridMate::DataSet<AZStd::vector<AZ::ComponentId> > m_bindMap;
};
typedef AZStd::intrusive_ptr<NetBindingComponentChunk> NetBindingComponentChunkPtr;
} // namespace AZ
#endif // AZFRAMEWORK_NET_BINDING_COMPONENT_CHUNK_H
#pragma once
@@ -1,51 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#define AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <GridMate/Replica/ReplicaCommon.h>
namespace AzFramework
{
/**
* NetBindingEventsBus
* Throws networking related entity events
*/
class NetBindingEvents
: public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::EntityId BusIdType;
virtual ~NetBindingEvents() {}
/**
* Called on authoritative(Master) entity when ownership of this entity is about to be transferred to another peer
* Returning false from this call will result in denying request for ownership transfer
*/
virtual bool OnEntityAcceptChangeOwnership(GridMate::PeerId requestor, const GridMate::ReplicaContext& rc) { (void)requestor; (void)rc; return true; }
/**
* Called when ownership transfer of an entity is finished.
*/
virtual void OnEntityChangeOwnership(const GridMate::ReplicaContext& rc) { (void)rc; }
};
typedef AZ::EBus<NetBindingEvents> NetBindingEventsBus;
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_EVENTS_BUS_H
#pragma once
@@ -1,112 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#define AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/parallel/mutex.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <AzCore/Slice/SliceComponent.h>
namespace AzFramework
{
/**
* The NetBindingSystemComponent notifies net binding handlers of binding events on this bus.
* The net binding component implements this interface and listens on the NetBindingHandlerBus.
*/
class NetBindingHandlerInterface
: public AZ::EBusTraits
{
public:
AZ_RTTI(NetBindingHandlerInterface, "{9F84E9FE-81A0-4105-9C51-6C42C83FECAF}");
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::EntityId BusIdType;
virtual ~NetBindingHandlerInterface() {}
/**
* Called to let the entity know that it should bind to the network.
* If bindTo is set, it means that the entity is a proxy and the handler
* should bind the entity to the specified
* replica, otherwise it should bind to a new replica and add it via
* NetBindingSystemBus::AddReplicaMaster.
*/
virtual void BindToNetwork(GridMate::ReplicaPtr bindTo) = 0;
/**
* Called to let the entity know that it should unbind from the network.
*/
virtual void UnbindFromNetwork() = 0;
/**
* Returns true if the entity is bound to the network.
*/
virtual bool IsEntityBoundToNetwork() = 0;
/**
* Returns true if the entity is authoritative on the local node.
*/
virtual bool IsEntityAuthoritative() = 0;
/**
* Flags the entity as part of the level slice.
*/
virtual void MarkAsLevelSliceEntity() = 0;
/**
* Set the slice instance id that this entity was spawned by and belongs to.
*/
virtual void SetSliceInstanceId(const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) = 0;
/**
* Sets the Replica Priority
*/
virtual void SetReplicaPriority(GridMate::ReplicaPriority replicaPriority) = 0;
/**
* Request entity ownership to a given peer (by default to local peer)
*/
virtual void RequestEntityChangeOwnership(GridMate::PeerId peerId = GridMate::InvalidReplicaPeerId) = 0;
/**
* Gets the Replica Priority
*/
virtual GridMate::ReplicaPriority GetReplicaPriority() const = 0;
};
typedef AZ::EBus<NetBindingHandlerInterface> NetBindingHandlerBus;
/**
* Set of queries that might want to be made about the networking system
* mainly wraps up EBus calls to keep the implementing code a bit more readable
*/
class NetQuery
{
public:
AZ_RTTI(NetQuery, "{AA4C5699-889D-4A73-9AD2-53EB03D8BB99}");
virtual ~NetQuery() = default;
static AZ_FORCE_INLINE bool IsEntityAuthoritative(AZ::EntityId entityId)
{
bool result = true;
EBUS_EVENT_ID_RESULT(result,entityId,NetBindingHandlerBus,IsEntityAuthoritative);
return result;
}
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_HANDLER_BUS_H
#pragma once
@@ -1,119 +0,0 @@
/*
* 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.
*
*/
#pragma once
#ifndef AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
#define AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Asset/AssetCommon.h>
#include <GridMate/Replica/ReplicaCommon.h>
#include <GridMate/Session/Session.h>
#include <AzCore/Slice/SliceComponent.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
}
namespace AzFramework
{
const AZ::SliceComponent::SliceInstanceId UnspecifiedSliceInstanceId = AZ::Uuid::CreateNull();
/**
*/
typedef AZ::u32 NetBindingContextSequence;
const NetBindingContextSequence UnspecifiedNetBindingContextSequence = 0;
/**
*/
struct NetBindingSliceContext
{
NetBindingContextSequence m_contextSequence;
AZ::Data::AssetId m_sliceAssetId;
AZ::EntityId m_staticEntityId;
AZ::EntityId m_runtimeEntityId;
/**
* \brief uniquely identifies the slice instance that this entity is being replicated from
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
};
/**
* The net binding system implements this interface and listens on the NetBindingSystemBus.
*
* Network binding is activated when OnNetworkSessionActivated event is received with the binding session,
* and is deactivated by the OnNetworkSessionDeactivated event.
*/
class NetBindingSystemInterface
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
virtual ~NetBindingSystemInterface() {}
//! Returns true if a network session is available and entities should bind themselves to the network.
virtual bool ShouldBindToNetwork() = 0;
//! Returns the current entity context sequence
virtual NetBindingContextSequence GetCurrentContextSequence() = 0;
//! Get a level entity's static id.
virtual AZ::EntityId GetStaticIdFromEntityId(AZ::EntityId entity) = 0;
//! Get a level entity's id based on the static id
virtual AZ::EntityId GetEntityIdFromStaticId(AZ::EntityId staticEntityId) = 0;
//! Adds a bound replica to the network session as master.
virtual void AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica) = 0;
//! Spawn and bind an entity from a slice
virtual void SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext) = 0;
//! Spawn and bind an entity from stream
virtual void SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext) = 0;
//! De-spawn an entity: deactivates or removes the entity.
/**
* /note @sliceInstanceId is the slice instance that the entity belongs to. If it's a level entity, then this should be AZ::Uuid::CreateNull()
*/
virtual void UnbindGameEntity(AZ::EntityId entity, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) = 0;
};
typedef AZ::EBus<NetBindingSystemInterface> NetBindingSystemBus;
class NetBindingSystemEvents
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//! Notification that a network session is created
virtual void OnNetworkSessionCreated(GridMate::GridSession* session) { (void)session; }
//! Notification that a network session is ready
virtual void OnNetworkSessionActivated(GridMate::GridSession* session) { (void)session; }
//! Notification that a network session is no longer available
virtual void OnNetworkSessionDeactivated(GridMate::GridSession* session) { (void)session; }
};
typedef AZ::EBus<NetBindingSystemEvents> NetBindingSystemEventsBus;
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_SYSTEM_BUS_H
@@ -1,66 +0,0 @@
/*
* 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 <AzCore/Serialization/EditContext.h>
#include <AzFramework/Network/NetBindingSystemComponent.h>
namespace AzFramework
{
NetBindingSystemComponent::NetBindingSystemComponent()
{
}
NetBindingSystemComponent::~NetBindingSystemComponent()
{
}
void NetBindingSystemComponent::Activate()
{
NetBindingSystemImpl::Init();
}
void NetBindingSystemComponent::Deactivate()
{
NetBindingSystemImpl::Shutdown();
}
void NetBindingSystemComponent::Reflect(AZ::ReflectContext* context)
{
NetBindingSystemImpl::Reflect(context);
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<NetBindingSystemComponent, AZ::Component>()
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetBindingSystemComponent>(
"NetBinding System", "Performs network binding for game entities.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Engine")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
}
void NetBindingSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("NetBindingSystemService", 0xa0ad6656));
}
void NetBindingSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("NetBindingSystemService", 0xa0ad6656));
}
} // namespace AzFramework
@@ -1,53 +0,0 @@
/*
* 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 AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#define AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#include <AzFramework/Network/NetBindingSystemImpl.h>
#include <AzCore/Component/Component.h>
namespace AZ
{
class ReflectContext;
}
namespace AzFramework
{
/**
* NetBindingSystemComponent exposes NetBindingSystemImpl as a component
*/
class NetBindingSystemComponent
: public AZ::Component
, public NetBindingSystemImpl
{
friend class NetBindingSystemContextData;
public:
AZ_COMPONENT(NetBindingSystemComponent, "{B96548CC-0866-4BB3-A87B-BF0C4F69E8AC}");
NetBindingSystemComponent();
~NetBindingSystemComponent() override;
//////////////////////////////////////////////////////////////////////////
// Component overrides
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
//////////////////////////////////////////////////////////////////////////
};
} // namespace AzFramework
#endif // AZFRAMEWORK_NET_BINDING_SYSTEM_COMPONENT_H
#pragma once
@@ -1,957 +0,0 @@
/*
* 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 <AzFramework/Network/NetBindingSystemImpl.h>
#include <AzFramework/Network/NetBindingComponent.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Entity/SliceGameEntityOwnershipServiceBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Slice/SliceAsset.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <GridMate/Replica/ReplicaChunkDescriptor.h>
#include <GridMate/Replica/ReplicaFunctions.h>
//#define Extra_Tracing
#undef Extra_Tracing
#if defined(Extra_Tracing)
#include <AzCore/Debug/Timer.h>
#define AZ_ExtraTracePrintf(window, ...) AZ::Debug::Trace::Instance().Printf(window, __VA_ARGS__);
#else
#define AZ_ExtraTracePrintf(window, ...)
#endif
namespace AzFramework
{
const AZStd::chrono::milliseconds NetBindingSystemImpl::s_sliceBindingTimeout = AZStd::chrono::milliseconds(5000);
namespace
{
NetBindingHandlerInterface* GetNetBindingHandler(AZ::Entity* entity)
{
NetBindingHandlerInterface* handler = nullptr;
for (AZ::Component* component : entity->GetComponents())
{
handler = azrtti_cast<NetBindingHandlerInterface*>(component);
if (handler)
{
break;
}
}
return handler;
}
}
NetBindingSliceInstantiationHandler::~NetBindingSliceInstantiationHandler()
{
// m_bindRequests in NetBindingSystemImpl could be cleaned before slice instantiation finished
if (m_state == State::Spawning)
{
AzFramework::SliceInstantiationResultBus::Handler::BusDisconnect();
SliceGameEntityOwnershipServiceRequestBus::Broadcast(
&SliceGameEntityOwnershipServiceRequests::CancelDynamicSliceInstantiation, m_ticket
);
}
for (AZ::Entity* entity : m_boundEntities)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Cleanup - deleting %llu\n", entity->GetId());
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, entity->GetId());
}
}
void NetBindingSliceInstantiationHandler::InstantiateEntities()
{
if (m_sliceAssetId.IsValid())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "InstantiateEntities sliceid %s\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
if (AZ::Data::AssetManager::IsReady())
{
auto remapFunc = [bindingQueue=m_bindingQueue](AZ::EntityId originalId, bool /*isEntityId*/, const AZStd::function<AZ::EntityId()>&) -> AZ::EntityId
{
auto iter = bindingQueue.find(originalId);
if (iter != bindingQueue.end())
{
return iter->second.m_desiredRuntimeEntityId;
}
return AZ::Entity::MakeId();
};
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AZ::DynamicSliceAsset>(m_sliceAssetId, AZ::Data::AssetLoadBehavior::Default);
SliceGameEntityOwnershipServiceRequestBus::BroadcastResult(m_ticket,
&SliceGameEntityOwnershipServiceRequests::InstantiateDynamicSlice, asset, AZ::Transform::Identity(), remapFunc);
SliceInstantiationResultBus::Handler::BusConnect(m_ticket);
m_state = State::Spawning;
}
else
{
AZ_Warning("NetBindingSystemImpl", false, "AssetManager was not ready when attempting to instantiate sliceid %s\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
InstantiationFailureCleanup();
}
}
}
bool NetBindingSliceInstantiationHandler::IsInstantiated() const
{
return m_state == State::Spawned;
}
bool NetBindingSliceInstantiationHandler::IsANewSliceRequest() const
{
return m_state == State::NewRequest && m_sliceAssetId.IsValid() && !m_ticket.IsValid();
}
bool NetBindingSliceInstantiationHandler::IsBindingComplete() const
{
return !SliceInstantiationResultBus::Handler::BusIsConnected() && m_bindingQueue.empty();
}
bool NetBindingSliceInstantiationHandler::HasActiveEntities() const
{
for (const AZ::Entity* entity : m_boundEntities)
{
if (entity->GetState() == AZ::Entity::State::Active)
{
return true;
}
}
return false;
}
void NetBindingSliceInstantiationHandler::OnSlicePreInstantiate(const AZ::Data::AssetId& /*sliceAssetId*/, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
const auto& entityMapping = sliceAddress.GetInstance()->GetEntityIdToBaseMap();
const AZ::SliceComponent::EntityList& sliceEntities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
for (AZ::Entity *sliceEntity : sliceEntities)
{
auto it = entityMapping.find(sliceEntity->GetId());
AZ_Assert(it != entityMapping.end(), "Failed to retrieve static entity id for a slice entity!");
const AZ::EntityId staticEntityId = it->second;
auto itBindRecord = m_bindingQueue.find(staticEntityId);
if (itBindRecord != m_bindingQueue.end())
{
AZ_Assert(GetNetBindingHandler(sliceEntity), "Slice entity matched the static id of replicated entity, but there is no valid NetBindingHandlerInterface on it!");
itBindRecord->second.m_actualRuntimeEntityId = sliceEntity->GetId();
}
else if (GetNetBindingHandler(sliceEntity))
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "OnSlicePreInstantiate late bindRequest, slice %s, staticid %llu, spawned %llu\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(staticEntityId),
static_cast<AZ::u64>(sliceEntity->GetId()));
BindRequest& request = m_bindingQueue[staticEntityId];
request.m_desiredRuntimeEntityId = staticEntityId;
request.m_actualRuntimeEntityId = sliceEntity->GetId();
request.m_requestTime = m_bindTime;
request.m_state = BindRequest::State::PlaceholderBind;
}
sliceEntity->SetRuntimeActiveByDefault(false);
}
}
void NetBindingSliceInstantiationHandler::OnSliceInstantiated(const AZ::Data::AssetId& /*sliceAssetId*/, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress)
{
SliceInstantiationResultBus::Handler::BusDisconnect();
CloseEntityMap(sliceAddress.GetInstance()->GetEntityIdMap());
const AZ::SliceComponent::EntityList sliceEntities = sliceAddress.GetInstance()->GetInstantiated()->m_entities;
for (AZ::Entity *sliceEntity : sliceEntities)
{
auto it = sliceAddress.GetInstance()->GetEntityIdToBaseMap().find(sliceEntity->GetId());
AZ_Assert(it != sliceAddress.GetInstance()->GetEntityIdToBaseMap().end(), "Failed to retrieve static entity id for a slice entity!");
const AZ::EntityId staticEntityId = it->second;
const auto itUnbound = m_bindingQueue.find(staticEntityId);
if (itUnbound == m_bindingQueue.end())
{
/*
* Remove entities that aren't meant to be net bounded.
*/
if (!GetNetBindingHandler(sliceEntity))
{
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, sliceEntity->GetId());
continue;
}
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Adding %llu \n", sliceEntity->GetId());
m_boundEntities.push_back(sliceEntity);
}
m_state = State::Spawned;
}
void NetBindingSliceInstantiationHandler::OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId)
{
SliceInstantiationResultBus::Handler::BusDisconnect();
AZ_UNUSED(sliceAssetId);
AZ_TracePrintf("NetBindingSystemImpl", "Failed to instantiate a slice %s!", sliceAssetId.ToString<AZStd::string>().c_str());
InstantiationFailureCleanup();
}
void NetBindingSliceInstantiationHandler::InstantiationFailureCleanup()
{
m_boundEntities.clear();
m_bindingQueue.clear();
// With m_bindingQueue empty, this slice instance handler will be removed on the next tick of NetBindingSystemImpl
m_state = State::Failed;
}
void NetBindingSliceInstantiationHandler::UseCacheFor(BindRequest& request, const AZ::EntityId& staticEntityId)
{
AZ_Warning("NetBindingSystemImpl", !m_staticToRuntimeEntityMap.empty(), "An empty slice, really? static %llu",
static_cast<AZ::u64>(staticEntityId));
const auto actualRuntimeIter = m_staticToRuntimeEntityMap.find(staticEntityId);
if (actualRuntimeIter == m_staticToRuntimeEntityMap.end())
{
AZ_Warning("NetBindingSystemImpl", false, "Wrong mapping, expected cache to have entity %llu for slice %s \n",
static_cast<AZ::u64>(staticEntityId),
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
#if defined(Extra_Tracing)
for (auto& item: m_staticToRuntimeEntityMap)
{
AZ_UNUSED(item);
AZ_ExtraTracePrintf("NetBindingSystemImpl", "mapping had %llu to %llu \n",
static_cast<AZ::u64>(item.first),
static_cast<AZ::u64>(item.second));
}
#endif
return;
}
const AZ::EntityId actualRuntimeEntityId = actualRuntimeIter->second;
const auto itCache = AZStd::find_if(m_boundEntities.begin(), m_boundEntities.end(), [&actualRuntimeEntityId](AZ::Entity* entity) {
return entity->GetId() == actualRuntimeEntityId;
});
if (itCache != m_boundEntities.end())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "OnSlicePreInstantiate late bindRequest, slice %s, staticid %llu, spawned %llu\n",
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(staticEntityId),
static_cast<AZ::u64>(actualRuntimeEntityId));
request.m_actualRuntimeEntityId = actualRuntimeEntityId;
request.m_desiredRuntimeEntityId = staticEntityId;
}
else
{
AZ_Warning("NetBindingSystemImpl", false, "Expected cache to have entity %llu for slice %s \n",
static_cast<AZ::u64>(request.m_desiredRuntimeEntityId),
m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
}
}
void NetBindingSliceInstantiationHandler::CloseEntityMap(
const AZ::SliceComponent::EntityIdToEntityIdMap& staticToRuntimeMap)
{
m_staticToRuntimeEntityMap.clear();
for (auto& item : staticToRuntimeMap)
{
m_staticToRuntimeEntityMap[item.first] = item.second;
}
}
NetBindingSystemContextData::NetBindingSystemContextData()
: m_bindingContextSequence("BindingContextSequence", UnspecifiedNetBindingContextSequence)
{
}
void NetBindingSystemContextData::OnReplicaActivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
NetBindingSystemImpl* system = static_cast<NetBindingSystemImpl*>(NetBindingSystemBus::FindFirstHandler());
AZ_Assert(system, "NetBindingSystemContextData requires a valid NetBindingSystemComponent to function!");
system->OnContextDataActivated(this);
}
void NetBindingSystemContextData::OnReplicaDeactivate(const GridMate::ReplicaContext& rc)
{
(void)rc;
NetBindingSystemImpl* system = static_cast<NetBindingSystemImpl*>(NetBindingSystemBus::FindFirstHandler());
if (system)
{
system->OnContextDataDeactivated(this);
}
}
NetBindingSystemImpl::NetBindingSystemImpl()
: m_bindingSession(nullptr)
, m_currentBindingContextSequence(UnspecifiedNetBindingContextSequence)
, m_isAuthoritativeRootSliceLoad(false)
, m_overrideRootSliceLoadAuthoritative(false)
{
}
NetBindingSystemImpl::~NetBindingSystemImpl()
{
}
void NetBindingSystemImpl::Init()
{
NetBindingSystemBus::Handler::BusConnect();
NetBindingSystemEventsBus::Handler::BusConnect();
// Start listening for game context events
EntityContextId gameContextId = EntityContextId::CreateNull();
EBUS_EVENT_RESULT(gameContextId, GameEntityContextRequestBus, GetGameEntityContextId);
if (!gameContextId.IsNull())
{
EntityContextEventBus::Handler::BusConnect(gameContextId);
}
}
void NetBindingSystemImpl::Shutdown()
{
EntityContextEventBus::Handler::BusDisconnect();
NetBindingSystemEventsBus::Handler::BusDisconnect();
NetBindingSystemBus::Handler::BusDisconnect();
m_contextData.reset();
}
bool NetBindingSystemImpl::ShouldBindToNetwork()
{
return m_contextData && m_contextData->ShouldBindToNetwork();
}
NetBindingContextSequence NetBindingSystemImpl::GetCurrentContextSequence()
{
return m_currentBindingContextSequence;
}
bool NetBindingSystemImpl::ReadyToAddReplica() const
{
return m_bindingSession && m_bindingSession->GetReplicaMgr();
}
void NetBindingSystemImpl::AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica)
{
bool addReplica = ShouldBindToNetwork();
AZ_Assert(addReplica, "Entities shouldn't be binding to the network right now!");
if (addReplica)
{
if (ReadyToAddReplica())
{
m_bindingSession->GetReplicaMgr()->AddMaster(replica);
}
else
{
m_addMasterRequests.push_back(AZStd::make_pair(entity->GetId(), replica));
}
}
}
AZ::EntityId NetBindingSystemImpl::GetStaticIdFromEntityId(AZ::EntityId entityId)
{
AZ::EntityId staticId = entityId; // if no static id mapping is found, then the static id is the same as the runtime id
// If entity came from a slice, try to get the mapping from it
AZ::SliceComponent::SliceInstanceAddress sliceInfo;
SliceEntityRequestBus::EventResult(sliceInfo, entityId, &SliceEntityRequestBus::Events::GetOwningSlice);
AZ::SliceComponent::SliceInstance* sliceInstance = sliceInfo.GetInstance();
if (sliceInstance)
{
const auto it = sliceInstance->GetEntityIdToBaseMap().find(entityId);
if (it != sliceInstance->GetEntityIdToBaseMap().end())
{
staticId = it->second;
}
}
return staticId;
}
AZ::EntityId NetBindingSystemImpl::GetEntityIdFromStaticId(AZ::EntityId staticEntityId)
{
AZ::EntityId runtimeId = AZ::EntityId();
// if we can find an entity with the static id, then the static id is the same as the runtime id.
AZ::Entity* entity = nullptr;
EBUS_EVENT(AZ::ComponentApplicationBus, FindEntity, staticEntityId);
if (entity)
{
runtimeId = staticEntityId;
}
return runtimeId;
}
void NetBindingSystemImpl::SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext)
{
auto& sliceQueue = m_bindRequests[bindToContext.m_contextSequence];
const bool slicePresent = sliceQueue.find(bindToContext.m_sliceInstanceId) != sliceQueue.end();
auto iterSliceRequest = sliceQueue.insert_key(bindToContext.m_sliceInstanceId);
NetBindingSliceInstantiationHandler& sliceHandler = iterSliceRequest.first->second;
sliceHandler.m_sliceAssetId = bindToContext.m_sliceAssetId;
sliceHandler.m_sliceInstanceId = bindToContext.m_sliceInstanceId;
BindRequest& request = sliceHandler.m_bindingQueue[bindToContext.m_staticEntityId];
if (!slicePresent)
{
request.m_state = BindRequest::State::FirstBindInSlice;
}
else
{
request.m_state = BindRequest::State::LateBind;
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "SpawnEntityFromSlice late, slice %s, static %llu, desired %llu, state %d \n",
bindToContext.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
static_cast<AZ::u64>(bindToContext.m_staticEntityId),
static_cast<AZ::u64>(bindToContext.m_runtimeEntityId),
request.m_state);
sliceHandler.m_bindTime = Now();
request.m_bindTo = bindTo;
request.m_desiredRuntimeEntityId = bindToContext.m_runtimeEntityId;
request.m_requestTime = Now();
if (sliceHandler.IsInstantiated())
{
// The slice has been instantiated now, thus we have to use the cache to populated the request with the entity.
sliceHandler.UseCacheFor(request, bindToContext.m_staticEntityId);
}
}
void NetBindingSystemImpl::SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext)
{
auto& requestQueue = m_spawnRequests[addToContext];
requestQueue.push_back();
SpawnRequest& request = requestQueue.back();
request.m_bindTo = bindTo;
request.m_useEntityId = useEntityId;
request.m_spawnDataBuffer.resize_no_construct(spawnData.GetLength());
spawnData.Read(request.m_spawnDataBuffer.size(), request.m_spawnDataBuffer.data());
}
void NetBindingSystemImpl::OnNetworkSessionActivated(GridMate::GridSession* session)
{
AZ_Assert(!m_bindingSession, "We already have an active session! Was the previous session deactivated?");
if (!m_bindingSession)
{
m_bindingSession = session;
if (m_bindingSession->IsHost())
{
GridMate::Replica* replica = CreateSystemReplica();
session->GetReplicaMgr()->AddMaster(replica);
}
}
}
void NetBindingSystemImpl::OnNetworkSessionDeactivated(GridMate::GridSession* session)
{
if (session == m_bindingSession)
{
m_bindingSession = nullptr;
}
}
void NetBindingSystemImpl::UnbindGameEntity(AZ::EntityId entityId, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
if (!m_bindRequests.empty())
{
const auto itCurrentContextQueue = m_bindRequests.lower_bound(GetCurrentContextSequence());
if (itCurrentContextQueue != m_bindRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
const auto itSliceHandler = itCurrentContextQueue->second.find(sliceInstanceId);
if (itSliceHandler != itCurrentContextQueue->second.end())
{
NetBindingSliceInstantiationHandler& sliceHandler = itSliceHandler->second;
for (AZ::Entity* entity : sliceHandler.m_boundEntities)
{
if (entity->GetId() == entityId)
{
entity->Deactivate();
return;
}
}
// clean any relevant bind requests as well
const auto bindQueueItem = sliceHandler.m_bindingQueue.find(entityId);
if (bindQueueItem != sliceHandler.m_bindingQueue.end())
{
sliceHandler.m_bindingQueue.erase(bindQueueItem);
return;
}
}
}
}
}
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Not in cache - deleting %llu \n", entityId);
EBUS_EVENT(GameEntityContextRequestBus, DestroyGameEntity, entityId);
}
void NetBindingSystemImpl::OnEntityContextReset()
{
const bool isContextOwner = m_contextData && m_contextData->IsMaster() && m_bindingSession && m_bindingSession->IsHost();
if (isContextOwner)
{
++m_currentBindingContextSequence;
NetBindingSystemContextData* context = static_cast<NetBindingSystemContextData*>(m_contextData.get());
context->m_bindingContextSequence.Set(m_currentBindingContextSequence);
}
}
bool NetBindingSystemImpl::IsAuthoritateLoad() const
{
if (m_overrideRootSliceLoadAuthoritative)
{
return m_isAuthoritativeRootSliceLoad;
}
return !m_bindingSession || m_bindingSession->IsHost();
}
void NetBindingSystemImpl::UpdateClock(float deltaTime)
{
m_currentTime += AZStd::chrono::milliseconds(aznumeric_cast<int>(deltaTime * AZStd::milli::den));
}
AZStd::chrono::system_clock::time_point NetBindingSystemImpl::Now() const
{
return m_currentTime;
}
void NetBindingSystemImpl::OnEntityContextLoadedFromStream(const AZ::SliceComponent::EntityList& contextEntities)
{
const bool isAuthoritativeLoad = IsAuthoritateLoad();
for (AZ::Entity* entity : contextEntities)
{
NetBindingHandlerInterface* netBinder = GetNetBindingHandler(entity);
if (netBinder)
{
netBinder->MarkAsLevelSliceEntity();
}
if (!isAuthoritativeLoad && netBinder)
{
entity->SetRuntimeActiveByDefault(false);
auto& slicesQueue = m_bindRequests[GetCurrentContextSequence()];
auto& sliceHandler = slicesQueue[UnspecifiedSliceInstanceId];
BindRequest& request = sliceHandler.m_bindingQueue[entity->GetId()];
request.m_actualRuntimeEntityId = entity->GetId();
request.m_requestTime = Now();
}
}
}
void NetBindingSystemImpl::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
AZ_UNUSED(time);
UpdateClock(deltaTime);
UpdateContextSequence();
#if defined(Extra_Tracing)
static AZ::Debug::Timer sTimer;
sTimer.Stamp();
#endif
ProcessBindRequests();
#if defined(Extra_Tracing)
const float seconds = sTimer.StampAndGetDeltaTimeInSeconds();
static float debugPeriod = 2.f;
static float accumulator = 0;
static float totalTimeTaken = 0;
static AZ::u32 totalTicks = 0;
accumulator += deltaTime;
totalTimeTaken += seconds;
totalTicks++;
if (accumulator >= debugPeriod)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "ProcessBindRequests() took %f sec \n", totalTicks > 0 ? totalTimeTaken / totalTicks : 0);
accumulator -= debugPeriod;
totalTimeTaken = 0;
totalTicks = 0;
}
#endif
ProcessSpawnRequests();
}
int NetBindingSystemImpl::GetTickOrder()
{
return AZ::TICK_PLACEMENT + 1;
}
void NetBindingSystemImpl::UpdateContextSequence()
{
NetBindingSystemContextData* contextChunk = static_cast<NetBindingSystemContextData*>(m_contextData.get());
if (m_currentBindingContextSequence != contextChunk->m_bindingContextSequence.Get())
{
m_currentBindingContextSequence = contextChunk->m_bindingContextSequence.Get();
}
}
GridMate::Replica* NetBindingSystemImpl::CreateSystemReplica()
{
AZ_Assert(m_bindingSession->IsHost(), "CreateSystemReplica should only be called on the host!");
GridMate::Replica* replica = GridMate::Replica::CreateReplica("NetBindingSystem");
NetBindingSystemContextData* contextChunk = GridMate::CreateReplicaChunk<NetBindingSystemContextData>();
replica->AttachReplicaChunk(contextChunk);
return replica;
}
void NetBindingSystemImpl::OnContextDataActivated(GridMate::ReplicaChunkPtr contextData)
{
AZ_Assert(!m_contextData, "We already have our context!");
m_contextData = contextData;
// Make sure we always have the unspecified entry. This should also
// be the lower_bound in the map and assuming it is always there
// makes things simpler.
m_spawnRequests.insert(UnspecifiedNetBindingContextSequence);
m_bindRequests.insert(UnspecifiedNetBindingContextSequence);
if (contextData->IsMaster())
{
++m_currentBindingContextSequence;
static_cast<NetBindingSystemContextData*>(contextData.get())->m_bindingContextSequence.Set(m_currentBindingContextSequence);
}
else
{
UpdateContextSequence();
}
AZ::TickBus::Handler::BusConnect();
EBUS_EVENT(AzFramework::NetBindingHandlerBus, BindToNetwork, nullptr);
}
void NetBindingSystemImpl::OnContextDataDeactivated(GridMate::ReplicaChunkPtr contextData)
{
AZ_Assert(m_contextData == contextData, "This is not our context!");
m_contextData = nullptr;
AZ::TickBus::Handler::BusDisconnect();
m_spawnRequests.clear();
m_bindRequests.clear();
m_addMasterRequests.clear();
m_currentBindingContextSequence = UnspecifiedNetBindingContextSequence;
}
void NetBindingSystemImpl::ProcessSpawnRequests()
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "NetBindingSystemComponent requires a valid SerializeContext in order to spawn entities!");
const auto spawnFunc = [=](SpawnRequest& spawnData, AZ::EntityId useEntityId, bool addToContext)
{
AZ::Entity* proxyEntity = nullptr;
AZ::ObjectStream::ClassReadyCB readyCB([&](void* classPtr, const AZ::Uuid& classId, AZ::SerializeContext* sc)
{
(void)classId;
(void)sc;
proxyEntity = static_cast<AZ::Entity*>(classPtr);
});
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > stream(&spawnData.m_spawnDataBuffer);
AZ::ObjectStream::LoadBlocking(&stream, *serializeContext, readyCB);
AZ_Warning("NetBindingSystemImpl", proxyEntity, "Could not spawn entity from stream %llu", useEntityId);
if (proxyEntity)
{
proxyEntity->SetId(useEntityId);
if (!BindAndActivate(proxyEntity, spawnData.m_bindTo, addToContext, AZ::Uuid::CreateNull()))
{
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(
contextId, proxyEntity->GetId(), &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
if (contextId.IsNull())
{
delete proxyEntity;
}
else
{
GameEntityContextRequestBus::Broadcast(
&GameEntityContextRequestBus::Events::DestroyGameEntity, proxyEntity->GetId());
}
}
}
};
if (!m_spawnRequests.empty())
{
SpawnRequestContextContainerType::iterator itContextQueue = m_spawnRequests.lower_bound(UnspecifiedNetBindingContextSequence);
AZ_Assert(itContextQueue->first == UnspecifiedNetBindingContextSequence, "We should always have the unspecified (aka global entity) spawn queue!");//
// Process requests for global entities (not part of any context)
SpawnRequestContainerType& globalQueue = itContextQueue->second;
for (SpawnRequest& request : globalQueue)
{
spawnFunc(request, request.m_useEntityId, false);
}
globalQueue.clear();
if (GetCurrentContextSequence() != UnspecifiedNetBindingContextSequence)
{
++itContextQueue;
// Clear any obsolete requests (any contexts below the current context sequence)
SpawnRequestContextContainerType::iterator itCurrentContextQueue = m_spawnRequests.lower_bound(GetCurrentContextSequence());
if (itContextQueue != itCurrentContextQueue)
{
m_spawnRequests.erase(itContextQueue, itCurrentContextQueue);
}
// Spawn any entities for the current context
if (itCurrentContextQueue != m_spawnRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
for (SpawnRequest& request : itCurrentContextQueue->second)
{
spawnFunc(request, request.m_useEntityId, true);
}
itCurrentContextQueue->second.clear();
}
}
}
}
}
void NetBindingSystemImpl::ProcessBindRequests()
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "NetBindingSystemComponent requires a valid SerializeContext in order to spawn entities!");
if (!m_bindRequests.empty())
{
BindRequestContextContainerType::iterator itContextQueue = m_bindRequests.lower_bound(UnspecifiedNetBindingContextSequence);
AZ_Assert(itContextQueue->first == UnspecifiedNetBindingContextSequence, "We should always have the unspecified/global spawn queue!");
if (GetCurrentContextSequence() != UnspecifiedNetBindingContextSequence)
{
++itContextQueue;
// Clear any obsolete requests (any contexts below the current context sequence)
BindRequestContextContainerType::iterator itCurrentContextQueue = m_bindRequests.lower_bound(GetCurrentContextSequence());
if (itContextQueue != itCurrentContextQueue)
{
m_bindRequests.erase(itContextQueue, itCurrentContextQueue);
}
// Spawn any proxy entities for the current context
if (itCurrentContextQueue != m_bindRequests.end())
{
if (itCurrentContextQueue->first == GetCurrentContextSequence())
{
for (auto itSliceHandler = itCurrentContextQueue->second.begin(); itSliceHandler != itCurrentContextQueue->second.end(); /*++itSliceHandler*/)
{
NetBindingSliceInstantiationHandler& sliceHandler = itSliceHandler->second;
// If this is a new slice request, instantiate it
if (sliceHandler.IsANewSliceRequest())
{
sliceHandler.InstantiateEntities();
}
/*
* A slice instance is kept alive for caching purposes. As we check each bind request for its readiness,
* we are also going to check if the slice instance itself has become inactive and needs to be removed.
*/
bool mightBeInactiveSlice = true;
if (sliceHandler.m_bindingQueue.empty() && sliceHandler.HasActiveEntities())
{
// The slice instance is spawned and full bound.
mightBeInactiveSlice = false;
}
// If the entity is ready to be bound to the network, bind it.
// NOTE: It is possible for entities spawned from a slice containing multiple entities with net binding
// to never receive their replica counterpart, either because the replica was destroyed, or was interest
// filtered. We don't have a very good pipeline to prevent these slices from being authored, so if we
// encounter them, we will delete them after a timeout.
for (auto itRequest = sliceHandler.m_bindingQueue.begin(); itRequest != sliceHandler.m_bindingQueue.end(); /*++itRequest*/)
{
BindRequest& request = itRequest->second;
if (request.m_bindTo != GridMate::InvalidReplicaId && request.m_actualRuntimeEntityId.IsValid())
{
AZ::Entity* proxyEntity = nullptr;
EBUS_EVENT_RESULT(proxyEntity, AZ::ComponentApplicationBus, FindEntity, request.m_actualRuntimeEntityId);
AZ_Warning("NetBindingSystemImpl", proxyEntity, "Could not find entity for binding %llu", request.m_actualRuntimeEntityId);
if (proxyEntity)
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "BindAndActivate desired id %llu, actual %llu, slice %s \n",
static_cast<AZ::u64>(request.m_desiredRuntimeEntityId),
static_cast<AZ::u64>(request.m_actualRuntimeEntityId),
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
BindAndActivate(proxyEntity, request.m_bindTo, false, sliceHandler.m_sliceInstanceId);
}
itRequest = sliceHandler.m_bindingQueue.erase(itRequest);
// The slice instance is not fully bound. It may remain for a while for caching purposes.
mightBeInactiveSlice = false;
}
else if (AZStd::chrono::milliseconds(Now() - request.m_requestTime) > s_sliceBindingTimeout)
{
// If the real request never showed up, then no need for a trace
if (request.m_state == BindRequest::State::FirstBindInSlice ||
request.m_state == BindRequest::State::LateBind)
{
AZ_TracePrintf("NetBindingSystemImpl", "Entity with static id [%llu], slice [%s]\n is still unbound after %llu ms. Discarding unbound entity.\n",
static_cast<AZ::u64>(request.m_actualRuntimeEntityId),
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str(),
s_sliceBindingTimeout.count());
}
switch (sliceHandler.m_state)
{
case NetBindingSliceInstantiationHandler::State::NewRequest:
case NetBindingSliceInstantiationHandler::State::Spawning:
// The slice instance isn't ready yet. We will wait to consider the timing logic until it is ready.
mightBeInactiveSlice = false;
break;
case NetBindingSliceInstantiationHandler::State::Spawned:
case NetBindingSliceInstantiationHandler::State::Failed:
// Now the timing logic for removing the slice instance becomes valid.
mightBeInactiveSlice = true;
break;
default:
break;
}
++itRequest;
}
else
{
mightBeInactiveSlice = false;
++itRequest;
}
}
if (mightBeInactiveSlice && !sliceHandler.HasActiveEntities())
{
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Removing inactive slice %s \n",
sliceHandler.m_sliceInstanceId.ToString<AZStd::string>(false, false).c_str());
itSliceHandler = itCurrentContextQueue->second.erase(itSliceHandler);
}
else
{
++itSliceHandler;
}
}
}
}
}
}
// Spawn replicas for any local entities that are still valid
for (auto& addRequest : m_addMasterRequests)
{
AZ::Entity* entity = nullptr;
EBUS_EVENT_RESULT(entity, AZ::ComponentApplicationBus, FindEntity, addRequest.first);
if (entity)
{
m_bindingSession->GetReplicaMgr()->AddMaster(addRequest.second);
}
}
m_addMasterRequests.clear();
}
bool NetBindingSystemImpl::BindAndActivate(AZ::Entity* entity, GridMate::ReplicaId replicaId, bool addToContext,
const AZ::SliceComponent::SliceInstanceId& sliceInstanceId)
{
bool success = false;
if ( ShouldBindToNetwork() )
{
const GridMate::ReplicaPtr bindTo = m_contextData->GetReplicaManager()->FindReplica(replicaId);
if (bindTo)
{
if (addToContext)
{
EBUS_EVENT(GameEntityContextRequestBus, AddGameEntity, entity);
}
if (entity->GetState() == AZ::Entity::State::Constructed)
{
entity->Init();
}
NetBindingHandlerInterface* binding = GetNetBindingHandler(entity);
AZ_Warning("NetBindingSystemImpl", binding, "Can't find NetBindingComponent on entity %llu (%s)!", static_cast<AZ::u64>(entity->GetId()), entity->GetName().c_str());
if (binding)
{
binding->BindToNetwork(bindTo);
binding->SetSliceInstanceId(sliceInstanceId);
entity->Activate();
success = true;
}
}
else
{
// NOTE: It is possible for entities spawned from a slice containing multiple entities with net binding
// to never receive their replica counterpart, either because the replica was destroyed, or was interest
// filtered.
AZ_ExtraTracePrintf("NetBindingSystemImpl", "Failed to bind entity %llu - could not find replica %u", entity->GetId(), replicaId);
}
}
return success;
}
void NetBindingSystemImpl::Reflect(AZ::ReflectContext* context)
{
if (context)
{
// We need to register the chunk type, and this would be a good time to do so.
if (!GridMate::ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(NetBindingSystemContextData::GetChunkName())))
{
GridMate::ReplicaChunkDescriptorTable::Get().RegisterChunkType<AzFramework::NetBindingSystemContextData>();
}
}
}
} // namespace AzFramework
@@ -1,311 +0,0 @@
/*
* 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.
*
*/
#pragma once
#include <AzFramework/Network/NetBindingSystemBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Slice/SliceInstantiationBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/unordered_map.h>
#include <GridMate/Serialize/CompressionMarshal.h>
namespace AzFramework
{
/**
* \brief Represents a request to bind a particular replica to an entity
*/
class BindRequest
{
public:
BindRequest()
: m_bindTo(GridMate::InvalidReplicaId)
, m_state(State::None)
{
}
GridMate::ReplicaId m_bindTo;
AZ::EntityId m_desiredRuntimeEntityId;
AZ::EntityId m_actualRuntimeEntityId;
AZStd::chrono::system_clock::time_point m_requestTime;
/**
* \brief Represents the state of this bind request and it's relation to the slice instantiation process
*/
enum class State : AZ::u8
{
None,
/**
* \brief This is the first request that led to instantiating a slice
*/
FirstBindInSlice,
/**
* \brief The request is a placeholder in case a real bind request arrives later.
* Some part of the slice may never be bound (e.g. if a replica is omitted by Interest Manager)
*/
PlaceholderBind,
/**
* \brief The real request did arrive to replace a placeholder request.
*/
LateBind,
};
State m_state;
};
typedef AZStd::unordered_map<AZ::EntityId, BindRequest> BindRequestContainerType;
/**
* \brief Represents a slice instance being instantiated and bound to replicas
* \note It's possible that only some of the entities are activated and bound to replicas.
*/
class NetBindingSliceInstantiationHandler
: public SliceInstantiationResultBus::Handler
{
public:
~NetBindingSliceInstantiationHandler() override;
void InstantiateEntities();
bool IsInstantiated() const;
bool IsANewSliceRequest() const;
bool IsBindingComplete() const;
/**
* \note Returns false if there are no entities in the slice or the slice instance isn't ready yet.
* \return true if any of the entities from the slice are active
*/
bool HasActiveEntities() const;
//////////////////////////////////////////////////////////////////////////
// SliceInstantiationResultBus
void OnSlicePreInstantiate(const AZ::Data::AssetId& sliceAssetId, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress) override;
void OnSliceInstantiated(const AZ::Data::AssetId& sliceAssetId, const AZ::SliceComponent::SliceInstanceAddress& sliceAddress) override;
void OnSliceInstantiationFailed(const AZ::Data::AssetId& sliceAssetId) override;
//////////////////////////////////////////////////////////////////////////
void InstantiationFailureCleanup();
void UseCacheFor(BindRequest& request, const AZ::EntityId& staticEntityId);
void CloseEntityMap(const AZ::SliceComponent::EntityIdToEntityIdMap& staticToRuntimeMap);
AZ::Data::AssetId m_sliceAssetId;
BindRequestContainerType m_bindingQueue;
SliceInstantiationTicket m_ticket;
/**
* \breif a cache of entities that might be networked at some point
* \note they might be bound and unbound if their replicas leave and come back in the view
*/
AZStd::vector<AZ::Entity*> m_boundEntities;
/**
* \brief identifies which slice instance the instantiation will be performed for
*/
AZ::SliceComponent::SliceInstanceId m_sliceInstanceId;
/**
* \brief when was the request to spawn a slice and bind it made
*/
AZStd::chrono::system_clock::time_point m_bindTime;
AZ::SliceComponent::EntityIdToEntityIdMap m_staticToRuntimeEntityMap;
/**
* \brief The state of the slice instance.
*/
enum class State
{
/**
* \brief Has not started instantiating the slice instance.
*/
NewRequest,
/**
* \brief Waiting on the slice to spawn.
*/
Spawning,
/**
* \brief Successfully spawned the slice assets.
*/
Spawned,
/**
* \brief Failed to spawn the slice.
*/
Failed
};
State m_state = State::NewRequest;
};
/**
* NetBindingSystemImpl works in conjunction with NetBindingComponent and
* NetBindingComponentChunk to perform network binding for game entities.
*
* It is responsible for adding entity replicas to the network on the master side
* and servicing entity spawn requests from the network on the proxy side, as
* well as detecting network availability and triggering network binding/unbinding.
*
* The system is first activated on the host side when OnNetworkSessionActivated event
* is received, and NetBindingSystemContextData is created.
* The system becomes fully operational when the NetBindingSystemContextData is activated
* and bound to the system, and remains operational as long as the NetBindingSystemContextData
* remains valid.
*
* Level switching is tracked by a monotonically increasing context sequence number controlled
* by the host. Spawn and bind operations are deferred until the correct sequence number
* is reached. Spawning is always performed from the game thread.
*/
class NetBindingSystemImpl
: public NetBindingSystemBus::Handler
, public NetBindingSystemEventsBus::Handler
, public EntityContextEventBus::Handler
, public AZ::TickBus::Handler
{
friend class NetBindingSystemContextData;
public:
NetBindingSystemImpl();
~NetBindingSystemImpl() override;
static void Reflect(AZ::ReflectContext* context);
virtual void Init();
virtual void Shutdown();
static const AZStd::chrono::milliseconds s_sliceBindingTimeout;
//////////////////////////////////////////////////////////////////////////
// NetBindingSystemBus
bool ShouldBindToNetwork() override;
NetBindingContextSequence GetCurrentContextSequence() override;
void AddReplicaMaster(AZ::Entity* entity, GridMate::ReplicaPtr replica) override;
AZ::EntityId GetStaticIdFromEntityId(AZ::EntityId entity) override;
AZ::EntityId GetEntityIdFromStaticId(AZ::EntityId staticEntityId) override;
void SpawnEntityFromSlice(GridMate::ReplicaId bindTo, const NetBindingSliceContext& bindToContext) override;
void SpawnEntityFromStream(AZ::IO::GenericStream& spawnData, AZ::EntityId useEntityId, GridMate::ReplicaId bindTo, NetBindingContextSequence addToContext) override;
void OnNetworkSessionActivated(GridMate::GridSession* session) override;
void OnNetworkSessionDeactivated(GridMate::GridSession* session) override;
void UnbindGameEntity(AZ::EntityId entity, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// EntityContextEventBus::Handler
void OnEntityContextReset() override;
void OnEntityContextLoadedFromStream(const AZ::SliceComponent::EntityList& contextEntities) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
//////////////////////////////////////////////////////////////////////////
protected:
//! Called by the NetBindingContext chunk when it is activated
void OnContextDataActivated(GridMate::ReplicaChunkPtr contextData);
//! Called by the NetBindingContext chunk when it is deactivated
void OnContextDataDeactivated(GridMate::ReplicaChunkPtr contextData);
//! Update the current binding context sequence
virtual void UpdateContextSequence();
//! Process pending spawn requests
virtual void ProcessSpawnRequests();
//! Process pending bind requests
virtual void ProcessBindRequests();
//! Performs final stage of entity spawning process
virtual bool BindAndActivate(AZ::Entity* entity, GridMate::ReplicaId replicaId, bool addToContext, const AZ::SliceComponent::SliceInstanceId& sliceInstanceId);
//! Called on the host to spawn the net binding system replica
virtual GridMate::Replica* CreateSystemReplica();
AZ_FORCE_INLINE bool ReadyToAddReplica() const;
class SpawnRequest
{
public:
GridMate::ReplicaId m_bindTo;
AZ::EntityId m_useEntityId;
AZStd::vector<AZ::u8> m_spawnDataBuffer;
};
typedef AZStd::list<SpawnRequest> SpawnRequestContainerType;
typedef AZStd::map<NetBindingContextSequence, SpawnRequestContainerType> SpawnRequestContextContainerType;
typedef AZStd::unordered_map<AZ::SliceComponent::SliceInstanceId, NetBindingSliceInstantiationHandler> SliceRequestContainerType;
typedef AZStd::map<NetBindingContextSequence, SliceRequestContainerType> BindRequestContextContainerType;
GridMate::GridSession* m_bindingSession;
GridMate::ReplicaChunkPtr m_contextData;
NetBindingContextSequence m_currentBindingContextSequence;
SpawnRequestContextContainerType m_spawnRequests;
BindRequestContextContainerType m_bindRequests;
AZStd::list<AZStd::pair<AZ::EntityId, GridMate::ReplicaPtr>> m_addMasterRequests;
/**
* \brief override how root slice entities' replicas should be loaded
*
* We occasionally get GameContextBridge replica (that tells us what level to load) before we get
* a replica that tells us that we are connecting to a network sessions, thus we may not figure out in time if we
* need to load the root slice entities with NetBindingComponent as master replicas or proxy replicas.
* This is a fix until proper order is established.
*
* \param isAuthoritative true if root slice entities with NetBindingComponents to be loaded authoritatively
*/
void OverrideRootSliceLoadMode(bool isAuthoritative)
{
m_isAuthoritativeRootSliceLoad = isAuthoritative;
m_overrideRootSliceLoadAuthoritative = true;
}
private:
/**
* \brief True if the root slice is to be loaded authoritatively
*/
bool m_isAuthoritativeRootSliceLoad;
/**
* \brief True if root slice loading mode was overriden, otherwise the mode would be determined via m_bindingSession
*/
bool m_overrideRootSliceLoadAuthoritative;
/**
* \brief A helper method to figure the mode of loading root slice entities' replicas
* \return True if the root slice entities is to be loaded authoritatively
*/
bool IsAuthoritateLoad() const;
void UpdateClock(float deltaTime);
AZStd::chrono::system_clock::time_point Now() const;
AZStd::chrono::system_clock::time_point m_currentTime;
};
class NetBindingSystemContextData
: public GridMate::ReplicaChunk
{
public:
AZ_CLASS_ALLOCATOR(NetBindingSystemContextData, AZ::SystemAllocator, 0);
static const char* GetChunkName() { return "NetBindingSystemContextData"; }
NetBindingSystemContextData();
bool IsReplicaMigratable() override { return true; }
bool IsBroadcast() override { return true; }
void OnReplicaActivate(const GridMate::ReplicaContext& rc) override;
void OnReplicaDeactivate(const GridMate::ReplicaContext& rc) override;
GridMate::DataSet<AZ::u32, GridMate::VlqU32Marshaler> m_bindingContextSequence;
};
} // namespace AzFramework
@@ -1,38 +0,0 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
namespace AzFramework
{
class NetworkContext;
/**
* The NetSystemRequestBus services requests for global networking systems in AzFramework
*/
class NetSystemRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
NetSystemRequests() = default;
virtual ~NetSystemRequests() = default;
virtual NetworkContext* GetNetworkContext() = 0;
};
using NetSystemRequestBus = AZ::EBus<NetSystemRequests>;
}
@@ -1,378 +0,0 @@
/*
* 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 <AzFramework/Network/NetworkContext.h>
#include <AzFramework/Network/NetBindable.h>
#include <GridMate/Replica/DataSet.h>
namespace AzFramework
{
NetworkContext::DescBase::DescBase(const char* name, ptrdiff_t offset)
: m_name(name)
, m_offset(offset)
{
}
NetworkContext::FieldDescBase::FieldDescBase(const char* name, ptrdiff_t offset)
: DescBase(name, offset)
, m_dataSetIdx(static_cast<size_t>(-1))
{
}
NetworkContext::RpcDescBase::RpcDescBase(const char* name, ptrdiff_t offset)
: DescBase(name, offset)
, m_rpcIdx(static_cast<size_t>(-1))
{
}
NetworkContext::CtorDataBase::CtorDataBase(const char* name)
: m_name(name)
{
}
NetworkContext::ClassBuilder::ClassBuilder(NetworkContext* context, ClassDescPtr binding)
: m_binding(binding)
, m_context(context)
{
}
NetworkContext::ClassBuilder::~ClassBuilder()
{
if (m_context->IsRemovingReflection())
{
if (m_binding->UnregisterChunkType)
{
m_binding->UnregisterChunkType();
}
}
else
{
if (m_binding->RegisterChunkType)
{
m_binding->RegisterChunkType();
}
}
}
NetworkContext::ClassDesc::ClassDesc(const char* name, const AZ::Uuid& typeId /* = AZ::Uuid() */)
: m_name(name)
, m_typeId(typeId)
{
}
///////////////////////////////////////////////////////////////////////////
/// NetworkContext
///////////////////////////////////////////////////////////////////////////
NetworkContext::NetworkContext()
{
}
NetworkContext::~NetworkContext()
{
}
size_t NetworkContext::GetReflectedChunkSize(const AZ::Uuid& typeId) const
{
size_t totalSize = 0;
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
for (const auto& field : binding->m_chunkDesc.m_fields)
{
totalSize += field->GetDataSetSize();
}
for (const auto& rpc : binding->m_chunkDesc.m_rpcs)
{
totalSize += rpc->GetRpcSize();
}
}
return totalSize;
}
bool NetworkContext::UsesSelfAsChunk(const AZ::Uuid& typeId) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
return !binding->m_chunkDesc.m_external && binding->m_chunkDesc.m_fields.size() > 0;
}
return false;
}
bool NetworkContext::UsesExternalChunk(const AZ::Uuid& typeId) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
return binding->m_chunkDesc.m_external && (AZ::u32(binding->m_chunkDesc.m_chunkId) != 0);
}
return false;
}
ReplicaChunkBase* NetworkContext::CreateReplicaChunk(const AZ::Uuid& typeId)
{
const auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ClassDescPtr binding = it->second;
if (binding->CreateReplicaChunk)
{
ReplicaChunkDescriptor* descriptor = ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(binding->m_chunkDesc.m_chunkId);
AZ_Assert(descriptor, "NetworkContext cannot find replica chunk descriptor for %s. Did you remember to register the chunk type?", binding->m_name);
ReplicaChunkDescriptorTable::Get().BeginConstructReplicaChunk(descriptor);
ReplicaChunkBase* chunk = binding->CreateReplicaChunk();
ReplicaChunkDescriptorTable::Get().EndConstructReplicaChunk();
chunk->Init(descriptor);
return chunk;
}
}
/*
* Special case: empty declarations such as:
*
* static void Reflect() {
* ....
* NetworkContext->Class<MyComponent>();
* }
*
* Result in no ReplicaChunks being created. It's treated as a no-op. No replication will be performed.
*/
return nullptr;
}
void NetworkContext::DestroyReplicaChunk(ReplicaChunkBase* chunk)
{
ReplicaChunkClassId chunkId = chunk->GetDescriptor()->GetChunkTypeId();
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
ClassDescPtr binding = it->second;
binding->DestroyReplicaChunk(chunk);
return;
}
AZ_Warning("NetworkContext", false, "DestroyReplicaChunk could not find a binding for %s", chunk->GetDescriptor()->GetChunkName());
}
void NetworkContext::Bind(NetBindable* instance, ReplicaChunkPtr chunk, NetworkContextBindMode mode)
{
const AZ::Uuid& typeId = instance->RTTI_GetType();
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
ClassDescPtr binding = it->second;
if (chunk)
{
ReplicaChunkClassId chunkId = chunk->GetDescriptor()->GetChunkTypeId();
AZ_Assert(binding->m_chunkDesc.m_chunkId == chunkId, "NetworkContext detected a type mismatch while trying to bind an instance to a ReplicaChunk");
if (binding->m_chunkDesc.m_chunkId == chunkId)
{
if (!binding->m_chunkDesc.m_external)
{
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(chunk.get());
refChunk->Bind(instance, mode);
}
}
}
else
{
if (binding->BindRpcs)
{
binding->BindRpcs(instance);
}
}
}
}
void NetworkContext::EnumerateFields(const ReplicaChunkClassId& chunkId, FieldVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& field : chunkDesc.m_fields)
{
visitor(field.get());
}
}
}
void NetworkContext::EnumerateFields(const AZ::Uuid& typeId, FieldVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& field : chunkDesc.m_fields)
{
visitor(field.get());
}
}
}
void NetworkContext::EnumerateRpcs(const ReplicaChunkClassId& chunkId, RpcVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& rpc : chunkDesc.m_rpcs)
{
visitor(rpc.get());
}
}
}
void NetworkContext::EnumerateRpcs(const AZ::Uuid& typeId, RpcVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& rpc : chunkDesc.m_rpcs)
{
visitor(rpc.get());
}
}
}
void NetworkContext::EnumerateCtorData(const ReplicaChunkClassId& chunkId, CtorVisitor visitor) const
{
auto it = m_chunkBindings.find(chunkId);
if (it != m_chunkBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& ctor : chunkDesc.m_ctors)
{
visitor(ctor.get());
}
}
}
void NetworkContext::EnumerateCtorData(const AZ::Uuid& typeId, CtorVisitor visitor) const
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
const ChunkDesc& chunkDesc = it->second->m_chunkDesc;
for (const auto& ctor : chunkDesc.m_ctors)
{
visitor(ctor.get());
}
}
}
///////////////////////////////////////////////////////////////////////////
ReflectedReplicaChunkBase::ReflectedReplicaChunkBase()
: m_ctorBuffer(GridMate::EndianType::IgnoreEndian, 0)
{
}
///////////////////////////////////////////////////////////////////////////
NetworkContextChunkDescriptor::NetworkContextChunkDescriptor(const char* name, size_t size, const AZ::Uuid& typeId)
: ReplicaChunkDescriptor(name, size)
, m_typeId(typeId)
{
}
ReplicaChunkBase* NetworkContextChunkDescriptor::CreateFromStream(UnmarshalContext& ctx)
{
AZ_Assert(!m_typeId.IsNull(), "No typeid associated with NetworkContextChunkDescriptor, cannot spawn Chunk");
if (!m_typeId.IsNull())
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to construct ReflectedReplicaChunk");
ReplicaChunkBase* replicaChunk = netContext->CreateReplicaChunk(m_typeId);
if (ctx.m_hasCtorData && ctx.m_iBuf)
{
NetworkContextChunkDescriptor* netChunkDesc = static_cast<NetworkContextChunkDescriptor*>(replicaChunk->GetDescriptor());
if (netChunkDesc->IsAuto())
{
// copy each ctor data field into the ctor buffer
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(replicaChunk);
netContext->EnumerateCtorData(m_typeId,
[&ctx, refChunk](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Copy(*ctx.m_iBuf, refChunk->m_ctorBuffer);
});
}
}
return replicaChunk;
}
return nullptr;
}
void NetworkContextChunkDescriptor::DeleteReplicaChunk(ReplicaChunkBase* chunk)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to destroy ReflectedReplicaChunk");
netContext->DestroyReplicaChunk(chunk);
}
void NetworkContextChunkDescriptor::MarshalCtorData(ReplicaChunkBase* chunk, WriteBuffer& buffer)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to collect ctor data for ReflectedReplicaChunk");
NetBindable* netBindable = static_cast<NetBindable*>(chunk->GetHandler());
NetworkContextChunkDescriptor* netChunkDesc = static_cast<NetworkContextChunkDescriptor*>(chunk->GetDescriptor());
if (!netChunkDesc->IsAuto())
{
return;
}
if (netBindable) // chunk is bound, get source data from the netBindable
{
netContext->EnumerateCtorData(m_typeId,
[netBindable, &buffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Marshal(netBindable, buffer);
});
}
else // chunk is not bound yet, copy the ctor data for forwarding
{
ReflectedReplicaChunkBase* refChunk = static_cast<ReflectedReplicaChunkBase*>(chunk);
ReadBuffer src(refChunk->m_ctorBuffer.GetEndianType(), refChunk->m_ctorBuffer.Get(), refChunk->m_ctorBuffer.Size());
netContext->EnumerateCtorData(m_typeId,
[&src, &buffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Copy(src, buffer);
});
}
}
void NetworkContextChunkDescriptor::DiscardCtorStream(UnmarshalContext& ctx)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to skip ctor data for ReflectedReplicaChunk");
if (ctx.m_hasCtorData)
{
// Iterate over all of the ctor data and unmarshal it with no destination,
// which will advance the buffer past the ctor data for this object
netContext->EnumerateCtorData(m_typeId,
[&ctx](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Unmarshal(*ctx.m_iBuf, nullptr);
});
}
}
}
@@ -1,969 +0,0 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Network/NetSystemBus.h>
#include <AzFramework/Network/NetBindable.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/typetraits/is_base_of.h>
#include <AzCore/std/functional.h>
namespace AzFramework
{
class NetBindable;
using GridMate::ReplicaChunkInterface;
using GridMate::ReplicaChunkBase;
using GridMate::ReplicaChunk;
using GridMate::ReplicaChunkDescriptor;
using GridMate::DefaultReplicaChunkDescriptor;
using GridMate::ReplicaChunkDescriptorTable;
using GridMate::ReplicaChunkClassId;
using GridMate::ReplicaChunkPtr;
using GridMate::Rpc;
using GridMate::ZoneMask;
using GridMate::ZoneMask_All;
using GridMate::UnmarshalContext;
using GridMate::ReadBuffer;
using GridMate::WriteBuffer;
using GridMate::WriteBufferDynamic;
///////////////////////////////////////////////////////////////////////////
// GridMate ReplicaChunk/ReplicaChunkDescriptors
///////////////////////////////////////////////////////////////////////////
class ReflectedReplicaChunkBase
: public ReplicaChunkBase
, public ReplicaChunkInterface
{
friend NetworkContext;
public:
ReflectedReplicaChunkBase();
bool IsReplicaMigratable() override { return true; }
/// Returns the chunk type name, e.g. "ReflectedReplicaChunk<MyClass>"
virtual const char* GetName() const = 0;
/// Returns the linear size of the chunk including DataSets and RPCs
virtual size_t GetSize() const = 0;
/// Returns a pointer to the start of the DataSet/RPC storage allocated with the chunk
virtual AZ::u8* GetDataStart() const = 0;
/// Binds an instance of the reflected class to this chunk
virtual void Bind(NetBindable* instance, NetworkContextBindMode mode) = 0;
/// Removes network bindings from the bound NetBindable
virtual void Unbind() = 0;
WriteBufferDynamic m_ctorBuffer; ///< Buffer to hold ctor data before the chunk is bound
};
/// This will be the header for a blob in memory:
/// The layout looks like:
/// * ReflectedReplicaChunk<T>
/// * DataSets
/// * RPCs
template <class ClassType>
class ReflectedReplicaChunk
: public ReflectedReplicaChunkBase
{
friend NetworkContext;
public:
static const char* GetChunkName();
static size_t GetChunkSize();
public:
AZ_CLASS_ALLOCATOR(ReflectedReplicaChunk, AZ::SystemAllocator, 0);
ReflectedReplicaChunk()
: m_dataSets(reinterpret_cast<AZ::u8*>(this) + sizeof(*this))
{
}
const char* GetName() const override { return GetChunkName(); }
size_t GetSize() const override { return GetChunkSize(); }
AZ::u8* GetDataStart() const override { return const_cast<AZ::u8*>(m_dataSets); }
void Bind(NetBindable* instance, NetworkContextBindMode mode) override;
void Unbind() override;
private:
const AZ::u8* m_dataSets; ///< Points to the beginning of the datasets for this chunk
};
class NetworkContextChunkDescriptor
: public ReplicaChunkDescriptor
{
public:
NetworkContextChunkDescriptor(const char* name, size_t size, const AZ::Uuid& typeId = AZ::Uuid());
ReplicaChunkBase* CreateFromStream(UnmarshalContext& ctx) override;
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override;
void DiscardCtorStream(UnmarshalContext&) override;
void MarshalCtorData(ReplicaChunkBase*, WriteBuffer&) override;
void Bind(const AZ::Uuid& typeId) { m_typeId = typeId; }
virtual bool IsAuto() const { return false; }
private:
AZ::Uuid m_typeId; ///< TypeId of the class this descriptor represents (not the chunk type)
};
template <class ClassType, ZoneMask mask = ZoneMask_All>
class AutoChunkDescriptor
: public NetworkContextChunkDescriptor
{
public:
AutoChunkDescriptor()
: NetworkContextChunkDescriptor(ReflectedReplicaChunk<ClassType>::GetChunkName(), ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZ::RttiTypeId<ClassType>())
{
}
ZoneMask GetZoneMask() const override { return mask; }
bool IsAuto() const override { return true; }
};
template <class ChunkType, ZoneMask mask = ZoneMask_All>
class ExternalChunkDescriptor
: public NetworkContextChunkDescriptor
{
public:
ExternalChunkDescriptor()
: NetworkContextChunkDescriptor(ChunkType::GetChunkName(), sizeof(ChunkType))
{}
ZoneMask GetZoneMask() const override { return mask; }
};
///////////////////////////////////////////////////////////////////////////
/// NetworkContext can be used to reflect classes for network serialization
/// It will automatically generate ReplicaChunks and bind them to instances
/// when requested. It also serves as a binding registry for binding a class
/// to the ReplicaChunk that should be used to replicate it.
///////////////////////////////////////////////////////////////////////////
class NetworkContext
: public AZ::ReflectContext
{
public:
/// @cond EXCLUDE_DOCS
class ClassBuilder;
class ClassDesc;
using ClassDescPtr = AZStd::intrusive_ptr<ClassDesc>;
using ClassBuilderPtr = AZStd::intrusive_ptr<ClassBuilder>;
using ClassBindings = AZStd::unordered_map<AZ::Uuid, ClassDescPtr>;
using ChunkBindings = AZStd::unordered_map<ReplicaChunkClassId, ClassDescPtr>;
using ClassInfo = ClassBuilder; ///< @deprecated Use NetworkContext::ClassBuilder
using ClassInfoPtr = ClassBuilderPtr; ///< @deprecated Use NetworkContext::ClassBuilderPtr
/// @endcond
class IntrusiveRefCounted
{
public:
virtual ~IntrusiveRefCounted() {}
private:
// refcount
template<class T>
friend struct AZStd::IntrusivePtrCountPolicy;
mutable unsigned int m_refCount = 0;
AZ_FORCE_INLINE void add_ref() { ++m_refCount; }
AZ_FORCE_INLINE void release()
{
AZ_Assert(m_refCount > 0, "Reference count logic error, trying to remove reference when refcount is 0");
if (--m_refCount == 0)
{
delete this;
}
}
};
/**
* Interface for recording classes, chunks, and datasets
* When destructed at the end of reflection, it will register/unregister the ChunkDescriptor
*/
class ClassBuilder
: public IntrusiveRefCounted
{
friend class NetworkContext;
protected:
AZ_CLASS_ALLOCATOR(ClassBuilder, AZ::SystemAllocator, 0);
ClassBuilder(NetworkContext* context, ClassDescPtr binding);
public:
~ClassBuilder();
ClassBuilderPtr operator->() { return this; }
/// Bind a ReplicaChunk type to this class for network serialization
template <class ChunkType, typename DescriptorType = ExternalChunkDescriptor<ChunkType> >
ClassBuilderPtr Chunk();
/// Bind a NetBindable's Field
template <class ClassType, typename FieldType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableFieldBase, FieldType>::value, ClassBuilderPtr>::type
Field(const char* name, FieldType ClassType::* address);
/// Declare an external chunk's DataSet
template <class ClassType, typename DataSetType>
typename AZStd::enable_if<AZStd::is_base_of<DataSetBase, DataSetType>::value, ClassBuilderPtr>::type
Field(const char* name, DataSetType ClassType::* address);
/// Bind an Rpc::BindInterface for this chunk
template <class ClassType, // class this RPC is part of
class InterfaceType = ClassType, // class implementing the RPC, must derive from ReplicaChunkInterface
typename ... Args,
class Traits = RpcDefaultTraits,
typename RpcBindType = typename Rpc<Args...>::template BindInterface<InterfaceType, bool (InterfaceType::*)(typename Args::Type..., const RpcContext&), Traits> >
typename AZStd::enable_if<AZStd::is_base_of<RpcBase, RpcBindType>::value, ClassBuilderPtr>::type
RPC(const char* name, RpcBindType ClassType::* rpc);
/// Bind a NetBindable::Rpc for this NetBindable
template <class ClassType,
class InterfaceType = ClassType,
typename ... Args,
class Traits = RpcDefaultTraits,
typename RpcBindType = typename NetBindable::Rpc<Args...>::template Bind<InterfaceType, bool (InterfaceType::*)(Args..., const RpcContext&), Traits> >
typename AZStd::enable_if<AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value, ClassBuilderPtr>::type
RPC(const char* name, RpcBindType ClassType::* rpc);
#define CTOR_DATA_OVERLOAD(_getsig, _setsig) \
template <class ClassType, class DataType, typename MarshalerType = Marshaler<DataType> > \
ClassBuilderPtr CtorData(const char* name, _getsig, _setsig, const MarshalerType&marshaler = MarshalerType()) \
{ \
return CtorDataImpl<ClassType, DataType>(name, getter, setter, marshaler); \
}
/// Bind a getter/setter pair for data required during object construction
// this has to be done via overload so that the user does not have to explicitly provide
// the template arguments, they can be divined from the function call
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)(), void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)() const, void (ClassType::* setter)(const DataType&));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType(ClassType::* getter)() const, void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(DataType & (ClassType::* getter)() const, void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)(), void (ClassType::* setter)(DataType));
CTOR_DATA_OVERLOAD(const DataType&(ClassType::* getter)() const, void (ClassType::* setter)(DataType));
#undef CTOR_DATA_OVERLOAD
private:
template <class ClassType,
class DataType,
class GetterFunction,
class SetterFunction,
typename MarshalerType = Marshaler<DataType> >
ClassBuilderPtr CtorDataImpl(const char* name, GetterFunction getter, SetterFunction setter, const MarshalerType& marshaler = MarshalerType());
private:
ClassDescPtr m_binding;
NetworkContext* m_context;
};
class DescBase
: public IntrusiveRefCounted
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(DescBase, AZ::SystemAllocator, 0);
DescBase(const char* name, ptrdiff_t offset);
virtual ~DescBase() {}
const char* GetName() const { return m_name; }
ptrdiff_t GetOffset() const { return m_offset; }
protected:
const char* m_name; ///< Field name, will be used as DataSet debug name
ptrdiff_t m_offset; ///< Offset from an instance pointer (a ReplicaChunk or the actual class instance)
};
class FieldDescBase
: public DescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(FieldDescBase, AZ::SystemAllocator, 0);
FieldDescBase(const char* name, ptrdiff_t offset);
virtual ~FieldDescBase() {}
virtual void ConstructDataSet(void*) const = 0;
virtual void DestructDataSet(void*) const = 0;
virtual size_t GetDataSetSize() const = 0;
size_t GetDataSetIndex() const { return m_dataSetIdx; }
protected:
size_t m_dataSetIdx;
};
/**
* Represents a DataSet in a chunk or class
* NOTE: m_offset in this class is the offset from ReplicaChunk* -> DataSet
*/
template <typename DataSetType>
class DataSetDesc
: public FieldDescBase
{
public:
AZ_CLASS_ALLOCATOR(DataSetDesc, AZ::SystemAllocator, 0);
DataSetDesc(const char* name, ptrdiff_t offset);
void ConstructDataSet(void*) const override {}
void DestructDataSet(void*) const override {}
size_t GetDataSetSize() const override { return sizeof(DataSetType); }
};
/**
* Represents a field in a chunk, responsible for creating a DataSet<T, Marshaler, Throttler>
* that represents the field
* NOTE: m_offset in this class is the offset from NetBindable* -> NetBindable::Field
*/
template <typename FieldType>
class NetBindableFieldDesc
: public FieldDescBase
{
public:
using DataSetType = typename FieldType::DataSetType;
public:
AZ_CLASS_ALLOCATOR(NetBindableFieldDesc, AZ::SystemAllocator, 0);
NetBindableFieldDesc(const char* name, ptrdiff_t offset);
void ConstructDataSet(void* mem) const override { FieldType::ConstructDataSet(mem, m_name); }
void DestructDataSet(void* mem) const override { FieldType::DestructDataSet(mem); }
size_t GetDataSetSize() const override { return sizeof(DataSetType); }
};
class RpcDescBase
: public DescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(RpcDescBase, AZ::SystemAllocator, 0);
RpcDescBase(const char* name, ptrdiff_t offset);
virtual ~RpcDescBase() {}
virtual void ConstructRpc(void*) const {}
virtual void DestructRpc(void*) const {}
virtual size_t GetRpcSize() const { return 0; }
size_t GetRpcIndex() const { return m_rpcIdx; }
protected:
size_t m_rpcIdx;
};
template <typename RpcBindType>
class NetBindableRpcDesc
: public RpcDescBase
{
friend class NetworkContext;
public:
AZ_CLASS_ALLOCATOR(NetBindableRpcDesc, AZ::SystemAllocator, 0);
NetBindableRpcDesc(const char* name, ptrdiff_t offset)
: RpcDescBase(name, offset)
{
static_assert((AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value), "NetBindableRpcDesc is intended for use only with NetBindableRpcs");
}
void ConstructRpc(void* mem) const override { RpcBindType::ConstructRpc(mem, m_name); }
void DestructRpc(void* mem) const override { RpcBindType::DestructRpc(mem); }
size_t GetRpcSize() const override { return sizeof(typename RpcBindType::BindInterfaceType); }
};
class CtorDataBase
: public IntrusiveRefCounted
{
public:
AZ_CLASS_ALLOCATOR(CtorDataBase, AZ::SystemAllocator, 0);
CtorDataBase(const char* name);
virtual ~CtorDataBase() {}
virtual void Marshal(NetBindable* netBindable, WriteBuffer& buffer) const = 0;
virtual void Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const = 0;
virtual void Copy(ReadBuffer& src, WriteBuffer& dest) const = 0;
protected:
const char* m_name;
};
template <class ClassType, class DataType, typename MarshalerType>
class CtorDataDesc
: public CtorDataBase
{
using GetterFunction = AZStd::function<DataType(ClassType*)>;
using SetterFunction = AZStd::function<void (ClassType*, const DataType&)>;
public:
AZ_CLASS_ALLOCATOR(CtorDataDesc, AZ::SystemAllocator, 0);
CtorDataDesc(const char* name, GetterFunction get, SetterFunction set)
: CtorDataBase(name)
, m_get(get)
, m_set(set)
{}
CtorDataDesc(const char* name, DataType(ClassType::* getter)(), void (ClassType::* setter)(const DataType&))
: CtorDataBase(name)
, m_get(AZStd::bind(getter, AZStd::placeholders::_1))
, m_set(AZStd::bind(setter, AZStd::placeholders::_1, AZStd::placeholders::_2))
{}
void Marshal(NetBindable* netBindable, WriteBuffer& buffer) const override;
void Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const override;
virtual void Copy(ReadBuffer& src, WriteBuffer& dest) const override;
GetterFunction m_get;
SetterFunction m_set;
MarshalerType m_marshaler;
};
struct ChunkDesc
{
public:
using Fields = AZStd::vector<AZStd::intrusive_ptr<FieldDescBase> >;
using Rpcs = AZStd::vector<AZStd::intrusive_ptr<RpcDescBase> >;
using Ctors = AZStd::vector<AZStd::intrusive_ptr<CtorDataBase> >;
const char* m_name = nullptr; ///< The name of the chunk
ReplicaChunkClassId m_chunkId; ///< The registered id of the ReplicaChunk this class will use
Fields m_fields; ///< list of data fields in the ReplicaChunk
Rpcs m_rpcs; ///< list of RPCs in the ReplicaChunk
Ctors m_ctors; ///< list of ctor callbacks to gather/apply ctor data
bool m_external = false; ///< If true, this chunk is separate from the class bound to it
};
/**
* Contains the chunk factory and field descriptions for a given class
*/
class ClassDesc
: public IntrusiveRefCounted
{
public:
AZ_CLASS_ALLOCATOR(ClassDesc, AZ::SystemAllocator, 0);
ClassDesc(const char* name = nullptr, const AZ::Uuid& typeId = AZ::Uuid());
public:
const char* m_name; ///< The name of the class that is bound
AZ::Uuid m_typeId; ///< The type that this binding represents (null for chunks)
ChunkDesc m_chunkDesc; ///< Descriptor for the chunk for this type
/// Functor which will register the ReplicaChunkDescriptor with the global registry
AZStd::function<bool()> RegisterChunkType;
/// Functor to unregister the ReplicaChunkDescriptor (during reflection removal)
AZStd::function<void()> UnregisterChunkType;
/// Functor which will create a ReplicaChunk and bind it to the given instance
AZStd::function<ReplicaChunkBase*()> CreateReplicaChunk;
/// Functor which can destroy a ReplicaChunk and free its memory
AZStd::function<void(ReplicaChunkBase*)> DestroyReplicaChunk;
/// Functor which binds an instance of this class to its RPCs for local dispatch
AZStd::function<void(NetBindable* bindable)> BindRpcs;
};
AZ_CLASS_ALLOCATOR(NetworkContext, AZ::SystemAllocator, 0);
AZ_RTTI(NetworkContext, "{B1172D4A-EA1B-441D-AAE6-A9933DAECA8A}", AZ::ReflectContext);
NetworkContext();
virtual ~NetworkContext();
/// Register a class with the NetworkContext for replication
template <class ClassType>
ClassBuilderPtr Class();
/// Create a replica chunk for a given class
ReplicaChunkBase* CreateReplicaChunk(const AZ::Uuid& typeId);
/// Create a replica chunk for a given class, template version
template <class ClassType>
ReplicaChunkBase* CreateReplicaChunk();
/// Destroy a replica chunk for a given class
void DestroyReplicaChunk(ReplicaChunkBase * chunk);
/// Bind an instance and a chunk to each other
void Bind(NetBindable * instance, ReplicaChunkPtr chunk, NetworkContextBindMode mode);
/// Returns whether or not a given type uses a reflected (automatic) ReplicaChunk
bool UsesSelfAsChunk(const AZ::Uuid & typeId) const;
/// Returns whether or not a given type uses a custom ReplicaChunk
bool UsesExternalChunk(const AZ::Uuid & typeId) const;
/// Return the size of the the chunk which will represent the given type
size_t GetReflectedChunkSize(const AZ::Uuid & typeId) const;
using FieldVisitor = AZStd::function<void(FieldDescBase*)>;
void EnumerateFields(const ReplicaChunkClassId&chunkId, FieldVisitor visitor) const;
void EnumerateFields(const AZ::Uuid & typeId, FieldVisitor visitor) const;
using RpcVisitor = AZStd::function<void(RpcDescBase*)>;
void EnumerateRpcs(const ReplicaChunkClassId&chunkId, RpcVisitor visitor) const;
void EnumerateRpcs(const AZ::Uuid & typeId, RpcVisitor visitor) const;
using CtorVisitor = AZStd::function<void(CtorDataBase*)>;
void EnumerateCtorData(const ReplicaChunkClassId&chunkId, CtorVisitor visitor) const;
void EnumerateCtorData(const AZ::Uuid & typeId, CtorVisitor visitor) const;
private:
template <class ClassType>
void InitReflectedChunkBinding(ClassDescPtr binding);
template <class ChunkType, typename DescriptorType = ExternalChunkDescriptor<ChunkType> >
void InitExternalChunkBinding(ClassDescPtr binding);
private:
ClassBindings m_classBindings;
ChunkBindings m_chunkBindings;
};
///////////////////////////////////////////////////////////////////////////
template <class ClassType>
NetworkContext::ClassBuilderPtr NetworkContext::Class()
{
static_assert((AZStd::is_base_of<NetBindable, ClassType>::value), "Classes reflected through NetworkContext must be derived from NetBindable");
const AZ::Uuid& typeId = AZ::AzTypeInfo<ClassType>::Uuid();
ClassDescPtr binding = nullptr;
if (IsRemovingReflection()) // Just remove the entire class definition
{
auto it = m_classBindings.find(typeId);
if (it != m_classBindings.end())
{
binding = it->second;
m_chunkBindings.erase(binding->m_chunkDesc.m_chunkId);
m_classBindings.erase(it);
}
}
else
{
auto ret = m_classBindings.insert_key(typeId);
AZ_Assert(ret.second, "Cannot register more than one type with the same Uuid in the NetworkContext");
binding = ret.first->second = aznew ClassDesc(AZ::AzTypeInfo<ClassType>::Name(), AZ::AzTypeInfo<ClassType>::Uuid());
}
return aznew ClassBuilder(this, binding);
}
template <class ClassType>
void NetworkContext::InitReflectedChunkBinding(ClassDescPtr binding)
{
if (!binding->RegisterChunkType)
{
binding->m_chunkDesc.m_name = ReflectedReplicaChunk<ClassType>::GetChunkName();
ReplicaChunkClassId chunkClassId = ReplicaChunkClassId(binding->m_chunkDesc.m_name);
m_chunkBindings[chunkClassId] = binding;
NetworkContext* netContext = this;
binding->RegisterChunkType = [chunkClassId, netContext]()
{
bool result = ReplicaChunkDescriptorTable::Get().RegisterChunkType<ReflectedReplicaChunk<ClassType>, AutoChunkDescriptor<ClassType> >();
ReplicaChunkDescriptor* desc = ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(chunkClassId);
ReplicaChunkDescriptorTable::Get().BeginConstructReplicaChunk(desc);
// The offset recorded in NetBindableFields is the offset in the NetBindable
// We must compute the offset of the generated DataSets here and record the
// index from the descriptor
ptrdiff_t offset = sizeof(ReflectedReplicaChunk<ClassType>); // data sets are right after the ReflectedReplicaChunk<> in memory
netContext->EnumerateFields(chunkClassId,
[desc, &offset](FieldDescBase* field)
{
desc->RegisterDataSet(field->m_name, offset);
field->m_dataSetIdx = desc->GetDataSetIndex(offset);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[desc, &offset](RpcDescBase* rpc)
{
desc->RegisterRPC(rpc->m_name, offset);
rpc->m_rpcIdx = desc->GetRpcIndex(offset);
offset += rpc->GetRpcSize();
});
AZ_Assert(offset == static_cast<ptrdiff_t>(ReflectedReplicaChunk<ClassType>::GetChunkSize()), "Overflow/underflow while registering DataSets for %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
ReplicaChunkDescriptorTable::Get().EndConstructReplicaChunk();
return result;
};
binding->UnregisterChunkType = [chunkClassId]()
{
ReplicaChunkDescriptorTable::Get().UnregisterReplicaChunkDescriptor(chunkClassId);
};
binding->CreateReplicaChunk = [netContext, chunkClassId]()
{
ReflectedReplicaChunkBase* chunk = new(azmalloc(ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZStd::alignment_of<ReflectedReplicaChunk<ClassType> >::value, AZ::SystemAllocator, ReflectedReplicaChunk<ClassType>::GetChunkName()))ReflectedReplicaChunk<ClassType>();
AZ::u8* dataStart = chunk->GetDataStart();
AZ::u8* dataEnd = reinterpret_cast<AZ::u8*>(chunk) + chunk->GetSize();
ptrdiff_t offset = 0;
netContext->EnumerateFields(chunkClassId,
[&offset, dataStart, dataEnd](FieldDescBase* field)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* dataSetMem = reinterpret_cast<void*>(dataStart + offset);
field->ConstructDataSet(dataSetMem);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[&offset, dataStart, dataEnd](RpcDescBase* rpc)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* rpcMem = reinterpret_cast<void*>(dataStart + offset);
rpc->ConstructRpc(rpcMem);
offset += rpc->GetRpcSize();
});
AZ_Assert((dataStart + offset) == dataEnd, "Overflow/underflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
return chunk;
};
binding->DestroyReplicaChunk = [netContext, chunkClassId](ReplicaChunkBase* chunkBase)
{
AZ_Assert(chunkBase->GetDescriptor()->GetChunkTypeId() == chunkClassId, "Mismatched chunk type id for %s (0x%p)", ReflectedReplicaChunk<ClassType>::GetChunkName(), chunkBase);
ReflectedReplicaChunkBase* chunk = static_cast<ReflectedReplicaChunkBase*>(chunkBase);
chunk->Unbind();
AZ::u8* dataStart = chunk->GetDataStart();
AZ::u8* dataEnd = reinterpret_cast<AZ::u8*>(chunk) + chunk->GetSize();
ptrdiff_t offset = 0;
netContext->EnumerateFields(chunkClassId,
[&offset, dataStart, dataEnd](FieldDescBase* field)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in dtor while destroying %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* dataSetMem = reinterpret_cast<void*>(dataStart + offset);
field->DestructDataSet(dataSetMem);
offset += field->GetDataSetSize();
});
netContext->EnumerateRpcs(chunkClassId,
[&offset, dataStart, dataEnd](RpcDescBase* rpc)
{
AZ_Assert((dataStart + offset) < dataEnd, "Overflow in NetworkContext::CreateReplicaChunk while creating %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
void* rpcMem = reinterpret_cast<void*>(dataStart + offset);
rpc->DestructRpc(rpcMem);
offset += rpc->GetRpcSize();
});
AZ_Assert((dataStart + offset) == dataEnd, "Overflow/underflow in dtor while destroying %s", ReflectedReplicaChunk<ClassType>::GetChunkName());
chunk->~ReflectedReplicaChunkBase();
azfree(chunk, AZ::SystemAllocator, ReflectedReplicaChunk<ClassType>::GetChunkSize(), AZStd::alignment_of<ReflectedReplicaChunk<ClassType> >::value);
};
binding->BindRpcs = [netContext, chunkClassId](NetBindable* bindable)
{
ClassType* derivedInstance = static_cast<ClassType*>(bindable);
netContext->EnumerateRpcs(chunkClassId,
[derivedInstance](const RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
bindableRpc->Bind(derivedInstance);
});
};
binding->m_chunkDesc.m_chunkId = chunkClassId;
}
}
template <class ChunkType, typename DescriptorType>
void NetworkContext::InitExternalChunkBinding(ClassDescPtr binding)
{
if (!binding->RegisterChunkType)
{
binding->m_chunkDesc.m_name = ChunkType::GetChunkName();
ReplicaChunkClassId chunkClassId = ReplicaChunkClassId(binding->m_chunkDesc.m_name);
m_chunkBindings[chunkClassId] = binding;
const AZ::Uuid& typeId = binding->m_typeId;
NetworkContext* netContext = this;
binding->RegisterChunkType = [chunkClassId, typeId, netContext]()
{
bool result = ReplicaChunkDescriptorTable::Get().RegisterChunkType<ChunkType, DescriptorType>();
NetworkContextChunkDescriptor* desc = static_cast<NetworkContextChunkDescriptor*>(ReplicaChunkDescriptorTable::Get().FindReplicaChunkDescriptor(chunkClassId));
desc->Bind(typeId);
netContext->EnumerateFields(chunkClassId,
[desc](FieldDescBase* field)
{
desc->RegisterDataSet(field->m_name, field->m_offset);
field->m_dataSetIdx = desc->GetDataSetIndex(field->m_offset);
});
netContext->EnumerateRpcs(chunkClassId,
[desc](RpcDescBase* rpc)
{
desc->RegisterRPC(rpc->m_name, rpc->m_offset);
});
return result;
};
binding->UnregisterChunkType = [chunkClassId]()
{
return ReplicaChunkDescriptorTable::Get().UnregisterReplicaChunkDescriptor(chunkClassId);
};
binding->CreateReplicaChunk = []()
{
return aznew ChunkType();
};
binding->DestroyReplicaChunk = [](ReplicaChunkBase* chunk)
{
delete chunk;
};
binding->m_chunkDesc.m_chunkId = chunkClassId;
}
}
template <class ClassType>
ReplicaChunkBase* NetworkContext::CreateReplicaChunk()
{
return CreateReplicaChunk(AZ::AzTypeInfo<ClassType>::Uuid());
}
///////////////////////////////////////////////////////////////////////////
template <class DataSetType>
NetworkContext::DataSetDesc<DataSetType>::DataSetDesc(const char* name, ptrdiff_t offset)
: NetworkContext::FieldDescBase(name, offset)
{
}
///////////////////////////////////////////////////////////////////////////
template <typename FieldType>
NetworkContext::NetBindableFieldDesc<FieldType>::NetBindableFieldDesc(const char* name, ptrdiff_t offset)
: NetworkContext::FieldDescBase(name, offset)
{
}
///////////////////////////////////////////////////////////////////////////
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Marshal(NetBindable* netBindable, WriteBuffer& buffer) const
{
ClassType* instance = static_cast<ClassType*>(netBindable);
DataType data = m_get(instance);
buffer.Write(data, m_marshaler);
}
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Unmarshal(ReadBuffer& buffer, NetBindable* netBindable) const
{
ClassType* instance = static_cast<ClassType*>(netBindable);
DataType data;
buffer.Read(data, m_marshaler);
if (instance)
{
m_set(instance, data);
}
}
template <class ClassType, class DataType, typename MarshalerType>
void NetworkContext::CtorDataDesc<ClassType, DataType, MarshalerType>::Copy(ReadBuffer& src, WriteBuffer& dest) const
{
DataType data;
src.Read(data, m_marshaler);
dest.Write(data, m_marshaler);
}
///////////////////////////////////////////////////////////////////////////
template <class ChunkType, typename DescriptorType>
NetworkContext::ClassBuilderPtr NetworkContext::ClassBuilder::Chunk()
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkBase, ChunkType>::value), "ReplicaChunks being registered with the NetworkContext must derive from ReplicaChunk");
static_assert((AZStd::is_base_of<NetworkContextChunkDescriptor, DescriptorType>::value), "Chunk bindings via NetworkContext must use a NetworkContextChunkDescriptor derived descriptor");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a ReplicaChunk for a class which has not been declared to the NetworkContext");
AZ_Assert(!m_binding->m_chunkDesc.m_chunkId, "Cannot register more than one ReplicaChunk binding for a class in the NetworkContext");
m_context->InitExternalChunkBinding<ChunkType, DescriptorType>(m_binding);
m_binding->m_chunkDesc.m_external = true;
}
return this;
}
template <class ClassType, typename FieldType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableFieldBase, FieldType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::Field(const char* name, FieldType ClassType::* address)
{
if (!m_context->IsRemovingReflection())
{
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a field for a class which has not been declared to the NetworkContext");
AZ_Assert(!m_binding->m_chunkDesc.m_external, "Cannot register a NetBindable::Field from within an external chunk");
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*address));
m_binding->m_chunkDesc.m_fields.push_back(aznew NetBindableFieldDesc<FieldType>(name, offset));
}
return this;
}
template <class ClassType, typename DataSetType>
typename AZStd::enable_if<AZStd::is_base_of<DataSetBase, DataSetType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::Field(const char* name, DataSetType ClassType::* address)
{
if (!m_context->IsRemovingReflection())
{
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register a field for a class which has not been declared to the NetworkContext");
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*address));
m_binding->m_chunkDesc.m_fields.push_back(aznew DataSetDesc<DataSetType>(name, offset));
}
return this;
}
template <class ClassType, class InterfaceType, typename ... Args, class Traits, typename RpcBindType>
typename AZStd::enable_if<AZStd::is_base_of<RpcBase, RpcBindType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::RPC(const char* name, RpcBindType ClassType::* rpc)
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkInterface, InterfaceType>::value), "Cannot bind an RPC call to an object which is not a ReplicaChunkInterface");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register an RPC for a class which has not been declared to the NetworkContext");
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*rpc));
m_binding->m_chunkDesc.m_rpcs.push_back(aznew RpcDescBase(name, offset));
}
return this;
}
template <class ClassType, class InterfaceType, typename ... Args, class Traits, typename RpcBindType>
typename AZStd::enable_if<AZStd::is_base_of<NetBindableRpcBase, RpcBindType>::value, NetworkContext::ClassBuilderPtr>::type
NetworkContext::ClassBuilder::RPC(const char* name, RpcBindType ClassType::* rpc)
{
if (!m_context->IsRemovingReflection())
{
static_assert((AZStd::is_base_of<ReplicaChunkInterface, InterfaceType>::value), "Cannot bind an RPC call to an object which is not a ReplicaChunkInterface");
AZ_Assert(!m_binding->m_typeId.IsNull(), "Cannot register an RPC for a class which has not been declared to the NetworkContext");
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
ptrdiff_t offset = reinterpret_cast<ptrdiff_t>(&(reinterpret_cast<ClassType const volatile*>(0)->*rpc));
m_binding->m_chunkDesc.m_rpcs.push_back(aznew NetBindableRpcDesc<RpcBindType>(name, offset));
}
return this;
}
template <class ClassType, class DataType, typename GetterFunction, typename SetterFunction, typename MarshalerType>
NetworkContext::ClassBuilderPtr NetworkContext::ClassBuilder::CtorDataImpl(const char* name, GetterFunction getter, SetterFunction setter, const MarshalerType&)
{
if (!m_context->IsRemovingReflection())
{
m_context->InitReflectedChunkBinding<ClassType>(m_binding);
auto get = [getter](NetBindable* nb) -> DataType { return (*static_cast<ClassType*>(nb).*getter)(); };
auto set = [setter](NetBindable* nb, const DataType& data) { (*static_cast<ClassType*>(nb).*setter)(data); };
m_binding->m_chunkDesc.m_ctors.push_back(aznew CtorDataDesc<ClassType, DataType, MarshalerType>(name, get, set));
}
return this;
}
///////////////////////////////////////////////////////////////////////////
template <class ClassType>
const char* ReflectedReplicaChunk<ClassType>::GetChunkName()
{
static char name[128] = { 0 };
if (!name[0])
{
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), "ReflectedReplicaChunk<");
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), AZ::AzTypeInfo<ClassType>::Name());
AZ::Internal::AzTypeInfoSafeCat(name, AZ_ARRAY_SIZE(name), ">");
}
return name;
}
template <class ClassType>
size_t ReflectedReplicaChunk<ClassType>::GetChunkSize()
{
static size_t chunkSize = 0;
if (chunkSize == 0)
{
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
AZ_Assert(netContext, "No NetworkContext found while trying to compute chunk size");
if (!netContext)
{
return 0;
}
chunkSize = sizeof(ReflectedReplicaChunk<ClassType>) + netContext->GetReflectedChunkSize(AZ::AzTypeInfo<ClassType>::Uuid());
}
return chunkSize;
}
template <class ClassType>
void ReflectedReplicaChunk<ClassType>::Bind(NetBindable* instance, NetworkContextBindMode mode)
{
SetHandler(instance);
ClassType* derivedInstance = azrtti_cast<ClassType*>(instance);
AZ_Assert(derivedInstance, "Unable to convert NetBindable to %s", AZ::AzTypeInfo<ClassType>::Name());
ReplicaChunkDescriptor* desc = GetDescriptor();
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
netContext->EnumerateFields(desc->GetChunkTypeId(),
[this, derivedInstance, desc, mode](NetworkContext::FieldDescBase* field)
{
NetBindableFieldBase* bindableField = reinterpret_cast<NetBindableFieldBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + field->GetOffset());
DataSetBase* dataSet = desc->GetDataSet(this, field->GetDataSetIndex());
bindableField->Bind(dataSet, mode);
});
netContext->EnumerateRpcs(desc->GetChunkTypeId(),
[this, derivedInstance, desc](NetworkContext::RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
RpcBase* rpcBase = desc->GetRpc(this, rpc->GetRpcIndex());
bindableRpc->Bind(rpcBase);
});
// Transfer any stored ctor data from the buffer -> NetBindable instance
if (m_ctorBuffer.Size() > 0)
{
ReadBuffer ctorBuffer(m_ctorBuffer.GetEndianType(), m_ctorBuffer.Get(), m_ctorBuffer.Size());
netContext->EnumerateCtorData(desc->GetChunkTypeId(),
[instance, &ctorBuffer](NetworkContext::CtorDataBase* ctorData)
{
ctorData->Unmarshal(ctorBuffer, instance);
});
}
}
template <class ClassType>
void ReflectedReplicaChunk<ClassType>::Unbind()
{
ReplicaChunkInterface* handler = GetHandler();
if (!handler || handler == this)
{
return;
}
NetBindable* netBindable = static_cast<NetBindable*>(handler);
ClassType* derivedInstance = azrtti_cast<ClassType*>(netBindable);
AZ_Assert(derivedInstance, "Unable to convert NetBindable to %s. Have you forgotten to derive your component from AzFramework::NetBindable?", AZ::AzTypeInfo<ClassType>::Name());
if (derivedInstance)
{
ReplicaChunkDescriptor* desc = GetDescriptor();
NetworkContext* netContext = nullptr;
EBUS_EVENT_RESULT(netContext, NetSystemRequestBus, GetNetworkContext);
netContext->EnumerateFields(desc->GetChunkTypeId(),
[derivedInstance](NetworkContext::FieldDescBase* field)
{
NetBindableFieldBase* bindableField = reinterpret_cast<NetBindableFieldBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + field->GetOffset());
bindableField->Bind(nullptr, NetworkContextBindMode::NonAuthoritative);
});
netContext->EnumerateRpcs(desc->GetChunkTypeId(),
[derivedInstance](NetworkContext::RpcDescBase* rpc)
{
NetBindableRpcBase* bindableRpc = reinterpret_cast<NetBindableRpcBase*>(reinterpret_cast<AZ::u8*>(derivedInstance) + rpc->GetOffset());
bindableRpc->Bind(derivedInstance);
});
}
// We have disconnected from the handler and erased any connections from DataFields or Rpcs
SetHandler(nullptr);
}
} // namespace AZ
@@ -13,6 +13,7 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
namespace Physics
@@ -26,12 +26,9 @@
#include <AzCore/std/string/conversions.h>
#include <AzFramework/Script/ScriptComponent.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include <AzFramework/Network/NetworkContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <GridMate/Replica/ReplicaChunk.h>
#include <AzFramework/IO/LocalFileIO.h>
@@ -429,83 +426,60 @@ namespace AzFramework
{
LSV_BEGIN(lua, 1);
// calling format __index(table,key)
ScriptNetBindingTable* netBindingTable = reinterpret_cast<ScriptNetBindingTable*>(lua_touserdata(lua, lua_upvalueindex(1)));
AZ::ScriptContext::FromNativeContext(lua)->Error(AZ::ScriptContext::ErrorType::Warning, true,
"Property %s not found in entity table. Please push this property to your slice to avoid decrease in performance.", lua_tostring(lua, -1));
int lookupKey = lua_gettop(lua);
bool readValue = false;
if (netBindingTable != nullptr)
int lookupTable = lookupKey - 1;
// This is a slow function and it's made slow so we don't cache any extra data.
// This is done because this function will be called only the exported components
// and script are not in sync and we added new properties.
lua_getmetatable(lua, -2); // get the metatable which will be the top property table
int entityProperties = lua_gettop(lua);
if (lua_getmetatable(lua, -1) == 0) // get the metatable of the property which will be the original table
{
AZ_Error("ScriptComponent",netBindingTable->GetScriptContext() != nullptr,"ScriptNetBindingTable is missing ScriptContext.");
AZ_Error("ScriptComponent",netBindingTable->GetScriptContext() == nullptr || netBindingTable->GetScriptContext()->NativeContext() == lua,"Trying to use a NetBindingTable in wrong lua context");
AZ::ScriptContext* scriptContext = netBindingTable->GetScriptContext();
if (scriptContext)
{
AZ::ScriptDataContext stackContext;
scriptContext->ReadStack(stackContext);
readValue = netBindingTable->InspectTableValue(stackContext);
}
// we are looking at top level properties
lua_pushvalue(lua, -2); // copy the key
lua_rawget(lua, -2); // read the value
}
if (!readValue)
else
{
AZ::ScriptContext::FromNativeContext(lua)->Error(AZ::ScriptContext::ErrorType::Warning, true,
"Property %s not found in entity table. Please push this property to your slice to avoid decrease in performance.", lua_tostring(lua, -1));
int lookupKey = lua_gettop(lua);
int lookupTable = lookupKey - 1;
// This is a slow function and it's made slow so we don't cache any extra data.
// This is done because this function will be called only the exported components
// and script are not in sync and we added new properties.
lua_getmetatable(lua, -2); // get the metatable which will be the top property table
int entityProperties = lua_gettop(lua);
if (lua_getmetatable(lua, -1) == 0) // get the metatable of the property which will be the original table
// we are looking into the sub table, so do a slow traversal
int scriptProperties = lua_gettop(lua);
if (!Properties__IndexFindSubtable(lua, lookupTable, entityProperties, scriptProperties))
{
// we are looking at top level properties
lua_pushvalue(lua, -2); // copy the key
lua_rawget(lua, -2); // read the value
lua_pushnil(lua);
return 1; // we did not find the table
}
else
{
// we are looking into the sub table, so do a slow traversal
int scriptProperties = lua_gettop(lua);
if (!Properties__IndexFindSubtable(lua, lookupTable, entityProperties, scriptProperties))
{
lua_pushnil(lua);
return 1; // we did not find the table
}
else
{
lua_pushvalue(lua, lookupKey);
lua_rawget(lua, -2);
}
}
if (lua_istable(lua, -1))
{
// if we are here the target table is on the top if the stack
lua_pushstring(lua, ScriptComponent::DefaultFieldName);
lua_pushvalue(lua, lookupKey);
lua_rawget(lua, -2);
if (lua_isnil(lua, -1))
{
// parent table is a group, pop the value and return the table
lua_pop(lua, 1);
}
}
// Duplicate the value, so once the storage is done its on top of the stack, and returned
lua_pushvalue(lua, -1);
// Push key, and then move it below the value
lua_pushvalue(lua, lookupKey);
lua_insert(lua, -2);
// Cache the value so that subsequent accesses to this property don't result in warnings
lua_rawset(lua, lookupTable);
}
if (lua_istable(lua, -1))
{
// if we are here the target table is on the top if the stack
lua_pushstring(lua, ScriptComponent::DefaultFieldName);
lua_rawget(lua, -2);
if (lua_isnil(lua, -1))
{
// parent table is a group, pop the value and return the table
lua_pop(lua, 1);
}
}
// Duplicate the value, so once the storage is done its on top of the stack, and returned
lua_pushvalue(lua, -1);
// Push key, and then move it below the value
lua_pushvalue(lua, lookupKey);
lua_insert(lua, -2);
// Cache the value so that subsequent accesses to this property don't result in warnings
lua_rawset(lua, lookupTable);
return 1;
}
//=========================================================================
@@ -515,30 +489,7 @@ namespace AzFramework
{
LSV_BEGIN_VARIABLE(lua);
// calling format __newindex(table,key,value)
ScriptNetBindingTable* netBindingTable = reinterpret_cast<ScriptNetBindingTable*>(lua_touserdata(lua, lua_upvalueindex(1)));
if (netBindingTable != nullptr)
{
AZ_Error("ScriptContext",netBindingTable->GetScriptContext() != nullptr,"ScriptNetBindingTable is missing ScriptContext.");
AZ_Error("ScriptContext",netBindingTable->GetScriptContext() == nullptr || netBindingTable->GetScriptContext()->NativeContext() == lua,"Trying to use a NetBindingTable in wrong lua context");
AZ::ScriptContext* scriptContext = netBindingTable->GetScriptContext();
if (scriptContext)
{
AZ::ScriptDataContext stackContext;
scriptContext->ReadStack(stackContext);
const bool assignedValue = netBindingTable->AssignTableValue(stackContext);
if (assignedValue)
{
LSV_END_VARIABLE(0);
return 0;
}
}
}
// If we didn't assign the value above, we want
// to raw set the value to avoid coming back in here.
// We want to raw set the value to avoid coming back in here.
lua_rawset(lua, 1);
LSV_END_VARIABLE(-2);
return 0;
@@ -553,7 +504,6 @@ namespace AzFramework
// [8/9/2013]
//=========================================================================
const char* ScriptComponent::NetRPCFieldName = "NetRPCs";
const char* ScriptComponent::DefaultFieldName = "default";
ScriptComponent::ScriptComponent()
@@ -561,7 +511,6 @@ namespace AzFramework
, m_contextId(AZ::ScriptContextIds::DefaultScriptContextId)
, m_script(AZ::Data::AssetLoadBehavior::PreLoad)
, m_table(LUA_NOREF)
, m_netBindingTable(nullptr)
{
m_properties.m_name = "Properties";
}
@@ -573,8 +522,6 @@ namespace AzFramework
ScriptComponent::~ScriptComponent()
{
m_properties.Clear();
delete m_netBindingTable;
}
//=========================================================================
@@ -604,11 +551,6 @@ namespace AzFramework
return m_properties.GetProperty(propertyName);
}
const AZ::ScriptProperty* ScriptComponent::GetNetworkedScriptProperty(const char* propertyName) const
{
return m_netBindingTable->FindScriptProperty(propertyName);
}
void ScriptComponent::Init()
{
// Grab the script context
@@ -622,11 +564,6 @@ namespace AzFramework
//=========================================================================
void ScriptComponent::Activate()
{
if (m_isSyncEnabled && m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
// if we have valid asset listen for script asset events, like reload
if (m_script.GetId().IsValid())
{
@@ -681,43 +618,6 @@ namespace AzFramework
LoadScript();
}
//=========================================================================
// ScriptComponent::GetNetworkBinding
//=========================================================================
GridMate::ReplicaChunkPtr ScriptComponent::GetNetworkBinding()
{
if (m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
return m_netBindingTable->GetNetworkBinding();
}
//=========================================================================
// ScriptComponent::SetNetworkBinding
//=========================================================================
void ScriptComponent::SetNetworkBinding(GridMate::ReplicaChunkPtr chunk)
{
if (m_netBindingTable == nullptr)
{
m_netBindingTable = aznew ScriptNetBindingTable();
}
m_netBindingTable->SetNetworkBinding(chunk);
}
//=========================================================================
// ScriptComponent::UnbindFromNetwork
//=========================================================================
void ScriptComponent::UnbindFromNetwork()
{
if (m_netBindingTable)
{
m_netBindingTable->UnbindFromNetwork();
}
}
//=========================================================================
// LoadScript
//=========================================================================
@@ -741,11 +641,6 @@ namespace AzFramework
AZ_PROFILE_SCOPE_DYNAMIC(AZ::Debug::ProfileCategory::Script, "Unload: %s", m_script.GetHint().c_str());
DestroyEntityTable();
if (m_netBindingTable)
{
m_netBindingTable->Unload();
}
}
//=========================================================================
@@ -798,12 +693,10 @@ namespace AzFramework
// set the __index so we can read values in case we change the script
// after we export the component
lua_pushliteral(lua, "__index");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__Index, 1);
lua_rawset(lua, -3);
lua_pushliteral(lua, "__newindex");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_rawset(lua, -3);
}
@@ -835,8 +728,7 @@ namespace AzFramework
{
const char* tableName = lua_tolstring(lua, -2, nullptr);
if (strncmp(tableName, "__", 2) == 0 || // skip metatables
strcmp(tableName, propertyTableName) == 0 || // Skip the Properties table
strcmp(tableName, ScriptComponent::NetRPCFieldName) == 0) // Want to skip the RPC table as well
strcmp(tableName, propertyTableName) == 0) // Skip the Properties table
{
break;
}
@@ -904,13 +796,10 @@ namespace AzFramework
}
lua_createtable(lua, 0, 1); // Create entity table;
int entityStackIndex = lua_gettop(lua);
[[maybe_unused]] int entityStackIndex = lua_gettop(lua);
// Stack: ScriptRootTable PropertiesTable EntityTable
// Create our network binding.
CreateNetworkBindingTable(baseStackIndex, entityStackIndex);
if (basePropertyTable > -1) // if property table exists
{
CreatePropertyGroup(m_properties, basePropertyTable, lua_gettop(lua), basePropertyTable, true);
@@ -932,11 +821,6 @@ namespace AzFramework
// Keep the entity table in the registry
m_table = luaL_ref(lua, LUA_REGISTRYINDEX);
if (m_netBindingTable)
{
m_netBindingTable->FinalizeNetworkTable(m_context, m_table);
}
// call OnActivate
lua_pushliteral(lua, "OnActivate");
lua_rawget(lua, baseStackIndex); // ScriptTable[OnActivate]
@@ -993,18 +877,6 @@ namespace AzFramework
}
}
//=========================================================================
// CreateNetworkBindingTable
// [6/27/2016]
//=========================================================================
void ScriptComponent::CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex)
{
if (m_netBindingTable)
{
m_netBindingTable->CreateNetworkBindingTable(m_context, baseStackIndex, entityStackIndex);
}
}
//=========================================================================
// CreatePropertyGroup
// [3/3/2014]
@@ -1028,12 +900,10 @@ namespace AzFramework
// Ensure that this instance of Properties table has the proper __index and __newIndex metamethods.
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
lua_pushliteral(lua, "__index"); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index
lua_pushlightuserdata(lua, m_netBindingTable); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index m_netBinding
lua_pushcclosure(lua, &Internal::Properties__Index, 1); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
lua_pushliteral(lua, "__newindex");
lua_pushlightuserdata(lua, m_netBindingTable);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
@@ -1050,55 +920,6 @@ namespace AzFramework
{
AZ::ScriptProperty* prop = group.m_properties[i];
if (m_netBindingTable != nullptr)
{
lua_pushlstring(lua, prop->m_name.c_str(), prop->m_name.length());
lua_rawget(lua, propertyGroupTableIndex);
// Stack: ... SomePropertyInThePropertiesTable. This may be any basic lua type (number, string, table etc)
if (lua_istable(lua, -1))
{
bool isNetworkedProperty = false;
AZ::ScriptDataContext stackContext;
// If we find a table value. We want to inspect it for information.
if (m_context->ReadStack(stackContext))
{
// check if the current property, which is a table, has a sub-table called "netSynched"
lua_pushliteral(lua, "netSynched"); // Stack: ... SomePropertyInThePropertiesTable netSynched
lua_rawget(lua, -2); // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable/nil
if (stackContext.IsTable(-1))
{
AZ::ScriptDataContext networkTableContext;
if (stackContext.InspectTable(-1, networkTableContext)) // Stack: ... SomePropertyInThePropertiesTable NetSynchedSubTable NetSynchedSubTable nil nil
{
// RegisterDataSet will make sure our __NewIndex function callback will be triggered whenever modifying netSynched Properties.
//isNetworkedProperty = true;
isNetworkedProperty = m_netBindingTable->RegisterDataSet(networkTableContext, prop);
}
}
// Network binding table
lua_pop(lua, 1); // Stack: ... SomePropertyInThePropertiesTable
}
// Pop this PropertiesTable's property
lua_pop(lua, 1);
// If the property is networked, we don't want to copy it over into the table.
if (isNetworkedProperty)
{
continue;
}
}
else
{
// Remove the value we just pushed onto the stack
lua_pop(lua, 1);
}
}
lua_pushlstring(lua, prop->m_name.c_str(), prop->m_name.length());
if (prop->Write(*m_context))
{
@@ -1157,7 +978,7 @@ namespace AzFramework
return true;
};
serializeContext->Class<ScriptComponent, AZ::Component, NetBindable>()
serializeContext->Class<ScriptComponent, AZ::Component>()
->Version(3, converter)
->Field("ContextID", &ScriptComponent::m_contextId)
->Field("Properties", &ScriptComponent::m_properties)
@@ -1174,8 +995,6 @@ namespace AzFramework
AZ::ScriptProperties::Reflect(reflection);
}
}
ScriptNetBindingTable::Reflect(reflection);
}
//=========================================================================
@@ -20,8 +20,6 @@
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzFramework/Network/NetBindable.h>
namespace AZ
{
class ScriptProperty;
@@ -37,8 +35,6 @@ namespace AzToolsFramework
namespace AzFramework
{
class ScriptNetBindingTable;
struct ScriptCompileRequest;
using WriteFunction = AZStd::function< AZ::Outcome<void, AZStd::string>(const ScriptCompileRequest&, AZ::IO::GenericStream& in, AZ::IO::GenericStream& out) >;
@@ -92,15 +88,13 @@ namespace AzFramework
class ScriptComponent
: public AZ::Component
, private AZ::Data::AssetBus::Handler
, public AzFramework::NetBindable
{
friend class AzToolsFramework::Components::ScriptEditorComponent;
public:
static const char* NetRPCFieldName;
static const char* DefaultFieldName;
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", NetBindable);
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", AZ::Component);
/// \red ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
@@ -116,7 +110,6 @@ namespace AzFramework
// Methods used for unit tests
AZ::ScriptProperty* GetScriptProperty(const char* propertyName);
const AZ::ScriptProperty* GetNetworkedScriptProperty(const char* propertyName) const;
protected:
ScriptComponent(const ScriptComponent&) = delete;
@@ -133,13 +126,6 @@ namespace AzFramework
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// NetBindable
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
//////////////////////////////////////////////////////////////////////////
/// Load script (unless already by other instances) and creates the script instance into the VM
void LoadScript();
/// Removes the script instance and unloads the script (unless needed by other instances)
@@ -152,8 +138,6 @@ namespace AzFramework
void CreateEntityTable();
void DestroyEntityTable();
void CreateNetworkBindingTable(int baseStackIndex, int entityStackIndex);
void CreatePropertyGroup(const ScriptPropertyGroup& group, int propertyGroupTableIndex, int parentIndex, int metatableIndex, bool isRoot);
AZ::ScriptContext* m_context; ///< Context in which the script will be running
@@ -161,7 +145,6 @@ namespace AzFramework
AZ::Data::Asset<AZ::ScriptAsset> m_script; ///< Reference to the script asset used for this component.
int m_table; ///< Cached table index
ScriptPropertyGroup m_properties; ///< List with all properties that were tweaked in the editor and should override values in the m_sourceScriptName class inside m_script.
ScriptNetBindingTable* m_netBindingTable; ///< Table that will hold our networked script values, and manage callbacks
};
} // namespace AZ
@@ -1,573 +0,0 @@
/*
* 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 <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include <AzFramework/Network/DynamicSerializableFieldMarshaler.h>
#include <AzFramework/Network/EntityIdMarshaler.h>
#include "AzFramework/Script/ScriptMarshal.h"
namespace AzFramework
{
////////////////////////////
// ScriptPropertyMarshaler
////////////////////////////
template<class T>
bool UnmarshalGenericType(AZ::DynamicSerializableField& serializableField, GridMate::ReadBuffer& rb)
{
bool valueChanged = true;
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
// Store the old value, to compare with the unmarshaled value, to signal
T oldValue = (*serializableField.Get<T>());
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
if (serializableField.m_typeId == T::TYPEINFO_Uuid())
{
valueChanged = !(oldValue == (*serializableField.Get<T>()));
}
return valueChanged;
}
class ScriptPropertyTableMarshalerHelper
{
public:
template<typename T>
static void MarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, GridMate::WriteBuffer& wb, const AZ::ScriptPropertyTable* scriptPropertyTable)
{
GridMate::Marshaler<AZ::u32> sizeMarshaler;
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
auto& valueMap = genericClassKeyMap->GetPairMapping();
// We will write out all of our keys. Since it is easier to write out nil values for the properties.
sizeMarshaler.Marshal(wb,static_cast<AZ::u32>(valueMap.size()));
GridMate::Marshaler<T> keyMarshaler;
for (auto& mapPair : valueMap)
{
keyMarshaler.Marshal(wb,mapPair.first);
scriptPropertyMarshaler.Marshal(wb,mapPair.second.m_valueProperty);
}
}
else
{
sizeMarshaler.Marshal(wb,0);
}
}
template<typename T>
static bool UnmarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, AZ::ScriptPropertyTable* scriptPropertyTable, GridMate::ReadBuffer& rb)
{
bool valueChanged = false;
AZ::SerializeContext* useContext = nullptr;
EBUS_EVENT_RESULT(useContext, AZ::ComponentApplicationBus, GetSerializeContext);
if (useContext)
{
const AZ::SerializeContext::ClassData* classData = useContext->FindClassData(T::TYPEINFO_Uuid());
if (classData && classData->m_factory)
{
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
// This whole thing is an in-place map update.
// to try to minimize the number of allocations. We try to re-use objects as much as possible.
//
// Two phase approach: Step one, update all of the existing properties, while keeping track of all of the used keys.
// Step two, go through and delete any unupdated keys from the mapping.
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
AZStd::unordered_set<T> newKeys;
GridMate::Marshaler<AZ::u32> sizeMarshaler;
AZ::u32 mapSize;
sizeMarshaler.Unmarshal(mapSize,rb);
auto& valueMap = genericClassKeyMap->GetPairMapping();
GridMate::Marshaler<T> keyMarshaler;
for (unsigned int i=0; i < mapSize; ++i)
{
T propertyKey;
keyMarshaler.Unmarshal(propertyKey,rb);
newKeys.insert(propertyKey);
auto valueIter = valueMap.find(propertyKey);
if (valueIter != valueMap.end())
{
if (scriptPropertyMarshaler.UnmarshalToPointer(valueIter->second.m_valueProperty,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* newValueProperty = nullptr;
scriptPropertyMarshaler.UnmarshalToPointer(newValueProperty,rb);
AZ::ScriptPropertyGenericClassMap::MapValuePair newPair;
newPair.m_valueProperty = newValueProperty;
T* serializableData = nullptr;
serializableData = static_cast<T*>(classData->m_factory->Create("ScriptProperty"));
(*serializableData) = propertyKey;
AZ::ScriptPropertyGenericClass* genericPropertyClass = aznew AZ::ScriptPropertyGenericClass();
genericPropertyClass->Set<T>(serializableData);
newPair.m_keyProperty = genericPropertyClass;
valueMap.emplace(propertyKey,newPair);
}
}
// Delete all of the unused keyes from the map
auto valueIter = valueMap.begin();
while (valueIter != valueMap.end())
{
if (newKeys.find(valueIter->first) == newKeys.end())
{
valueChanged = true;
valueIter->second.Destroy();
valueIter = valueMap.erase(valueIter);
}
else
{
++valueIter;
}
}
}
}
}
return valueChanged;
}
};
void ScriptPropertyMarshaler::Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& property) const
{
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
if (property == nullptr)
{
// Write out a nil property if we have a nullptr property
nameMarshaler.Marshal(wb,"");
idMarshaler.Marshal(wb,0);
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
return;
}
// Common points:
// Always going to marshal the uuid of the type(or something similar)
// so we know what type we have on the other side.
//
// Next need to pass along the name field.
const AZ::Uuid& typeId = azrtti_typeid(property);
nameMarshaler.Marshal(wb,property->m_name);
idMarshaler.Marshal(wb,property->m_id);
// Method 1:
// - Allow each ScriptProperty to marshal itself.
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate.
// cont.Marshal(wb);
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyBoolean*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<double> doubleMarshaler;
doubleMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyNumber*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyString*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
const AZ::DynamicSerializableField& serializableField = static_cast<const AZ::ScriptPropertyGenericClass*>(property)->GetSerializableField();
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Marshal(wb,serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::TYPEINFO_Uuid())
{
const AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<const AZ::ScriptPropertyTable*>(property);
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
mapSizeMarshaler.Marshal(wb,static_cast<AZ::u32>(scriptPropertyTable->m_indexMapping.size()));
GridMate::Marshaler<int> indexMarshaler;
// Currently only support integers as keys inside of the table.
for (auto& mapPair : scriptPropertyTable->m_indexMapping)
{
indexMarshaler.Marshal(wb,mapPair.first);
this->Marshal(wb,mapPair.second);
}
mapSizeMarshaler.Marshal(wb, static_cast<AZ::u32>(scriptPropertyTable->m_keyMapping.size()));
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (auto& mapPair : scriptPropertyTable->m_keyMapping)
{
// For hashed values. The name of the script property is the same as the hash it should be using.
// We still synchronize the Crc so we can unmarshal in place on the other side.
hashMarshaler.Marshal(wb,mapPair.first);
Marshal(wb,mapPair.second);
}
// EntityId's
ScriptPropertyTableMarshalerHelper::MarshalScriptPropertyGenericMap<AZ::EntityId>((*this), wb, scriptPropertyTable);
}
else
{
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
}
}
bool ScriptPropertyMarshaler::UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const
{
bool typeChanged = false;
AZ::Uuid typeId;
AZ::u64 id;
AZStd::string name;
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
nameMarshaler.Unmarshal(name,rb);
idMarshaler.Unmarshal(id,rb);
typeMarshaler.Unmarshal(typeId,rb);
if (target == nullptr || typeId != azrtti_typeid(target))
{
typeChanged = true;
AZ::ScriptProperty* actualScriptProperty = nullptr;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyBoolean();
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyNumber();
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyString();
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyGenericClass();
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyTable();
}
else
{
actualScriptProperty = aznew AZ::ScriptPropertyNil();
}
actualScriptProperty->m_name = name;
delete target;
target = actualScriptProperty;
}
// Update our ID
target->m_id = id;
// Method 1:
// - Allow each ScriptProperty to unmarshal itself
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate
// actualScriptProperty->Unmarshal(rb);
//
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
bool valueChanged = false;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
AZ::ScriptPropertyBoolean* booleanProperty = static_cast<AZ::ScriptPropertyBoolean*>(target);
bool oldValue = booleanProperty->m_value;
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Unmarshal(booleanProperty->m_value,rb);
valueChanged = !(oldValue == booleanProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
AZ::ScriptPropertyString* stringProperty = static_cast<AZ::ScriptPropertyString*>(target);
AZStd::string oldValue = stringProperty->m_value;
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Unmarshal(stringProperty->m_value,rb);
valueChanged = !(oldValue == stringProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
AZ::ScriptPropertyNumber* numberProperty = static_cast<AZ::ScriptPropertyNumber*>(target);
double oldValue = numberProperty->m_value;
GridMate::Marshaler<double> numberMarshaler;
numberMarshaler.Unmarshal(numberProperty->m_value,rb);
valueChanged = !(oldValue == numberProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
AZ::ScriptPropertyGenericClass* genericProperty = static_cast<AZ::ScriptPropertyGenericClass*>(target);
AZ::DynamicSerializableField& serializableField = genericProperty->m_value;
AZ::DynamicSerializableField oldField;
oldField.CopyDataFrom(serializableField);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
valueChanged = !oldField.IsEqualTo(serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<AZ::ScriptPropertyTable*>(target);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
// Unmarshal all of the indexes properties
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<int> newIndexes;
GridMate::Marshaler<int> indexMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
int index = 0;
indexMarshaler.Unmarshal(index,rb);
auto mapIter = scriptPropertyTable->m_indexMapping.find(index);
if (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto insertResult = scriptPropertyTable->m_indexMapping.emplace(index,scriptProperty);
mapIter = insertResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
newIndexes.insert(index);
}
}
auto mapIter = scriptPropertyTable->m_indexMapping.begin();
while (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (newIndexes.find(mapIter->first) == newIndexes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the hashed values
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<AZ::u32> newHashes;
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
AZ::u32 newHash;
hashMarshaler.Unmarshal(newHash, rb);
auto mapIter = scriptPropertyTable->m_keyMapping.find(newHash);
if (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto emplaceResult = scriptPropertyTable->m_keyMapping.emplace(newHash,scriptProperty);
mapIter = emplaceResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
newHashes.insert(newHash);
}
}
auto mapIter = scriptPropertyTable->m_keyMapping.begin();
while (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (newHashes.find(mapIter->first) == newHashes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the generic properties
// EntityId's
if (ScriptPropertyTableMarshalerHelper::UnmarshalScriptPropertyGenericMap<AZ::EntityId>((*this), scriptPropertyTable, rb))
{
valueChanged = true;
}
}
return typeChanged || valueChanged;
}
////////////////////////////
// ScriptPropertyThrottler
////////////////////////////
ScriptPropertyThrottler::ScriptPropertyThrottler()
: m_isDirty(true)
{
}
void ScriptPropertyThrottler::SignalDirty()
{
m_isDirty = true;
}
bool ScriptPropertyThrottler::WithinThreshold(AZ::ScriptProperty* newValue) const
{
return newValue == nullptr || !m_isDirty;
}
void ScriptPropertyThrottler::UpdateBaseline(AZ::ScriptProperty* baseline)
{
(void)baseline;
m_isDirty = false;
}
}
@@ -1,94 +0,0 @@
/*
* 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 AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#define AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzCore/RTTI/BehaviorObjectSignals.h>
namespace AZ
{
class ScriptProperty;
}
namespace AzFramework
{
/**
* Specalized helper marshaler for ScriptProperty class
*/
class ScriptPropertyMarshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& cont) const;
bool UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const;
};
class ScriptPropertyThrottler
{
public:
ScriptPropertyThrottler();
void SignalDirty();
bool WithinThreshold(AZ::ScriptProperty* newValue) const;
void UpdateBaseline(AZ::ScriptProperty* baseline);
private:
bool m_isDirty;
};
/**
* Specialized helper marshaler to help with the vector creation/destruction
*/
class ScriptRPCMarshaler
{
public:
typedef AZStd::vector< AZ::ScriptProperty* > Container;
ScriptRPCMarshaler()
{
}
AZ_FORCE_INLINE void Marshal(GridMate::WriteBuffer& wb, const Container& container) const
{
AZ_Assert(container.size() < USHRT_MAX, "Container has too many elements for marshaling!");
AZ::u16 size = static_cast<AZ::u16>(container.size());
wb.Write(size);
for (const auto& i : container)
{
m_marshaler.Marshal(wb, i);
}
}
AZ_FORCE_INLINE void Unmarshal(Container& container, GridMate::ReadBuffer& rb) const
{
container.clear();
AZ::u16 size;
rb.Read(size);
container.reserve(size);
for (AZ::u16 i = 0; i < size; ++i)
{
AZ::ScriptProperty* readProperty = nullptr;
m_marshaler.UnmarshalToPointer(readProperty, rb);
container.insert(container.end(), readProperty);
}
}
protected:
ScriptPropertyMarshaler m_marshaler;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -1,320 +0,0 @@
/*
* 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 AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#define AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Replica/ReplicaChunkInterface.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Script/ScriptPropertyTable.h>
#include <AzCore/Script/ScriptPropertyWatcherBus.h>
#include <AzFramework/Script/ScriptMarshal.h>
namespace AzFramework
{
class ScriptPropertyDataSet;
class ScriptComponentReplicaChunk;
// ScriptNetBindingTable will act as the go between for the ScriptComponent and the Replica's.
// It will also allow for holding of values in the case where you haven't been bound to a replica chunk yet and the
// script tries to interact with something that is networked.
//
// Allows for scripts to be re-used seamlessly in a offline vs online scenario(and support for going from offline to online),
// including RPCs(will alawys call the master version if offline)
class ScriptNetBindingTable
: public GridMate::ReplicaChunkInterface
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptPropertyDataSet;
// Helper struct to keep track of a a ScriptContext
// and the entityTableReference. Mainly used for
// calling in to functions in LUA where we want
// to push in the table reference as the first parameter
struct EntityScriptContext
{
public:
EntityScriptContext();
void Unload();
bool HasEntityTableRegistryIndex() const;
int GetEntityTableRegistryIndex() const;
bool HasScriptContext() const;
AZ::ScriptContext* GetScriptContext() const;
void ConfigureContext(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
private:
bool SanityCheckContext() const;
AZ::ScriptContext* m_scriptContext;
int m_entityTableRegistryIndex;
};
class NetworkedTableValue;
friend NetworkedTableValue;
typedef AZStd::unordered_map<AZStd::string, NetworkedTableValue> NetworkedTableMap;
class RPCBindingHelper;
friend RPCBindingHelper;
typedef AZStd::unordered_map<AZStd::string, RPCBindingHelper> RPCHelperMap;
// Helper class that will wrap up our interactions with the actual stored value
// to hide the general use case of if we are connected to a replica or not.
//
// Additionally this will serve as a holding ground for a 'networked'
// value that doesn't have a dataset.
//
// Lastly holds onto the Callback references.
class NetworkedTableValue
{
public:
AZ_CLASS_ALLOCATOR(NetworkedTableValue, AZ::SystemAllocator, 0);
NetworkedTableValue(AZ::ScriptProperty* initialValue = nullptr);
~NetworkedTableValue();
void Destroy();
// Methods to register this value to a chunk
bool HasDataSet() const;
void RegisterDataSet(ScriptPropertyDataSet* dataSet);
void UnbindFromDataSet();
ScriptPropertyDataSet* GetDataSet() const;
// Information kept in order to force these values to use a particular dataset for debugging.
bool HasForcedDataSetIndex() const;
void SetForcedDataSetIndex(int index);
int GetForcedDataSetIndex() const;
// Callback functions
bool HasCallback() const;
void RegisterCallback(int functionReference);
void ReleaseCallback(AZ::ScriptContext& scriptContext);
void InvokeCallback(EntityScriptContext& scriptContext, const GridMate::TimeContext& timeContext);
bool AssignValue(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
bool InspectValue(AZ::ScriptContext* scriptContext) const;
// Methods used for unit tests
const AZ::ScriptProperty* GetShimmedScriptProperty() const { return m_shimmedScriptProperty; }
private:
// This value will be used if we have a networked property, but don't have a valid chunk yet.
// Works as a temporary store, which will be resolved once we get assigned to a DataSet
AZ::ScriptProperty* m_shimmedScriptProperty;
// The data set we are bound to
ScriptPropertyDataSet* m_dataSet;
int m_forcedDataSetIndex;
int m_functionReference;
};
// Future thoughts
// - Move the actual RPC meta table creation
// into this guy
class RPCBindingHelper
{
public:
AZ_CLASS_ALLOCATOR(RPCBindingHelper, AZ::SystemAllocator, 0);
RPCBindingHelper();
~RPCBindingHelper();
void ReleaseTableIndex(AZ::ScriptContext& scriptContext);
bool IsValid() const;
void SetMasterFunction(int masterReference);
bool InvokeMaster(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
void SetProxyFunction(int masterReference);
void InvokeProxy(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
private:
int m_masterReference;
int m_proxyReference;
};
public:
AZ_CLASS_ALLOCATOR(ScriptNetBindingTable, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* reflect);
ScriptNetBindingTable();
~ScriptNetBindingTable();
void Unload();
void CreateNetworkBindingTable(AZ::ScriptContext* scriptContext, int baseTableIndex, int entityTableIndex);
void FinalizeNetworkTable(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
AZ::ScriptContext* GetScriptContext() const;
bool IsMaster() const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// DataSet Functionality
//
// Called when the script wants to bind a function callback to when
// a value changes
//
// Might change this to just be register DataSet
bool RegisterDataSet(AZ::ScriptDataContext& stackContext, AZ::ScriptProperty* scriptProperty);
// Called when the script wants to assign a value to the script value
bool AssignTableValue(AZ::ScriptDataContext& stackContext);
// Called when the script wants to know the value of a script value.
bool InspectTableValue(AZ::ScriptDataContext& stackContext) const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
/// RPC Functionality
void RegisterRPC(AZ::ScriptDataContext& rpcTableContext, const AZStd::string& rpcName, int elementIndex, int tableStackIndex);
bool InvokeRPC(AZ::ScriptDataContext& stackContext);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Netbinding Interface duplication here to be called from the ScriptComponent
GridMate::ReplicaChunkPtr GetNetworkBinding();
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk);
void UnbindFromNetwork();
void OnPropertyUpdate(AZ::ScriptProperty*const& scriptProperty, const GridMate::TimeContext& tc);
bool OnInvokeRPC(AZStd::string functionName, AZStd::vector< AZ::ScriptProperty*> properties, const GridMate::RpcContext& rpcContext);
// Methods used for unit tests
const AZ::ScriptProperty* FindScriptProperty(const AZStd::string& name) const;
private:
void RegisterMetaTableCache();
template<typename PropertyType, typename PropertyArrayType>
AZ::ScriptPropertyTable* ConvertPropertyArrayToTable(PropertyArrayType* arrayProperty)
{
AZ::ScriptPropertyTable* scriptPropertyTable = aznew AZ::ScriptPropertyTable(arrayProperty->m_name.c_str());
PropertyType propertyType;
for (unsigned int i=0; i < arrayProperty->m_values.size(); ++i)
{
propertyType.m_value = arrayProperty->m_values[i];
// Offset by 1 to deal with lua 1 indexing.
// Table will make a clone of our object.
scriptPropertyTable->SetTableValue(i+1, &propertyType);
}
return scriptPropertyTable;
}
void AssignDataSets();
NetworkedTableValue* FindTableValue(const AZStd::string& name);
const NetworkedTableValue* FindTableValue(const AZStd::string& name) const;
EntityScriptContext m_entityScriptContext;
GridMate::ReplicaChunkPtr m_replicaChunk;
NetworkedTableMap m_networkedTable;
RPCHelperMap m_rpcHelperMap;
};
// Typedeffing out the RPC and DataSet definitions.
typedef GridMate::Rpc< GridMate::RpcArg< AZStd::string >, GridMate::RpcArg< ScriptRPCMarshaler::Container, ScriptRPCMarshaler > >::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnInvokeRPC> ScriptPropertyRPC;
typedef GridMate::DataSet<AZ::ScriptProperty*, ScriptPropertyMarshaler, ScriptPropertyThrottler>::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnPropertyUpdate> ScriptPropertyDataSetType;
class ScriptComponentReplicaChunk;
// Specialized DataSet used by the ScriptProperties, just to add some wrapped around functionality
// and to allow me to manipulate the DataSet throttler in order to properly manage a dirty flag
class ScriptPropertyDataSet
: public ScriptPropertyDataSetType
, public AZ::ScriptPropertyWatcherBus::Handler
, public AZ::ScriptPropertyWatcher
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptNetBindingTable::NetworkedTableValue;
const char* GetDataSetName();
public:
ScriptPropertyDataSet();
~ScriptPropertyDataSet();
bool IsReserved() const;
bool UpdateScriptProperty(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
void SetScriptProperty(AZ::ScriptProperty* scriptProperty);
void OnObjectModified() override;
private:
void Reserve(ScriptNetBindingTable::NetworkedTableValue* reserver);
void Release(ScriptNetBindingTable::NetworkedTableValue* reserver);
ScriptNetBindingTable::NetworkedTableValue* m_reserver;
};
// The actual ReplicaChunk that the script will use
class ScriptComponentReplicaChunk
: public GridMate::ReplicaChunkBase
{
public:
AZ_CLASS_ALLOCATOR(ScriptComponentReplicaChunk, AZ::SystemAllocator,0);
static const int k_maxScriptableDataSets = GM_MAX_DATASETS_IN_CHUNK;
static const char* GetChunkName() { return "ScriptComponentReplicaChunk"; }
// Might want to add some type of comment field into the various fields so this can be properly parsed
// and determined what we are actually sending.
ScriptComponentReplicaChunk();
~ScriptComponentReplicaChunk();
bool IsReplicaMigratable() override;
AZ::u32 CalculateDirtyDataSetMask(GridMate::MarshalContext& marshalContext) override;
// Called from the Master, will assign the table value to the DataSet specified by the helper.
bool AssignDataSet(ScriptNetBindingTable::NetworkedTableValue& helper);
// Called from teh Proxy. Will Assign the TableValue to the DataSet that contains the target property
void AssignDataSetForProperty(ScriptNetBindingTable::NetworkedTableValue& helper, AZ::ScriptProperty* targetProperty);
// Only called inside of an assert, checks that the DataSet that the targetProperty is in is the same as the assumedDataSet
// Used to confirm that we don't get a confusion between master/proxy about which ScriptProperty is assigned to which DataSet.
bool SanityCheckDataSet(AZ::ScriptProperty* targetProperty, ScriptPropertyDataSet* assumedDataSet);
ScriptPropertyRPC m_scriptRPC;
private:
AZ::u32 m_enabledDataSetMask;
ScriptPropertyDataSet m_propertyDataSets[k_maxScriptableDataSets];
};
}
#endif
@@ -177,7 +177,7 @@ namespace AzFramework
Neighborhood::NeighborReplicaPtr replicaChunk = GridMate::CreateReplicaChunk<Neighborhood::NeighborReplica>(session->GetMyMember()->GetId().Compact(), m_component->m_settings->m_persistentName.c_str(), Neighborhood::NEIGHBOR_CAP_LUA_VM | Neighborhood::NEIGHBOR_CAP_LUA_DEBUGGER);
replicaChunk->SetDisplayName(m_component->m_settings->m_persistentName.c_str());
replica->AttachReplicaChunk(replicaChunk);
session->GetReplicaMgr()->AddMaster(replica);
session->GetReplicaMgr()->AddPrimary(replica);
}
}
@@ -407,11 +407,6 @@ namespace AzFramework
{
if (input->m_state == InputChannel::State::Began)
{
if (input->m_state == InputChannel::State::Updated)
{
return;
}
m_translation |= translationFromKey(input->m_channelId);
if (m_translation != TranslationType::Nil)
{
@@ -161,26 +161,6 @@ set(FILES
Metrics/MetricsPlainTextNameRegistration.h
Network/AssetProcessorConnection.cpp
Network/AssetProcessorConnection.h
Network/DynamicSerializableFieldMarshaler.h
Network/EntityIdMarshaler.h
Network/InterestManagerComponent.h
Network/InterestManagerComponent.cpp
Network/NetBindable.h
Network/NetBindable.cpp
Network/NetBindingEventsBus.h
Network/NetBindingHandlerBus.h
Network/NetBindingSystemBus.h
Network/NetBindingComponent.h
Network/NetBindingComponent.cpp
Network/NetBindingComponentChunk.h
Network/NetBindingComponentChunk.cpp
Network/NetBindingSystemImpl.h
Network/NetBindingSystemImpl.cpp
Network/NetBindingSystemComponent.h
Network/NetBindingSystemComponent.cpp
Network/NetworkContext.h
Network/NetworkContext.cpp
Network/NetSystemBus.h
Network/SocketConnection.cpp
Network/SocketConnection.h
Logging/LogFile.cpp
@@ -203,10 +183,6 @@ set(FILES
Script/ScriptDebugAgentBus.h
Script/ScriptDebugMsgReflection.cpp
Script/ScriptDebugMsgReflection.h
Script/ScriptMarshal.h
Script/ScriptMarshal.cpp
Script/ScriptNetBindings.h
Script/ScriptNetBindings.cpp
Script/ScriptRemoteDebugging.cpp
Script/ScriptRemoteDebugging.h
StreamingInstall/StreamingInstall.h
@@ -279,6 +255,7 @@ set(FILES
Physics/ClassConverters.cpp
Physics/ClassConverters.h
Physics/MaterialBus.h
Physics/WindBus.h
Process/ProcessCommunicator.cpp
Process/ProcessCommunicator.h
Process/ProcessWatcher.cpp