Merge pull request #544 from aws-lumberyard-dev/MultiplayerComponents

Multiplayer components, visibility fixes for gameplay runtime and localprediction player controller component.
This commit is contained in:
kberg-amzn
2021-05-10 10:23:15 -07:00
committed by GitHub
114 changed files with 1743 additions and 619 deletions
+13 -13
View File
@@ -620,24 +620,24 @@ public:
bool IsPointVisible(const Vec3& p) const;
//sphere-frustum test
bool IsSphereVisible_F(const Sphere& s) const;
uint8 IsSphereVisible_FH(const Sphere& s) const; //this is going to be the exact version of sphere-culling
bool IsSphereVisible_F(const ::Sphere& s) const;
uint8 IsSphereVisible_FH(const ::Sphere& s) const; //this is going to be the exact version of sphere-culling
// AABB-frustum test
// Fast
bool IsAABBVisible_F(const AABB& aabb) const;
uint8 IsAABBVisible_FH(const AABB& aabb, bool* pAllInside) const;
uint8 IsAABBVisible_FH(const AABB& aabb) const;
bool IsAABBVisible_F(const ::AABB& aabb) const;
uint8 IsAABBVisible_FH(const ::AABB& aabb, bool* pAllInside) const;
uint8 IsAABBVisible_FH(const ::AABB& aabb) const;
// Exact
bool IsAABBVisible_E(const AABB& aabb) const;
uint8 IsAABBVisible_EH(const AABB& aabb, bool* pAllInside) const;
uint8 IsAABBVisible_EH(const AABB& aabb) const;
bool IsAABBVisible_E(const ::AABB& aabb) const;
uint8 IsAABBVisible_EH(const ::AABB& aabb, bool* pAllInside) const;
uint8 IsAABBVisible_EH(const ::AABB& aabb) const;
// Multi-camera
bool IsAABBVisible_EHM(const AABB& aabb, bool* pAllInside) const;
bool IsAABBVisible_EM(const AABB& aabb) const;
bool IsAABBVisible_FM(const AABB& aabb) const;
bool IsAABBVisible_EHM(const ::AABB& aabb, bool* pAllInside) const;
bool IsAABBVisible_EM(const ::AABB& aabb) const;
bool IsAABBVisible_FM(const ::AABB& aabb) const;
//OBB-frustum test
bool IsOBBVisible_F(const Vec3& wpos, const OBB& obb) const;
@@ -1386,7 +1386,7 @@ inline bool CCamera::IsPointVisible(const Vec3& p) const
// return values
// CULL_EXCLUSION = sphere outside of frustum (very fast rejection-test)
// CULL_INTERSECT = sphere and frustum intersects or sphere in completely inside frustum
inline bool CCamera::IsSphereVisible_F(const Sphere& s) const
inline bool CCamera::IsSphereVisible_F(const ::Sphere& s) const
{
if ((m_fp[0] | s.center) > s.radius)
{
@@ -1427,7 +1427,7 @@ inline bool CCamera::IsSphereVisible_F(const Sphere& s) const
// CULL_EXCLUSION = sphere outside of frustum (very fast rejection-test)
// CULL_INTERSECT = sphere intersects the borders of the frustum, further checks necessary
// CULL_INCLUSION = sphere is complete inside the frustum, no further checks necessary
inline uint8 CCamera::IsSphereVisible_FH(const Sphere& s) const
inline uint8 CCamera::IsSphereVisible_FH(const ::Sphere& s) const
{
f32 nc, rc, lc, tc, bc, cc;
if ((nc = m_fp[0] | s.center) > s.radius)
+1 -1
View File
@@ -794,7 +794,7 @@ struct HWVSphere
radius = r;
}
ILINE HWVSphere(const Sphere& sp)
ILINE HWVSphere(const ::Sphere& sp)
{
center = HWVLoadVecUnaligned(&sp.center);
radius = SIMDFLoadFloat(sp.radius);
+2 -2
View File
@@ -1179,7 +1179,7 @@ namespace Distance {
// float result = Distance::Point_TriangleSq( pos, triangle );
//----------------------------------------------------------------------------------
template<typename F>
ILINE F Sphere_TriangleSq(const Sphere& s, const Triangle_tpl<F>& t)
ILINE F Sphere_TriangleSq(const ::Sphere& s, const Triangle_tpl<F>& t)
{
F sqdistance = Distance::Point_TriangleSq(s.center, t) - (s.radius * s.radius);
if (sqdistance < 0)
@@ -1190,7 +1190,7 @@ namespace Distance {
}
template<typename F>
ILINE F Sphere_TriangleSq(const Sphere& s, const Triangle_tpl<F>& t, Vec3_tpl<F>& output)
ILINE F Sphere_TriangleSq(const ::Sphere& s, const Triangle_tpl<F>& t, Vec3_tpl<F>& output)
{
F sqdistance = Distance::Point_TriangleSq(s.center, t, output) - (s.radius * s.radius);
if (sqdistance < 0)
+5 -5
View File
@@ -792,7 +792,7 @@ namespace Intersect {
//--- 0x03 = two intersection, lineseg has ENTRY and EXIT point --
//----------------------------------------------------------------------------------
inline unsigned char Line_Sphere(const Line& line, const Sphere& s, Vec3& i0, Vec3& i1)
inline unsigned char Line_Sphere(const Line& line, const ::Sphere& s, Vec3& i0, Vec3& i1)
{
Vec3 end = line.pointonline + line.direction;
@@ -830,7 +830,7 @@ namespace Intersect {
//--- 0x03 = two intersection, lineseg has ENTRY and EXIT point --
//----------------------------------------------------------------------------------
inline unsigned char Ray_Sphere(const Ray& ray, const Sphere& s, Vec3& i0, Vec3& i1)
inline unsigned char Ray_Sphere(const Ray& ray, const ::Sphere& s, Vec3& i0, Vec3& i1)
{
Vec3 end = ray.origin + ray.direction;
float a = ray.direction | ray.direction;
@@ -863,7 +863,7 @@ namespace Intersect {
return intersection;
}
inline bool Ray_SphereFirst(const Ray& ray, const Sphere& s, Vec3& intPoint)
inline bool Ray_SphereFirst(const Ray& ray, const ::Sphere& s, Vec3& intPoint)
{
Vec3 p2;
unsigned char res = Ray_Sphere(ray, s, intPoint, p2);
@@ -886,7 +886,7 @@ namespace Intersect {
//--- 0x02 = one intersection, lineseg has just an EXIT point but no ENTRY point (ls.start is inside the sphere) --
//--- 0x03 = two intersection, lineseg has ENTRY and EXIT point --
//----------------------------------------------------------------------------------
inline unsigned char Lineseg_Sphere(const Lineseg& ls, const Sphere& s, Vec3& i0, Vec3& i1)
inline unsigned char Lineseg_Sphere(const Lineseg& ls, const ::Sphere& s, Vec3& i0, Vec3& i1)
{
Vec3 dir = (ls.end - ls.start);
@@ -931,7 +931,7 @@ namespace Intersect {
}
inline bool Lineseg_SphereFirst(const Lineseg& lineseg, const Sphere& s, Vec3& intPoint)
inline bool Lineseg_SphereFirst(const Lineseg& lineseg, const ::Sphere& s, Vec3& intPoint)
{
Vec3 p2;
uint8 res = Lineseg_Sphere(lineseg, s, intPoint, p2);
+9 -9
View File
@@ -103,7 +103,7 @@ namespace Overlap {
// Checks if a point is inside a sphere.
// Example:
// bool result=Overlap::Point_Sphere( point, sphere );
ILINE bool Point_Sphere(const Vec3& p, const Sphere& s)
ILINE bool Point_Sphere(const Vec3& p, const ::Sphere& s)
{
Vec3 distc = p - s.center;
f32 sqrad = s.radius * s.radius;
@@ -407,7 +407,7 @@ namespace Overlap {
//! check if a Lineseg and a Sphere overlap
inline bool Lineseg_Sphere(const Lineseg& ls, const Sphere& s)
inline bool Lineseg_Sphere(const Lineseg& ls, const ::Sphere& s)
{
float radius2 = s.radius * s.radius;
@@ -729,7 +729,7 @@ namespace Overlap {
* 0 = no overlap
* 1 = overlap
*----------------------------------------------------------------------------------*/
ILINE bool Sphere_AABB(const Sphere& s, const AABB& aabb)
ILINE bool Sphere_AABB(const ::Sphere& s, const AABB& aabb)
{
Vec3 center(s.center);
@@ -746,7 +746,7 @@ namespace Overlap {
}
// As Sphere_AABB but ignores z parts
ILINE bool Sphere_AABB2D(const Sphere& s, const AABB& aabb)
ILINE bool Sphere_AABB2D(const ::Sphere& s, const AABB& aabb)
{
Vec3 center(s.center);
@@ -776,7 +776,7 @@ namespace Overlap {
* 0x01 = Sphere and AABB overlap
* 0x02 = Sphere in inside AABB
*/
ILINE char Sphere_AABB_Inside(const Sphere& s, const AABB& aabb)
ILINE char Sphere_AABB_Inside(const ::Sphere& s, const AABB& aabb)
{
if (Sphere_AABB(s, aabb))
{
@@ -819,7 +819,7 @@ namespace Overlap {
//--- 0 = no overlap ---------------------------
//--- 1 = overlap -----------------
//----------------------------------------------------------------------------------
inline bool Sphere_OBB(const Sphere& s, const OBB& obb)
inline bool Sphere_OBB(const ::Sphere& s, const OBB& obb)
{
//first we transform the sphere-center into the AABB-space of the OBB
Vec3 SphereInOBBSpace = s.center * obb.m33;
@@ -861,7 +861,7 @@ namespace Overlap {
//--- 0 = no overlap ---------------------------
//--- 1 = overlap -----------------
//----------------------------------------------------------------------------------
inline bool Sphere_Sphere(const Sphere& s1, const Sphere& s2)
inline bool Sphere_Sphere(const ::Sphere& s1, const ::Sphere& s2)
{
Vec3 distc = s1.center - s2.center;
f32 sqrad = (s1.radius + s2.radius) * (s1.radius + s2.radius);
@@ -884,7 +884,7 @@ namespace Overlap {
//--- 1 = overlap -----------------
//----------------------------------------------------------------------------------
template<typename F>
ILINE bool Sphere_Triangle(const Sphere& s, const Triangle_tpl<F>& t)
ILINE bool Sphere_Triangle(const ::Sphere& s, const Triangle_tpl<F>& t)
{
//create a "bouding sphere" around triangle for fast rejection test
Vec3_tpl<F> middle = (t.v0 + t.v1 + t.v2) * (1 / 3.0f);
@@ -899,7 +899,7 @@ namespace Overlap {
SqRad0 = (F)fsel(SqRad0 - SqRad2, SqRad0, SqRad2);
//first simple rejection-test...
if (Sphere_Sphere(s, Sphere(middle, sqrt_tpl(SqRad0))) == 0)
if (Sphere_Sphere(s, ::Sphere(middle, sqrt_tpl(SqRad0))) == 0)
{
return 0; //overlap not possible
}
+1 -1
View File
@@ -109,7 +109,7 @@ struct NavigationBlocker
, costMultMod(0)
, radialDecay(false) {AZ_Assert(false, "Should never get called"); }
Sphere sphere;
::Sphere sphere;
bool radialDecay;
bool directional;
@@ -14,6 +14,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Sfmt.h>
#include <AzCore/Math/Crc.h>
@@ -173,7 +174,11 @@ namespace AZ
//=========================================================================
void ComponentDescriptor::ReleaseDescriptor()
{
EBUS_EVENT(ComponentApplicationBus, UnregisterComponentDescriptor, this);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
if (componentApplication != nullptr)
{
componentApplication->UnregisterComponentDescriptor(this);
}
delete this;
}
} // namespace AZ
@@ -526,6 +526,11 @@ namespace AZ
// are destroyed
m_commandLine = {};
m_entityAddedEvent.DisconnectAllHandlers();
m_entityRemovedEvent.DisconnectAllHandlers();
m_entityActivatedEvent.DisconnectAllHandlers();
m_entityDeactivatedEvent.DisconnectAllHandlers();
DestroyAllocator();
}
@@ -980,6 +985,26 @@ namespace AZ
handler.Connect(m_entityRemovedEvent);
}
void ComponentApplication::RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler& handler)
{
handler.Connect(m_entityActivatedEvent);
}
void ComponentApplication::RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler& handler)
{
handler.Connect(m_entityDeactivatedEvent);
}
void ComponentApplication::SignalEntityActivated(AZ::Entity* entity)
{
m_entityActivatedEvent.Signal(entity);
}
void ComponentApplication::SignalEntityDeactivated(AZ::Entity* entity)
{
m_entityDeactivatedEvent.Signal(entity);
}
//=========================================================================
// AddEntity
// [5/30/2012]
@@ -204,6 +204,10 @@ namespace AZ
void UnregisterComponentDescriptor(const ComponentDescriptor* descriptor) override final;
void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler& handler) override final;
void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler& handler) override final;
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler& handler) override final;
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler& handler) override final;
void SignalEntityActivated(Entity* entity) override final;
void SignalEntityDeactivated(Entity* entity) override final;
bool AddEntity(Entity* entity) override;
bool RemoveEntity(Entity* entity) override;
bool DeleteEntity(const EntityId& id) override;
@@ -382,6 +386,8 @@ namespace AZ
AZStd::unique_ptr<SettingsRegistryInterface> m_settingsRegistry;
EntityAddedEvent m_entityAddedEvent;
EntityRemovedEvent m_entityRemovedEvent;
EntityAddedEvent m_entityActivatedEvent;
EntityRemovedEvent m_entityDeactivatedEvent;
AZ::IConsole* m_console{};
Descriptor m_descriptor;
bool m_isStarted{ false };
@@ -72,6 +72,8 @@ namespace AZ
using EntityAddedEvent = AZ::Event<AZ::Entity*>;
using EntityRemovedEvent = AZ::Event<AZ::Entity*>;
using EntityActivatedEvent = AZ::Event<AZ::Entity*>;
using EntityDeactivatedEvent = AZ::Event<AZ::Entity*>;
//! Interface that components can use to make requests of the main application.
class ComponentApplicationRequests
@@ -102,6 +104,22 @@ namespace AZ
//! @param handler the event handler to signal.
virtual void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler& handler) = 0;
//! Registers an event handler that will be signalled whenever an entity is added.
//! @param handler the event handler to signal.
virtual void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler& handler) = 0;
//! Registers an event handler that will be signalled whenever an entity is removed.
//! @param handler the event handler to signal.
virtual void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler& handler) = 0;
//! Signals that the provided entity has been activated.
//! @param entity the entity being activated.
virtual void SignalEntityActivated(AZ::Entity* entity) = 0;
//! Signals that the provided entity has been deactivated.
//! @param entity the entity being deactivated.
virtual void SignalEntityDeactivated(AZ::Entity* entity) = 0;
//! Adds an entity to the application's registry.
//! Calling Init() on an entity automatically performs this operation.
//! @param entity A pointer to the entity to add to the application's registry.
@@ -112,7 +112,11 @@ namespace AZ
{
EBUS_EVENT(EntitySystemBus, OnEntityDestruction, m_id);
EBUS_EVENT_ID(m_id, EntityBus, OnEntityDestruction, m_id);
EBUS_EVENT(ComponentApplicationBus, RemoveEntity, this);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
if (componentApplication != nullptr)
{
componentApplication->RemoveEntity(this);
}
m_stateEvent.Signal(State::Init, State::Destroying);
}
@@ -216,12 +220,22 @@ namespace AZ
EBUS_EVENT_ID(m_id, EntityBus, OnEntityActivated, m_id);
EBUS_EVENT(EntitySystemBus, OnEntityActivated, m_id);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
if (componentApplication != nullptr)
{
componentApplication->SignalEntityActivated(this);
}
}
void Entity::Deactivate()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzCore);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
if (componentApplication != nullptr)
{
componentApplication->SignalEntityDeactivated(this);
}
EBUS_EVENT_ID(m_id, EntityBus, OnEntityDeactivated, m_id);
EBUS_EVENT(EntitySystemBus, OnEntityDeactivated, m_id);
@@ -126,9 +126,11 @@ namespace AZ
char buffer[MaxLogBufferSize];
const AZStd::size_t length = azvsnprintf(buffer, MaxLogBufferSize, format, args);
buffer[AZStd::min<AZStd::size_t>(length, MaxLogBufferSize - 2)] = '\n';
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 1)] = '\0';
m_logEvent.Signal(level, buffer, file, function, line);
// Force a new-line before calling the AZ::Debug::Trace functions, as they assume a newline is present
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 2)] = '\n';
switch (level)
{
case LogLevel::Warn:
@@ -142,8 +144,6 @@ namespace AZ
AZ::Debug::Trace::Output("Logger", buffer);
break;
}
m_logEvent.Signal(level, buffer, file, function, line);
}
void LoggerSystemComponent::SetLevel(const AZ::ConsoleCommandContainer& arguments)
@@ -233,6 +233,14 @@ namespace AZ
AZ_Assert(handler->m_event == this, "Entry event does not match");
handler->Disconnect();
}
// Free up any owned memory
AZStd::vector<Handler*> freeHandlers;
m_handlers.swap(freeHandlers);
AZStd::vector<Handler*> freeAdds;
m_addList.swap(freeAdds);
AZStd::stack<size_t> freeFree;
m_freeList.swap(freeFree);
}
@@ -33,6 +33,10 @@ namespace UnitTest
MOCK_METHOD1(UnregisterComponentDescriptor, void (const AZ::ComponentDescriptor*));
MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&));
MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&));
MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&));
MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&));
MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*));
MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*));
MOCK_METHOD1(RemoveEntity, bool (AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool (const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string (const AZ::EntityId&));
@@ -49,9 +49,13 @@ namespace UnitTest
// ComponentApplicationBus
AZ::ComponentApplication* GetApplication() override { return nullptr; }
void RegisterComponentDescriptor(const AZ::ComponentDescriptor*) override {}
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override {}
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override {}
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override {}
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override {}
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override {}
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override {}
void SignalEntityActivated(AZ::Entity*) override {}
void SignalEntityDeactivated(AZ::Entity*) override {}
bool AddEntity(AZ::Entity*) override { return true; }
bool RemoveEntity(AZ::Entity*) override { return true; }
bool DeleteEntity(const AZ::EntityId&) override { return true; }
@@ -1232,6 +1232,10 @@ namespace UnitTest
void UnregisterComponentDescriptor(const ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(Entity*) override { }
void SignalEntityDeactivated(Entity*) override { }
bool AddEntity(Entity*) override { return false; }
bool RemoveEntity(Entity*) override { return false; }
bool DeleteEntity(const EntityId&) override { return false; }
@@ -1252,6 +1256,7 @@ namespace UnitTest
m_serializeContext.reset(aznew AZ::SerializeContext());
ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
@@ -1270,6 +1275,7 @@ namespace UnitTest
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
ComponentApplicationBus::Handler::BusDisconnect();
}
@@ -11,10 +11,12 @@
*/
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Quaternion.h>
@@ -693,9 +695,8 @@ namespace AzFramework
parentId = handler->GetParentId();
}
#endif
AZ::Entity* parentEntity = nullptr;
EBUS_EVENT_RESULT(parentEntity, AZ::ComponentApplicationBus, FindEntity, parentEntityId);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
AZ::Entity* parentEntity = (componentApplication != nullptr) ? componentApplication->FindEntity(parentEntityId) : nullptr;
AZ_Assert(parentEntity, "We expect to have a parent entity associated with the provided parent's entity Id.");
if (parentEntity)
{
@@ -744,8 +745,8 @@ namespace AzFramework
m_parentId = parentId;
if (m_parentId.IsValid())
{
AZ::Entity* parentEntity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationBus::Events::FindEntity, m_parentId);
AZ::ComponentApplicationRequests* componentApplication = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
AZ::Entity* parentEntity = (componentApplication != nullptr) ? componentApplication->FindEntity(m_parentId) : nullptr;
m_parentActive = parentEntity && (parentEntity->GetState() == AZ::Entity::State::Active);
m_onNewParentKeepWorldTM = isKeepWorldTM;
@@ -832,6 +833,12 @@ namespace AzFramework
EBUS_EVENT_PTR(m_notificationBus, AZ::TransformNotificationBus, OnTransformChanged, m_localTM, m_worldTM);
m_transformChangedEvent.Signal(m_localTM, m_worldTM);
AzFramework::IEntityBoundsUnion* boundsUnion = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get();
if (boundsUnion != nullptr)
{
boundsUnion->OnTransformUpdated(GetEntity());
}
}
void TransformComponent::ComputeWorldTM()
@@ -93,6 +93,8 @@ namespace AzFramework
InitContext();
GameEntityContextRequestBus::Handler::BusConnect();
m_entityVisibilityBoundsUnionSystem.Connect();
}
//=========================================================================
@@ -100,6 +102,8 @@ namespace AzFramework
//=========================================================================
void GameEntityContextComponent::Deactivate()
{
m_entityVisibilityBoundsUnionSystem.Disconnect();
GameEntityContextRequestBus::Handler::BusDisconnect();
DestroyContext();
@@ -18,6 +18,7 @@
#include <AzCore/Component/Component.h>
#include <AzFramework/Entity/GameEntityContextBus.h>
#include <AzFramework/Entity/SliceGameEntityOwnershipService.h>
#include <AzFramework/Visibility/EntityVisibilityBoundsUnionSystem.h>
#include "EntityContext.h"
@@ -91,6 +92,9 @@ namespace AzFramework
{
required.push_back(AZ_CRC("SliceSystemService", 0x1a5b7aad));
}
private:
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
};
} // namespace AzFramework
@@ -139,7 +139,8 @@ namespace AzFramework
"Implementers of IntersectionRequestBus must also implement BoundsRequestBus to ensure valid "
"bounds are returned");
m_registeredEntities.Update({ entityId, CalculateEntityWorldBoundsUnion(entityId) });
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
m_registeredEntities.Update({ entityId, CalculateEntityWorldBoundsUnion(entity) });
}
m_dirtyEntities.clear();
@@ -12,6 +12,7 @@
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Render/GeometryIntersectionStructures.h>
@@ -27,7 +28,8 @@ namespace AZ
namespace AzFramework
{
//! Implemented by components that provide bounds for use with various systems.
class BoundsRequests : public AZ::ComponentBus
class BoundsRequests
: public AZ::ComponentBus
{
public:
static void Reflect(AZ::ReflectContext* context);
@@ -37,6 +39,7 @@ namespace AzFramework
//! more than one component may be providing a bound. It isn't guaranteed which bound
//! will be returned by a single call to GetWorldBounds.
virtual AZ::Aabb GetWorldBounds() = 0;
//! Returns an axis aligned bounding box in local space.
//! @note It is preferred to use CalculateEntityLocalBoundsUnion in the general case as
//! more than one component may be providing a bound. It isn't guaranteed which bound
@@ -46,17 +49,15 @@ namespace AzFramework
protected:
~BoundsRequests() = default;
};
using BoundsRequestBus = AZ::EBus<BoundsRequests>;
//! Returns a union of all local Aabbs provided by components implementing the BoundsRequestBus.
//! @note It is preferred to call this function as opposed to GetLocalBounds directly as more than one
//! component may be implementing this bus on an Entity and so only the first result (Aabb) will be returned.
inline AZ::Aabb CalculateEntityLocalBoundsUnion(const AZ::EntityId entityId)
inline AZ::Aabb CalculateEntityLocalBoundsUnion(const AZ::Entity* entity)
{
AZ::EBusReduceResult<AZ::Aabb, AabbUnionAggregator> aabbResult(AZ::Aabb::CreateNull());
BoundsRequestBus::EventResult(
aabbResult, entityId, &BoundsRequestBus::Events::GetLocalBounds);
BoundsRequestBus::EventResult(aabbResult, entity->GetId(), &BoundsRequestBus::Events::GetLocalBounds);
if (aabbResult.value.IsValid())
{
@@ -69,18 +70,18 @@ namespace AzFramework
//! Returns a union of all world Aabbs provided by components implementing the BoundsRequestBus.
//! @note It is preferred to call this function as opposed to GetWorldBounds directly as more than one
//! component may be implementing this bus on an Entity and so only the first result (Aabb) will be returned.
inline AZ::Aabb CalculateEntityWorldBoundsUnion(const AZ::EntityId entityId)
inline AZ::Aabb CalculateEntityWorldBoundsUnion(const AZ::Entity* entity)
{
AZ::EBusReduceResult<AZ::Aabb, AabbUnionAggregator> aabbResult(AZ::Aabb::CreateNull());
BoundsRequestBus::EventResult(aabbResult, entityId, &BoundsRequestBus::Events::GetWorldBounds);
BoundsRequestBus::EventResult(aabbResult, entity->GetId(), &BoundsRequestBus::Events::GetWorldBounds);
if (aabbResult.value.IsValid())
{
return aabbResult.value;
}
AZ::Vector3 worldTranslation = AZ::Vector3::CreateZero();
AZ::TransformBus::EventResult(worldTranslation, entityId, &AZ::TransformBus::Events::GetWorldTranslation);
AZ::TransformInterface* transformInterface = entity->GetTransform();
const AZ::Vector3 worldTranslation = transformInterface->GetWorldTranslation();
return AZ::Aabb::CreateCenterHalfExtents(worldTranslation, AZ::Vector3(0.5f));
}
} // namespace AzFramework
@@ -23,9 +23,11 @@ namespace AzFramework
{
//! Provides an interface to retrieve and update the union of all Aabbs on a single Entity.
//! @note This will be the combination/union of all individual Component Aabbs.
class EntityBoundsUnionRequests : public AZ::EBusTraits
class IEntityBoundsUnion
{
public:
AZ_RTTI(IEntityBoundsUnion, "{106968DD-43C0-478E-8045-523E0BF5D0F5}");
//! Requests the cached union of component Aabbs to be recalculated as one may have changed.
//! @note This is used to drive event driven updates to the visibility system.
virtual void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) = 0;
@@ -38,9 +40,21 @@ namespace AzFramework
//! also be called explicitly (e.g. For testing purposes).
virtual void ProcessEntityBoundsUnionRequests() = 0;
//! Notifies the EntityBoundsUnion system that an entities transform has been modified.
//! @param entity the entity whose transform has been modified.
virtual void OnTransformUpdated(AZ::Entity* entity) = 0;
protected:
~EntityBoundsUnionRequests() = default;
~IEntityBoundsUnion() = default;
};
using EntityBoundsUnionRequestBus = AZ::EBus<EntityBoundsUnionRequests>;
// EBus wrapper for ScriptCanvas
class IEntityBoundsUnionTraits
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
};
using IEntityBoundsUnionRequestBus = AZ::EBus<IEntityBoundsUnion, IEntityBoundsUnionTraits>;
} // namespace AzFramework
@@ -17,69 +17,70 @@
namespace AzFramework
{
EntityVisibilityBoundsUnionSystem::EntityVisibilityBoundsUnionSystem()
: m_entityActivatedEventHandler([this](AZ::Entity* entity) { OnEntityActivated(entity); })
, m_entityDeactivatedEventHandler([this](AZ::Entity* entity) { OnEntityDeactivated(entity); })
{
;
}
void EntityVisibilityBoundsUnionSystem::Connect()
{
EntityBoundsUnionRequestBus::Handler::BusConnect();
AZ::TransformNotificationBus::Router::BusRouterConnect();
AZ::EntitySystemBus::Handler::BusConnect();
AZ::Interface<IEntityBoundsUnion>::Register(this);
IEntityBoundsUnionRequestBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Get()->RegisterEntityActivatedEventHandler(m_entityActivatedEventHandler);
AZ::Interface<AZ::ComponentApplicationRequests>::Get()->RegisterEntityDeactivatedEventHandler(m_entityDeactivatedEventHandler);
}
void EntityVisibilityBoundsUnionSystem::Disconnect()
{
m_entityActivatedEventHandler.Disconnect();
m_entityDeactivatedEventHandler.Disconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::EntitySystemBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Router::BusRouterDisconnect();
EntityBoundsUnionRequestBus::Handler::BusDisconnect();
IEntityBoundsUnionRequestBus::Handler::BusDisconnect();
AZ::Interface<IEntityBoundsUnion>::Unregister(this);
}
static void SetUserDataEntityId(VisibilityEntry& visibilityEntry, const AZ::EntityId entityId)
{
static_assert(
sizeof(AZ::EntityId) <= sizeof(visibilityEntry.m_userData), "Ensure EntityId fits into m_userData");
visibilityEntry.m_typeFlags = VisibilityEntry::TYPE_Entity;
std::memcpy(&visibilityEntry.m_userData, &entityId, sizeof(AZ::EntityId));
}
void EntityVisibilityBoundsUnionSystem::OnEntityActivated(const AZ::EntityId& entityId)
void EntityVisibilityBoundsUnionSystem::OnEntityActivated(AZ::Entity* entity)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
// ignore any entity that might activate which does not have a TransformComponent
if (!AZ::TransformBus::HasHandlers(entityId))
if (entity->GetTransform() == nullptr)
{
return;
}
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entityId);
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it == m_entityVisibilityBoundsUnionInstanceMapping.end())
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
AZ::TransformInterface* transformInterface = entity->GetTransform();
const AZ::Vector3 entityPosition = transformInterface->GetWorldTranslation();
EntityVisibilityBoundsUnionInstance instance;
instance.m_worldTransform = worldFromLocal;
instance.m_localEntityBoundsUnion = CalculateEntityLocalBoundsUnion(entityId);
SetUserDataEntityId(instance.m_visibilityEntry, entityId);
instance.m_localEntityBoundsUnion = CalculateEntityLocalBoundsUnion(entity);
instance.m_visibilityEntry.m_typeFlags = VisibilityEntry::TYPE_Entity;
instance.m_visibilityEntry.m_userData = static_cast<void*>(entity);
auto next_it = m_entityVisibilityBoundsUnionInstanceMapping.insert({entityId, instance});
UpdateVisibilitySystem(next_it.first->second);
auto next_it = m_entityVisibilityBoundsUnionInstanceMapping.insert({ entity, instance });
UpdateVisibilitySystem(entity, next_it.first->second);
}
}
void EntityVisibilityBoundsUnionSystem::OnEntityDeactivated(const AZ::EntityId& entityId)
void EntityVisibilityBoundsUnionSystem::OnEntityDeactivated(AZ::Entity* entity)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
// ignore any entity that might deactivate which does not have a TransformComponent
if (!AZ::TransformBus::HasHandlers(entityId))
if (entity->GetTransform() == nullptr)
{
return;
}
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entityId);
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
if (IVisibilitySystem* visibilitySystem = AZ::Interface<IVisibilitySystem>::Get())
@@ -90,7 +91,7 @@ namespace AzFramework
}
}
void EntityVisibilityBoundsUnionSystem::UpdateVisibilitySystem(EntityVisibilityBoundsUnionInstance& instance)
void EntityVisibilityBoundsUnionSystem::UpdateVisibilitySystem(AZ::Entity* entity, EntityVisibilityBoundsUnionInstance& instance)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
@@ -98,8 +99,8 @@ namespace AzFramework
{
// note: worldEntityBounds will not be a 'tight-fit' Aabb but that of a transformed local aabb
// there will be some wasted space but it should be sufficient for the visibility system
const AZ::Aabb worldEntityBoundsUnion =
localEntityBoundsUnions.GetTransformedAabb(instance.m_worldTransform);
AZ::TransformInterface* transformInterface = entity->GetTransform();
const AZ::Aabb worldEntityBoundsUnion = localEntityBoundsUnions.GetTransformedAabb(transformInterface->GetWorldTM());
IVisibilitySystem* visibilitySystem = AZ::Interface<IVisibilitySystem>::Get();
if (visibilitySystem && !worldEntityBoundsUnion.IsClose(instance.m_visibilityEntry.m_boundingVolume))
{
@@ -111,19 +112,27 @@ namespace AzFramework
void EntityVisibilityBoundsUnionSystem::RefreshEntityLocalBoundsUnion(const AZ::EntityId entityId)
{
// track entities that need their bounds union to be recalculated
m_entityIdsBoundsDirty.insert(entityId);
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
if (entity != nullptr)
{
// track entities that need their bounds union to be recalculated
m_entityBoundsDirty.insert(entity);
}
}
AZ::Aabb EntityVisibilityBoundsUnionSystem::GetEntityLocalBoundsUnion(const AZ::EntityId entityId) const
{
// if the EntityId is not found in the mapping then return a null Aabb, this is to mimic
// as closely as possible the behavior of an individual GetLocalBounds call to an Entity that
// had been deleted (there would be no response, leaving the default value assigned)
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entityId);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
if (entity != nullptr)
{
return instance_it->second.m_localEntityBoundsUnion;
// if the entity is not found in the mapping then return a null Aabb, this is to mimic
// as closely as possible the behavior of an individual GetLocalBounds call to an Entity that
// had been deleted (there would be no response, leaving the default value assigned)
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
return instance_it->second.m_localEntityBoundsUnion;
}
}
return AZ::Aabb::CreateNull();
@@ -134,45 +143,29 @@ namespace AzFramework
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
// iterate over all entities whose bounds changed and recalculate them
for (const auto& entityId : m_entityIdsBoundsDirty)
for (const auto& entity : m_entityBoundsDirty)
{
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entityId);
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
instance_it->second.m_localEntityBoundsUnion = CalculateEntityLocalBoundsUnion(entityId);
}
}
auto allDirtyEntityIds = m_entityIdsTransformDirty;
allDirtyEntityIds.insert(m_entityIdsBoundsDirty.begin(), m_entityIdsBoundsDirty.end());
for (const auto& dirtyEntityId : allDirtyEntityIds)
{
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(dirtyEntityId);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
UpdateVisibilitySystem(instance_it->second);
instance_it->second.m_localEntityBoundsUnion = CalculateEntityLocalBoundsUnion(entity);
UpdateVisibilitySystem(entity, instance_it->second);
}
}
// clear dirty entities once the visibility system has been updated
m_entityIdsBoundsDirty.clear();
m_entityIdsTransformDirty.clear();
m_entityBoundsDirty.clear();
}
void EntityVisibilityBoundsUnionSystem::OnTransformChanged(
[[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
void EntityVisibilityBoundsUnionSystem::OnTransformUpdated(AZ::Entity* entity)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzFramework);
const AZ::EntityId entityId = *AZ::TransformNotificationBus::GetCurrentBusId();
m_entityIdsTransformDirty.insert(entityId);
// update the world transform of the visibility bounds union
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entityId);
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
instance_it->second.m_worldTransform = world;
UpdateVisibilitySystem(entity, instance_it->second);
}
}
@@ -23,12 +23,12 @@ namespace AzFramework
{
//! Provide a unified hook between entities and the visibility system.
class EntityVisibilityBoundsUnionSystem
: public EntityBoundsUnionRequestBus::Handler
, private AZ::EntitySystemBus::Handler
, private AZ::TransformNotificationBus::Router
: public IEntityBoundsUnionRequestBus::Handler
, private AZ::TickBus::Handler
{
public:
EntityVisibilityBoundsUnionSystem();
void Connect();
void Disconnect();
@@ -36,34 +36,31 @@ namespace AzFramework
void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) override;
AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const override;
void ProcessEntityBoundsUnionRequests() override;
void OnTransformUpdated(AZ::Entity* entity) override;
private:
struct EntityVisibilityBoundsUnionInstance
{
AZ::Transform m_worldTransform = AZ::Transform::CreateIdentity(); //!< The world transform of the Entity.
AZ::Aabb m_localEntityBoundsUnion =
AZ::Aabb::CreateNull(); //!< Entity union bounding volume in local space.
AZ::Aabb m_localEntityBoundsUnion = AZ::Aabb::CreateNull(); //!< Entity union bounding volume in local space.
VisibilityEntry m_visibilityEntry; //!< Hook into the IVisibilitySystem interface.
};
using UniqueEntityIds = AZStd::unordered_set<AZ::EntityId>;
using UniqueEntities = AZStd::set<AZ::Entity*>;
using EntityVisibilityBoundsUnionInstanceMapping =
AZStd::unordered_map<AZ::EntityId, EntityVisibilityBoundsUnionInstance>;
AZStd::unordered_map<AZ::Entity*, EntityVisibilityBoundsUnionInstance>;
void OnEntityActivated(AZ::Entity* entity);
void OnEntityDeactivated(AZ::Entity* entity);
// TickBus overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// EntitySystemBus overrides ...
void OnEntityActivated(const AZ::EntityId& entityId) override;
void OnEntityDeactivated(const AZ::EntityId& entityId) override;
// TransformNotificationBus overrides ...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
void UpdateVisibilitySystem(EntityVisibilityBoundsUnionInstance& instance);
void UpdateVisibilitySystem(AZ::Entity* entity, EntityVisibilityBoundsUnionInstance& instance);
EntityVisibilityBoundsUnionInstanceMapping m_entityVisibilityBoundsUnionInstanceMapping;
UniqueEntityIds m_entityIdsBoundsDirty;
UniqueEntityIds m_entityIdsTransformDirty;
UniqueEntities m_entityBoundsDirty;
AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler;
AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler;
};
} // namespace AzFramework
@@ -14,6 +14,7 @@
#include <AzCore/Console/Console.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/ShapeIntersection.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Visibility/IVisibilitySystem.h>
@@ -66,6 +67,7 @@ namespace AzFramework
octreeDebug.m_nodeBounds.push_back(nodeData.m_bounds);
}
visibleEntityIdsOut.reserve(visibleEntityIdsOut.size() + nodeData.m_entries.size());
for (const auto* visibilityEntry : nodeData.m_entries)
{
if (ed_visibility_showDebug)
@@ -88,8 +90,7 @@ namespace AzFramework
octreeDebug.m_entryAabbsInFrustum.push_back(visibilityEntry->m_boundingVolume);
}
AZ::EntityId entityId;
std::memcpy(&entityId, &visibilityEntry->m_userData, sizeof(AZ::EntityId));
AZ::EntityId entityId = static_cast<AZ::Entity*>(visibilityEntry->m_userData)->GetId();
visibleEntityIdsOut.push_back(entityId);
}
});
@@ -19,9 +19,10 @@ namespace AzNetworking
static const int32_t FloatHashMinValue = (INT_MIN >> 7);
static const int32_t FloatHashMaxValue = (INT_MAX >> 7);
AZ::HashValue64 HashSerializer::GetHash() const
AZ::HashValue32 HashSerializer::GetHash() const
{
return m_hash;
// Just truncate the upper bits
return static_cast<AZ::HashValue32>(m_hash);
}
SerializerMode HashSerializer::GetSerializerMode() const
@@ -27,7 +27,7 @@ namespace AzNetworking
HashSerializer() = default;
AZ::HashValue64 GetHash() const;
AZ::HashValue32 GetHash() const;
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
@@ -0,0 +1,162 @@
/*
* 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 <AzNetworking/Serialization/StringifySerializer.h>
namespace AzNetworking
{
StringifySerializer::StringifySerializer(char delimeter, bool outputFieldNames, const AZStd::string& seperator)
: m_delimeter(delimeter)
, m_outputFieldNames(outputFieldNames)
, m_separator(seperator)
{
;
}
const AZStd::string& StringifySerializer::GetString() const
{
return m_string;
}
const StringifySerializer::StringMap& StringifySerializer::GetValueMap() const
{
return m_map;
}
SerializerMode StringifySerializer::GetSerializerMode() const
{
return SerializerMode::ReadFromObject;
}
bool StringifySerializer::Serialize(bool& value, const char* name)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(char& value, const char* name, char, char)
{
const int val = value; // Print chars as integers
return ProcessData(name, val);
}
bool StringifySerializer::Serialize(int8_t& value, const char* name, int8_t, int8_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int16_t& value, const char* name, int16_t, int16_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int32_t& value, const char* name, int32_t, int32_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(int64_t& value, const char* name, int64_t, int64_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint8_t& value, const char* name, uint8_t, uint8_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint16_t& value, const char* name, uint16_t, uint16_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint32_t& value, const char* name, uint32_t, uint32_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(uint64_t& value, const char* name, uint64_t, uint64_t)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(float& value, const char* name, float, float)
{
return ProcessData(name, value);
}
bool StringifySerializer::Serialize(double& value, const char* name, double, double)
{
return ProcessData(name, value);
}
bool StringifySerializer::SerializeBytes(uint8_t* buffer, uint32_t, bool isString, uint32_t&, const char* name)
{
if (isString)
{
AZ::CVarFixedString value = reinterpret_cast<char*>(buffer);
return ProcessData(name, value);
}
return false;
}
bool StringifySerializer::BeginObject(const char* name, const char*)
{
m_prefixSizeStack.push_back(m_prefix.size());
m_prefix += name;
m_prefix += ".";
return true;
}
bool StringifySerializer::EndObject(const char*, const char*)
{
m_prefix.resize(m_prefixSizeStack.back());
m_prefixSizeStack.pop_back();
return true;
}
const uint8_t* StringifySerializer::GetBuffer() const
{
return nullptr;
}
uint32_t StringifySerializer::GetCapacity() const
{
return 0;
}
uint32_t StringifySerializer::GetSize() const
{
return 0;
}
template <typename T>
bool StringifySerializer::ProcessData(const char* name, const T& value)
{
// Only add delimeters after we have processed at least one element
if (!m_string.empty())
{
m_string += m_delimeter;
}
if (m_outputFieldNames)
{
m_string += m_prefix;
m_string += name;
m_string += m_separator;
}
AZ::CVarFixedString string = AZ::ConsoleTypeHelpers::ValueToString(value);
m_string += string.c_str();
m_map[m_prefix + name] = string.c_str();
return true;
}
}
@@ -0,0 +1,80 @@
/*
* 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 <AzNetworking/Serialization/ISerializer.h>
#include <AzCore/std/containers/map.h>
namespace AzNetworking
{
// StringifySerializer
// Generate a debug string of a serializable object
class StringifySerializer
: public ISerializer
{
public:
using StringMap = AZStd::map<AZStd::string, AZStd::string>;
StringifySerializer(char delimeter = ' ', bool outputFieldNames = true, const AZStd::string& seperator = "=");
// GetString
// After serializing objects, get the serialized values as a single string
const AZStd::string& GetString() const;
// GetValueMap
// After serializing objects, get the serialized values as key value pairs
const StringMap& GetValueMap() const;
// ISerializer interfaces
SerializerMode GetSerializerMode() const override;
bool Serialize(bool& value, const char* name) override;
bool Serialize(char& value, const char* name, char minValue, char maxValue) override;
bool Serialize(int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
bool Serialize(int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
bool Serialize(int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
bool Serialize(int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
bool Serialize(uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
bool Serialize(float& value, const char* name, float minValue, float maxValue) override;
bool Serialize(double& value, const char* name, double minValue, double maxValue) override;
bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
bool BeginObject(const char* name, const char* typeName) override;
bool EndObject(const char* name, const char* typeName) override;
const uint8_t* GetBuffer() const override;
uint32_t GetCapacity() const override;
uint32_t GetSize() const override;
void ClearTrackedChangesFlag() override {}
bool GetTrackedChangesFlag() const override { return false; }
// ISerializer interfaces
private:
template <typename T>
bool ProcessData(const char* name, const T& value);
private:
char m_delimeter;
bool m_outputFieldNames = true;
StringMap m_map;
AZStd::string m_string;
AZStd::string m_prefix;
AZStd::string m_separator;
AZStd::deque<AZStd::size_t> m_prefixSizeStack;
};
}
@@ -65,6 +65,8 @@ set(FILES
Serialization/NetworkOutputSerializer.cpp
Serialization/NetworkOutputSerializer.h
Serialization/NetworkOutputSerializer.inl
Serialization/StringifySerializer.cpp
Serialization/StringifySerializer.h
Serialization/TrackChangedSerializer.h
Serialization/TrackChangedSerializer.inl
TcpTransport/TcpConnection.cpp
@@ -189,9 +189,6 @@ namespace AzToolsFramework
SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
EditorLegacyGameModeNotificationBus::Handler::BusConnect();
m_entityVisibilityBoundsUnionSystem.Connect();
}
//=========================================================================
@@ -199,8 +196,6 @@ namespace AzToolsFramework
//=========================================================================
void EditorEntityContextComponent::Deactivate()
{
m_entityVisibilityBoundsUnionSystem.Disconnect();
EditorLegacyGameModeNotificationBus::Handler::BusDisconnect();
SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
@@ -189,8 +189,6 @@ namespace AzToolsFramework
//! EditorEntityContextRequestBus::Events::AddRequiredComponents()
AZ::ComponentTypeList m_requiredEditorComponentTypes;
//! Edit time visibility management integrating entities with the IVisibilitySystem.
AzFramework::EntityVisibilityBoundsUnionSystem m_entityVisibilityBoundsUnionSystem;
bool m_isLegacySliceService;
UndoSystem::UndoCacheInterface* m_undoCacheInterface = nullptr;
@@ -25,6 +25,7 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Components/TransformComponent.h>
#include <AzFramework/Visibility/EntityBoundsUnionBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -265,6 +266,13 @@ namespace AzToolsFramework
AZ::TransformNotificationBus::Event(
GetEntityId(), &TransformNotification::OnTransformChanged, localTM, worldTM);
m_transformChangedEvent.Signal(localTM, worldTM);
AzFramework::IEntityBoundsUnion* boundsUnion = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get();
if (boundsUnion != nullptr)
{
boundsUnion->OnTransformUpdated(GetEntity());
}
}
}
@@ -933,15 +941,14 @@ namespace AzToolsFramework
{
return nullptr;
}
AZ::Entity* pEntity = nullptr;
EBUS_EVENT_RESULT(pEntity, AZ::ComponentApplicationBus, FindEntity, otherEntityId);
if (!pEntity)
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(otherEntityId);
if (!entity)
{
return nullptr;
}
return pEntity->FindComponent<TransformComponent>();
return entity->FindComponent<TransformComponent>();
}
AZ::TransformInterface* TransformComponent::GetParent()
@@ -68,6 +68,7 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
const AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
AzFramework::EntityDebugDisplayEventBus::Event(
entityId, &AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
viewportInfo, debugDisplay);
@@ -84,10 +85,9 @@ namespace AzToolsFramework
if (ed_visibility_showAggregateEntityTransformedLocalBounds)
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
AZ::Transform worldFromLocal = entity->GetTransform()->GetWorldTM();
if (const AZ::Aabb localAabb = AzFramework::CalculateEntityLocalBoundsUnion(entityId); localAabb.IsValid())
if (const AZ::Aabb localAabb = AzFramework::CalculateEntityLocalBoundsUnion(entity); localAabb.IsValid())
{
const AZ::Aabb worldAabb = localAabb.GetTransformedAabb(worldFromLocal);
debugDisplay.SetColor(AZ::Colors::Turquoise);
@@ -97,7 +97,7 @@ namespace AzToolsFramework
if (ed_visibility_showAggregateEntityWorldBounds)
{
if (const AZ::Aabb worldAabb = AzFramework::CalculateEntityWorldBoundsUnion(entityId); worldAabb.IsValid())
if (const AZ::Aabb worldAabb = AzFramework::CalculateEntityWorldBoundsUnion(entity); worldAabb.IsValid())
{
debugDisplay.SetColor(AZ::Colors::Magenta);
debugDisplay.DrawWireBox(worldAabb.GetMin(), worldAabb.GetMax());
@@ -13,7 +13,9 @@
#include "EditorSelectionUtil.h"
#include <AzCore/Math/Aabb.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
@@ -28,7 +30,8 @@ namespace AzToolsFramework
{
if (Centered(pivot))
{
if (const AZ::Aabb localBound = AzFramework::CalculateEntityLocalBoundsUnion(entityId);
const AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
if (const AZ::Aabb localBound = AzFramework::CalculateEntityLocalBoundsUnion(entity);
localBound.IsValid())
{
return localBound.GetCenter();
@@ -62,8 +62,8 @@ namespace UnitTest
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative y-axis moved just back from the origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
@@ -101,8 +101,8 @@ namespace UnitTest
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative x-axis moved along the x-axis and tilted slightly down
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
@@ -143,15 +143,15 @@ namespace UnitTest
SetupRowOfEntities(AZ::Vector3::CreateAxisX(-20.0f), AZ::Vector3::CreateAxisX(2.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
const AZ::EntityId entityIdToMove = m_editorEntityIds[10];
AZ::TransformBus::Event(
entityIdToMove, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3::CreateAxisZ(100.0f));
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the negative y-axis moved just back from the origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
@@ -241,8 +241,8 @@ namespace UnitTest
{
m_localAabb = localAabb;
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
}
TEST_F(EditorVisibilityFixture, UpdatedBoundsIntersectingFrustumAddsVisibleEntity)
@@ -264,8 +264,8 @@ namespace UnitTest
entityId, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(40.0f, -3.0f, 20.0f));
// request the entity union bounds system to update
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
// create default camera looking down the positive x-axis moved to position offset from world origin
AzFramework::CameraState cameraState = AzFramework::CreateDefaultCamera(
@@ -288,8 +288,8 @@ namespace UnitTest
testBoundComponent->ChangeBounds(AZ::Aabb::CreateFromMinMax(AZ::Vector3(-2.5f), AZ::Vector3(2.5f)));
// perform an 'update' of the visibility system
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
AzFramework::IEntityBoundsUnionRequestBus::Broadcast(
&AzFramework::IEntityBoundsUnionRequestBus::Events::ProcessEntityBoundsUnionRequests);
entityVisibilityQuery.UpdateVisibility(cameraState);
@@ -1100,6 +1100,10 @@ namespace UnitTest
void UnregisterComponentDescriptor(const ComponentDescriptor*) override {}
void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler&) override {}
void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler&) override {}
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override {}
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override {}
void SignalEntityActivated(Entity*) override {}
void SignalEntityDeactivated(Entity*) override {}
bool AddEntity(Entity*) override { return true; }
bool RemoveEntity(Entity*) override { return true; }
bool DeleteEntity(const EntityId&) override { return true; }
@@ -1125,6 +1129,7 @@ namespace UnitTest
AllocatorsFixture::SetUp();
ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
m_serializeContext.reset(aznew AZ::SerializeContext(true, true));
Entity::Reflect(m_serializeContext.get());
@@ -1139,6 +1144,7 @@ namespace UnitTest
m_descriptors.set_capacity(0);
m_serializeContext.reset();
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
ComponentApplicationBus::Handler::BusDisconnect();
AllocatorsFixture::TearDown();
+4
View File
@@ -279,6 +279,10 @@ namespace UnitTest
MOCK_METHOD1(UnregisterComponentDescriptor, void (const AZ::ComponentDescriptor*));
MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&));
MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&));
MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&));
MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&));
MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*));
MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*));
MOCK_METHOD1(RemoveEntity, bool (AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool (const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string (const AZ::EntityId&));
@@ -364,6 +364,10 @@ namespace AZ
MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(RegisterEntityAddedEventHandler, void(AZ::EntityAddedEvent::Handler&));
MOCK_METHOD1(RegisterEntityRemovedEventHandler, void(AZ::EntityRemovedEvent::Handler&));
MOCK_METHOD1(RegisterEntityActivatedEventHandler, void(AZ::EntityActivatedEvent::Handler&));
MOCK_METHOD1(RegisterEntityDeactivatedEventHandler, void(AZ::EntityDeactivatedEvent::Handler&));
MOCK_METHOD1(SignalEntityActivated, void(AZ::Entity*));
MOCK_METHOD1(SignalEntityDeactivated, void(AZ::Entity*));
MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&));
@@ -577,6 +577,10 @@ namespace AWSClientAuthUnitTest
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(AZ::Entity*) override { }
void SignalEntityDeactivated(AZ::Entity*) override { }
bool AddEntity(AZ::Entity*) override { return true; }
bool RemoveEntity(AZ::Entity*) override { return true; }
bool DeleteEntity(const AZ::EntityId&) override { return true; }
@@ -105,6 +105,10 @@ namespace UnitTest
void UnregisterComponentDescriptor(const ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(Entity*) override { }
void SignalEntityDeactivated(Entity*) override { }
bool AddEntity(Entity*) override { return false; }
bool RemoveEntity(Entity*) override { return false; }
bool DeleteEntity(const EntityId&) override { return false; }
@@ -134,6 +138,7 @@ namespace UnitTest
// Adding this handler to allow utility functions access the serialize context
ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
@@ -212,6 +217,7 @@ namespace UnitTest
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
ComponentApplicationBus::Handler::BusDisconnect();
AllocatorsBase::TeardownAllocator();
}
@@ -75,6 +75,7 @@ namespace UnitTest
// Adding this handler to allow utility functions access the serialize context
ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
// Startup default local FileIO (hits OSAllocator) if not already setup.
if (IO::FileIOBase::GetInstance() == nullptr)
@@ -113,6 +114,7 @@ namespace UnitTest
delete IO::FileIOBase::GetInstance();
IO::FileIOBase::SetInstance(nullptr);
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
ComponentApplicationBus::Handler::BusDisconnect();
m_jsonRegistrationContext->EnableRemoveReflection();
@@ -41,6 +41,10 @@ namespace UnitTest
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(AZ::Entity*) override { }
void SignalEntityDeactivated(AZ::Entity*) override { }
bool AddEntity(AZ::Entity*) override { return false; }
bool RemoveEntity(AZ::Entity*) override { return false; }
bool DeleteEntity(const AZ::EntityId&) override { return false; }
@@ -38,6 +38,10 @@ namespace UnitTest
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(AZ::Entity*) override { }
void SignalEntityDeactivated(AZ::Entity*) override { }
bool AddEntity(AZ::Entity*) override { return false; }
bool RemoveEntity(AZ::Entity*) override { return false; }
bool DeleteEntity(const AZ::EntityId&) override { return false; }
@@ -296,8 +296,7 @@ namespace AZ
m_configuration.m_modelAsset = modelAsset;
MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model);
MaterialReceiverNotificationBus::Event(m_entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, m_entityId);
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
}
}
@@ -253,8 +253,7 @@ namespace AZ
m_configuration.m_outerLength = dimensions.GetY();
m_configuration.m_outerHeight = dimensions.GetZ();
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, m_entityId);
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
// clamp the inner extents to the outer extents
m_configuration.m_innerWidth = AZStd::min(m_configuration.m_innerWidth, m_configuration.m_outerWidth);
@@ -82,8 +82,7 @@ namespace AZ
// Update RenderActorInstance local bounding box
m_localAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetStaticBasedAABB().GetMin(), m_actorInstance->GetStaticBasedAABB().GetMax());
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, m_entityId);
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
}
AZ::Aabb AtomActorInstance:: GetWorldBounds()
@@ -504,8 +504,7 @@ namespace EMotionFX
{
m_renderActorInstance->OnTick(deltaTime);
m_renderActorInstance->UpdateBounds();
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
// Optimization: Set the actor instance invisible when character is out of camera view. This will stop the joint transforms update, except the root joint.
// Calling it after the bounds on the render actor updated.
@@ -125,6 +125,10 @@ protected:
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(AZ::Entity* entity) override { }
void SignalEntityDeactivated(AZ::Entity* entity) override { }
bool AddEntity(AZ::Entity*) override { return false; }
bool RemoveEntity(AZ::Entity*) override { return false; }
bool DeleteEntity(const AZ::EntityId&) override { return false; }
@@ -12,7 +12,7 @@
#include "LmbrCentral_precompiled.h"
#include "EditorBaseShapeComponent.h"
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/EditContext.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
@@ -214,8 +214,7 @@ namespace LmbrCentral
{
if (changeReason == ShapeChangeReasons::ShapeChanged)
{
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
}
}
} // namespace LmbrCentral
@@ -378,9 +378,7 @@ namespace LmbrCentral
{
SplineComponentNotificationBus::Event(
GetEntityId(), &SplineComponentNotificationBus::Events::OnSplineChanged);
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
}
AZ::SplinePtr EditorSplineComponent::GetSpline()
@@ -304,6 +304,10 @@ public:
void UnregisterComponentDescriptor(const ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(Entity*) override { }
void SignalEntityDeactivated(Entity*) override { }
bool AddEntity(Entity*) override { return true; }
bool RemoveEntity(Entity*) override { return true; }
bool DeleteEntity(const AZ::EntityId&) override { return true; }
@@ -329,6 +333,7 @@ public:
m_serializeContext = aznew SerializeContext(true, true);
ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
m_sliceDescriptor = SliceComponent::CreateDescriptor();
m_mockAssetDescriptor = MockAssetRefComponent::CreateDescriptor();
@@ -358,6 +363,7 @@ public:
void TearDown() override
{
m_catalog->DisableCatalog();
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
ComponentApplicationBus::Handler::BusDisconnect();
Data::AssetManager::Destroy();
@@ -12,11 +12,13 @@
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
namespace Multiplayer
{
class EntityReplicationManager;
enum class ConnectionDataType
{
ClientToServer,
@@ -42,8 +44,8 @@ namespace Multiplayer
virtual EntityReplicationManager& GetReplicationManager() = 0;
//! Creates and manages sending updates to the remote endpoint.
//! @param serverGameTimeMs current server game time in milliseconds
virtual void Update(AZ::TimeMs serverGameTimeMs) = 0;
//! @param hostTimeMs current server game time in milliseconds
virtual void Update(AZ::TimeMs hostTimeMs) = 0;
//! Returns whether update messages can be sent to the connection.
//! @return true if update messages can be sent
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
namespace Multiplayer
{
+10 -1
View File
@@ -15,6 +15,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/DataStructures/ByteBuffer.h>
#include <Include/INetworkTime.h>
#include <Include/MultiplayerStats.h>
namespace AzNetworking
@@ -56,7 +57,7 @@ namespace Multiplayer
//! Gets the type of Agent this IMultiplayer impl represents
//! @return The type of agents represented
virtual MultiplayerAgentType GetAgentType() = 0;
virtual MultiplayerAgentType GetAgentType() const = 0;
//! Sets the type of this Multiplayer connection and calls any related callback
//! @param state The state of this connection
@@ -78,6 +79,14 @@ namespace Multiplayer
//! @param readyForEntityUpdates Ready for entity updates or not
virtual void SendReadyForEntityUpdates(bool readyForEntityUpdates) = 0;
//! Returns the current server time in milliseconds.
//! This can be one of three possible values:
//! 1. On the host outside of rewind scope, this will return the latest application elapsed time in ms.
//! 2. On the host within rewind scope, this will return the rewound time in ms.
//! 3. On the client, this will return the most recently replicated server time in ms.
//! @return the current server time in milliseconds
virtual AZ::TimeMs GetCurrentHostTimeMs() const = 0;
//! Returns the gem name associated with the provided component index.
//! @param netComponentId the componentId to return the gem name of
//! @return the name of the gem that contains the requested component
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Asset/AssetCommon.h>
@@ -14,13 +14,10 @@
#include <AzCore/Time/ITime.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <Include/MultiplayerTypes.h>
namespace Multiplayer
{
//! This is a strong typedef for representing the number of application frames since application start.
AZ_TYPE_SAFE_INTEGRAL(ApplicationFrameId, uint32_t);
static constexpr ApplicationFrameId InvalidApplicationFrameId = ApplicationFrameId{0xFFFFFFFF};
//! @class INetworkTime
//! @brief This is an AZ::Interface<> for managing multiplayer specific time related operations.
class INetworkTime
@@ -31,30 +28,24 @@ namespace Multiplayer
INetworkTime() = default;
virtual ~INetworkTime() = default;
//! Converts from an ApplicationFrameId to a corresponding TimeMs.
//! @param frameId the ApplicationFrameId to convert to a TimeMs
//! @return the TimeMs that corresponds to the provided ApplicationFrameId
virtual AZ::TimeMs ConvertFrameIdToTimeMs(ApplicationFrameId frameId) const = 0;
//! Returns true if the host timeMs and frameId has been temporarily altered.
//! @return true if the host timeMs and frameId has been altered, false otherwise
virtual bool IsTimeRewound() const = 0;
//! Converts from a TimeMs to an ApplicationFrameId.
//! @param timeMs the TimeMs to convert to an ApplicationFrameId
//! @return the ApplicationFrameId that corresponds to the provided TimeMs
virtual ApplicationFrameId ConvertTimeMsToFrameId(AZ::TimeMs timeMs) const = 0;
//! Retrieves the hosts current frameId (may be rewound on the server during backward reconciliation).
//! @return the hosts current frameId
virtual HostFrameId GetHostFrameId() const = 0;
//! Returns true if the application frameId has been temporarily altered.
//! @return true if the application frameId has been altered, false otherwise
virtual bool IsApplicationFrameIdRewound() const = 0;
//! Retrieves the unaltered hosts current frameId.
//! @return the hosts current frameId, unaltered by any scoped time instance
virtual HostFrameId GetUnalteredHostFrameId() const = 0;
//! Retrieves the applications current frameId (may be rewound on the server during backward reconciliation).
//! @return the applications current frameId
virtual ApplicationFrameId GetApplicationFrameId() const = 0;
//! Increments the hosts current frameId.
virtual void IncrementHostFrameId() = 0;
//! Retrieves the unaltered applications current frameId.
//! @return the applications current frameId, unaltered by any scoped time instance
virtual ApplicationFrameId GetUnalteredApplicationFrameId() const = 0;
//! Increments the applications current frameId.
virtual void IncrementApplicationFrameId() = 0;
//! Retrieves the hosts current timeMs (may be rewound on the server during backward reconciliation).
//! @return the hosts current timeMs
virtual AZ::TimeMs GetHostTimeMs() const = 0;
//! Synchronizes rewindable entity state for the current application time.
virtual void SyncRewindableEntityState() = 0;
@@ -66,14 +57,15 @@ namespace Multiplayer
//! Get the controlling connection that may be currently altering global game time.
//! Note this abstraction is required at a relatively high level to allow for 'don't rewind the shooter' semantics
//! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered applicationFrameId
//! @return the ApplicationFrameId taking into account the provided rewinding connectionId
virtual ApplicationFrameId GetApplicationFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
//! @param rewindConnectionId if this parameter matches the current rewindConnectionId, it will return the unaltered hostFrameId
//! @return the HostFrameId taking into account the provided rewinding connectionId
virtual HostFrameId GetHostFrameIdForRewindingConnection(AzNetworking::ConnectionId rewindConnectionId) const = 0;
//! Alters the current ApplicationFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new ApplicationFrameId to use
//! Alters the current HostFrameId and binds that alteration to the provided ConnectionId.
//! @param frameId the new HostFrameId to use
//! @param timeMs the new HostTimeMs to use
//! @param rewindConnectionId the rewinding ConnectionId
virtual void AlterApplicationFrameId(ApplicationFrameId frameId, AzNetworking::ConnectionId rewindConnectionId) = 0;
virtual void AlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId rewindConnectionId) = 0;
AZ_DISABLE_COPY_MOVE(INetworkTime);
};
@@ -93,22 +85,22 @@ namespace Multiplayer
class ScopedAlterTime final
{
public:
inline ScopedAlterTime(ApplicationFrameId frameId, AzNetworking::ConnectionId connectionId)
inline ScopedAlterTime(HostFrameId frameId, AZ::TimeMs timeMs, AzNetworking::ConnectionId connectionId)
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
m_previousApplicationFrameId = time->GetApplicationFrameId();
m_previousHostFrameId = time->GetHostFrameId();
m_previousHostTimeMs = time->GetHostTimeMs();
m_previousRewindConnectionId = time->GetRewindingConnectionId();
time->AlterApplicationFrameId(frameId, connectionId);
time->AlterTime(frameId, timeMs, connectionId);
}
inline ~ScopedAlterTime()
{
INetworkTime* time = AZ::Interface<INetworkTime>::Get();
time->AlterApplicationFrameId(m_previousApplicationFrameId, m_previousRewindConnectionId);
time->AlterTime(m_previousHostFrameId, m_previousHostTimeMs, m_previousRewindConnectionId);
}
private:
ApplicationFrameId m_previousApplicationFrameId = InvalidApplicationFrameId;
HostFrameId m_previousHostFrameId = InvalidHostFrameId;
AZ::TimeMs m_previousHostTimeMs = AZ::TimeMs{ 0 };
AzNetworking::ConnectionId m_previousRewindConnectionId = AzNetworking::InvalidConnectionId;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ApplicationFrameId);
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
namespace Multiplayer
@@ -14,6 +14,12 @@
namespace Multiplayer
{
MultiplayerStats::Metric::Metric()
{
AZStd::uninitialized_fill_n(m_callHistory.data(), RingbufferSamples, 0);
AZStd::uninitialized_fill_n(m_byteHistory.data(), RingbufferSamples, 0);
}
void MultiplayerStats::ReserveComponentStats(NetComponentId netComponentId, uint16_t propertyCount, uint16_t rpcCount)
{
const uint16_t netComponentIndex = aznumeric_cast<uint16_t>(netComponentId);
@@ -71,6 +77,29 @@ namespace Multiplayer
{
m_totalHistoryTimeMs = metricFrameTimeMs * static_cast<AZ::TimeMs>(RingbufferSamples);
m_recordMetricIndex = ++m_recordMetricIndex % RingbufferSamples;
for (ComponentStats& componentStats : m_componentStats)
{
for (Metric& metric : componentStats.m_propertyUpdatesSent)
{
metric.m_callHistory[m_recordMetricIndex] = 0;
metric.m_byteHistory[m_recordMetricIndex] = 0;
}
for (Metric& metric : componentStats.m_propertyUpdatesRecv)
{
metric.m_callHistory[m_recordMetricIndex] = 0;
metric.m_byteHistory[m_recordMetricIndex] = 0;
}
for (Metric& metric : componentStats.m_rpcsSent)
{
metric.m_callHistory[m_recordMetricIndex] = 0;
metric.m_byteHistory[m_recordMetricIndex] = 0;
}
for (Metric& metric : componentStats.m_rpcsRecv)
{
metric.m_callHistory[m_recordMetricIndex] = 0;
metric.m_byteHistory[m_recordMetricIndex] = 0;
}
}
}
static void CombineMetrics(MultiplayerStats::Metric& outArg1, const MultiplayerStats::Metric& arg2)
@@ -37,6 +37,7 @@ namespace Multiplayer
using MetricRingbuffer = AZStd::array<uint64_t, RingbufferSamples>;
struct Metric
{
Metric();
uint64_t m_totalCalls = 0;
uint64_t m_totalBytes = 0;
MetricRingbuffer m_callHistory;
@@ -36,6 +36,12 @@ namespace Multiplayer
AZ_TYPE_SAFE_INTEGRAL(PropertyIndex, uint16_t);
AZ_TYPE_SAFE_INTEGRAL(RpcIndex, uint16_t);
AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t);
//! This is a strong typedef for representing the number of application frames since application start.
AZ_TYPE_SAFE_INTEGRAL(HostFrameId, uint32_t);
static constexpr HostFrameId InvalidHostFrameId = HostFrameId{ 0xFFFFFFFF };
using LongNetworkString = AZ::CVarFixedString;
using ReliabilityType = AzNetworking::ReliabilityType;
@@ -122,3 +128,5 @@ AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetEntityId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::NetComponentId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::PropertyIndex);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::RpcIndex);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::HostFrameId);
@@ -138,4 +138,4 @@ namespace Multiplayer
};
}
#include "Source/NetworkEntity/NetworkEntityHandle.inl"
#include <Include/NetworkEntityHandle.inl>
@@ -1,6 +1,6 @@
#include <AzCore/Component/Component.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
{% for Component in dataFiles %}
{% set ComponentDerived = Component.attrib['OverrideComponent']|booleanTrue %}
{% set ControllerDerived = Component.attrib['OverrideController']|booleanTrue %}
@@ -170,12 +170,12 @@ AZ::Event<{{ Property.attrib['Type'] }}>
{% set PropertyName = UpperFirst(Property.attrib['Name']) %}
{{ ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }}
{% if IsOverride %}
void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) override {}
void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) override {}
{% else %}
//! {{ PropertyName }} Handler
//! {{ Property.attrib['Description'] }}
//! HandleOn {{ HandleOn }}
virtual void Handle{{ PropertyName }}({{ ', '.join(paramDefines) }}) = 0;
virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) = 0;
{% endif %}
{% endmacro %}
{#
@@ -221,11 +221,11 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }
#include <AzCore/EBus/Event.h>
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkTime/RewindableObject.h>
#include <Include/MultiplayerTypes.h>
{% call(Include) AutoComponentMacros.ParseIncludes(Component) %}
@@ -435,7 +435,7 @@ namespace {{ Component.attrib['Namespace'] }}
//! MultiplayerComponent interface
//! @{
NetComponentId GetNetComponentId() const override;
bool HandleRpcMessage(Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override;
bool SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) override;
void NotifyStateDeltaChanges(Multiplayer::ReplicationRecord& replicationRecord) override;
bool HasController() const override;
@@ -336,7 +336,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Authority, "Entity proxy does not have authority");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% if Property.attrib['IsReliable']|booleanTrue %}
{# if the rpc is not reliable we can simply drop it, also note message reliability type is default reliable in EntityRpcMessage #}
@@ -350,7 +350,7 @@ case {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ Upp
if (m_controller)
{
AZ_Assert(GetNetBindComponent()->GetNetEntityRole() == Multiplayer::NetEntityRole::Autonomous, "Entity proxy does not have autonomy");
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
m_controller->Handle{{ UpperFirst(Property.attrib['Name']) }}(invokingConnection, {{ ', '.join(rpcParamList) }});
}
{% else %}
Handle{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(rpcParamList) }});
@@ -1251,7 +1251,12 @@ namespace {{ Component.attrib['Namespace'] }}
#pragma warning(push)
#pragma warning(disable: 4065) // switch statement contains 'default' but no 'case' labels
bool {{ ComponentBaseName }}::HandleRpcMessage([[maybe_unused]] Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& message)
bool {{ ComponentBaseName }}::HandleRpcMessage
(
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
[[maybe_unused]] Multiplayer::NetEntityRole remoteRole,
Multiplayer::NetworkEntityRpcMessage& message
)
{
const {{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure rpcType = static_cast<{{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure>(message.GetRpcIndex());
switch (rpcType)
@@ -12,15 +12,16 @@
<Include File="Include/MultiplayerTypes.h"/>
<Include File="Source/NetworkInput/NetworkInput.h"/>
<Include File="Source/NetworkInput/NetworkInputArray.h"/>
<Include File="Source/NetworkInput/NetworkInputHistory.h"/>
<Include File="Source/NetworkInput/NetworkInputVector.h"/>
<Include File="Source/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{0}" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Authority" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" GenerateEventBindings="false" />
<RemoteProcedure Name="SendClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server move / input RPC">
<Param Type="Multiplayer::NetworkInputVector" Name="inputArray" />
<Param Type="uint32_t" Name="stateHash" />
<Param Type="Multiplayer::NetworkInputArray" Name="inputArray" />
<Param Type="AZ::HashValue32" Name="stateHash" />
<Param Type="AzNetworking::PacketEncodingBuffer" Name="clientState" Description="This is for debugging desyncs only; release games should not populate this parameter" />
</RemoteProcedure>
@@ -30,6 +31,6 @@
</RemoteProcedure>
<RemoteProcedure Name="SendMigrateClientInput" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="true" IsReliable="false" Description="Client to server migrate move / input RPC">
<Param Type="Multiplayer::MigrateNetworkInputVector" Name="inputArray" />
<Param Type="Multiplayer::NetworkInputMigrationVector" Name="inputArray" />
</RemoteProcedure>
</Component>
@@ -3,6 +3,7 @@
<PacketGroup Name="MultiplayerPackets" PacketStart="CorePackets::PacketType::MAX">
<Include File="AzNetworking/AutoGen/CorePackets.AutoPackets.h" />
<Include File="Include/MultiplayerTypes.h" />
<Include File="Include/INetworkTime.h" />
<Include File="Source/NetworkEntity/NetworkEntityRpcMessage.h" />
<Include File="Source/NetworkEntity/NetworkEntityUpdateMessage.h" />
@@ -35,6 +36,7 @@
<Packet Name="EntityUpdates" Desc="A packet that contains multiple entity updates">
<Member Type="AZ::TimeMs" Name="hostTimeMs" Init="AZ::TimeMs{ 0 }" />
<Member Type="Multiplayer::HostFrameId" Name="hostFrameId" Init="Multiplayer::InvalidHostFrameId" />
<Member Type="Multiplayer::NetworkEntityUpdateMessage" Name="entityMessages" Container="Vector" Count="Multiplayer::MaxAggregateEntityMessages" SuppressFromInitializerList="true" />
</Packet>
@@ -13,9 +13,41 @@
#include <Source/Components/LocalPredictionPlayerInputComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzNetworking/Serialization/HashSerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Serialization/StringifySerializer.h>
#include <AzNetworking/Serialization/TrackChangedSerializer.h>
namespace Multiplayer
{
AZ_CVAR(AZ::TimeMs, cl_InputRateMs, AZ::TimeMs{ 33 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Rate at which to sample and process client inputs");
AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay");
#ifndef _RELEASE
AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat");
#endif
AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs");
AZ_CVAR(double, sv_MaxBankTimeWindowSec, 0.2, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum bank time we allow before we start rejecting autonomous proxy move inputs due to anticheat kicking in");
AZ_CVAR(double, sv_BankTimeDecay, 0.025, nullptr, AZ::ConsoleFunctorFlags::Null, "Amount to decay bank time by, in case of more permanent shifts in client latency");
AZ_CVAR(AZ::TimeMs, sv_MinCorrectionTimeMs, AZ::TimeMs{ 100 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time to wait between sending out corrections in order to avoid flooding corrections on high-latency connections");
AZ_CVAR(AZ::TimeMs, sv_InputUpdateTimeMs, AZ::TimeMs{ 5 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum time between component updates");
// Debug helper functions
AZStd::string GetInputString(NetworkInput& input)
{
AzNetworking::StringifySerializer serializer(',', false);
input.Serialize(serializer);
return serializer.GetString();
}
AZStd::string GetCorrectionDataString(NetBindComponent* netBindComponent)
{
AzNetworking::StringifySerializer serializer(',', false);
netBindComponent->SerializeEntityCorrection(serializer);
return serializer.GetString();
}
void LocalPredictionPlayerInputComponent::LocalPredictionPlayerInputComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@@ -27,30 +59,544 @@ namespace Multiplayer
LocalPredictionPlayerInputComponentBase::Reflect(context);
}
void LocalPredictionPlayerInputComponent::OnInit()
{
;
}
void LocalPredictionPlayerInputComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
LocalPredictionPlayerInputComponentController::LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent)
: LocalPredictionPlayerInputComponentControllerBase(parent)
, m_autonomousUpdateEvent([this]() { UpdateAutonomous(m_autonomousUpdateEvent.TimeInQueueMs()); }, AZ::Name("AutonomousUpdate Event"))
, m_updateBankedTimeEvent([this]() { UpdateBankedTime(m_updateBankedTimeEvent.TimeInQueueMs()); }, AZ::Name("BankTimeUpdate Event"))
, m_migrateStartHandler([this](ClientInputId migratedInputId) { OnMigrateStart(migratedInputId); })
, m_migrateEndHandler([this]() { OnMigrateEnd(); })
{
if (GetNetEntityRole() == NetEntityRole::Autonomous)
{
m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
parent.GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler);
parent.GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler);
}
}
void LocalPredictionPlayerInputComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
if (entityIsMigrating == EntityIsMigrating::True)
{
m_allowMigrateClientInput = true;
m_serverMigrateFrameId = AZ::Interface<INetworkTime>::Get()->GetHostFrameId();
}
}
void LocalPredictionPlayerInputComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
;
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInput
(
[[maybe_unused]] const Multiplayer::NetworkInputVector& inputArray,
[[maybe_unused]] const uint32_t& stateHash,
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputArray& inputArray,
const AZ::HashValue32& stateHash,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& clientState
)
{
;
// After receiving the first input from the client, start the update event to check for slow hacking
if (!m_updateBankedTimeEvent.IsScheduled())
{
m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true);
}
if (invokingConnection == nullptr)
{
// Discard any input messages that were locally dispatched or sent by disconnected clients
return;
}
const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs();
const double clientInputRateSec = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
m_lastInputReceivedTimeMs = currentTimeMs;
// Keep track of last inputs received, also allows us to update frame ids
m_lastInputReceived = inputArray;
// Figure out which index from the input array we want
// we start at the oldest input that has not been processed
int32_t inputArrayIndex = -1;
for (int32_t i = NetworkInputArray::MaxElements - 1; i >= 0; --i)
{
// Find an input that is newer than the last one we processed
if (m_lastInputReceived[i].GetClientInputId() > GetLastInputId())
{
inputArrayIndex = i;
break;
}
}
if (inputArrayIndex < 0)
{
AZLOG
(
NET_Prediction,
"Discarding old or out of order move input (current: %u, received %u)",
aznumeric_cast<uint32_t>(GetLastInputId()),
aznumeric_cast<uint32_t>(m_lastInputReceived[0].GetClientInputId())
);
return;
}
bool lostInput = false;
if (GetLastInputId() < inputArray.GetPreviousInputId())
{
// last move id processed is older than the previous input id, we missed some input packets
lostInput = true;
}
SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration
while (inputArrayIndex >= 0)
{
NetworkInput& input = m_lastInputReceived[inputArrayIndex];
// Anticheat, if we're receiving too many inputs, and fall outside our variable latency input window
// Discard move input events, client may be speed hacking
if (m_clientBankedTime < sv_MaxBankTimeWindowSec)
{
m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary
{
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, static_cast<float>(clientInputRateSec));
}
if (lostInput)
{
AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
else
{
AZLOG(NET_Prediction, "Processed InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
}
else
{
AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast<uint32_t>(input.GetClientInputId()));
}
--inputArrayIndex;
}
if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs))
{
m_lastCorrectionSentTimeMs = currentTimeMs;
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash();
if (stateHash != localAuthorityHash)
{
// Produce correction for client
AzNetworking::PacketEncodingBuffer correction;
correction.Resize(correction.GetCapacity());
AzNetworking::NetworkInputSerializer serializer(correction.GetBuffer(), correction.GetCapacity());
// only deserialize if we have data (for client/server profile/debug mismatches)
if (correction.GetSize() > 0)
{
GetNetBindComponent()->SerializeEntityCorrection(serializer);
}
correction.Resize(serializer.GetSize());
// Send correction
SendClientInputCorrection(GetLastInputId(), correction);
#ifdef _DEBUG
// In debug, show which states caused the correction
AZStd::string clientStateString;
AZStd::string serverStateString;
{
// Write in client state
AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer);
// Read out state values
AzNetworking::StringifySerializer clientValues;
GetNetBindComponent()->SerializeEntityCorrection(clientValues);
// Restore server state
AzNetworking::NetworkOutputSerializer serverStateSerializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serverStateSerializer);
// Read out state values
AzNetworking::StringifySerializer serverValues;
GetNetBindComponent()->SerializeEntityCorrection(serverValues);
AZStd::map<AZStd::string, AZStd::pair<AZStd::string, AZStd::string>> mapComparison;
// put the server value in the first part of the pair
for (const auto& pair : serverValues.GetValueMap())
{
mapComparison[pair.first].first = pair.second;
}
// put the client value in the second part of the pair
for (const auto& pair : clientValues.GetValueMap())
{
mapComparison[pair.first].second = pair.second;
}
bool firstIt = true;
for (const auto& mapPair : mapComparison)
{
if (mapPair.second.first != mapPair.second.second)
{
if (!firstIt)
{
clientStateString += ",";
serverStateString += ",";
}
firstIt = false;
AZStd::string clientValue = mapPair.second.second.empty() ? "<no value>" : mapPair.second.second;
AZStd::string serverValue = mapPair.second.first.empty() ? "<no value>" : mapPair.second.first;
clientStateString += mapPair.first + "=" + clientValue;
serverStateString += mapPair.first + "=" + serverValue;
}
}
}
#else
const AZStd::string clientStateString = "available in debug only";
const AZStd::string serverStateString = "available in debug only";
#endif
AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str());
}
}
}
void LocalPredictionPlayerInputComponentController::HandleSendMigrateClientInput
(
[[maybe_unused]] const Multiplayer::MigrateNetworkInputVector& inputArray
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputMigrationVector& inputArray
)
{
;
if (!m_allowMigrateClientInput)
{
AZLOG_ERROR("Client attempting to SendMigrateClientInput message when server was not expecting it. This may be an attempt to cheat");
return;
}
// We only allow the client to send this message exactly once, when the component has been migrated
// Any further processing of these messages from the client would be exploitable
m_allowMigrateClientInput = false;
if (invokingConnection == nullptr)
{
// Discard any input migration messages that were locally dispatched or sent by disconnected clients
return;
}
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
// Copy array so we can modify input ids
NetworkInputMigrationVector inputArrayCopy = inputArray;
for (uint32_t i = 0; i < inputArrayCopy.GetSize(); ++i)
{
NetworkInput& input = inputArrayCopy[i];
++ModifyLastInputId();
input.SetClientInputId(GetLastInputId());
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Migrated InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Don't bother checking for corrections here, the next regular input will trigger any corrections if necessary
// Also don't bother with any cheat detection here, because the input array is limited in size and at most and can only be sent once
// So this highly constrains anything a malicious client can do
}
}
void LocalPredictionPlayerInputComponentController::HandleSendClientInputCorrection
(
[[maybe_unused]] const Multiplayer::ClientInputId& inputId,
[[maybe_unused]] const AzNetworking::PacketEncodingBuffer& correction
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
)
{
;
AZ_Assert(inputId <= m_clientInputId, "Invalid correction frame id, correction is for a move the client has not yet submitted to the server");
if (inputId > m_clientInputId)
{
AZLOG_ERROR("Discarding correction for non-existent move, correction represents a move we haven't sent to the server yet");
return;
}
if (inputId <= m_lastCorrectionInputId)
{
AZLOG(NET_Prediction, "Discarding old correction for client frame %u", aznumeric_cast<uint32_t>(inputId));
return;
}
m_lastCorrectionInputId = inputId;
// Apply the correction
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), correction.GetSize());
GetNetBindComponent()->SerializeEntityCorrection(serializer);
m_correctionEvent.Signal();
AZLOG
(
NET_Prediction,
"Corrected InputId=%d - o=[%s]",
aznumeric_cast<int32_t>(m_lastCorrectionInputId),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
const uint32_t inputHistorySize = m_inputHistory.Size();
const uint32_t historicalDelta = aznumeric_cast<uint32_t>(m_clientInputId - inputId); // Do not replay the move we just corrected, that was already processed by the server
// If this correction is for a move outside our input history window, just start replaying from the oldest move we have available
const uint32_t startReplayIndex = (inputHistorySize > historicalDelta) ? (inputHistorySize - historicalDelta) : 0;
// Flag that we are replaying inputs
struct ScopedReplayingInput
{
ScopedReplayingInput(LocalPredictionPlayerInputComponentController* instance)
: m_instance(instance)
{
m_instance->m_replayingInput = true;
}
~ScopedReplayingInput()
{
m_instance->m_replayingInput = false;
}
LocalPredictionPlayerInputComponentController* m_instance;
};
ScopedReplayingInput markReplayingInput(this);
const float clientInputRateSec = static_cast<float>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
for (uint32_t replayIndex = startReplayIndex; replayIndex < inputHistorySize; ++replayIndex)
{
// Reprocess the input for this frame
NetworkInput& input = m_inputHistory[replayIndex];
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId());
GetNetBindComponent()->ProcessInput(input, clientInputRateSec);
AZLOG
(
NET_Prediction,
"Replayed InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
}
bool LocalPredictionPlayerInputComponentController::IsReplayingInput() const
{
return m_replayingInput;
}
bool LocalPredictionPlayerInputComponentController::IsMigrating() const
{
return m_lastMigratedInputId != ClientInputId{ 0 };
}
ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const
{
return m_clientInputId;
}
HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const
{
// If the client has sent us an invalid server frame id
// this is because they are in the process of migrating from one server to another
// In this situation, use whatever the server frame id was when this component was migrated
// This will match the closest state to what the client sees
return (input.GetHostFrameId() == InvalidHostFrameId) ? m_serverMigrateFrameId : input.GetHostFrameId();
}
void LocalPredictionPlayerInputComponentController::CorrectionEventAddHandle(CorrectionEvent::Handler& handler)
{
handler.Connect(m_correctionEvent);
}
void LocalPredictionPlayerInputComponentController::OnMigrateStart(ClientInputId migratedInputId)
{
m_lastMigratedInputId = migratedInputId;
}
void LocalPredictionPlayerInputComponentController::OnMigrateEnd()
{
NetworkInputMigrationVector inputArray;
// Roll up all inputs that the new server doesn't have and send them now
for (AZStd::size_t i = 0; i < m_inputHistory.Size(); ++i)
{
NetworkInput& input = m_inputHistory[i];
// New server already has these inputs
if (input.GetClientInputId() <= m_lastMigratedInputId)
{
continue;
}
// Clear out the old server frame id
// We don't know what server frame ids to use for the new server yet, but the new server will figure out how to deal with this
input.SetHostFrameId(InvalidHostFrameId);
// New server doesn't have these inputs
if (!inputArray.PushBack(input))
{
break; // Reached capacity
}
}
// Send these inputs to the server
SendMigrateClientInput(inputArray);
// Done migrating
m_lastMigratedInputId = ClientInputId{ 0 };
}
void LocalPredictionPlayerInputComponentController::UpdateAutonomous(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
#ifndef _RELEASE
m_moveAccumulator += deltaTime * cl_DebugHackTimeMultiplier;
#else
m_moveAccumulator += deltaTime;
#endif
const uint32_t maxClientInputs = inputRate > 0.0 ? static_cast<uint32_t>(maxRewindHistory / inputRate) : 0;
INetworkTime* networkTime = AZ::Interface<INetworkTime>::Get();
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
while (m_moveAccumulator >= inputRate)
{
m_moveAccumulator -= inputRate;
++m_clientInputId;
NetworkInputArray inputArray(GetEntityHandle());
NetworkInput& input = inputArray[0];
input.SetClientInputId(m_clientInputId);
input.SetHostFrameId(networkTime->GetHostFrameId());
input.SetHostTimeMs(multiplayer->GetCurrentHostTimeMs());
// Allow components to form the input for this frame
GetNetBindComponent()->CreateInput(input, inputRate);
// Process the input for this frame
GetNetBindComponent()->ProcessInput(input, inputRate);
AZLOG
(
NET_Prediction,
"Processed InutId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(m_clientInputId),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
// Generate a hash based on the current client predicted states
AzNetworking::HashSerializer hashSerializer;
GetNetBindComponent()->SerializeEntityCorrection(hashSerializer);
// In debug, send the entire client output state to the server to make it easier to debug desync issues
AzNetworking::PacketEncodingBuffer processInputResult;
#ifdef _DEBUG
AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity());
GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer);
processInputResult.Resize(processInputResultSerializer.GetSize());
#endif
// Save this input and discard move history outside our client rewind window
m_inputHistory.PushBack(input);
while (m_inputHistory.Size() > maxClientInputs)
{
m_inputHistory.PopFront();
}
const size_t inputHistorySize = m_inputHistory.Size();
// Form the rest of the input array using the n most recent elements in the history buffer
// NOTE: inputArray[0] has already been initialized hence start at i = 1
for (uint32_t i = 1; i < NetworkInputArray::MaxElements; ++i)
{
if (i < inputHistorySize)
{
inputArray[i] = m_inputHistory[inputHistorySize - 1 - i];
}
else // History is too small?
{
// Plug in the most recent input
inputArray[i] = input;
}
}
// Send the input to server (only when we are not migrating)
if (!IsMigrating())
{
SendClientInput(inputArray, hashSerializer.GetHash(), processInputResult);
}
}
}
void LocalPredictionPlayerInputComponentController::UpdateBankedTime(AZ::TimeMs deltaTimeMs)
{
const double deltaTime = static_cast<double>(deltaTimeMs) / 1000.0;
const double inputRate = static_cast<double>(static_cast<AZ::TimeMs>(cl_InputRateMs)) / 1000.0;
const double maxRewindHistory = static_cast<double>(static_cast<AZ::TimeMs>(cl_MaxRewindHistoryMs)) / 1000.0;
// Update banked time accumulator
m_clientBankedTime -= deltaTime;
// Forcibly tick any clients who are too far behind our variable latency window
// Client may be slow hacking
if (m_clientBankedTime < -sv_MaxBankTimeWindowSec)
{
m_clientBankedTime = -sv_MaxBankTimeWindowSec; // clamp to boundary
NetworkInput& input = m_lastInputReceived[0];
{
ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), AzNetworking::InvalidConnectionId);
GetNetBindComponent()->ProcessInput(input, inputRate);
}
AZLOG
(
NET_Prediction,
"Forced InputId=%d - i=[%s] o=[%s]",
aznumeric_cast<int32_t>(input.GetClientInputId()),
GetInputString(input).c_str(),
GetCorrectionDataString(GetNetBindComponent()).c_str()
);
}
// Decay our bank time window, in case the remote endpoint has suffered a more persistent shift in latency, this should cause the client to eventually recover
m_clientBankedTime = m_clientBankedTime * (1.0 - sv_BankTimeDecay);
}
}
@@ -13,9 +13,12 @@
#pragma once
#include <Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.h>
#include <Source/Components/NetBindComponent.h>
namespace Multiplayer
{
using CorrectionEvent = AZ::Event<>;
class LocalPredictionPlayerInputComponent
: public LocalPredictionPlayerInputComponentBase
{
@@ -24,24 +27,87 @@ namespace Multiplayer
static void Reflect([[maybe_unused]] AZ::ReflectContext* context);
void OnInit() override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnInit() override;
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
};
class LocalPredictionPlayerInputComponentController
: public LocalPredictionPlayerInputComponentControllerBase
{
public:
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent) : LocalPredictionPlayerInputComponentControllerBase(parent) {}
LocalPredictionPlayerInputComponentController(LocalPredictionPlayerInputComponent& parent);
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {}
void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override;
void HandleSendClientInput(const Multiplayer::NetworkInputVector& inputArray, const uint32_t& stateHash, const AzNetworking::PacketEncodingBuffer& clientState) override;
void HandleSendMigrateClientInput(const Multiplayer::MigrateNetworkInputVector& inputArray) override;
void HandleSendClientInputCorrection(const Multiplayer::ClientInputId& inputId, const AzNetworking::PacketEncodingBuffer& correction) override;
void HandleSendClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputArray& inputArray,
const AZ::HashValue32& stateHash,
const AzNetworking::PacketEncodingBuffer& clientState
) override;
void HandleSendMigrateClientInput
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::NetworkInputMigrationVector& inputArray
) override;
void HandleSendClientInputCorrection
(
AzNetworking::IConnection* invokingConnection,
const Multiplayer::ClientInputId& inputId,
const AzNetworking::PacketEncodingBuffer& correction
) override;
//! Return true if we're currently replaying inputs after a correction.
//! If this value returns true, effects, audio, and other cosmetic triggers should be suppressed
//! @return true if we're within correction scope and replaying inputs
bool IsReplayingInput() const;
//! Return true if we're currently migrating from one host to another.
//! @return boolean true if we're currently migrating from one host to another
bool IsMigrating() const;
ClientInputId GetLastInputId() const;
HostFrameId GetInputFrameId(const NetworkInput& input) const;
void CorrectionEventAddHandle(CorrectionEvent::Handler& handler);
private:
void OnMigrateStart(ClientInputId migratedInputId);
void OnMigrateEnd();
void UpdateAutonomous(AZ::TimeMs deltaTimeMs);
void UpdateBankedTime(AZ::TimeMs deltaTimeMs);
// Implicitly sorted player input history, back() is the input that corresponds to the latest client input Id
NetworkInputHistory m_inputHistory;
// Anti-cheat accumulator for clients who purposely mess with their clock rate
NetworkInputArray m_lastInputReceived;
AZ::ScheduledEvent m_autonomousUpdateEvent; // Drives autonomous input collection
AZ::ScheduledEvent m_updateBankedTimeEvent; // Drives authority bank time updates
CorrectionEvent m_correctionEvent;
EntityMigrationStartEvent::Handler m_migrateStartHandler;
EntityMigrationEndEvent::Handler m_migrateEndHandler;
double m_moveAccumulator = 0.0;
double m_clientBankedTime = 0.0;
AZ::TimeMs m_lastInputReceivedTimeMs = AZ::TimeMs{ 0 };
AZ::TimeMs m_lastCorrectionSentTimeMs = AZ::TimeMs{ 0 };
ClientInputId m_clientInputId = ClientInputId{ 0 };
ClientInputId m_lastCorrectionInputId = ClientInputId{ 0 };
ClientInputId m_lastMigratedInputId = ClientInputId{ 0 }; // Used to resend inputs that were queued during a migration event
HostFrameId m_serverMigrateFrameId = InvalidHostFrameId;
bool m_replayingInput = false; // True if we're replaying inputs under a correction event (use this to suppress effects or audio)
bool m_allowMigrateClientInput = false; // True if this component was migrated, we will allow the client to send us migrated inputs (one time only)
};
}
@@ -15,7 +15,7 @@
#include <AzCore/Component/Component.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/DataStructures/FixedSizeBitsetView.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/MultiplayerTypes.h>
#include <Include/IMultiplayer.h>
@@ -69,7 +69,7 @@ namespace Multiplayer
virtual NetComponentId GetNetComponentId() const = 0;
virtual bool HandleRpcMessage(NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0;
virtual bool SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) = 0;
virtual void NotifyStateDeltaChanges(ReplicationRecord& replicationRecord) = 0;
virtual bool HasController() const = 0;
@@ -12,7 +12,7 @@
#pragma once
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Math/Aabb.h>
namespace Multiplayer
@@ -13,10 +13,10 @@
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerComponent.h>
#include <Source/Components/MultiplayerController.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkInput/NetworkInput.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
@@ -192,12 +192,12 @@ namespace Multiplayer
return bounds;
}
bool NetBindComponent::HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
bool NetBindComponent::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message)
{
auto findIt = m_multiplayerComponentMap.find(message.GetComponentId());
if (findIt != m_multiplayerComponentMap.end())
{
return findIt->second->HandleRpcMessage(remoteRole, message);
return findIt->second->HandleRpcMessage(invokingConnection, remoteRole, message);
}
return false;
}
@@ -274,9 +274,19 @@ namespace Multiplayer
m_localNotificationRecord.Clear();
}
void NetBindComponent::NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId)
void NetBindComponent::NotifyMigrationStart(ClientInputId migratedInputId)
{
m_entityMigrationEvent.Signal(m_netEntityHandle, remoteHostId, connectionId);
m_entityMigrationStartEvent.Signal(migratedInputId);
}
void NetBindComponent::NotifyMigrationEnd()
{
m_entityMigrationEndEvent.Signal();
}
void NetBindComponent::NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId)
{
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId);
}
void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler)
@@ -289,9 +299,19 @@ namespace Multiplayer
eventHandler.Connect(m_dirtiedEvent);
}
void NetBindComponent::AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler)
void NetBindComponent::AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEvent);
eventHandler.Connect(m_entityMigrationStartEvent);
}
void NetBindComponent::AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityMigrationEndEvent);
}
void NetBindComponent::AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler)
{
eventHandler.Connect(m_entityServerMigrationEvent);
}
bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer)
@@ -21,8 +21,9 @@
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <Source/NetworkEntity/EntityReplication/ReplicationRecord.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Include/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/MultiplayerTypes.h>
#include <AzCore/EBus/Event.h>
@@ -34,7 +35,9 @@ namespace Multiplayer
using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using EntityDirtiedEvent = AZ::Event<>;
using EntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
using EntityMigrationStartEvent = AZ::Event<ClientInputId>;
using EntityMigrationEndEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, HostId, AzNetworking::ConnectionId>;
//! @class NetBindComponent
//! @brief Component that provides net-binding to a networked entity.
@@ -72,7 +75,7 @@ namespace Multiplayer
void ProcessInput(NetworkInput& networkInput, float deltaTime);
AZ::Aabb GetRewindBoundsForInput(const NetworkInput& networkInput, float deltaTime) const;
bool HandleRpcMessage(NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole remoteRole, NetworkEntityRpcMessage& message);
bool HandlePropertyChangeMessage(AzNetworking::ISerializer& serializer, bool notifyChanges = true);
RpcSendEvent& GetSendAuthorityToClientRpcEvent();
@@ -84,11 +87,15 @@ namespace Multiplayer
void MarkDirty();
void NotifyLocalChanges();
void NotifyMigration(HostId remoteHostId, AzNetworking::ConnectionId connectionId);
void NotifyMigrationStart(ClientInputId migratedInputId);
void NotifyMigrationEnd();
void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId);
void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler);
void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler);
void AddEntityMigrationEventHandler(EntityMigrationEvent::Handler& eventHandler);
void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler);
void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler);
void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler);
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
@@ -119,7 +126,7 @@ namespace Multiplayer
ReplicationRecord m_currentRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_totalRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_predictableRecord = NetEntityRole::InvalidRole;
ReplicationRecord m_predictableRecord = NetEntityRole::Autonomous;
ReplicationRecord m_localNotificationRecord = NetEntityRole::InvalidRole;
PrefabEntityId m_prefabEntityId;
AZStd::unordered_map<NetComponentId, MultiplayerComponent*> m_multiplayerComponentMap;
@@ -133,7 +140,9 @@ namespace Multiplayer
EntityStopEvent m_entityStopEvent;
EntityDirtiedEvent m_dirtiedEvent;
EntityMigrationEvent m_entityMigrationEvent;
EntityMigrationStartEvent m_entityMigrationStartEvent;
EntityMigrationEndEvent m_entityMigrationEndEvent;
EntityServerMigrationEvent m_entityServerMigrationEvent;
AZ::Event<> m_onRemove;
RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle;
AZ::Event<>::Handler m_handleMarkedDirty;
@@ -50,8 +50,9 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ClientToServerConnectionData::Update([[maybe_unused]] AZ::TimeMs serverGameTimeMs)
void ClientToServerConnectionData::Update(AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
m_entityReplicationManager.SendUpdates(hostTimeMs);
}
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -32,7 +33,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -35,7 +35,7 @@ namespace Multiplayer
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityMigrationEventHandler(m_controlledEntityMigrationHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount);
@@ -63,7 +63,7 @@ namespace Multiplayer
return m_entityReplicationManager;
}
void ServerToClientConnectionData::Update(AZ::TimeMs serverGameTimeMs)
void ServerToClientConnectionData::Update(AZ::TimeMs hostTimeMs)
{
m_entityReplicationManager.ActivatePendingEntities();
@@ -73,7 +73,7 @@ namespace Multiplayer
// potentially false if we just migrated the player, if that is the case, don't send any more updates
if (netBindComponent != nullptr && (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority))
{
m_entityReplicationManager.SendUpdates(serverGameTimeMs);
m_entityReplicationManager.SendUpdates(hostTimeMs);
}
}
}
@@ -12,7 +12,8 @@
#pragma once
#include <Source/ConnectionData/IConnectionData.h>
#include <Include/IConnectionData.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
namespace Multiplayer
{
@@ -33,7 +34,7 @@ namespace Multiplayer
ConnectionDataType GetConnectionDataType() const override;
AzNetworking::IConnection* GetConnection() const override;
EntityReplicationManager& GetReplicationManager() override;
void Update(AZ::TimeMs serverGameTimeMs) override;
void Update(AZ::TimeMs hostTimeMs) override;
bool CanSendUpdates() const override;
void SetCanSendUpdates(bool canSendUpdates) override;
//! @}
@@ -49,7 +50,7 @@ namespace Multiplayer
EntityReplicationManager m_entityReplicationManager;
NetworkEntityHandle m_controlledEntity;
EntityStopEvent::Handler m_controlledEntityRemovedHandler;
EntityMigrationEvent::Handler m_controlledEntityMigrationHandler;
EntityServerMigrationEvent::Handler m_controlledEntityMigrationHandler;
AzNetworking::IConnection* m_connection = nullptr;
bool m_canSendUpdates = false;
};
@@ -235,7 +235,7 @@ namespace Multiplayer
if (m_displayMultiplayerStats)
{
if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_HorizontalScrollbar))
if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_None))
{
IMultiplayer* multiplayer = AZ::Interface<IMultiplayer>::Get();
const Multiplayer::MultiplayerStats& stats = multiplayer->GetStats();
@@ -254,7 +254,7 @@ namespace Multiplayer
if (ImGui::BeginTable("", 5, flags))
{
// The first column will use the default _WidthStretch when ScrollX is Off and _WidthFixed when ScrollX is On
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_NoHide, TEXT_BASE_WIDTH * 36.0f);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Total Calls", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
ImGui::TableSetupColumn("Total Bytes", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
ImGui::TableSetupColumn("Calls/Sec", ImGuiTableColumnFlags_WidthFixed, TEXT_BASE_WIDTH * 12.0f);
@@ -12,7 +12,7 @@
#pragma once
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
namespace Multiplayer
{
@@ -127,7 +127,8 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ::TimeMs serverGameTimeMs = AZ::GetElapsedTimeMs();
AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs();
// Handle deferred local rpc messages that were generated during the updates
m_networkEntityManager.DispatchLocalDeferredRpcMessages();
@@ -137,18 +138,19 @@ namespace Multiplayer
m_networkEntityManager.NotifyEntitiesDirtied();
MultiplayerStats& stats = GetStats();
stats.TickStats(deltaTimeMs);
stats.m_entityCount = GetNetworkEntityManager()->GetEntityCount();
stats.m_serverConnectionCount = 0;
stats.m_clientConnectionCount = 0;
// Send out the game state update to all connections
{
auto sendNetworkUpdates = [serverGameTimeMs, &stats](IConnection& connection)
auto sendNetworkUpdates = [hostTimeMs, &stats](IConnection& connection)
{
if (connection.GetUserData() != nullptr)
{
IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection.GetUserData());
connectionData->Update(serverGameTimeMs);
connectionData->Update(hostTimeMs);
if (connectionData->GetConnectionDataType() == ConnectionDataType::ServerToClient)
{
stats.m_clientConnectionCount++;
@@ -481,7 +483,7 @@ namespace Multiplayer
}
}
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType()
MultiplayerAgentType MultiplayerSystemComponent::GetAgentType() const
{
return m_agentType;
}
@@ -528,6 +530,18 @@ namespace Multiplayer
});
}
AZ::TimeMs MultiplayerSystemComponent::GetCurrentHostTimeMs() const
{
if (GetAgentType() == MultiplayerAgentType::Client)
{
return m_lastReplicatedHostTimeMs;
}
else // ClientServer or DedicatedServer
{
return m_networkTime.GetHostTimeMs();
}
}
const char* MultiplayerSystemComponent::GetComponentGemName(NetComponentId netComponentId) const
{
return GetMultiplayerComponentRegistry()->GetComponentGemName(netComponentId);
@@ -84,12 +84,13 @@ namespace Multiplayer
//! IMultiplayer interface
//! @{
MultiplayerAgentType GetAgentType() override;
MultiplayerAgentType GetAgentType() const override;
void InitializeMultiplayer(MultiplayerAgentType state) override;
void AddConnectionAcquiredHandler(ConnectionAcquiredEvent::Handler& handler) override;
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
void SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override;
const char* GetComponentGemName(NetComponentId netComponentId) const override;
const char* GetComponentName(NetComponentId netComponentId) const override;
const char* GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const override;
@@ -119,5 +120,8 @@ namespace Multiplayer
SessionInitEvent m_initEvent;
SessionShutdownEvent m_shutdownEvent;
ConnectionAcquiredEvent m_connAcquiredEvent;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId;
};
}
@@ -14,14 +14,14 @@
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityUpdateMessage.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Include/IEntityDomain.h>
#include <Include/IMultiplayer.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
@@ -93,10 +93,10 @@ namespace Multiplayer
}
}
void EntityReplicationManager::SendUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs)
{
m_frameTimeMs = AZ::GetElapsedTimeMs();
SendEntityUpdates(serverGameTimeMs);
SendEntityUpdates(hostTimeMs);
SendEntityRpcs(m_deferredRpcMessagesReliable, true);
SendEntityRpcs(m_deferredRpcMessagesUnreliable, false);
@@ -118,7 +118,7 @@ namespace Multiplayer
void EntityReplicationManager::SendEntityUpdatesPacketHelper
(
AZ::TimeMs serverGameTimeMs,
AZ::TimeMs hostTimeMs,
EntityReplicatorList& toSendList,
uint32_t maxPayloadSize,
AzNetworking::IConnection& connection
@@ -127,7 +127,8 @@ namespace Multiplayer
uint32_t pendingPacketSize = 0;
EntityReplicatorList replicatorUpdatedList;
MultiplayerPackets::EntityUpdates entityUpdatePacket;
entityUpdatePacket.SetHostTimeMs(serverGameTimeMs);
entityUpdatePacket.SetHostTimeMs(hostTimeMs);
entityUpdatePacket.SetHostFrameId(InvalidHostFrameId);
// Serialize everything
while (!toSendList.empty())
{
@@ -249,7 +250,7 @@ namespace Multiplayer
return toSendList;
}
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs serverGameTimeMs)
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs)
{
EntityReplicatorList toSendList = GenerateEntityUpdateList();
@@ -264,7 +265,7 @@ namespace Multiplayer
// While our to send list is not empty, build up another packet to send
do
{
SendEntityUpdatesPacketHelper(serverGameTimeMs, toSendList, m_maxPayloadSize, m_connection);
SendEntityUpdatesPacketHelper(hostTimeMs, toSendList, m_maxPayloadSize, m_connection);
} while (!toSendList.empty());
}
@@ -747,7 +748,7 @@ namespace Multiplayer
bool EntityReplicationManager::HandleEntityUpdateMessage
(
[[maybe_unused]] AzNetworking::IConnection* connection,
[[maybe_unused]] AzNetworking::IConnection* invokingConnection,
const AzNetworking::IPacketHeader& packetHeader,
const NetworkEntityUpdateMessage& updateMessage
)
@@ -803,7 +804,7 @@ namespace Multiplayer
return handled;
}
bool EntityReplicationManager::HandleEntityRpcMessage([[maybe_unused]] AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message)
bool EntityReplicationManager::HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message)
{
EntityReplicator* entityReplicator = GetEntityReplicator(message.GetEntityId());
const bool isReplicatorValid = (entityReplicator != nullptr) && !entityReplicator->IsMarkedForRemoval();
@@ -815,7 +816,7 @@ namespace Multiplayer
}
else
{
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(invokingConnection, message);
}
}
@@ -833,8 +834,7 @@ namespace Multiplayer
);
return false;
}
return entityReplicator->HandleRpcMessage(message);
return entityReplicator->HandleRpcMessage(nullptr, message);
}
AZ::TimeMs EntityReplicationManager::GetResendTimeoutTimeMs() const
@@ -877,7 +877,6 @@ namespace Multiplayer
AzNetworking::TimeoutResult EntityReplicationManager::OrphanedEntityRpcs::HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item)
{
NetEntityId timedOutEntityId = aznumeric_cast<NetEntityId>(item.m_userData);
auto entityRpcsIter = m_entityRpcMap.find(timedOutEntityId);
if (entityRpcsIter != m_entityRpcMap.end())
{
@@ -887,7 +886,6 @@ namespace Multiplayer
}
m_entityRpcMap.erase(entityRpcsIter);
}
return AzNetworking::TimeoutResult::Delete;
}
@@ -1089,7 +1087,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{
netBindComponent->NotifyMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
}
bool didSucceed = true;
@@ -1127,7 +1125,7 @@ namespace Multiplayer
}
}
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message)
bool EntityReplicationManager::HandleMessage([[maybe_unused]] AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message)
{
EntityReplicator* replicator = GetEntityReplicator(message.GetEntityId());
{
@@ -13,11 +13,11 @@
#pragma once
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/ReplicationWindows/IReplicationWindow.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <Include/IReplicationWindow.h>
#include <Include/IEntityDomain.h>
#include <Include/NetworkEntityHandle.h>
#include <AzNetworking/DataStructures/TimeoutQueue.h>
#include <AzNetworking/PacketLayer/IPacketHeader.h>
#include <AzCore/std/containers/map.h>
@@ -59,7 +59,7 @@ namespace Multiplayer
HostId GetRemoteHostId() const;
void ActivatePendingEntities();
void SendUpdates(AZ::TimeMs serverGameTimeMs);
void SendUpdates(AZ::TimeMs hostTimeMs);
void Clear(bool forMigration);
bool SetEntityRebasing(NetworkEntityHandle& entityHandle);
@@ -82,10 +82,10 @@ namespace Multiplayer
void AddAutonomousEntityReplicatorCreatedHandle(AZ::Event<NetEntityId>::Handler& handler);
bool HandleMessage(AzNetworking::IConnection* connection, MultiplayerPackets::EntityMigration& message);
bool HandleMessage(AzNetworking::IConnection* invokingConnection, MultiplayerPackets::EntityMigration& message);
bool HandleEntityDeleteMessage(EntityReplicator* entityReplicator, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* connection, NetworkEntityRpcMessage& message);
bool HandleEntityUpdateMessage(AzNetworking::IConnection* invokingConnection, const AzNetworking::IPacketHeader& packetHeader, const NetworkEntityUpdateMessage& updateMessage);
bool HandleEntityRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& message);
AZ::TimeMs GetResendTimeoutTimeMs() const;
@@ -118,9 +118,9 @@ namespace Multiplayer
using EntityReplicatorList = AZStd::deque<EntityReplicator*>;
EntityReplicatorList GenerateEntityUpdateList();
void SendEntityUpdatesPacketHelper(AZ::TimeMs serverGameTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdatesPacketHelper(AZ::TimeMs hostTimeMs, EntityReplicatorList& toSendList, uint32_t maxPayloadSize, AzNetworking::IConnection& connection);
void SendEntityUpdates(AZ::TimeMs serverGameTimeMs);
void SendEntityUpdates(AZ::TimeMs hostTimeMs);
void SendEntityRpcs(RpcMessages& deferredRpcs, bool reliable);
void MigrateEntityInternal(NetEntityId entityId);
@@ -155,31 +155,27 @@ namespace Multiplayer
OrphanedEntityRpcs(EntityReplicationManager& replicationManager);
void Update();
bool DispatchOrphanedRpcs(EntityReplicator& entityReplicator);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityPrcMessage);
void AddOrphanedRpc(NetEntityId entityId, NetworkEntityRpcMessage& entityRpcMessage);
AZStd::size_t Size() const { return m_entityRpcMap.size(); }
private:
AzNetworking::TimeoutResult HandleTimeout(AzNetworking::TimeoutQueue::TimeoutItem& item) override;
struct OrphanedRpcs
{
OrphanedRpcs() = default;
OrphanedRpcs(OrphanedRpcs&& rhs)
{
m_rpcMessages.swap(rhs.m_rpcMessages);
m_timeoutId = rhs.m_timeoutId;
rhs.m_timeoutId = AzNetworking::TimeoutId{ 0 };
m_rpcMessages.swap(rhs.m_rpcMessages);
}
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
RpcMessages m_rpcMessages;
AzNetworking::TimeoutId m_timeoutId = AzNetworking::TimeoutId{ 0 };
};
typedef AZStd::unordered_map<NetEntityId, OrphanedRpcs> EntityRpcMap;
EntityRpcMap m_entityRpcMap;
AzNetworking::TimeoutQueue m_timeoutQueue;
EntityReplicationManager& m_replicationManager;
};
OrphanedEntityRpcs m_orphanedEntityRpcs;
EntityReplicatorMap m_entityReplicatorMap;
@@ -628,7 +628,7 @@ namespace Multiplayer
return result;
}
bool EntityReplicator::HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage)
bool EntityReplicator::HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage)
{
// Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics
MultiplayerStats& stats = AZ::Interface<IMultiplayer>::Get()->GetStats();
@@ -676,7 +676,7 @@ namespace Multiplayer
switch (result)
{
case RpcValidationResult::HandleRpc:
return m_netBindComponent->HandleRpcMessage(GetRemoteNetworkRole(), entityRpcMessage);
return m_netBindComponent->HandleRpcMessage(invokingConnection, GetRemoteNetworkRole(), entityRpcMessage);
case RpcValidationResult::DropRpc:
return true;
case RpcValidationResult::DropRpcAndDisconnect:
@@ -703,7 +703,7 @@ namespace Multiplayer
break;
}
AZ_Assert(false, "Unhandled ERpcValidationResult %d", result);
AZ_Assert(false, "Unhandled RpcValidationResult %d", result);
return false;
}
}
@@ -75,8 +75,8 @@ namespace Multiplayer
const PropertyPublisher* GetPropertyPublisher() const;
PropertySubscriber* GetPropertySubscriber();
// Handlers for messages
bool HandleRpcMessage(NetworkEntityRpcMessage& entityRpcMessage);
// Handlers for Rpc messages
bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetworkEntityRpcMessage& entityRpcMessage);
//! AZ::EntityBus overrides
//! @{
@@ -1,41 +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 <Source/NetworkEntity/INetworkEntityManager.h>
namespace Multiplayer
{
//! @class INetworkEntityDomain
//! @brief A class that determines if an entity should belong to a particular EntityManager.
class INetworkEntityDomain
{
public:
using EntitiesNotInDomain = AZStd::unordered_set<NetEntityId>;
virtual ~INetworkEntityDomain() = default;
//! Enable Entity Domain Exit Tracking for entities on the server.
//! @param ownedEntitySet
virtual void ActivateTracking(const INetworkEntityManager::OwnedEntitySet& ownedEntitySet) = 0;
//! Return the set of entities not in this domain.
//! @param outEntitiesNotInDomain
virtual void RetrieveEntitiesNotInDomain(EntitiesNotInDomain& outEntitiesNotInDomain) const = 0;
//! Returns whether or not an entity should be owned by an entity manager.
//! @param entityHandle the handle of the entity to check for inclusion in the domain
//! @return false if this entity should not belong to the entity manger, true if it could be owned by the entity manager
virtual bool IsInDomain(const ConstNetworkEntityHandle& entityHandle) const = 0;
};
}
@@ -11,8 +11,8 @@
*/
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/Components/NetBindComponent.h>
#include <Include/INetworkEntityManager.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -10,7 +10,7 @@
*
*/
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/Components/NetBindComponent.h>
#include <Source/Components/MultiplayerController.h>
@@ -218,7 +218,7 @@ namespace Multiplayer
{
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
AZ_Assert(netBindComponent != nullptr, "Attempting to send an RPC to an entity with no NetBindComponent");
netBindComponent->HandleRpcMessage(NetEntityRole::Server, rpcMessage);
netBindComponent->HandleRpcMessage(nullptr, NetEntityRole::Server, rpcMessage);
}
}
m_localDeferredRpcMessages.clear();
@@ -15,11 +15,11 @@
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/EntityDomains/IEntityDomain.h>
#include <Include/IEntityDomain.h>
#include <Include/INetworkEntityManager.h>
#include <Source/NetworkEntity/NetworkSpawnableLibrary.h>
#include <Source/Components/MultiplayerComponentRegistry.h>
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
@@ -13,7 +13,7 @@
#pragma once
#include <Include/MultiplayerTypes.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Component/Entity.h>
@@ -48,19 +48,34 @@ namespace Multiplayer
return m_inputId;
}
void NetworkInput::SetServerTimeMs(AZ::TimeMs serverTimeMs)
void NetworkInput::SetHostFrameId(HostFrameId hostFrameId)
{
m_serverTimeMs = serverTimeMs;
m_hostFrameId = hostFrameId;
}
AZ::TimeMs NetworkInput::GetServerTimeMs() const
HostFrameId NetworkInput::GetHostFrameId() const
{
return m_serverTimeMs;
return m_hostFrameId;
}
AZ::TimeMs& NetworkInput::ModifyServerTimeMs()
HostFrameId& NetworkInput::ModifyHostFrameId()
{
return m_serverTimeMs;
return m_hostFrameId;
}
void NetworkInput::SetHostTimeMs(AZ::TimeMs hostTimeMs)
{
m_hostTimeMs = hostTimeMs;
}
AZ::TimeMs NetworkInput::GetHostTimeMs() const
{
return m_hostTimeMs;
}
AZ::TimeMs& NetworkInput::ModifyHostTimeMs()
{
return m_hostTimeMs;
}
void NetworkInput::AttachNetBindComponent(NetBindComponent* netBindComponent)
@@ -76,17 +91,19 @@ namespace Multiplayer
bool NetworkInput::Serialize(AzNetworking::ISerializer& serializer)
{
if (!serializer.Serialize(m_inputId, "InputId"))
if (!serializer.Serialize(m_inputId, "InputId")
|| !serializer.Serialize(m_hostTimeMs, "HostTimeMs")
|| !serializer.Serialize(m_hostFrameId, "HostFrameId"))
{
return false;
}
uint8_t componentInputCount = static_cast<uint8_t>(m_componentInputs.size());
uint16_t componentInputCount = static_cast<uint16_t>(m_componentInputs.size());
serializer.Serialize(componentInputCount, "ComponentInputCount");
m_componentInputs.resize(componentInputCount);
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
for (uint8_t i = 0; i < componentInputCount; ++i)
for (uint16_t i = 0; i < componentInputCount; ++i)
{
// We need to do a little extra work here, the delta serializer won't actually write out values if they were the same as the parent.
// We need to make sure we don't lose state that is intrinsic to the underlying type
@@ -148,7 +165,7 @@ namespace Multiplayer
void NetworkInput::CopyInternal(const NetworkInput& rhs)
{
m_inputId = rhs.m_inputId;
m_serverTimeMs = rhs.m_serverTimeMs;
m_hostTimeMs = rhs.m_hostTimeMs;
m_componentInputs.resize(rhs.m_componentInputs.size());
for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i)
{
@@ -12,8 +12,9 @@
#pragma once
#include <Source/NetworkInput/IMultiplayerComponentInput.h>
#include <Source/NetworkEntity/NetworkEntityHandle.h>
#include <Include/IMultiplayerComponentInput.h>
#include <Include/INetworkTime.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/RTTI/TypeSafeIntegral.h>
namespace Multiplayer
@@ -21,8 +22,6 @@ namespace Multiplayer
// Forwards
class NetBindComponent;
AZ_TYPE_SAFE_INTEGRAL(ClientInputId, uint16_t);
//! @class NetworkInput
//! @brief A single networked client input command.
class NetworkInput final
@@ -30,8 +29,8 @@ namespace Multiplayer
public:
//! Intentionally restrict instancing of these objects to associated containers classes only
//! This is a mechanism used to restrict calling autonomous client predicted setter functions to the ProcessInput call chain only
friend class NetworkInputVector;
friend class MigrateNetworkInputVector;
friend class NetworkInputArray;
friend class NetworkInputMigrationVector;
friend class NetworkInputHistory;
friend class NetworkInputChild;
@@ -42,9 +41,13 @@ namespace Multiplayer
ClientInputId GetClientInputId() const;
ClientInputId& ModifyClientInputId();
void SetServerTimeMs(AZ::TimeMs serverTimeMs);
AZ::TimeMs GetServerTimeMs() const;
AZ::TimeMs& ModifyServerTimeMs();
void SetHostFrameId(HostFrameId hostFrameId);
HostFrameId GetHostFrameId() const;
HostFrameId& ModifyHostFrameId();
void SetHostTimeMs(AZ::TimeMs hostTimeMs);
AZ::TimeMs GetHostTimeMs() const;
AZ::TimeMs& ModifyHostTimeMs();
void AttachNetBindComponent(NetBindComponent* netBindComponent);
@@ -72,10 +75,9 @@ namespace Multiplayer
MultiplayerComponentInputVector m_componentInputs;
ClientInputId m_inputId = ClientInputId{ 0 };
AZ::TimeMs m_serverTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_hostFrameId = InvalidHostFrameId;
AZ::TimeMs m_hostTimeMs = AZ::TimeMs{ 0 };
ConstNetworkEntityHandle m_owner;
bool m_wasAttached = false;
};
}
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(Multiplayer::ClientInputId);
@@ -10,21 +10,21 @@
*
*/
#include <Source/NetworkInput/NetworkInputVector.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Source/NetworkInput/NetworkInputArray.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/DeltaSerializer.h>
namespace Multiplayer
{
NetworkInputVector::NetworkInputVector()
NetworkInputArray::NetworkInputArray()
: m_owner()
, m_inputs()
{
;
}
NetworkInputVector::NetworkInputVector(const ConstNetworkEntityHandle& entityHandle)
NetworkInputArray::NetworkInputArray(const ConstNetworkEntityHandle& entityHandle)
: m_owner(entityHandle)
, m_inputs()
{
@@ -38,27 +38,27 @@ namespace Multiplayer
}
}
NetworkInput& NetworkInputVector::operator[](uint32_t index)
NetworkInput& NetworkInputArray::operator[](uint32_t index)
{
return m_inputs[index].m_networkInput;
}
const NetworkInput& NetworkInputVector::operator[](uint32_t index) const
const NetworkInput& NetworkInputArray::operator[](uint32_t index) const
{
return m_inputs[index].m_networkInput;
}
void NetworkInputVector::SetPreviousInputId(ClientInputId previousInputId)
void NetworkInputArray::SetPreviousInputId(ClientInputId previousInputId)
{
m_previousInputId = previousInputId;
}
ClientInputId NetworkInputVector::GetPreviousInputId() const
ClientInputId NetworkInputArray::GetPreviousInputId() const
{
return m_previousInputId;
}
bool NetworkInputVector::Serialize(AzNetworking::ISerializer& serializer)
bool NetworkInputArray::Serialize(AzNetworking::ISerializer& serializer)
{
// Always serialize the full first element
if (!m_inputs[0].m_networkInput.Serialize(serializer))
@@ -105,67 +105,4 @@ namespace Multiplayer
serializer.Serialize(m_previousInputId, "PreviousInputId");
return true;
}
MigrateNetworkInputVector::MigrateNetworkInputVector()
: m_owner()
{
;
}
MigrateNetworkInputVector::MigrateNetworkInputVector(const ConstNetworkEntityHandle& entityHandle)
: m_owner(entityHandle)
{
;
}
uint32_t MigrateNetworkInputVector::GetSize() const
{
return aznumeric_cast<uint32_t>(m_inputs.size());
}
NetworkInput& MigrateNetworkInputVector::operator[](uint32_t index)
{
return m_inputs[index].m_networkInput;
}
const NetworkInput& MigrateNetworkInputVector::operator[](uint32_t index) const
{
return m_inputs[index].m_networkInput;
}
bool MigrateNetworkInputVector::PushBack(const NetworkInput& networkInput)
{
if (m_inputs.size() < m_inputs.capacity())
{
m_inputs.push_back(networkInput);
return true;
}
return false;
}
bool MigrateNetworkInputVector::Serialize(AzNetworking::ISerializer& serializer)
{
NetEntityId ownerId = m_owner.GetNetEntityId();
serializer.Serialize(ownerId, "OwnerId");
uint32_t inputCount = aznumeric_cast<uint32_t>(m_inputs.size());
serializer.Serialize(inputCount, "InputCount");
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
// make sure all the possible NetworkInputs get attached prior to serialization, this double sends the size, but this message is only sent on server migration
m_inputs.resize(inputCount);
m_owner = GetNetworkEntityManager()->GetEntity(ownerId);
NetBindComponent* netBindComponent = m_owner.GetNetBindComponent();
if (netBindComponent)
{
for (uint32_t i = 0; i < m_inputs.size(); ++i)
{
m_inputs[i].m_networkInput.AttachNetBindComponent(netBindComponent);
}
}
}
return serializer.Serialize(m_inputs, "Inputs");
}
}
@@ -0,0 +1,54 @@
/*
* 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 <Source/NetworkInput/NetworkInput.h>
#include <Include/NetworkEntityHandle.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace Multiplayer
{
//! @class NetworkInputArray
//! @brief An array of network inputs. Used to mitigate loss of input packets on the server. Compresses subsequent elements.
class NetworkInputArray final
{
public:
static constexpr uint32_t MaxElements = 8; // Never try to replicate a list larger than this amount
NetworkInputArray();
NetworkInputArray(const ConstNetworkEntityHandle& entityHandle);
~NetworkInputArray() = default;
NetworkInput& operator[](uint32_t index);
const NetworkInput& operator[](uint32_t index) const;
void SetPreviousInputId(ClientInputId previousInputId);
ClientInputId GetPreviousInputId() const;
bool Serialize(AzNetworking::ISerializer& serializer);
private:
struct Wrapper // Strictly a workaround to deal with the private constructor of NetworkInput
{
Wrapper() : m_networkInput() {}
Wrapper(const NetworkInput& networkInput) : m_networkInput(networkInput) {}
NetworkInput m_networkInput;
};
ConstNetworkEntityHandle m_owner;
AZStd::array<Wrapper, MaxElements> m_inputs;
ClientInputId m_previousInputId;
};
}
@@ -11,7 +11,7 @@
*/
#include <Source/NetworkInput/NetworkInputChild.h>
#include <Source/NetworkEntity/INetworkEntityManager.h>
#include <Include/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h>
namespace Multiplayer

Some files were not shown because too many files have changed in this diff Show More