Merge remote-tracking branch 'upstream/development' into Atom/santorac/MaterialPropertyRenameInMaterialComponent

monroegm-disable-blank-issue-2
santorac 4 years ago
commit 0972c4f1a3

@ -8,11 +8,14 @@
## Deploy CDK Applications ## Deploy CDK Applications
1. Go to the AWS IAM console and create an IAM role called o3de-automation-tests which adds your own account as as a trusted entity and uses the "AdministratorAccess" permissions policy. 1. Go to the AWS IAM console and create an IAM role called o3de-automation-tests which adds your own account as as a trusted entity and uses the "AdministratorAccess" permissions policy.
2. Copy {engine_root}\scripts\build\Platform\Windows\deploy_cdk_applications.cmd to your engine root folder. 2. Copy {engine_root}\scripts\build\Platform\Windows\deploy_cdk_applications.cmd to your engine root folder.
3. Open a new Command Prompt window at the engine root and set the following environment variables: 3. Open a new Command Prompt window at the engine root and set the following environment variables:
```
Set O3DE_AWS_PROJECT_NAME=AWSAUTO Set O3DE_AWS_PROJECT_NAME=AWSAUTO
Set O3DE_AWS_DEPLOY_REGION=us-east-1 Set O3DE_AWS_DEPLOY_REGION=us-east-1
Set O3DE_AWS_DEPLOY_ACCOUNT={your_aws_account_id}
Set ASSUME_ROLE_ARN=arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests Set ASSUME_ROLE_ARN=arn:aws:iam::{your_aws_account_id}:role/o3de-automation-tests
Set COMMIT_ID=HEAD Set COMMIT_ID=HEAD
```
4. In the same Command Prompt window, Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd. 4. In the same Command Prompt window, Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd.
## Run Automation Tests ## Run Automation Tests

@ -0,0 +1,97 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Math/Vector3.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzFramework/Physics/Material.h>
namespace Physics
{
//! The QuadMeshType specifies the property of the heightfield quad.
enum class QuadMeshType : uint8_t
{
SubdivideUpperLeftToBottomRight, //!< Subdivide the quad, from upper left to bottom right |\|, into two triangles.
SubdivideBottomLeftToUpperRight, //!< Subdivide the quad, from bottom left to upper right |/|, into two triangles.
Hole //!< The quad should be treated as a hole in the heightfield.
};
struct HeightMaterialPoint
{
float m_height{ 0.0f }; //!< Holds the height of this point in the heightfield relative to the heightfield entity location.
QuadMeshType m_quadMeshType{ QuadMeshType::SubdivideUpperLeftToBottomRight }; //!< By default, create two triangles like this |\|, where this point is in the upper left corner.
uint8_t m_materialIndex{ 0 }; //!< The surface material index for the upper left corner of this quad.
uint16_t m_padding{ 0 }; //!< available for future use.
};
//! An interface to provide heightfield values.
class HeightfieldProviderRequests
: public AZ::ComponentBus
{
public:
//! Returns the distance between each height in the map.
//! @return Vector containing Column Spacing, Rows Spacing.
virtual AZ::Vector2 GetHeightfieldGridSpacing() const = 0;
//! Returns the height field gridsize.
//! @param numColumns contains the size of the grid in the x direction.
//! @param numRows contains the size of the grid in the y direction.
virtual void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const = 0;
//! Returns the height field min and max height bounds.
//! @param minHeightBounds contains the minimum height that the heightfield can contain.
//! @param maxHeightBounds contains the maximum height that the heightfield can contain.
virtual void GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const = 0;
//! Returns the AABB of the heightfield.
//! This is provided separately from the shape AABB because the heightfield might choose to modify the AABB bounds.
//! @return AABB of the heightfield.
virtual AZ::Aabb GetHeightfieldAabb() const = 0;
//! Returns the world transform for the heightfield.
//! This is provided separately from the entity transform because the heightfield might want to clear out the rotation or scale.
//! @return world transform that should be used with the heightfield data.
virtual AZ::Transform GetHeightfieldTransform() const = 0;
//! Returns the list of materials used by the height field.
//! @return returns a vector of all materials.
virtual AZStd::vector<MaterialId> GetMaterialList() const = 0;
//! Returns the list of heights used by the height field.
//! @return the rows*columns vector of the heights.
virtual AZStd::vector<float> GetHeights() const = 0;
//! Returns the list of heights and materials used by the height field.
//! @return the rows*columns vector of the heights and materials.
virtual AZStd::vector<Physics::HeightMaterialPoint> GetHeightsAndMaterials() const = 0;
};
using HeightfieldProviderRequestsBus = AZ::EBus<HeightfieldProviderRequests>;
//! Broadcasts notifications when heightfield data changes - heightfield providers implement HeightfieldRequests bus.
class HeightfieldProviderNotifications
: public AZ::ComponentBus
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
//! Called whenever the heightfield data changes.
//! @param the AABB of the area of data that changed.
virtual void OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
{
}
protected:
~HeightfieldProviderNotifications() = default;
};
using HeightfieldProviderNotificationBus = AZ::EBus<HeightfieldProviderNotifications>;
} // namespace Physics

@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <gmock/gmock.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
namespace UnitTest
{
class MockHeightfieldProviderNotificationBusListener
: private Physics::HeightfieldProviderNotificationBus::Handler
{
public:
MockHeightfieldProviderNotificationBusListener(AZ::EntityId entityid)
{
Physics::HeightfieldProviderNotificationBus::Handler::BusConnect(entityid);
}
~MockHeightfieldProviderNotificationBusListener()
{
Physics::HeightfieldProviderNotificationBus::Handler::BusDisconnect();
}
MOCK_METHOD1(OnHeightfieldDataChanged, void(const AZ::Aabb&));
};
} // namespace UnitTest

@ -37,6 +37,7 @@ namespace Physics
REFLECT_SHAPETYPE_ENUM_VALUE(Sphere); REFLECT_SHAPETYPE_ENUM_VALUE(Sphere);
REFLECT_SHAPETYPE_ENUM_VALUE(Cylinder); REFLECT_SHAPETYPE_ENUM_VALUE(Cylinder);
REFLECT_SHAPETYPE_ENUM_VALUE(PhysicsAsset); REFLECT_SHAPETYPE_ENUM_VALUE(PhysicsAsset);
REFLECT_SHAPETYPE_ENUM_VALUE(Heightfield);
#undef REFLECT_SHAPETYPE_ENUM_VALUE #undef REFLECT_SHAPETYPE_ENUM_VALUE
} }
@ -305,4 +306,125 @@ namespace Physics
m_cachedNativeMesh = nullptr; m_cachedNativeMesh = nullptr;
} }
} }
void HeightfieldShapeConfiguration::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext
->RegisterGenericType<AZStd::shared_ptr<HeightfieldShapeConfiguration>>();
serializeContext->Class<HeightfieldShapeConfiguration, ShapeConfiguration>()
->Version(1);
}
}
HeightfieldShapeConfiguration::~HeightfieldShapeConfiguration()
{
SetCachedNativeHeightfield(nullptr);
}
HeightfieldShapeConfiguration::HeightfieldShapeConfiguration(const HeightfieldShapeConfiguration& other)
: ShapeConfiguration(other)
, m_gridResolution(other.m_gridResolution)
, m_numColumns(other.m_numColumns)
, m_numRows(other.m_numRows)
, m_samples(other.m_samples)
, m_minHeightBounds(other.m_minHeightBounds)
, m_maxHeightBounds(other.m_maxHeightBounds)
, m_cachedNativeHeightfield(nullptr)
{
}
HeightfieldShapeConfiguration& HeightfieldShapeConfiguration::operator=(const HeightfieldShapeConfiguration& other)
{
ShapeConfiguration::operator=(other);
m_gridResolution = other.m_gridResolution;
m_numColumns = other.m_numColumns;
m_numRows = other.m_numRows;
m_samples = other.m_samples;
m_minHeightBounds = other.m_minHeightBounds;
m_maxHeightBounds = other.m_maxHeightBounds;
// Prevent raw pointer from being copied
m_cachedNativeHeightfield = nullptr;
return *this;
}
void* HeightfieldShapeConfiguration::GetCachedNativeHeightfield() const
{
return m_cachedNativeHeightfield;
}
void HeightfieldShapeConfiguration::SetCachedNativeHeightfield(void* cachedNativeHeightfield) const
{
if (m_cachedNativeHeightfield)
{
Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::ReleaseNativeHeightfieldObject, m_cachedNativeHeightfield);
}
m_cachedNativeHeightfield = cachedNativeHeightfield;
}
AZ::Vector2 HeightfieldShapeConfiguration::GetGridResolution() const
{
return m_gridResolution;
}
void HeightfieldShapeConfiguration::SetGridResolution(const AZ::Vector2& gridResolution)
{
m_gridResolution = gridResolution;
}
int32_t HeightfieldShapeConfiguration::GetNumColumns() const
{
return m_numColumns;
}
void HeightfieldShapeConfiguration::SetNumColumns(int32_t numColumns)
{
m_numColumns = numColumns;
}
int32_t HeightfieldShapeConfiguration::GetNumRows() const
{
return m_numRows;
}
void HeightfieldShapeConfiguration::SetNumRows(int32_t numRows)
{
m_numRows = numRows;
}
const AZStd::vector<Physics::HeightMaterialPoint>& HeightfieldShapeConfiguration::GetSamples() const
{
return m_samples;
}
void HeightfieldShapeConfiguration::SetSamples(const AZStd::vector<Physics::HeightMaterialPoint>& samples)
{
m_samples = samples;
}
float HeightfieldShapeConfiguration::GetMinHeightBounds() const
{
return m_minHeightBounds;
}
void HeightfieldShapeConfiguration::SetMinHeightBounds(float minBounds)
{
m_minHeightBounds = minBounds;
}
float HeightfieldShapeConfiguration::GetMaxHeightBounds() const
{
return m_maxHeightBounds;
}
void HeightfieldShapeConfiguration::SetMaxHeightBounds(float maxBounds)
{
m_maxHeightBounds = maxBounds;
}
} }

@ -9,10 +9,13 @@
#pragma once #pragma once
#include <AzCore/Math/Vector3.h> #include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Quaternion.h> #include <AzCore/Math/Quaternion.h>
#include <AzCore/Asset/AssetCommon.h> #include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
namespace Physics namespace Physics
{ {
/// Used to identify shape configuration type from base class. /// Used to identify shape configuration type from base class.
@ -27,6 +30,7 @@ namespace Physics
Native, ///< Native shape configuration if user wishes to bypass generic shape configurations. Native, ///< Native shape configuration if user wishes to bypass generic shape configurations.
PhysicsAsset, ///< Shapes configured in the asset. PhysicsAsset, ///< Shapes configured in the asset.
CookedMesh, ///< Stores a blob of mesh data cooked for the specific engine. CookedMesh, ///< Stores a blob of mesh data cooked for the specific engine.
Heightfield ///< Interacts with the physics system heightfield
}; };
class ShapeConfiguration class ShapeConfiguration
@ -196,4 +200,52 @@ namespace Physics
mutable void* m_cachedNativeMesh = nullptr; mutable void* m_cachedNativeMesh = nullptr;
}; };
class HeightfieldShapeConfiguration
: public ShapeConfiguration
{
public:
AZ_CLASS_ALLOCATOR(HeightfieldShapeConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(HeightfieldShapeConfiguration, "{8DF47C83-D2A9-4E7C-8620-5E173E43C0B3}", ShapeConfiguration);
static void Reflect(AZ::ReflectContext* context);
HeightfieldShapeConfiguration() = default;
HeightfieldShapeConfiguration(const HeightfieldShapeConfiguration&);
HeightfieldShapeConfiguration& operator=(const HeightfieldShapeConfiguration&);
~HeightfieldShapeConfiguration();
ShapeType GetShapeType() const override
{
return ShapeType::Heightfield;
}
void* GetCachedNativeHeightfield() const;
void SetCachedNativeHeightfield(void* cachedNativeHeightfield) const;
AZ::Vector2 GetGridResolution() const;
void SetGridResolution(const AZ::Vector2& gridSpacing);
int32_t GetNumColumns() const;
void SetNumColumns(int32_t numColumns);
int32_t GetNumRows() const;
void SetNumRows(int32_t numRows);
const AZStd::vector<Physics::HeightMaterialPoint>& GetSamples() const;
void SetSamples(const AZStd::vector<Physics::HeightMaterialPoint>& samples);
float GetMinHeightBounds() const;
void SetMinHeightBounds(float minBounds);
float GetMaxHeightBounds() const;
void SetMaxHeightBounds(float maxBounds);
private:
//! The number of meters between each heightfield sample.
AZ::Vector2 m_gridResolution{ 1.0f };
//! The number of columns in the heightfield sample grid.
int32_t m_numColumns{ 0 };
//! The number of rows in the heightfield sample grid.
int32_t m_numRows{ 0 };
//! The minimum and maximum heights that can be used by this heightfield.
//! This can be used by the physics system to choose a more optimal heightfield data type internally (ex: int16, uint8)
float m_minHeightBounds{AZStd::numeric_limits<float>::lowest()};
float m_maxHeightBounds{AZStd::numeric_limits<float>::max()};
//! The grid of sample points for the heightfield.
AZStd::vector<Physics::HeightMaterialPoint> m_samples;
//! An optional storage pointer for the physics system to cache its native heightfield representation.
mutable void* m_cachedNativeHeightfield{ nullptr };
};
} // namespace Physics } // namespace Physics

@ -132,6 +132,10 @@ namespace Physics
virtual AZStd::shared_ptr<Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0; virtual AZStd::shared_ptr<Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0;
/// Releases the height field object created by the physics backend.
/// @param nativeHeightfieldObject Pointer to the height field object.
virtual void ReleaseNativeHeightfieldObject(void* nativeHeightfieldObject) = 0;
/// Releases the mesh object created by the physics backend. /// Releases the mesh object created by the physics backend.
/// @param nativeMeshObject Pointer to the mesh object. /// @param nativeMeshObject Pointer to the mesh object.
virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0; virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0;

@ -107,6 +107,7 @@ namespace Physics
PhysicsAssetShapeConfiguration::Reflect(context); PhysicsAssetShapeConfiguration::Reflect(context);
NativeShapeConfiguration::Reflect(context); NativeShapeConfiguration::Reflect(context);
CookedMeshShapeConfiguration::Reflect(context); CookedMeshShapeConfiguration::Reflect(context);
HeightfieldShapeConfiguration::Reflect(context);
AzPhysics::SystemInterface::Reflect(context); AzPhysics::SystemInterface::Reflect(context);
AzPhysics::Scene::Reflect(context); AzPhysics::Scene::Reflect(context);
AzPhysics::CollisionLayer::Reflect(context); AzPhysics::CollisionLayer::Reflect(context);

@ -0,0 +1,11 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
Mocks/MockHeightfieldProviderBus.h
)

@ -14,9 +14,8 @@ namespace AzFramework
{ {
AZ_CVAR(bool, bg_octreeUseQuadtree, false, nullptr, AZ::ConsoleFunctorFlags::ReadOnly, "If set to true, the visibility octrees will degenerate to a quadtree split along the X/Y plane"); AZ_CVAR(bool, bg_octreeUseQuadtree, false, nullptr, AZ::ConsoleFunctorFlags::ReadOnly, "If set to true, the visibility octrees will degenerate to a quadtree split along the X/Y plane");
AZ_CVAR(float, bg_octreeMaxWorldExtents, 16384.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum supported world size by the world octreeSystemComponent"); AZ_CVAR(float, bg_octreeMaxWorldExtents, 16384.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum supported world size by the world octreeSystemComponent");
AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split"); AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split");
AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation"); AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation");
static uint32_t GetChildNodeCount() static uint32_t GetChildNodeCount()
{ {
@ -25,14 +24,12 @@ namespace AzFramework
return (bg_octreeUseQuadtree) ? QuadtreeNodeChildCount : OctreeNodeChildCount; return (bg_octreeUseQuadtree) ? QuadtreeNodeChildCount : OctreeNodeChildCount;
} }
OctreeNode::OctreeNode(const AZ::Aabb& bounds) OctreeNode::OctreeNode(const AZ::Aabb& bounds)
: m_bounds(bounds) : m_bounds(bounds)
{ {
; ;
} }
OctreeNode::OctreeNode(OctreeNode&& rhs) OctreeNode::OctreeNode(OctreeNode&& rhs)
: m_bounds(rhs.m_bounds) : m_bounds(rhs.m_bounds)
, m_parent(rhs.m_parent) , m_parent(rhs.m_parent)
@ -46,7 +43,6 @@ namespace AzFramework
} }
} }
OctreeNode& OctreeNode::operator=(OctreeNode&& rhs) OctreeNode& OctreeNode::operator=(OctreeNode&& rhs)
{ {
m_bounds = rhs.m_bounds; m_bounds = rhs.m_bounds;
@ -63,7 +59,6 @@ namespace AzFramework
return *this; return *this;
} }
void OctreeNode::Insert(OctreeScene& octreeScene, VisibilityEntry* entry) void OctreeNode::Insert(OctreeScene& octreeScene, VisibilityEntry* entry)
{ {
AZ_Assert(entry->m_internalNode == nullptr, "Double-insertion: Insert invoked for an entry already bound to the OctreeScene"); AZ_Assert(entry->m_internalNode == nullptr, "Double-insertion: Insert invoked for an entry already bound to the OctreeScene");
@ -98,7 +93,6 @@ namespace AzFramework
} }
} }
void OctreeNode::Update(OctreeScene& octreeScene, VisibilityEntry* entry) void OctreeNode::Update(OctreeScene& octreeScene, VisibilityEntry* entry)
{ {
AZ_Assert(entry->m_internalNode == this, "Update invoked for an entry bound to a different OctreeNode"); AZ_Assert(entry->m_internalNode == this, "Update invoked for an entry bound to a different OctreeNode");
@ -129,7 +123,6 @@ namespace AzFramework
} }
} }
void OctreeNode::Remove(OctreeScene& octreeScene, VisibilityEntry* entry) void OctreeNode::Remove(OctreeScene& octreeScene, VisibilityEntry* entry)
{ {
AZ_Assert(entry->m_internalNode == this, "Remove invoked for an entry bound to a different OctreeNode"); AZ_Assert(entry->m_internalNode == this, "Remove invoked for an entry bound to a different OctreeNode");
@ -152,25 +145,30 @@ namespace AzFramework
} }
} }
void OctreeNode::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const void OctreeNode::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const
{ {
EnumerateHelper(aabb, callback); if (AZ::ShapeIntersection::Overlaps(aabb, m_bounds))
{
EnumerateHelper(aabb, callback);
}
} }
void OctreeNode::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const void OctreeNode::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const
{ {
EnumerateHelper(sphere, callback); if (AZ::ShapeIntersection::Overlaps(sphere, m_bounds))
{
EnumerateHelper(sphere, callback);
}
} }
void OctreeNode::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const void OctreeNode::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const
{ {
EnumerateHelper(frustum, callback); if (AZ::ShapeIntersection::Overlaps(frustum, m_bounds))
{
EnumerateHelper(frustum, callback);
}
} }
void OctreeNode::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const void OctreeNode::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
{ {
// Invoke the callback for the current node // Invoke the callback for the current node
@ -190,25 +188,21 @@ namespace AzFramework
} }
} }
const AZStd::vector<VisibilityEntry*>& OctreeNode::GetEntries() const const AZStd::vector<VisibilityEntry*>& OctreeNode::GetEntries() const
{ {
return m_entries; return m_entries;
} }
OctreeNode* OctreeNode::GetChildren() const OctreeNode* OctreeNode::GetChildren() const
{ {
return m_children; return m_children;
} }
bool OctreeNode::IsLeaf() const bool OctreeNode::IsLeaf() const
{ {
return m_children == nullptr; return m_children == nullptr;
} }
void OctreeNode::TryMerge(OctreeScene& octreeScene) void OctreeNode::TryMerge(OctreeScene& octreeScene)
{ {
if (IsLeaf()) if (IsLeaf())
@ -236,7 +230,6 @@ namespace AzFramework
} }
} }
template <typename T> template <typename T>
void OctreeNode::EnumerateHelper(const T& boundingVolume, const IVisibilityScene::EnumerateCallback& callback) const void OctreeNode::EnumerateHelper(const T& boundingVolume, const IVisibilityScene::EnumerateCallback& callback) const
{ {
@ -262,7 +255,6 @@ namespace AzFramework
} }
} }
void OctreeNode::Split(OctreeScene& octreeScene) void OctreeNode::Split(OctreeScene& octreeScene)
{ {
AZ_Assert(m_children == nullptr, "Split invoked on an octreeScene node that has already been split"); AZ_Assert(m_children == nullptr, "Split invoked on an octreeScene node that has already been split");
@ -312,7 +304,6 @@ namespace AzFramework
} }
} }
void OctreeNode::Merge(OctreeScene& octreeScene) void OctreeNode::Merge(OctreeScene& octreeScene)
{ {
AZ_Assert(m_children != nullptr, "Merge invoked on an octreeScene node that does not have children"); AZ_Assert(m_children != nullptr, "Merge invoked on an octreeScene node that does not have children");
@ -371,7 +362,6 @@ namespace AzFramework
} }
} }
void OctreeScene::RemoveEntry(VisibilityEntry& entry) void OctreeScene::RemoveEntry(VisibilityEntry& entry)
{ {
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sharedMutex); AZStd::lock_guard<AZStd::shared_mutex> lock(m_sharedMutex);
@ -382,35 +372,30 @@ namespace AzFramework
} }
} }
void OctreeScene::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const void OctreeScene::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const
{ {
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex); AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(aabb, callback); m_root.Enumerate(aabb, callback);
} }
void OctreeScene::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const void OctreeScene::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const
{ {
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex); AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(sphere, callback); m_root.Enumerate(sphere, callback);
} }
void OctreeScene::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const void OctreeScene::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const
{ {
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex); AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(frustum, callback); m_root.Enumerate(frustum, callback);
} }
void OctreeScene::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const void OctreeScene::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
{ {
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex); AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.EnumerateNoCull(callback); m_root.EnumerateNoCull(callback);
} }
uint32_t OctreeScene::GetEntryCount() const uint32_t OctreeScene::GetEntryCount() const
{ {
return m_entryCount; return m_entryCount;
@ -421,26 +406,22 @@ namespace AzFramework
return m_nodeCount; return m_nodeCount;
} }
uint32_t OctreeScene::GetFreeNodeCount() const uint32_t OctreeScene::GetFreeNodeCount() const
{ {
// Each entry represents GetChildNodeCount() nodes // Each entry represents GetChildNodeCount() nodes
return aznumeric_cast<uint32_t>(m_freeOctreeNodes.size() * GetChildNodeCount()); return aznumeric_cast<uint32_t>(m_freeOctreeNodes.size() * GetChildNodeCount());
} }
uint32_t OctreeScene::GetPageCount() const uint32_t OctreeScene::GetPageCount() const
{ {
return aznumeric_cast<uint32_t>(m_nodeCache.size()); return aznumeric_cast<uint32_t>(m_nodeCache.size());
} }
uint32_t OctreeScene::GetChildNodeCount() const uint32_t OctreeScene::GetChildNodeCount() const
{ {
return AzFramework::GetChildNodeCount(); return AzFramework::GetChildNodeCount();
} }
void OctreeScene::DumpStats() void OctreeScene::DumpStats()
{ {
AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::EntryCount = %u", GetName().GetCStr(), GetEntryCount()); AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::EntryCount = %u", GetName().GetCStr(), GetEntryCount());
@ -450,21 +431,18 @@ namespace AzFramework
AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::ChildNodeCount = %u", GetName().GetCStr(), GetChildNodeCount()); AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::ChildNodeCount = %u", GetName().GetCStr(), GetChildNodeCount());
} }
static inline uint32_t CreateNodeIndex(uint32_t page, uint32_t offset) static inline uint32_t CreateNodeIndex(uint32_t page, uint32_t offset)
{ {
AZ_Assert(page <= 0xFFFF && offset <= 0xFFFF, "Out of range values passed to CreateNodeIndex"); AZ_Assert(page <= 0xFFFF && offset <= 0xFFFF, "Out of range values passed to CreateNodeIndex");
return (page << 16) | offset; return (page << 16) | offset;
} }
static inline void ExtractPageAndOffsetFromIndex(uint32_t index, uint32_t& page, uint32_t& offset) static inline void ExtractPageAndOffsetFromIndex(uint32_t index, uint32_t& page, uint32_t& offset)
{ {
offset = index & 0x0000FFFF; offset = index & 0x0000FFFF;
page = index >> 16; page = index >> 16;
} }
uint32_t OctreeScene::AllocateChildNodes() uint32_t OctreeScene::AllocateChildNodes()
{ {
const uint32_t childCount = GetChildNodeCount(); const uint32_t childCount = GetChildNodeCount();
@ -508,14 +486,12 @@ namespace AzFramework
return CreateNodeIndex(nextChildPage, nextChildOffset); return CreateNodeIndex(nextChildPage, nextChildOffset);
} }
void OctreeScene::ReleaseChildNodes(uint32_t nodeIndex) void OctreeScene::ReleaseChildNodes(uint32_t nodeIndex)
{ {
m_nodeCount -= GetChildNodeCount(); m_nodeCount -= GetChildNodeCount();
m_freeOctreeNodes.push(nodeIndex); m_freeOctreeNodes.push(nodeIndex);
} }
OctreeNode* OctreeScene::GetChildNodesAtIndex(uint32_t nodeIndex) const OctreeNode* OctreeScene::GetChildNodesAtIndex(uint32_t nodeIndex) const
{ {
uint32_t childPage; uint32_t childPage;
@ -524,7 +500,6 @@ namespace AzFramework
return &(*m_nodeCache[childPage])[childOffset]; return &(*m_nodeCache[childPage])[childOffset];
} }
void OctreeSystemComponent::Reflect(AZ::ReflectContext* context) void OctreeSystemComponent::Reflect(AZ::ReflectContext* context)
{ {
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@ -534,19 +509,16 @@ namespace AzFramework
} }
} }
void OctreeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) void OctreeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{ {
provided.push_back(AZ_CRC("OctreeService")); provided.push_back(AZ_CRC("OctreeService"));
} }
void OctreeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) void OctreeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{ {
incompatible.push_back(AZ_CRC("OctreeService")); incompatible.push_back(AZ_CRC("OctreeService"));
} }
OctreeSystemComponent::OctreeSystemComponent() OctreeSystemComponent::OctreeSystemComponent()
{ {
AZ::Interface<IVisibilitySystem>::Register(this); AZ::Interface<IVisibilitySystem>::Register(this);
@ -555,7 +527,6 @@ namespace AzFramework
m_defaultScene = aznew OctreeScene(AZ::Name("DefaultVisibilityScene")); m_defaultScene = aznew OctreeScene(AZ::Name("DefaultVisibilityScene"));
} }
OctreeSystemComponent::~OctreeSystemComponent() OctreeSystemComponent::~OctreeSystemComponent()
{ {
AZ_Assert(m_scenes.empty(), "All IVisibilityScenes must be destroyed before shutdown"); AZ_Assert(m_scenes.empty(), "All IVisibilityScenes must be destroyed before shutdown");
@ -566,13 +537,11 @@ namespace AzFramework
AZ::Interface<IVisibilitySystem>::Unregister(this); AZ::Interface<IVisibilitySystem>::Unregister(this);
} }
void OctreeSystemComponent::Activate() void OctreeSystemComponent::Activate()
{ {
; ;
} }
void OctreeSystemComponent::Deactivate() void OctreeSystemComponent::Deactivate()
{ {
; ;
@ -591,7 +560,6 @@ namespace AzFramework
return newScene; return newScene;
} }
void OctreeSystemComponent::DestroyVisibilityScene(IVisibilityScene* visScene) void OctreeSystemComponent::DestroyVisibilityScene(IVisibilityScene* visScene)
{ {
for (auto iter = m_scenes.begin(); iter != m_scenes.end(); ++iter) for (auto iter = m_scenes.begin(); iter != m_scenes.end(); ++iter)
@ -606,7 +574,6 @@ namespace AzFramework
AZ_Assert(false, "visScene[\"%s\"] not found in the OctreeSystemComponent", visScene->GetName().GetCStr()); AZ_Assert(false, "visScene[\"%s\"] not found in the OctreeSystemComponent", visScene->GetName().GetCStr());
} }
IVisibilityScene* OctreeSystemComponent::FindVisibilityScene(const AZ::Name& sceneName) IVisibilityScene* OctreeSystemComponent::FindVisibilityScene(const AZ::Name& sceneName)
{ {
for (OctreeScene* scene : m_scenes) for (OctreeScene* scene : m_scenes)
@ -619,7 +586,6 @@ namespace AzFramework
return nullptr; return nullptr;
} }
void OctreeSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) void OctreeSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{ {
for (OctreeScene* scene : m_scenes) for (OctreeScene* scene : m_scenes)

@ -228,6 +228,7 @@ set(FILES
Physics/Configuration/SimulatedBodyConfiguration.cpp Physics/Configuration/SimulatedBodyConfiguration.cpp
Physics/Configuration/SystemConfiguration.h Physics/Configuration/SystemConfiguration.h
Physics/Configuration/SystemConfiguration.cpp Physics/Configuration/SystemConfiguration.cpp
Physics/HeightfieldProviderBus.h
Physics/SimulatedBodies/RigidBody.h Physics/SimulatedBodies/RigidBody.h
Physics/SimulatedBodies/RigidBody.cpp Physics/SimulatedBodies/RigidBody.cpp
Physics/SimulatedBodies/StaticRigidBody.h Physics/SimulatedBodies/StaticRigidBody.h
@ -251,6 +252,7 @@ set(FILES
Physics/Shape.h Physics/Shape.h
Physics/ShapeConfiguration.h Physics/ShapeConfiguration.h
Physics/ShapeConfiguration.cpp Physics/ShapeConfiguration.cpp
Physics/HeightfieldProviderBus.h
Physics/SystemBus.h Physics/SystemBus.h
Physics/ColliderComponentBus.h Physics/ColliderComponentBus.h
Physics/RagdollPhysicsBus.h Physics/RagdollPhysicsBus.h

@ -42,6 +42,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
NAMESPACE AZ NAMESPACE AZ
FILES_CMAKE FILES_CMAKE
Tests/framework_shared_tests_files.cmake Tests/framework_shared_tests_files.cmake
AzFramework/Physics/physics_mock_files.cmake
INCLUDE_DIRECTORIES INCLUDE_DIRECTORIES
PUBLIC PUBLIC
Tests Tests
@ -53,7 +54,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest AZ::AzTest
AZ::AzTestShared AZ::AzTestShared
) )
if(PAL_TRAIT_BUILD_HOST_TOOLS) if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target( ly_add_target(

@ -53,7 +53,7 @@ namespace AzNetworking
m_timeoutItemMap.erase(timeoutId); m_timeoutItemMap.erase(timeoutId);
} }
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts) void TimeoutQueue::UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts)
{ {
int32_t numTimeouts = 0; int32_t numTimeouts = 0;
if (maxTimeouts < 0) if (maxTimeouts < 0)
@ -103,7 +103,7 @@ namespace AzNetworking
// By this point, the item is definitely timed out // By this point, the item is definitely timed out
// Invoke the timeout function to see how to proceed // Invoke the timeout function to see how to proceed
const TimeoutResult result = timeoutHandler.HandleTimeout(mapItem); const TimeoutResult result = timeoutHandler(mapItem);
if (result == TimeoutResult::Refresh) if (result == TimeoutResult::Refresh)
{ {
@ -122,4 +122,10 @@ namespace AzNetworking
m_timeoutItemMap.erase(itemTimeoutId); m_timeoutItemMap.erase(itemTimeoutId);
} }
} }
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
{
TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); });
UpdateTimeouts(handler, maxTimeouts);
}
} }

@ -64,6 +64,12 @@ namespace AzNetworking
//! @param timeoutId the identifier of the item to remove //! @param timeoutId the identifier of the item to remove
void RemoveItem(TimeoutId timeoutId); void RemoveItem(TimeoutId timeoutId);
//! Updates timeouts for all items, invokes the provided timeout functor if required.
//! @param timeoutHandler lambda to invoke for all timeouts
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
using TimeoutHandler = AZStd::function<TimeoutResult(TimeoutQueue::TimeoutItem&)>;
void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
//! Updates timeouts for all items, invokes timeout handlers if required. //! Updates timeouts for all items, invokes timeout handlers if required.
//! @param timeoutHandler listener instance to call back on for timeouts //! @param timeoutHandler listener instance to call back on for timeouts
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration //! @param maxTimeouts the maximum number of timeouts to process before breaking iteration

@ -13,6 +13,7 @@
#include <QIcon> #include <QIcon>
#include <QToolButton> #include <QToolButton>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QPainter>
namespace AzQtComponents namespace AzQtComponents
{ {
@ -27,6 +28,13 @@ namespace AzQtComponents
setAttribute(Qt::WA_ShowWithoutActivating); setAttribute(Qt::WA_ShowWithoutActivating);
setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_DeleteOnClose);
m_borderRadius = toastConfiguration.m_borderRadius;
if (m_borderRadius > 0)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_TranslucentBackground);
}
m_ui->setupUi(this); m_ui->setupUi(this);
QIcon toastIcon; QIcon toastIcon;
@ -53,6 +61,13 @@ namespace AzQtComponents
m_ui->titleLabel->setText(toastConfiguration.m_title); m_ui->titleLabel->setText(toastConfiguration.m_title);
m_ui->mainLabel->setText(toastConfiguration.m_description); m_ui->mainLabel->setText(toastConfiguration.m_description);
// hide the optional description if none is provided so the title is centered vertically
if (toastConfiguration.m_description.isEmpty())
{
m_ui->mainLabel->setVisible(false);
m_ui->verticalLayout->removeWidget(m_ui->mainLabel);
}
m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count())); m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count()));
m_closeOnClick = toastConfiguration.m_closeOnClick; m_closeOnClick = toastConfiguration.m_closeOnClick;
@ -68,6 +83,24 @@ namespace AzQtComponents
{ {
} }
void ToastNotification::paintEvent(QPaintEvent* event)
{
if (m_borderRadius > 0)
{
QPainter p(this);
p.setPen(Qt::transparent);
QColor painterColor;
painterColor.setRgbF(0, 0, 0, 255);
p.setBrush(painterColor);
p.setRenderHint(QPainter::Antialiasing);
p.drawRoundedRect(rect(), m_borderRadius, m_borderRadius);
}
else
{
QDialog::paintEvent(event);
}
}
void ToastNotification::ShowToastAtCursor() void ToastNotification::ShowToastAtCursor()
{ {
QPoint globalCursorPos = QCursor::pos(); QPoint globalCursorPos = QCursor::pos();

@ -52,6 +52,8 @@ namespace AzQtComponents
void mousePressEvent(QMouseEvent* mouseEvent) override; void mousePressEvent(QMouseEvent* mouseEvent) override;
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
void paintEvent(QPaintEvent* event) override;
public slots: public slots:
void StartTimer(); void StartTimer();
void FadeOut(); void FadeOut();
@ -65,6 +67,7 @@ namespace AzQtComponents
bool m_closeOnClick; bool m_closeOnClick;
QTimer m_lifeSpan; QTimer m_lifeSpan;
uint32_t m_borderRadius = 0;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_fadeDuration; AZStd::chrono::milliseconds m_fadeDuration;

@ -191,7 +191,7 @@
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>40</width> <width>20</width>
<height>20</height> <height>20</height>
</size> </size>
</property> </property>

@ -37,6 +37,7 @@ namespace AzQtComponents
QString m_title; QString m_title;
QString m_description; QString m_description;
QString m_customIconImage; QString m_customIconImage;
uint32_t m_borderRadius = 0;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000); AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000);

@ -177,4 +177,14 @@ namespace AzToolsFramework
DisplayQueuedNotification(); DisplayQueuedNotification();
} }
} }
void ToastNotificationsView::SetOffset(const QPoint& offset)
{
m_offset = offset;
}
void ToastNotificationsView::SetAnchorPoint(const QPointF& anchorPoint)
{
m_anchorPoint = anchorPoint;
}
} }

@ -50,6 +50,9 @@ namespace AzToolsFramework
void OnShow(); void OnShow();
void UpdateToastPosition(); void UpdateToastPosition();
void SetOffset(const QPoint& offset);
void SetAnchorPoint(const QPointF& anchorPoint);
private: private:
ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration); ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration);
void DisplayQueuedNotification(); void DisplayQueuedNotification();

@ -43,7 +43,7 @@ ly_add_target(
3rdParty::pybind11 3rdParty::pybind11
AZ::AzCore AZ::AzCore
AZ::AzFramework AZ::AzFramework
AZ::AzQtComponents AZ::AzToolsFramework
) )
ly_add_target( ly_add_target(

@ -40,5 +40,6 @@
<file>Delete.svg</file> <file>Delete.svg</file>
<file>Download.svg</file> <file>Download.svg</file>
<file>in_progress.gif</file> <file>in_progress.gif</file>
<file>gem.svg</file>
</qresource> </qresource>
</RCC> </RCC>

@ -61,6 +61,24 @@ QTabBar::tab:focus {
color: #4082eb; color: #4082eb;
} }
#ToastNotification {
background-color: black;
border-radius: 20px;
border:1px solid #dddddd;
qproperty-minimumSize: 100px 50px;
}
#ToastNotification #icon_frame {
border-radius: 4px;
qproperty-minimumSize: 44px 20px;
}
#ToastNotification #iconLabel {
qproperty-minimumSize: 30px 20px;
qproperty-maximumSize: 30px 20px;
margin-left: 6px;
}
/************** General (Forms) **************/ /************** General (Forms) **************/
#formLineEditWidget, #formLineEditWidget,
@ -218,6 +236,10 @@ QTabBar::tab:focus {
color: #666666; color: #666666;
} }
#verticalSeparatingLine {
color: #888888;
}
/************** Project Settings **************/ /************** Project Settings **************/
#projectSettings { #projectSettings {
margin-top:42px; margin-top:42px;
@ -481,6 +503,14 @@ QProgressBar::chunk {
font-weight: 600; font-weight: 600;
} }
#gemCatalogMenuButton {
qproperty-flat: true;
max-width:36px;
min-width:36px;
max-height:24px;
min-height:24px;
}
#GemCatalogCartOverlayGemDownloadHeader { #GemCatalogCartOverlayGemDownloadHeader {
margin:0; margin:0;
padding: 0px; padding: 0px;

@ -0,0 +1,3 @@
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.3771 6.26808L18.0075 0.389195C17.9407 0.271396 17.844 0.173353 17.7271 0.105004C17.6101 0.0366557 17.4772 0.000430184 17.3418 0H4.15017C4.01474 0.000430184 3.88184 0.0366557 3.76492 0.105004C3.64801 0.173353 3.55125 0.271396 3.48444 0.389195L0.10459 6.26808C0.0213476 6.41083 -0.0136462 6.57661 0.00480427 6.74082C0.0232547 6.90502 0.0941655 7.05891 0.20701 7.17962L10.1724 17.7596C10.2442 17.8355 10.3308 17.896 10.4267 17.9373C10.5227 17.9787 10.6261 18 10.7306 18C10.8351 18 10.9385 17.9787 11.0345 17.9373C11.1305 17.896 11.217 17.8355 11.2888 17.7596L21.2542 7.17962C21.3703 7.06129 21.4451 6.90857 21.4672 6.74428C21.4894 6.57999 21.4578 6.41294 21.3771 6.26808ZM12.5998 7.17962L10.7562 13.7345L8.88195 7.17962H12.5998ZM8.42107 5.64332L7.25348 1.54654H14.218L13.0504 5.64332H8.42107ZM9.44526 14.687L2.31685 7.17962H7.24324L9.44526 14.687ZM14.2385 7.17962H19.1546L11.9853 14.7382L14.2385 7.17962ZM19.2878 5.64332H14.6789L15.8465 1.54654H16.9014L19.2878 5.64332ZM4.64178 1.54654H5.66598L6.83356 5.64332H2.23492L4.64178 1.54654Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -49,6 +49,8 @@ namespace O3DE::ProjectManager
m_stack->addWidget(m_gemCatalogScreen); m_stack->addWidget(m_gemCatalogScreen);
vLayout->addWidget(m_stack); vLayout->addWidget(m_stack);
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest);
// When there are multiple project templates present, we re-gather the gems when changing the selected the project template. // When there are multiple project templates present, we re-gather the gems when changing the selected the project template.
connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex) connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex)
{ {
@ -133,7 +135,7 @@ namespace O3DE::ProjectManager
} }
else else
{ {
emit GotoPreviousScreenRequest(); emit GoToPreviousScreenRequest();
} }
} }

@ -29,17 +29,17 @@ namespace O3DE::ProjectManager
topBarFrameWidget->setLayout(topBarHLayout); topBarFrameWidget->setLayout(topBarHLayout);
QTabWidget* tabWidget = new QTabWidget(); m_tabWidget = new QTabWidget();
tabWidget->setObjectName("engineTab"); m_tabWidget->setObjectName("engineTab");
tabWidget->tabBar()->setObjectName("engineTabBar"); m_tabWidget->tabBar()->setObjectName("engineTabBar");
tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus); m_tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_engineSettingsScreen = new EngineSettingsScreen(); m_engineSettingsScreen = new EngineSettingsScreen();
m_gemRepoScreen = new GemRepoScreen(); m_gemRepoScreen = new GemRepoScreen();
tabWidget->addTab(m_engineSettingsScreen, tr("General")); m_tabWidget->addTab(m_engineSettingsScreen, tr("General"));
tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories")); m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(tabWidget); topBarHLayout->addWidget(m_tabWidget);
vLayout->addWidget(topBarFrameWidget); vLayout->addWidget(topBarFrameWidget);
@ -61,4 +61,28 @@ namespace O3DE::ProjectManager
return true; return true;
} }
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
{
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
{
return true;
}
return false;
}
void EngineScreenCtrl::GoToScreen(ProjectManagerScreen screen)
{
if (screen == m_engineSettingsScreen->GetScreenEnum())
{
m_tabWidget->setCurrentWidget(m_engineSettingsScreen);
m_engineSettingsScreen->NotifyCurrentScreen();
}
else if (screen == m_gemRepoScreen->GetScreenEnum())
{
m_tabWidget->setCurrentWidget(m_gemRepoScreen);
m_gemRepoScreen->NotifyCurrentScreen();
}
}
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -11,6 +11,8 @@
#include <ScreenWidget.h> #include <ScreenWidget.h>
#endif #endif
QT_FORWARD_DECLARE_CLASS(QTabWidget)
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen) QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
@ -26,7 +28,10 @@ namespace O3DE::ProjectManager
QString GetTabText() override; QString GetTabText() override;
bool IsTab() override; bool IsTab() override;
bool ContainsScreen(ProjectManagerScreen screen) override;
void GoToScreen(ProjectManagerScreen screen) override;
QTabWidget* m_tabWidget = nullptr;
EngineSettingsScreen* m_engineSettingsScreen = nullptr; EngineSettingsScreen* m_engineSettingsScreen = nullptr;
GemRepoScreen* m_gemRepoScreen = nullptr; GemRepoScreen* m_gemRepoScreen = nullptr;
}; };

@ -8,12 +8,14 @@
#include <GemCatalog/GemCatalogHeaderWidget.h> #include <GemCatalog/GemCatalogHeaderWidget.h>
#include <AzCore/std/functional.h> #include <AzCore/std/functional.h>
#include <TagWidget.h>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMouseEvent> #include <QMouseEvent>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QMenu>
#include <QProgressBar> #include <QProgressBar>
#include <TagWidget.h>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
@ -404,6 +406,28 @@ namespace O3DE::ProjectManager
CartButton* cartButton = new CartButton(gemModel, downloadController); CartButton* cartButton = new CartButton(gemModel, downloadController);
hLayout->addWidget(cartButton); hLayout->addWidget(cartButton);
hLayout->addSpacing(16);
// Separating line
QFrame* vLine = new QFrame();
vLine->setFrameShape(QFrame::VLine);
vLine->setObjectName("verticalSeparatingLine");
hLayout->addWidget(vLine);
hLayout->addSpacing(16);
QMenu* gemMenu = new QMenu(this);
m_openGemReposAction = gemMenu->addAction(tr("Show Gem Repos"));
connect(m_openGemReposAction, &QAction::triggered, this,[this](){ emit OpenGemsRepo(); });
QPushButton* gemMenuButton = new QPushButton(this);
gemMenuButton->setObjectName("gemCatalogMenuButton");
gemMenuButton->setMenu(gemMenu);
gemMenuButton->setIcon(QIcon(":/menu.svg"));
gemMenuButton->setIconSize(QSize(36, 24));
hLayout->addWidget(gemMenuButton);
} }
void GemCatalogHeaderWidget::ReinitForProject() void GemCatalogHeaderWidget::ReinitForProject()

@ -15,13 +15,15 @@
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h> #include <GemCatalog/GemSortFilterProxyModel.h>
#include <TagWidget.h> #include <TagWidget.h>
#include <DownloadController.h>
#include <QFrame> #include <QFrame>
#include <QLabel> #include <QLabel>
#include <QDialog> #include <QDialog>
#include <QMoveEvent> #include <QMoveEvent>
#include <QHideEvent> #include <QHideEvent>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <DownloadController.h> #include <QAction>
#endif #endif
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
@ -84,8 +86,13 @@ namespace O3DE::ProjectManager
void ReinitForProject(); void ReinitForProject();
signals:
void OpenGemsRepo();
private: private:
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
inline constexpr static int s_height = 60; inline constexpr static int s_height = 60;
QAction* m_openGemReposAction = nullptr;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -38,6 +38,8 @@ namespace O3DE::ProjectManager
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController); m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
vLayout->addWidget(m_headerWidget); vLayout->addWidget(m_headerWidget);
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
QHBoxLayout* hLayout = new QHBoxLayout(); QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0); hLayout->setMargin(0);
vLayout->addLayout(hLayout); vLayout->addLayout(hLayout);
@ -64,6 +66,9 @@ namespace O3DE::ProjectManager
hLayout->addWidget(filterWidget); hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout); hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemInspector); hLayout->addWidget(m_gemInspector);
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
m_notificationsView->SetOffset(QPoint(10, 70));
} }
void GemCatalogScreen::ReinitForProject(const QString& projectPath) void GemCatalogScreen::ReinitForProject(const QString& projectPath)
@ -84,6 +89,7 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject(); m_headerWidget->ReinitForProject();
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter); connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
// Select the first entry after everything got correctly sized // Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{ QTimer::singleShot(200, [=]{
@ -92,6 +98,72 @@ namespace O3DE::ProjectManager
}); });
} }
void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies)
{
if (m_notificationsEnabled)
{
bool added = GemModel::IsAdded(modelIndex);
bool dependency = GemModel::IsAddedDependency(modelIndex);
bool gemStateChanged = (added && !dependency) || (!added && !dependency);
if (!gemStateChanged && !numChangedDependencies)
{
// no actual changes made
return;
}
QString notification;
if (gemStateChanged)
{
notification = GemModel::GetDisplayName(modelIndex);
if (numChangedDependencies > 0)
{
notification += " " + tr("and") + " ";
}
}
if (numChangedDependencies == 1 )
{
notification += "1 Gem " + tr("dependency");
}
else if (numChangedDependencies > 1)
{
notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies");
}
notification += " " + (added ? tr("activated") : tr("deactivated"));
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
toastConfiguration.m_customIconImage = ":/gem.svg";
toastConfiguration.m_borderRadius = 4;
toastConfiguration.m_duration = AZStd::chrono::milliseconds(3000);
m_notificationsView->ShowToastNotification(toastConfiguration);
}
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
m_notificationsView->OnHide();
}
void GemCatalogScreen::showEvent(QShowEvent* event)
{
ScreenWidget::showEvent(event);
m_notificationsView->OnShow();
}
void GemCatalogScreen::resizeEvent(QResizeEvent* event)
{
ScreenWidget::resizeEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::moveEvent(QMoveEvent* event)
{
ScreenWidget::moveEvent(event);
m_notificationsView->UpdateToastPosition();
}
void GemCatalogScreen::FillModel(const QString& projectPath) void GemCatalogScreen::FillModel(const QString& projectPath)
{ {
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
@ -105,6 +177,7 @@ namespace O3DE::ProjectManager
} }
m_gemModel->UpdateGemDependencies(); m_gemModel->UpdateGemDependencies();
m_notificationsEnabled = false;
// Gather enabled gems for the given project. // Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
@ -131,6 +204,8 @@ namespace O3DE::ProjectManager
{ {
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
} }
m_notificationsEnabled = true;
} }
else else
{ {
@ -194,6 +269,27 @@ namespace O3DE::ProjectManager
return EnableDisableGemsResult::Success; return EnableDisableGemsResult::Success;
} }
void GemCatalogScreen::HandleOpenGemRepo()
{
QVector<QModelIndex> gemsToBeAdded = m_gemModel->GatherGemsToBeAdded(true);
QVector<QModelIndex> gemsToBeRemoved = m_gemModel->GatherGemsToBeRemoved(true);
if (!gemsToBeAdded.empty() || !gemsToBeRemoved.empty())
{
QMessageBox::StandardButton warningResult = QMessageBox::warning(
nullptr, "Pending Changes",
"There are some unsaved changes to the gem selection,<br> they will be lost if you change screens.<br> Are you sure?",
QMessageBox::No | QMessageBox::Yes);
if (warningResult != QMessageBox::Yes)
{
return;
}
}
emit ChangeScreenRequest(ProjectManagerScreen::GemRepos);
}
ProjectManagerScreen GemCatalogScreen::GetScreenEnum() ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
{ {
return ProjectManagerScreen::GemCatalog; return ProjectManagerScreen::GemCatalog;

@ -10,6 +10,8 @@
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <ScreenWidget.h> #include <ScreenWidget.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
#include <GemCatalog/GemCatalogHeaderWidget.h> #include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemFilterWidget.h> #include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h> #include <GemCatalog/GemListView.h>
@ -41,9 +43,24 @@ namespace O3DE::ProjectManager
GemModel* GetGemModel() const { return m_gemModel; } GemModel* GetGemModel() const { return m_gemModel; }
DownloadController* GetDownloadController() const { return m_downloadController; } DownloadController* GetDownloadController() const { return m_downloadController; }
public slots:
void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
protected:
void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void moveEvent(QMoveEvent* event) override;
private slots:
void HandleOpenGemRepo();
private: private:
void FillModel(const QString& projectPath); void FillModel(const QString& projectPath);
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
GemListView* m_gemListView = nullptr; GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr; GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr; GemModel* m_gemModel = nullptr;
@ -52,5 +69,6 @@ namespace O3DE::ProjectManager
QVBoxLayout* m_filterWidgetLayout = nullptr; QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr; GemFilterWidget* m_filterWidget = nullptr;
DownloadController* m_downloadController = nullptr; DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
}; };
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -10,6 +10,7 @@
#include <GemCatalog/GemModel.h> #include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h> #include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h> #include <AzCore/Casting/numeric_cast.h>
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
namespace O3DE::ProjectManager namespace O3DE::ProjectManager
{ {
@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
AZ_Assert(gemModel, "Failed to obtain GemModel"); AZ_Assert(gemModel, "Failed to obtain GemModel");
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex); QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
uint32_t numChangedDependencies = 0;
if (IsAdded(modelIndex)) if (IsAdded(modelIndex))
{ {
for (const QModelIndex& dependency : dependencies) for (const QModelIndex& dependency : dependencies)
{ {
SetIsAddedDependency(*gemModel, dependency, true); if (!IsAddedDependency(dependency))
{
SetIsAddedDependency(*gemModel, dependency, true);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
} }
} }
else else
{ {
// still a dependency if some added gem depends on this one // still a dependency if some added gem depends on this one
SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex)); bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
if (IsAddedDependency(modelIndex) != hasDependentGems)
{
SetIsAddedDependency(model, modelIndex, hasDependentGems);
}
for (const QModelIndex& dependency : dependencies) for (const QModelIndex& dependency : dependencies)
{ {
SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency)); hasDependentGems = gemModel->HasDependentGems(dependency);
if (IsAddedDependency(dependency) != hasDependentGems)
{
SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
// if the gem was already added then the state didn't really change
if (!IsAdded(dependency))
{
numChangedDependencies++;
}
}
} }
} }
gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies);
} }
void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
} }
return result; return result;
} }
} // namespace O3DE::ProjectManager } // namespace O3DE::ProjectManager

@ -77,6 +77,9 @@ namespace O3DE::ProjectManager
int TotalAddedGems(bool includeDependencies = false) const; int TotalAddedGems(bool includeDependencies = false) const;
signals:
void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
private: private:
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames); void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems); void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);

@ -47,6 +47,14 @@ namespace O3DE::ProjectManager
return tr("Missing"); return tr("Missing");
} }
virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen)
{
return false;
}
virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen)
{
}
//! Notify this screen it is the current screen //! Notify this screen it is the current screen
virtual void NotifyCurrentScreen() virtual void NotifyCurrentScreen()
{ {
@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
signals: signals:
void ChangeScreenRequest(ProjectManagerScreen screen); void ChangeScreenRequest(ProjectManagerScreen screen);
void GotoPreviousScreenRequest(); void GoToPreviousScreenRequest();
void ResetScreenRequest(ProjectManagerScreen screen); void ResetScreenRequest(ProjectManagerScreen screen);
void NotifyCurrentProject(const QString& projectPath); void NotifyCurrentProject(const QString& projectPath);
void NotifyBuildProject(const ProjectInfo& projectInfo); void NotifyBuildProject(const ProjectInfo& projectInfo);

@ -83,11 +83,28 @@ namespace O3DE::ProjectManager
bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit) bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit)
{ {
ScreenWidget* newScreen = nullptr;
const auto iterator = m_screenMap.find(screen); const auto iterator = m_screenMap.find(screen);
if (iterator != m_screenMap.end()) if (iterator != m_screenMap.end())
{
newScreen = iterator.value();
}
else
{
// Check if screen is contained by another screen
for (ScreenWidget* checkingScreen : m_screenMap)
{
if (checkingScreen->ContainsScreen(screen))
{
newScreen = checkingScreen;
break;
}
}
}
if (newScreen)
{ {
ScreenWidget* currentScreen = GetCurrentScreen(); ScreenWidget* currentScreen = GetCurrentScreen();
ScreenWidget* newScreen = iterator.value();
if (currentScreen != newScreen) if (currentScreen != newScreen)
{ {
@ -109,6 +126,11 @@ namespace O3DE::ProjectManager
newScreen->NotifyCurrentScreen(); newScreen->NotifyCurrentScreen();
if (iterator == m_screenMap.end())
{
newScreen->GoToScreen(screen);
}
return true; return true;
} }
} }
@ -116,7 +138,7 @@ namespace O3DE::ProjectManager
return false; return false;
} }
bool ScreensCtrl::GotoPreviousScreen() bool ScreensCtrl::GoToPreviousScreen()
{ {
if (!m_screenVisitOrder.isEmpty()) if (!m_screenVisitOrder.isEmpty())
{ {
@ -171,7 +193,7 @@ namespace O3DE::ProjectManager
m_screenMap.insert(screen, newScreen); m_screenMap.insert(screen, newScreen);
connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen); connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen);
connect(newScreen, &ScreenWidget::GotoPreviousScreenRequest, this, &ScreensCtrl::GotoPreviousScreen); connect(newScreen, &ScreenWidget::GoToPreviousScreenRequest, this, &ScreensCtrl::GoToPreviousScreen);
connect(newScreen, &ScreenWidget::ResetScreenRequest, this, &ScreensCtrl::ResetScreen); connect(newScreen, &ScreenWidget::ResetScreenRequest, this, &ScreensCtrl::ResetScreen);
connect(newScreen, &ScreenWidget::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject); connect(newScreen, &ScreenWidget::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject);
connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject); connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject);

@ -41,7 +41,7 @@ namespace O3DE::ProjectManager
public slots: public slots:
bool ChangeToScreen(ProjectManagerScreen screen); bool ChangeToScreen(ProjectManagerScreen screen);
bool ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit = true); bool ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit = true);
bool GotoPreviousScreen(); bool GoToPreviousScreen();
void ResetScreen(ProjectManagerScreen screen); void ResetScreen(ProjectManagerScreen screen);
void ResetAllScreens(); void ResetAllScreens();
void DeleteScreen(ProjectManagerScreen screen); void DeleteScreen(ProjectManagerScreen screen);

@ -40,6 +40,10 @@ namespace O3DE::ProjectManager
m_updateSettingsScreen = new UpdateProjectSettingsScreen(); m_updateSettingsScreen = new UpdateProjectSettingsScreen();
m_gemCatalogScreen = new GemCatalogScreen(); m_gemCatalogScreen = new GemCatalogScreen();
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, [this](ProjectManagerScreen screen){
emit ChangeScreenRequest(screen);
});
m_stack = new QStackedWidget(this); m_stack = new QStackedWidget(this);
m_stack->setObjectName("body"); m_stack->setObjectName("body");
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
@ -118,7 +122,7 @@ namespace O3DE::ProjectManager
{ {
if (UpdateProjectSettings(true)) if (UpdateProjectSettings(true))
{ {
emit GotoPreviousScreenRequest(); emit GoToPreviousScreenRequest();
} }
} }
} }

@ -404,18 +404,18 @@ namespace AZ
bool AzslCompiler::ParseSrgPopulateRootConstantData(const rapidjson::Document& input, RootConstantData& rootConstantData) const bool AzslCompiler::ParseSrgPopulateRootConstantData(const rapidjson::Document& input, RootConstantData& rootConstantData) const
{ {
if (input.HasMember("InlineConstantBuffer")) if (input.HasMember("RootConstantBuffer"))
{ {
const rapidjson::Value& rootConstantBufferValue = input["InlineConstantBuffer"]; const rapidjson::Value& rootConstantBufferValue = input["RootConstantBuffer"];
AZ_Assert(rootConstantBufferValue.IsObject(), "InlineConstantBuffer is not an object"); AZ_Assert(rootConstantBufferValue.IsObject(), "RootConstantBuffer is not an object");
for (rapidjson::Value::ConstMemberIterator itr = rootConstantBufferValue.MemberBegin(); itr != rootConstantBufferValue.MemberEnd(); ++itr) for (rapidjson::Value::ConstMemberIterator itr = rootConstantBufferValue.MemberBegin(); itr != rootConstantBufferValue.MemberEnd(); ++itr)
{ {
AZStd::string_view rootConstantBufferMemberName = itr->name.GetString(); AZStd::string_view rootConstantBufferMemberName = itr->name.GetString();
const rapidjson::Value& rootConstantBufferMemberValue = itr->value; const rapidjson::Value& rootConstantBufferMemberValue = itr->value;
if (rootConstantBufferMemberName == "bufferForInlineConstants") if (rootConstantBufferMemberName == "bufferForRootConstants")
{ {
AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForInlineConstants is not an object"); AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForRootConstants is not an object");
for (rapidjson::Value::ConstMemberIterator itr2 = rootConstantBufferMemberValue.MemberBegin(); itr2 != rootConstantBufferMemberValue.MemberEnd(); ++itr2) for (rapidjson::Value::ConstMemberIterator itr2 = rootConstantBufferMemberValue.MemberBegin(); itr2 != rootConstantBufferMemberValue.MemberEnd(); ++itr2)
{ {
@ -442,14 +442,14 @@ namespace AZ
} }
} }
} }
else if (rootConstantBufferMemberName == "inputsForInlineConstants") else if (rootConstantBufferMemberName == "inputsForRootConstants")
{ {
AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForInlineConstants is not an array"); AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForRootConstants is not an array");
for (rapidjson::Value::ConstValueIterator itr2 = rootConstantBufferMemberValue.Begin(); itr2 != rootConstantBufferMemberValue.End(); ++itr2) for (rapidjson::Value::ConstValueIterator itr2 = rootConstantBufferMemberValue.Begin(); itr2 != rootConstantBufferMemberValue.End(); ++itr2)
{ {
const rapidjson::Value& rootConstantBufferValue2 = *itr2; const rapidjson::Value& rootConstantBufferValue2 = *itr2;
AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForInlineConstants is not an object"); AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForRootConstants is not an object");
SrgConstantData rootConstantInputs; SrgConstantData rootConstantInputs;

@ -81,7 +81,7 @@ namespace AZ
// Register Shader Asset Builder // Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 104; // ATOM-15871 shaderAssetBuilderDescriptor.m_version = 105; // [AZSL] Changing inlineConstant to rootConstant keyword work.
// .shader file changes trigger rebuilds // .shader file changes trigger rebuilds
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>(); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@ -96,7 +96,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
shaderVariantAssetBuilderDescriptor.m_version = 25; // ATOM-15871 shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>(); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);

@ -183,7 +183,7 @@ namespace AZ
// access the root constants reflection // access the root constants reflection
if (!azslc.ParseSrgPopulateRootConstantData( if (!azslc.ParseSrgPopulateRootConstantData(
outcomes[AzslSubProducts::srg].GetValue(), outcomes[AzslSubProducts::srg].GetValue(),
rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section)
{ {
AZ_Error(builderName, false, "Failed to obtain root constant data reflection"); AZ_Error(builderName, false, "Failed to obtain root constant data reflection");
return AssetBuilderSDK::ProcessJobResult_Failed; return AssetBuilderSDK::ProcessJobResult_Failed;

@ -561,7 +561,7 @@ namespace AZ
// Access the root constants reflection // Access the root constants reflection
if (!azslCompiler.ParseSrgPopulateRootConstantData( if (!azslCompiler.ParseSrgPopulateRootConstantData(
jsonOutcome.GetValue(), jsonOutcome.GetValue(),
rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section)
{ {
AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to obtain root constant data reflection"); AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to obtain root constant data reflection");
return false; return false;

@ -48,6 +48,14 @@
} }
] ]
}, },
"Supervariants":
[
{
"Name": "",
"PlusArguments": "--no-alignment-validation"
}
],
"DrawList" : "forward" "DrawList" : "forward"
} }

@ -49,5 +49,13 @@
] ]
}, },
"Supervariants":
[
{
"Name": "",
"PlusArguments": "--no-alignment-validation"
}
],
"DrawList" : "forward" "DrawList" : "forward"
} }

@ -469,12 +469,12 @@
"Path": "Passes/OpaqueParent.pass" "Path": "Passes/OpaqueParent.pass"
}, },
{ {
"Name": "ThumbnailPipeline", "Name": "ToolsPipeline",
"Path": "Passes/ThumbnailPipeline.pass" "Path": "Passes/ToolsPipeline.pass"
}, },
{ {
"Name": "ThumbnailPipelineRenderToTexture", "Name": "ToolsPipelineRenderToTexture",
"Path": "Passes/ThumbnailPipelineRenderToTexture.pass" "Path": "Passes/ToolsPipelineRenderToTexture.pass"
}, },
{ {
"Name": "TransparentParentTemplate", "Name": "TransparentParentTemplate",

@ -4,7 +4,7 @@
"ClassName": "PassAsset", "ClassName": "PassAsset",
"ClassData": { "ClassData": {
"PassTemplate": { "PassTemplate": {
"Name": "ThumbnailPipeline", "Name": "ToolsPipeline",
"PassClass": "ParentPass", "PassClass": "ParentPass",
"Slots": [ "Slots": [
{ {

@ -4,7 +4,7 @@
"ClassName": "PassAsset", "ClassName": "PassAsset",
"ClassData": { "ClassData": {
"PassTemplate": { "PassTemplate": {
"Name": "ThumbnailPipelineRenderToTexture", "Name": "ToolsPipelineRenderToTexture",
"PassClass": "RenderToTexturePass", "PassClass": "RenderToTexturePass",
"PassData": { "PassData": {
"$type": "RenderToTexturePassData", "$type": "RenderToTexturePassData",
@ -15,7 +15,7 @@
"PassRequests": [ "PassRequests": [
{ {
"Name": "Pipeline", "Name": "Pipeline",
"TemplateName": "ThumbnailPipeline", "TemplateName": "ToolsPipeline",
"Connections": [ "Connections": [
{ {
"LocalSlot": "SwapChainOutput", "LocalSlot": "SwapChainOutput",

@ -37,7 +37,7 @@
[ [
{ {
"Name": "", "Name": "",
"PlusArguments": "", "PlusArguments": "--no-alignment-validation",
"MinusArguments": "--strip-unused-srgs" "MinusArguments": "--strip-unused-srgs"
} }
] ]

@ -161,8 +161,8 @@ set(FILES
Passes/LutGeneration.pass Passes/LutGeneration.pass
Passes/MainPipeline.pass Passes/MainPipeline.pass
Passes/MainPipelineRenderToTexture.pass Passes/MainPipelineRenderToTexture.pass
Passes/ThumbnailPipeline.pass Passes/ToolsPipeline.pass
Passes/ThumbnailPipelineRenderToTexture.pass Passes/ToolsPipelineRenderToTexture.pass
Passes/MeshMotionVector.pass Passes/MeshMotionVector.pass
Passes/ModulateTexture.pass Passes/ModulateTexture.pass
Passes/MorphTarget.pass Passes/MorphTarget.pass

@ -61,7 +61,7 @@ namespace AtomToolsFramework
AZ::RPI::RenderPipelineDescriptor pipelineDesc; AZ::RPI::RenderPipelineDescriptor pipelineDesc;
pipelineDesc.m_mainViewTagName = "MainCamera"; pipelineDesc.m_mainViewTagName = "MainCamera";
pipelineDesc.m_name = pipelineName; pipelineDesc.m_name = pipelineName;
pipelineDesc.m_rootPassTemplate = "MainPipelineRenderToTexture"; pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture";
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue // We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost // [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost

@ -71,6 +71,12 @@
} }
} }
], ],
"FallbackConnections": [
{
"Input": "DepthLinearInput",
"Output": "DepthLinear"
}
],
"PassRequests": [ "PassRequests": [
{ {
"Name": "HairGlobalShapeConstraintsComputePass", "Name": "HairGlobalShapeConstraintsComputePass",

@ -12,7 +12,8 @@
"SlotType": "InputOutput", "SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget" "ScopeAttachmentUsage": "RenderTarget"
}, },
{ // used for copy from MSAA to regular RT {
// used for copy from MSAA to regular RT
"Name": "RenderTargetInputOnly", "Name": "RenderTargetInputOnly",
"SlotType": "Input", "SlotType": "Input",
"ScopeAttachmentUsage": "Shader" "ScopeAttachmentUsage": "Shader"
@ -29,7 +30,7 @@
// If DepthLinear is not availbale - connect to another viewport (non MSAA) image. // If DepthLinear is not availbale - connect to another viewport (non MSAA) image.
{ {
"Name": "DepthLinearInput", "Name": "DepthLinearInput",
"SlotType": "InputOutput" "SlotType": "Input"
}, },
{ {
"Name": "DepthLinear", "Name": "DepthLinear",
@ -71,6 +72,12 @@
} }
} }
], ],
"FallbackConnections": [
{
"Input": "DepthLinearInput",
"Output": "DepthLinear"
}
],
"PassRequests": [ "PassRequests": [
{ {
"Name": "HairGlobalShapeConstraintsComputePass", "Name": "HairGlobalShapeConstraintsComputePass",
@ -257,7 +264,8 @@
"Attachment": "HairColorRenderTarget" "Attachment": "HairColorRenderTarget"
} }
}, },
{ // The final render target - this is MSAA mode RT - would it be cheaper to {
// The final render target - this is MSAA mode RT - would it be cheaper to
// use non-MSAA and then copy? // use non-MSAA and then copy?
"LocalSlot": "RenderTargetInputOutput", "LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": { "AttachmentRef": {
@ -340,7 +348,8 @@
"TemplateName": "HairShortCutResolveColorPassTemplate", "TemplateName": "HairShortCutResolveColorPassTemplate",
"Enabled": true, "Enabled": true,
"Connections": [ "Connections": [
{ // The final render target - this is MSAA mode RT - would it be cheaper to {
// The final render target - this is MSAA mode RT - would it be cheaper to
// use non-MSAA and then copy? // use non-MSAA and then copy?
"LocalSlot": "RenderTargetInputOutput", "LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": { "AttachmentRef": {

@ -144,25 +144,11 @@ namespace AZ
void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable) void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable)
{ {
return;
// [To Do] - This part should be enabled (remove the return) to reduce overhead
// when Hair is disabled / doesn't exist in the scene.
// Currently it might break features such as fog that depend on the output and for some
// reason doesn't quite work for ShortCut.
// The current overhead is minimal (< 0.1 msec) and this Gem is disabled by default.
/*
if (!m_initialized)
{
return;
}
RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName); RPI::Ptr<RPI::Pass> desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName);
if (desiredPass) if (desiredPass)
{ {
desiredPass->SetEnabled(enable); desiredPass->SetEnabled(enable);
} }
*/
} }
bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance<HairRenderObject> renderObject) bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance<HairRenderObject> renderObject)
@ -184,15 +170,13 @@ namespace AZ
void HairFeatureProcessor::UpdateHairSkinning() void HairFeatureProcessor::UpdateHairSkinning()
{ {
// Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..) // Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..)
for (auto& hairRenderObject : m_hairRenderObjects)
for (auto objIter = m_hairRenderObjects.begin(); objIter != m_hairRenderObjects.end(); ++objIter)
{ {
if (!objIter->get()->IsEnabled()) if (hairRenderObject->IsEnabled())
{ {
return; hairRenderObject->Update();
} }
objIter->get()->Update();
} }
} }

@ -209,6 +209,7 @@ namespace Blast
AZStd::shared_ptr<Physics::Shape>( AZStd::shared_ptr<Physics::Shape>(
const Physics::ColliderConfiguration&, const Physics::ShapeConfiguration&)); const Physics::ColliderConfiguration&, const Physics::ShapeConfiguration&));
MOCK_METHOD1(ReleaseNativeMeshObject, void(void*)); MOCK_METHOD1(ReleaseNativeMeshObject, void(void*));
MOCK_METHOD1(ReleaseNativeHeightfieldObject, void(void*));
MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration&)); MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration&));
MOCK_METHOD0(GetDefaultMaterial, AZStd::shared_ptr<Physics::Material>()); MOCK_METHOD0(GetDefaultMaterial, AZStd::shared_ptr<Physics::Material>());
MOCK_METHOD1( MOCK_METHOD1(

@ -597,7 +597,7 @@ namespace EMotionFX
// reset several settings to rewind the motion instance // reset several settings to rewind the motion instance
motionInstance->ResetTimes(); motionInstance->ResetTimes();
motionInstance->SetIsFrozen(false); motionInstance->SetIsFrozen(false);
SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32); SetSyncIndex(animGraphInstance, InvalidIndex);
uniqueData->SetCurrentPlayTime(motionInstance->GetCurrentTime()); uniqueData->SetCurrentPlayTime(motionInstance->GetCurrentTime());
uniqueData->SetDuration(motionInstance->GetDuration()); uniqueData->SetDuration(motionInstance->GetDuration());
uniqueData->SetPreSyncTime(uniqueData->GetCurrentPlayTime()); uniqueData->SetPreSyncTime(uniqueData->GetCurrentPlayTime());

@ -213,12 +213,12 @@ namespace EMotionFX
*/ */
virtual void SkipOutput([[maybe_unused]] AnimGraphInstance* animGraphInstance) {} virtual void SkipOutput([[maybe_unused]] AnimGraphInstance* animGraphInstance) {}
MCORE_INLINE float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); } float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); }
virtual void SetCurrentPlayTime(AnimGraphInstance* animGraphInstance, float timeInSeconds) { FindOrCreateUniqueNodeData(animGraphInstance)->SetCurrentPlayTime(timeInSeconds); } virtual void SetCurrentPlayTime(AnimGraphInstance* animGraphInstance, float timeInSeconds) { FindOrCreateUniqueNodeData(animGraphInstance)->SetCurrentPlayTime(timeInSeconds); }
virtual float GetCurrentPlayTime(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetCurrentPlayTime(); } virtual float GetCurrentPlayTime(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetCurrentPlayTime(); }
MCORE_INLINE size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); } size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); }
MCORE_INLINE void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); } void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); }
virtual void SetPlaySpeed(AnimGraphInstance* animGraphInstance, float speedFactor) { FindOrCreateUniqueNodeData(animGraphInstance)->SetPlaySpeed(speedFactor); } virtual void SetPlaySpeed(AnimGraphInstance* animGraphInstance, float speedFactor) { FindOrCreateUniqueNodeData(animGraphInstance)->SetPlaySpeed(speedFactor); }
virtual float GetPlaySpeed(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetPlaySpeed(); } virtual float GetPlaySpeed(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetPlaySpeed(); }

@ -51,52 +51,52 @@ namespace EMotionFX
void Init(AnimGraphInstance* animGraphInstance, AnimGraphNode* node); void Init(AnimGraphInstance* animGraphInstance, AnimGraphNode* node);
void Init(AnimGraphNodeData* nodeData); void Init(AnimGraphNodeData* nodeData);
MCORE_INLINE AnimGraphNode* GetNode() const { return reinterpret_cast<AnimGraphNode*>(m_object); } AnimGraphNode* GetNode() const { return reinterpret_cast<AnimGraphNode*>(m_object); }
MCORE_INLINE void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast<AnimGraphObject*>(node); } void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast<AnimGraphObject*>(node); }
MCORE_INLINE void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; } void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; }
MCORE_INLINE size_t GetSyncIndex() const { return m_syncIndex; } size_t GetSyncIndex() const { return m_syncIndex; }
MCORE_INLINE void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; } void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; }
MCORE_INLINE float GetCurrentPlayTime() const { return m_currentTime; } float GetCurrentPlayTime() const { return m_currentTime; }
MCORE_INLINE void SetPlaySpeed(float speed) { m_playSpeed = speed; } void SetPlaySpeed(float speed) { m_playSpeed = speed; }
MCORE_INLINE float GetPlaySpeed() const { return m_playSpeed; } float GetPlaySpeed() const { return m_playSpeed; }
MCORE_INLINE void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; } void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; }
MCORE_INLINE float GetDuration() const { return m_duration; } float GetDuration() const { return m_duration; }
MCORE_INLINE void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; } void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; }
MCORE_INLINE float GetPreSyncTime() const { return m_preSyncTime; } float GetPreSyncTime() const { return m_preSyncTime; }
MCORE_INLINE void SetGlobalWeight(float weight) { m_globalWeight = weight; } void SetGlobalWeight(float weight) { m_globalWeight = weight; }
MCORE_INLINE float GetGlobalWeight() const { return m_globalWeight; } float GetGlobalWeight() const { return m_globalWeight; }
MCORE_INLINE void SetLocalWeight(float weight) { m_localWeight = weight; } void SetLocalWeight(float weight) { m_localWeight = weight; }
MCORE_INLINE float GetLocalWeight() const { return m_localWeight; } float GetLocalWeight() const { return m_localWeight; }
MCORE_INLINE uint8 GetInheritFlags() const { return m_inheritFlags; } uint8 GetInheritFlags() const { return m_inheritFlags; }
MCORE_INLINE bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; } bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; }
MCORE_INLINE void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; } void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; }
MCORE_INLINE void ClearInheritFlags() { m_inheritFlags = 0; } void ClearInheritFlags() { m_inheritFlags = 0; }
MCORE_INLINE uint8 GetPoseRefCount() const { return m_poseRefCount; } uint8 GetPoseRefCount() const { return m_poseRefCount; }
MCORE_INLINE void IncreasePoseRefCount() { m_poseRefCount++; } void IncreasePoseRefCount() { m_poseRefCount++; }
MCORE_INLINE void DecreasePoseRefCount() { m_poseRefCount--; } void DecreasePoseRefCount() { m_poseRefCount--; }
MCORE_INLINE void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; } void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; }
MCORE_INLINE uint8 GetRefDataRefCount() const { return m_refDataRefCount; } uint8 GetRefDataRefCount() const { return m_refDataRefCount; }
MCORE_INLINE void IncreaseRefDataRefCount() { m_refDataRefCount++; } void IncreaseRefDataRefCount() { m_refDataRefCount++; }
MCORE_INLINE void DecreaseRefDataRefCount() { m_refDataRefCount--; } void DecreaseRefDataRefCount() { m_refDataRefCount--; }
MCORE_INLINE void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; } void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; }
MCORE_INLINE void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; } void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; }
MCORE_INLINE AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; } AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; }
MCORE_INLINE const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; } const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; }
MCORE_INLINE AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; } AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; }
MCORE_INLINE void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; } void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; }
bool GetIsMirrorMotion() const { return m_isMirrorMotion; } bool GetIsMirrorMotion() const { return m_isMirrorMotion; }
void SetIsMirrorMotion(bool newValue) { m_isMirrorMotion = newValue; } void SetIsMirrorMotion(bool newValue) { m_isMirrorMotion = newValue; }

@ -326,7 +326,7 @@ namespace EMotionFX
uniqueData->m_totalSeconds = 0.0f; uniqueData->m_totalSeconds = 0.0f;
uniqueData->m_blendProgress = 0.0f; uniqueData->m_blendProgress = 0.0f;
m_targetNode->SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32); m_targetNode->SetSyncIndex(animGraphInstance, InvalidIndex);
// Trigger action // Trigger action
for (AnimGraphTriggerAction* action : m_actionSetup.GetActions()) for (AnimGraphTriggerAction* action : m_actionSetup.GetActions())

@ -46,6 +46,7 @@ namespace Physics
} }
MOCK_METHOD2(CreateShape, AZStd::shared_ptr<Physics::Shape>(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration)); MOCK_METHOD2(CreateShape, AZStd::shared_ptr<Physics::Shape>(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration));
MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject)); MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject));
MOCK_METHOD1(ReleaseNativeHeightfieldObject, void(void* nativeMeshObject));
MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration& materialConfiguration)); MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration& materialConfiguration));
MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount)); MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount));
MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3* vertices, AZ::u32 vertexCount, AZStd::vector<AZ::u8>& result)); MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3* vertices, AZ::u32 vertexCount, AZStd::vector<AZ::u8>& result));

@ -107,7 +107,16 @@ endif()
# Tests # Tests
################################################################################ ################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME GradientSignal.Mocks HEADERONLY
NAMESPACE Gem
FILES_CMAKE
gradientsignal_mocks_files.cmake
INCLUDE_DIRECTORIES
INTERFACE
Mocks
)
ly_add_target( ly_add_target(
NAME GradientSignal.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAME GradientSignal.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem NAMESPACE Gem
@ -122,6 +131,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest AZ::AzTest
Gem::GradientSignal.Static Gem::GradientSignal.Static
Gem::LmbrCentral Gem::LmbrCentral
Gem::GradientSignal.Mocks
) )
ly_add_googletest( ly_add_googletest(
NAME Gem::GradientSignal.Tests NAME Gem::GradientSignal.Tests

@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <gmock/gmock.h>
#include <AzCore/Component/ComponentApplication.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
namespace UnitTest
{
class MockGradientRequests
: private GradientSignal::GradientRequestBus::Handler
{
public:
MockGradientRequests(AZ::EntityId entityId)
{
GradientSignal::GradientRequestBus::Handler::BusConnect(entityId);
}
~MockGradientRequests()
{
GradientSignal::GradientRequestBus::Handler::BusDisconnect();
}
MOCK_CONST_METHOD1(GetValue, float(const GradientSignal::GradientSampleParams&));
MOCK_CONST_METHOD1(IsEntityInHierarchy, bool(const AZ::EntityId&));
};
} // namespace UnitTest

@ -0,0 +1,11 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(FILES
Mocks/GradientSignal/Ebuses/MockGradientRequestBus.h
)

@ -85,6 +85,10 @@ namespace ImGui
//! Calculate the min and maximum values for the present samples. //! Calculate the min and maximum values for the present samples.
void CalcMinMaxValues(float& outMin, float& outMax); void CalcMinMaxValues(float& outMin, float& outMax);
//! Set/get color used by either the lines in case ViewType is Lines or bars in case or Histogram.
void SetBarLineColor(const ImColor& color) { m_barLineColor = color; }
ImColor GetBarLineColor() const { return m_barLineColor; }
private: private:
// Set the Max Size and clear the container // Set the Max Size and clear the container
void SetMaxSize(int size); void SetMaxSize(int size);
@ -99,6 +103,7 @@ namespace ImGui
bool m_dispalyOverlays; bool m_dispalyOverlays;
ScaleMode m_scaleMode; //! Determines if the vertical range of the histogram will be manually specified, auto-expanded or automatically scaled based on the samples. ScaleMode m_scaleMode; //! Determines if the vertical range of the histogram will be manually specified, auto-expanded or automatically scaled based on the samples.
float m_autoScaleSpeed = 0.05f; //! Indicates how fast the min max values and the visible vertical range are adapting to new samples. float m_autoScaleSpeed = 0.05f; //! Indicates how fast the min max values and the visible vertical range are adapting to new samples.
ImColor m_barLineColor = ImColor(66, 166, 178); //! Color used by either the lines in case ViewType is Lines or bars in case or Histogram.
bool m_collapsed; bool m_collapsed;
bool m_drawMostRecentValueText; bool m_drawMostRecentValueText;
}; };

@ -147,6 +147,8 @@ namespace ImGui
float imGuiHistoWidgetHeight = m_collapsed ? histogramHeight : (histogramHeight - 15); float imGuiHistoWidgetHeight = m_collapsed ? histogramHeight : (histogramHeight - 15);
if (GetSize() > 0) if (GetSize() > 0)
{ {
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, m_barLineColor.Value);
switch (m_viewType) switch (m_viewType)
{ {
default: default:
@ -160,6 +162,8 @@ namespace ImGui
ImGui::PlotLines(AZStd::string::format("##%s_lines", m_histogramName.c_str()).c_str(), ImGui::LYImGuiUtils::s_histogramContainerGetter, this, GetSize(), 0, m_histogramName.c_str(), m_minScale, m_maxScale, ImVec2(histogramWidth - 10, imGuiHistoWidgetHeight)); ImGui::PlotLines(AZStd::string::format("##%s_lines", m_histogramName.c_str()).c_str(), ImGui::LYImGuiUtils::s_histogramContainerGetter, this, GetSize(), 0, m_histogramName.c_str(), m_minScale, m_maxScale, ImVec2(histogramWidth - 10, imGuiHistoWidgetHeight));
break; break;
} }
ImGui::PopStyleColor();
} }

@ -15,12 +15,6 @@
namespace LmbrCentral namespace LmbrCentral
{ {
/// Type ID for the AxisAlignedBoxShapeComponent
static const AZ::Uuid AxisAlignedBoxShapeComponentTypeId = "{641D817E-1BC6-406A-BBB2-218541808E45}";
/// Type ID for the EditorAxisAlignedBoxShapeComponent
static const AZ::Uuid EditorAxisAlignedBoxShapeComponentTypeId = "{8C027DF6-E157-4159-9BF8-F1B925466F1F}";
/// Provide a Component interface for AxisAlignedBoxShape functionality. /// Provide a Component interface for AxisAlignedBoxShape functionality.
class AxisAlignedBoxShapeComponent class AxisAlignedBoxShapeComponent
: public AZ::Component : public AZ::Component

@ -24,6 +24,12 @@ namespace LmbrCentral
/// Type ID for the BoxShapeConfig /// Type ID for the BoxShapeConfig
static const AZ::Uuid BoxShapeConfigTypeId = "{F034FBA2-AC2F-4E66-8152-14DFB90D6283}"; static const AZ::Uuid BoxShapeConfigTypeId = "{F034FBA2-AC2F-4E66-8152-14DFB90D6283}";
/// Type ID for the AxisAlignedBoxShapeComponent
static const AZ::Uuid AxisAlignedBoxShapeComponentTypeId = "{641D817E-1BC6-406A-BBB2-218541808E45}";
/// Type ID for the EditorAxisAlignedBoxShapeComponent
static const AZ::Uuid EditorAxisAlignedBoxShapeComponentTypeId = "{8C027DF6-E157-4159-9BF8-F1B925466F1F}";
/// Configuration data for BoxShapeComponent /// Configuration data for BoxShapeComponent
class BoxShapeConfig class BoxShapeConfig
: public ShapeComponentConfig : public ShapeComponentConfig

@ -57,6 +57,14 @@ namespace Multiplayer
const AzNetworking::PacketEncodingBuffer& correction const AzNetworking::PacketEncodingBuffer& correction
) override; ) override;
//! Forcibly enables ProcessInput to execute on the entity.
//! Note that this function is quite dangerous and should normally never be used
void ForceEnableAutonomousUpdate();
//! Forcibly disables ProcessInput from executing on the entity.
//! Note that this function is quite dangerous and should normally never be used
void ForceDisableAutonomousUpdate();
//! Return true if we're currently migrating from one host to another. //! 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 //! @return boolean true if we're currently migrating from one host to another
bool IsMigrating() const; bool IsMigrating() const;

@ -32,7 +32,7 @@ namespace Multiplayer
using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>; using EntityStopEvent = AZ::Event<const ConstNetworkEntityHandle&>;
using EntityDirtiedEvent = AZ::Event<>; using EntityDirtiedEvent = AZ::Event<>;
using EntitySyncRewindEvent = AZ::Event<>; using EntitySyncRewindEvent = AZ::Event<>;
using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&, AzNetworking::ConnectionId>; using EntityServerMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&>;
using EntityPreRenderEvent = AZ::Event<float>; using EntityPreRenderEvent = AZ::Event<float>;
using EntityCorrectionEvent = AZ::Event<>; using EntityCorrectionEvent = AZ::Event<>;
@ -113,7 +113,7 @@ namespace Multiplayer
void MarkDirty(); void MarkDirty();
void NotifyLocalChanges(); void NotifyLocalChanges();
void NotifySyncRewindState(); void NotifySyncRewindState();
void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId); void NotifyServerMigration(const HostId& remoteHostId);
void NotifyPreRender(float deltaTime); void NotifyPreRender(float deltaTime);
void NotifyCorrection(); void NotifyCorrection();

@ -58,11 +58,10 @@ namespace Multiplayer
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override; void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @} //! @}
protected: private:
//! Used by @NetworkHierarchyRootComponent //! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot); void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler; AZ::ChildChangedEvent::Handler m_childChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child); void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
@ -80,5 +79,8 @@ namespace Multiplayer
bool m_isHierarchyEnabled = true; bool m_isHierarchyEnabled = true;
void NotifyChildrenHierarchyDisbanded(); void NotifyChildrenHierarchyDisbanded();
AzNetworking::ConnectionId m_previousOwningConnectionId = AzNetworking::InvalidConnectionId;
void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override;
}; };
} }

@ -30,7 +30,6 @@ namespace Multiplayer
{ {
friend class NetworkHierarchyChildComponent; friend class NetworkHierarchyChildComponent;
friend class NetworkHierarchyRootComponentController; friend class NetworkHierarchyRootComponentController;
friend class ServerToClientReplicationWindow;
public: public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase); AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
@ -61,10 +60,9 @@ namespace Multiplayer
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer); bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
protected:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private: private:
void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
AZ::ChildChangedEvent::Handler m_childChangedHandler; AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler; AZ::ParentChangedEvent::Handler m_parentChangedHandler;
@ -81,16 +79,19 @@ namespace Multiplayer
//! Rebuilds hierarchy starting from this root component's entity. //! Rebuilds hierarchy starting from this root component's entity.
void RebuildHierarchy(); void RebuildHierarchy();
//! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy. //! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy.
//! Builds the hierarchy using breadth-first iterative method. //! Builds the hierarchy using breadth-first iterative method.
void InternalBuildHierarchyList(AZ::Entity* underEntity); void InternalBuildHierarchyList(AZ::Entity* underEntity);
void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity); void SetRootForEntity(AZ::Entity* previousKnownRoot, AZ::Entity* newRoot, const AZ::Entity* childEntity);
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations. //! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true; bool m_isHierarchyEnabled = true;
AzNetworking::ConnectionId m_previousOwningConnectionId = AzNetworking::InvalidConnectionId;
void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override;
friend class HierarchyBenchmarkBase; friend class HierarchyBenchmarkBase;
}; };

@ -45,7 +45,7 @@ namespace Multiplayer
using ClientMigrationStartEvent = AZ::Event<ClientInputId>; using ClientMigrationStartEvent = AZ::Event<ClientInputId>;
using ClientMigrationEndEvent = AZ::Event<>; using ClientMigrationEndEvent = AZ::Event<>;
using ClientDisconnectedEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>;
using NotifyClientMigrationEvent = AZ::Event<const HostId&, uint64_t, ClientInputId>; using NotifyClientMigrationEvent = AZ::Event<AzNetworking::ConnectionId, const HostId&, uint64_t, ClientInputId, NetEntityId>;
using NotifyEntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&>; using NotifyEntityMigrationEvent = AZ::Event<const ConstNetworkEntityHandle&, const HostId&>;
using ConnectionAcquiredEvent = AZ::Event<MultiplayerAgentDatum>; using ConnectionAcquiredEvent = AZ::Event<MultiplayerAgentDatum>;
using ServerAcceptanceReceivedEvent = AZ::Event<>; using ServerAcceptanceReceivedEvent = AZ::Event<>;
@ -136,10 +136,12 @@ namespace Multiplayer
virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0; virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0;
//! Signals a NotifyClientMigrationEvent with the provided parameters. //! Signals a NotifyClientMigrationEvent with the provided parameters.
//! @param hostId the host id of the host the client is migrating to //! @param connectionId the connection id of the client that is migrating
//! @param userIdentifier the user identifier the client will provide the new host to validate identity //! @param hostId the host id of the host the client is migrating to
//! @param lastClientInputId the last processed clientInputId by the current host //! @param userIdentifier the user identifier the client will provide the new host to validate identity
virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; //! @param lastClientInputId the last processed clientInputId by the current host
//! @param controlledEntityId the entityId of the clients autonomous entity
virtual void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) = 0;
//! Signals a NotifyEntityMigrationEvent with the provided parameters. //! Signals a NotifyEntityMigrationEvent with the provided parameters.
//! @param entityHandle the network entity handle of the entity being migrated //! @param entityHandle the network entity handle of the entity being migrated
@ -181,6 +183,18 @@ namespace Multiplayer
//! @return pointer to the filtered entity manager, or nullptr if not set //! @return pointer to the filtered entity manager, or nullptr if not set
virtual IFilterEntityManager* GetFilterEntityManager() = 0; virtual IFilterEntityManager* GetFilterEntityManager() = 0;
//! Registers a temp userId to allow a host to look up a players controlled entity in the event of a rejoin or migration event.
//! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts
//! @param controlledEntityId the controlled entityId of the players autonomous entity
virtual void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) = 0;
//! Completes a client migration event by informing the appropriate client to migrate between hosts.
//! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts
//! @param connectionId the connection id of the player being migrated
//! @param publicHostId the public address of the new host the client should connect to
//! @param migratedClientInputId the last clientInputId processed prior to migration
virtual void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) = 0;
//! Enables or disables automatic instantiation of netbound entities. //! Enables or disables automatic instantiation of netbound entities.
//! This setting is controlled by the networking layer and should not be touched //! This setting is controlled by the networking layer and should not be touched
//! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server //! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server

@ -29,7 +29,7 @@ namespace Multiplayer
using HostId = AzNetworking::IpAddress; using HostId = AzNetworking::IpAddress;
static const HostId InvalidHostId = HostId(); static const HostId InvalidHostId = HostId();
AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint32_t); AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint64_t);
static constexpr NetEntityId InvalidNetEntityId = static_cast<NetEntityId>(-1); static constexpr NetEntityId InvalidNetEntityId = static_cast<NetEntityId>(-1);
AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t); AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t);
@ -68,6 +68,7 @@ namespace Multiplayer
Server, // A simulated proxy on a server Server, // A simulated proxy on a server
Authority // An authoritative proxy on a server (full authority) Authority // An authoritative proxy on a server (full authority)
}; };
const char* GetEnumString(NetEntityRole value);
enum class ComponentSerializationType : uint8_t enum class ComponentSerializationType : uint8_t
{ {
@ -113,6 +114,24 @@ namespace Multiplayer
bool Serialize(AzNetworking::ISerializer& serializer); bool Serialize(AzNetworking::ISerializer& serializer);
}; };
inline const char* GetEnumString(NetEntityRole value)
{
switch (value)
{
case NetEntityRole::InvalidRole:
return "InvalidRole";
case NetEntityRole::Client:
return "Client";
case NetEntityRole::Autonomous:
return "Autonomous";
case NetEntityRole::Server:
return "Server";
case NetEntityRole::Authority:
return "Authority";
}
return "Unknown";
}
inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset) inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset)
: m_prefabName(name) : m_prefabName(name)
, m_entityOffset(entityOffset) , m_entityOffset(entityOffset)

@ -57,7 +57,6 @@ namespace Multiplayer
EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode); EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode);
~EntityReplicationManager() = default; ~EntityReplicationManager() = default;
void SetRemoteHostId(const HostId& hostId);
const HostId& GetRemoteHostId() const; const HostId& GetRemoteHostId() const;
void ActivatePendingEntities(); void ActivatePendingEntities();

@ -43,8 +43,7 @@ namespace Multiplayer
//! Constructor for an entity delete message. //! Constructor for an entity delete message.
//! @param entityId the networkId of the entity being deleted //! @param entityId the networkId of the entity being deleted
//! @param isMigrated whether or not the entity is being migrated or deleted //! @param isMigrated whether or not the entity is being migrated or deleted
//! @param takeOwnership true if the remote replicator should take ownership of the entity explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated);
explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated, bool takeOwnership);
NetworkEntityUpdateMessage& operator =(NetworkEntityUpdateMessage&& rhs); NetworkEntityUpdateMessage& operator =(NetworkEntityUpdateMessage&& rhs);
NetworkEntityUpdateMessage& operator =(const NetworkEntityUpdateMessage& rhs); NetworkEntityUpdateMessage& operator =(const NetworkEntityUpdateMessage& rhs);
@ -71,10 +70,6 @@ namespace Multiplayer
//! @return whether or not the entity was migrated //! @return whether or not the entity was migrated
bool GetWasMigrated() const; bool GetWasMigrated() const;
//! Gets the current value of TakeOwnership.
//! @return the current value of TakeOwnership
bool GetTakeOwnership() const;
//! Gets the current value of HasValidPrefabId. //! Gets the current value of HasValidPrefabId.
//! @return the current value of HasValidPrefabId //! @return the current value of HasValidPrefabId
bool GetHasValidPrefabId() const; bool GetHasValidPrefabId() const;
@ -110,7 +105,6 @@ namespace Multiplayer
NetEntityId m_entityId = InvalidNetEntityId; NetEntityId m_entityId = InvalidNetEntityId;
bool m_isDelete = false; bool m_isDelete = false;
bool m_wasMigrated = false; bool m_wasMigrated = false;
bool m_takeOwnership = false;
bool m_hasValidPrefabId = false; bool m_hasValidPrefabId = false;
PrefabEntityId m_prefabEntityId; PrefabEntityId m_prefabEntityId;

@ -13,9 +13,9 @@
<Include File="Multiplayer/MultiplayerTypes.h"/> <Include File="Multiplayer/MultiplayerTypes.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInput.h"/> <Include File="Multiplayer/NetworkInput/NetworkInput.h"/>
<Include File="Source/NetworkInput/NetworkInputArray.h"/> <Include File="Multiplayer/NetworkInput/NetworkInputArray.h"/>
<Include File="Source/NetworkInput/NetworkInputHistory.h"/> <Include File="Multiplayer/NetworkInput/NetworkInputHistory.h"/>
<Include File="Source/NetworkInput/NetworkInputMigrationVector.h"/> <Include File="Multiplayer/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="AzNetworking/DataStructures/ByteBuffer.h"/> <Include File="AzNetworking/DataStructures/ByteBuffer.h"/>
<NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Server" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="false" /> <NetworkProperty Type="Multiplayer::ClientInputId" Name="LastInputId" Init="Multiplayer::ClientInputId{ 0 }" ReplicateFrom="Authority" ReplicateTo="Server" IsRewindable="false" IsPredictable="false" IsPublic="false" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="false" />

@ -9,6 +9,7 @@
<Packet Name="Connect" HandshakePacket="true" Desc="Client connection packet, on success the server will reply with an Accept"> <Packet Name="Connect" HandshakePacket="true" Desc="Client connection packet, on success the server will reply with an Accept">
<Member Type="uint16_t" Name="networkProtocolVersion" Init="0" /> <Member Type="uint16_t" Name="networkProtocolVersion" Init="0" />
<Member Type="uint64_t" Name="temporaryUserId" Init="0" />
<Member Type="Multiplayer::LongNetworkString" Name="ticket" /> <Member Type="Multiplayer::LongNetworkString" Name="ticket" />
</Packet> </Packet>

@ -8,7 +8,7 @@
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h" OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Include File="Source/NetworkInput/NetworkInputChild.h"/> <Include File="Multiplayer/NetworkInput/NetworkInputChild.h"/>
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" /> <ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />

@ -354,6 +354,16 @@ namespace Multiplayer
} }
} }
void LocalPredictionPlayerInputComponentController::ForceEnableAutonomousUpdate()
{
m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true);
}
void LocalPredictionPlayerInputComponentController::ForceDisableAutonomousUpdate()
{
m_autonomousUpdateEvent.RemoveFromQueue();
}
bool LocalPredictionPlayerInputComponentController::IsMigrating() const bool LocalPredictionPlayerInputComponentController::IsMigrating() const
{ {
return m_lastMigratedInputId != ClientInputId{ 0 }; return m_lastMigratedInputId != ClientInputId{ 0 };

@ -394,9 +394,9 @@ namespace Multiplayer
m_syncRewindEvent.Signal(); m_syncRewindEvent.Signal();
} }
void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId) void NetBindComponent::NotifyServerMigration(const HostId& remoteHostId)
{ {
m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); m_entityServerMigrationEvent.Signal(m_netEntityHandle, remoteHostId);
} }
void NetBindComponent::NotifyPreRender(float deltaTime) void NetBindComponent::NotifyPreRender(float deltaTime)

@ -129,34 +129,48 @@ namespace Multiplayer
handler.Connect(m_networkHierarchyLeaveEvent); handler.Connect(m_networkHierarchyLeaveEvent);
} }
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot)
{ {
if (m_rootEntity != hierarchyRoot) if (newHierarchyRoot)
{ {
m_rootEntity = hierarchyRoot; if (m_rootEntity != newHierarchyRoot)
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{ {
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController()); m_rootEntity = newHierarchyRoot;
if (m_rootEntity)
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{ {
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId()); const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId); controller->SetHierarchyRoot(netRootId);
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
} }
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
m_networkHierarchyLeaveEvent.Signal(); GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
} m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
} }
}
else if ((previousHierarchyRoot && m_rootEntity == previousHierarchyRoot) || !previousHierarchyRoot)
{
m_rootEntity = nullptr;
if (m_rootEntity == nullptr) if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{ {
NotifyChildrenHierarchyDisbanded(); NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
controller->SetHierarchyRoot(InvalidNetEntityId);
} }
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_networkHierarchyLeaveEvent.Signal();
NotifyChildrenHierarchyDisbanded();
}
}
void NetworkHierarchyChildComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId)
{
NetworkHierarchyChildComponentBase::SetOwningConnectionId(connectionId);
if (IsHierarchicalChild() == false)
{
m_previousOwningConnectionId = connectionId;
} }
} }
@ -180,14 +194,18 @@ namespace Multiplayer
if (m_rootEntity != newRoot) if (m_rootEntity != newRoot)
{ {
m_rootEntity = newRoot; m_rootEntity = newRoot;
m_previousOwningConnectionId = GetNetBindComponent()->GetOwningConnectionId();
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId()); m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
} }
} }
else else
{ {
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_isHierarchyEnabled = false; m_isHierarchyEnabled = false;
m_rootEntity = nullptr; m_rootEntity = nullptr;
m_networkHierarchyLeaveEvent.Signal();
} }
} }
@ -203,11 +221,11 @@ namespace Multiplayer
{ {
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>()) if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{ {
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr); hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr, nullptr);
} }
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>()) else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{ {
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr); hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr, nullptr);
} }
} }
} }

@ -107,7 +107,7 @@ namespace Multiplayer
{ {
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId)) if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{ {
SetRootForEntity(nullptr, childEntity); SetRootForEntity(GetEntity(), nullptr, childEntity);
} }
} }
} }
@ -209,7 +209,7 @@ namespace Multiplayer
auto [rootComponent, childComponent] = GetHierarchyComponents(parentEntity); auto [rootComponent, childComponent] = GetHierarchyComponents(parentEntity);
if (rootComponent == nullptr && childComponent == nullptr) if (rootComponent == nullptr && childComponent == nullptr)
{ {
RebuildHierarchy(); SetRootForEntity(nullptr, nullptr, GetEntity());
} }
else else
{ {
@ -219,7 +219,7 @@ namespace Multiplayer
else else
{ {
// Detached from parent // Detached from parent
RebuildHierarchy(); SetRootForEntity(nullptr, nullptr, GetEntity());
} }
} }
@ -247,14 +247,14 @@ namespace Multiplayer
{ {
// This is a newly added entity to the network hierarchy. // This is a newly added entity to the network hierarchy.
hierarchyChanged = true; hierarchyChanged = true;
SetRootForEntity(GetEntity(), currentEntity); SetRootForEntity(nullptr, GetEntity(), currentEntity);
} }
} }
// These entities were removed since last rebuild. // These entities were removed since last rebuild.
for (const AZ::Entity* previousEntity : previousEntities) for (const AZ::Entity* previousEntity : previousEntities)
{ {
SetRootForEntity(nullptr, previousEntity); SetRootForEntity(GetEntity(), nullptr, previousEntity);
} }
if (!previousEntities.empty()) if (!previousEntities.empty())
@ -307,45 +307,66 @@ namespace Multiplayer
} }
} }
void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity) void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* previousKnownRoot, AZ::Entity* newRoot, const AZ::Entity* childEntity)
{ {
auto [hierarchyRootComponent, hierarchyChildComponent] = GetHierarchyComponents(childEntity); auto [hierarchyRootComponent, hierarchyChildComponent] = GetHierarchyComponents(childEntity);
if (hierarchyChildComponent) if (hierarchyChildComponent)
{ {
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root); hierarchyChildComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot);
} }
else if (hierarchyRootComponent) else if (hierarchyRootComponent)
{ {
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root); hierarchyRootComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot);
} }
} }
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot) void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot)
{ {
m_rootEntity = hierarchyRoot; if (newHierarchyRoot)
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{ {
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController()); if (m_rootEntity != newHierarchyRoot)
if (hierarchyRoot)
{ {
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId()); m_rootEntity = newHierarchyRoot;
controller->SetHierarchyRoot(netRootId);
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyRootComponentController* controller = static_cast<NetworkHierarchyRootComponentController*>(GetController());
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId);
}
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
} }
else }
else if ((previousHierarchyRoot && m_rootEntity == previousHierarchyRoot) || !previousHierarchyRoot)
{
m_rootEntity = nullptr;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{ {
NetworkHierarchyRootComponentController* controller = static_cast<NetworkHierarchyRootComponentController*>(GetController());
controller->SetHierarchyRoot(InvalidNetEntityId); controller->SetHierarchyRoot(InvalidNetEntityId);
} }
}
if (m_rootEntity == nullptr) GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
{ m_networkHierarchyLeaveEvent.Signal();
// We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy. // We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy.
RebuildHierarchy(); RebuildHierarchy();
} }
} }
void NetworkHierarchyRootComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId)
{
NetworkHierarchyRootComponentBase::SetOwningConnectionId(connectionId);
if (IsHierarchicalChild() == false)
{
m_previousOwningConnectionId = connectionId;
}
}
NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent) NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent)
: NetworkHierarchyRootComponentControllerBase(parent) : NetworkHierarchyRootComponentControllerBase(parent)
{ {
@ -370,7 +391,7 @@ namespace Multiplayer
void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime) void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime)
{ {
NetworkHierarchyRootComponent& component = GetParent(); NetworkHierarchyRootComponent& component = GetParent();
if(!component.IsHierarchicalRoot()) if (!component.IsHierarchicalRoot())
{ {
return; return;
} }
@ -386,7 +407,7 @@ namespace Multiplayer
for (AZ::Entity* child : entities) for (AZ::Entity* child : entities)
{ {
if(child == component.GetEntity()) if (child == component.GetEntity())
{ {
continue; // Avoid infinite recursion continue; // Avoid infinite recursion
} }

@ -23,22 +23,16 @@ namespace Multiplayer
ServerToClientConnectionData::ServerToClientConnectionData ServerToClientConnectionData::ServerToClientConnectionData
( (
AzNetworking::IConnection* connection, AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener, AzNetworking::IConnectionListener& connectionListener
NetworkEntityHandle controlledEntity
) )
: m_connection(connection) : m_connection(connection)
, m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); }) , m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); })
, m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); }) , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
, m_controlledEntity(controlledEntity) {
OnControlledEntityMigration(entityHandle, remoteHostId);
})
, m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient) , m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient)
{ {
NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent();
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount); m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount);
m_entityReplicationManager.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs); m_entityReplicationManager.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs);
} }
@ -54,6 +48,20 @@ namespace Multiplayer
m_controlledEntityRemovedHandler.Disconnect(); m_controlledEntityRemovedHandler.Disconnect();
} }
void ServerToClientConnectionData::SetControlledEntity(NetworkEntityHandle primaryPlayerEntity)
{
m_controlledEntityRemovedHandler.Disconnect();
m_controlledEntityMigrationHandler.Disconnect();
m_controlledEntity = primaryPlayerEntity;
NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent();
if (netBindComponent != nullptr)
{
netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler);
netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler);
}
}
ConnectionDataType ServerToClientConnectionData::GetConnectionDataType() const ConnectionDataType ServerToClientConnectionData::GetConnectionDataType() const
{ {
return ConnectionDataType::ServerToClient; return ConnectionDataType::ServerToClient;
@ -94,8 +102,7 @@ namespace Multiplayer
void ServerToClientConnectionData::OnControlledEntityMigration void ServerToClientConnectionData::OnControlledEntityMigration
( (
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle,
[[maybe_unused]] const HostId& remoteHostId, const HostId& remoteHostId
[[maybe_unused]] AzNetworking::ConnectionId connectionId
) )
{ {
ClientInputId migratedClientInputId = ClientInputId{ 0 }; ClientInputId migratedClientInputId = ClientInputId{ 0 };
@ -109,14 +116,12 @@ namespace Multiplayer
} }
// Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration // Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration
const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64(); const uint64_t temporaryUserIdentifier = AzNetworking::CryptoRand64();
// Tell the new host that a client is about to (re)join // Tell the new host that a client is about to (re)join
GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId); GetMultiplayer()->SendNotifyClientMigrationEvent(GetConnection()->GetConnectionId(), remoteHostId, temporaryUserIdentifier, migratedClientInputId, m_controlledEntity.GetNetEntityId());
// We need to send a MultiplayerPackets::ClientMigration packet to complete this process
// Tell the client who to join // This happens inside MultiplayerSystemComponent, once we're certain the remote host has appropriately prepared
MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId);
GetConnection()->SendReliablePacket(clientMigration);
m_controlledEntity = NetworkEntityHandle(); m_controlledEntity = NetworkEntityHandle();
m_canSendUpdates = false; m_canSendUpdates = false;

@ -20,11 +20,12 @@ namespace Multiplayer
ServerToClientConnectionData ServerToClientConnectionData
( (
AzNetworking::IConnection* connection, AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener, AzNetworking::IConnectionListener& connectionListener
NetworkEntityHandle controlledEntity
); );
~ServerToClientConnectionData() override; ~ServerToClientConnectionData() override;
void SetControlledEntity(NetworkEntityHandle primaryPlayerEntity);
//! IConnectionData interface //! IConnectionData interface
//! @{ //! @{
ConnectionDataType GetConnectionDataType() const override; ConnectionDataType GetConnectionDataType() const override;
@ -44,7 +45,7 @@ namespace Multiplayer
private: private:
void OnControlledEntityRemove(); void OnControlledEntityRemove();
void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId);
void OnGameplayStarted(); void OnGameplayStarted();
EntityReplicationManager m_entityReplicationManager; EntityReplicationManager m_entityReplicationManager;

@ -18,7 +18,6 @@ namespace Multiplayer
m_canSendUpdates = canSendUpdates; m_canSendUpdates = canSendUpdates;
} }
inline NetworkEntityHandle ServerToClientConnectionData::GetPrimaryPlayerEntity() inline NetworkEntityHandle ServerToClientConnectionData::GetPrimaryPlayerEntity()
{ {
return m_controlledEntity; return m_controlledEntity;

@ -74,7 +74,7 @@ namespace Multiplayer
{ {
ImGui::Text("%s", entity->GetId().ToString().c_str()); ImGui::Text("%s", entity->GetId().ToString().c_str());
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%u", GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId())); ImGui::Text("%llu", static_cast<AZ::u64>(GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId())));
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Text("%s", entity->GetName().c_str()); ImGui::Text("%s", entity->GetName().c_str());
ImGui::NextColumn(); ImGui::NextColumn();

@ -28,24 +28,29 @@ namespace Multiplayer
->Version(1); ->Version(1);
} }
} }
void MultiplayerDebugSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) void MultiplayerDebugSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{ {
provided.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); provided.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent"));
} }
void MultiplayerDebugSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) void MultiplayerDebugSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{ {
; ;
} }
void MultiplayerDebugSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile) void MultiplayerDebugSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile)
{ {
incompatbile.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); incompatbile.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent"));
} }
void MultiplayerDebugSystemComponent::Activate() void MultiplayerDebugSystemComponent::Activate()
{ {
#ifdef IMGUI_ENABLED #ifdef IMGUI_ENABLED
ImGui::ImGuiUpdateListenerBus::Handler::BusConnect(); ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
#endif #endif
} }
void MultiplayerDebugSystemComponent::Deactivate() void MultiplayerDebugSystemComponent::Deactivate()
{ {
#ifdef IMGUI_ENABLED #ifdef IMGUI_ENABLED
@ -75,6 +80,7 @@ namespace Multiplayer
ImGui::EndMenu(); ImGui::EndMenu();
} }
} }
void AccumulatePerSecondValues(const MultiplayerStats& stats, const MultiplayerStats::Metric& metric, float& outCallsPerSecond, float& outBytesPerSecond) void AccumulatePerSecondValues(const MultiplayerStats& stats, const MultiplayerStats::Metric& metric, float& outCallsPerSecond, float& outBytesPerSecond)
{ {
uint64_t summedCalls = 0; uint64_t summedCalls = 0;
@ -107,6 +113,7 @@ namespace Multiplayer
ImGui::Text("%11.2f", bytesPerSecond); ImGui::Text("%11.2f", bytesPerSecond);
return open; return open;
} }
bool DrawSummaryRow(const char* name, const MultiplayerStats& stats) bool DrawSummaryRow(const char* name, const MultiplayerStats& stats)
{ {
const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics(); const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics();
@ -123,6 +130,7 @@ namespace Multiplayer
AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond);
return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond);
} }
bool DrawComponentRow(const char* name, const MultiplayerStats& stats, NetComponentId netComponentId) bool DrawComponentRow(const char* name, const MultiplayerStats& stats, NetComponentId netComponentId)
{ {
const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId); const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId);
@ -139,6 +147,7 @@ namespace Multiplayer
AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond);
return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond);
} }
void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId) void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId)
{ {
MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry();
@ -503,4 +512,3 @@ void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth)
AZ::Interface<Multiplayer::IMultiplayerDebug>::Get()->HideEntityBandwidthDebugOverlay(); AZ::Interface<Multiplayer::IMultiplayerDebug>::Get()->HideEntityBandwidthDebugOverlay();
} }
} }

@ -8,6 +8,7 @@
#include <Multiplayer/MultiplayerConstants.h> #include <Multiplayer/MultiplayerConstants.h>
#include <Multiplayer/Components/MultiplayerComponent.h> #include <Multiplayer/Components/MultiplayerComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <MultiplayerSystemComponent.h> #include <MultiplayerSystemComponent.h>
#include <ConnectionData/ClientToServerConnectionData.h> #include <ConnectionData/ClientToServerConnectionData.h>
#include <ConnectionData/ServerToClientConnectionData.h> #include <ConnectionData/ServerToClientConnectionData.h>
@ -76,6 +77,7 @@ namespace Multiplayer
"The address of the remote server or host to connect to"); "The address of the remote server or host to connect to");
AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic");
AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic");
AZ_CVAR(uint16_t, sv_portRange, 999, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The range of ports the host will incrementally attempt to bind to when initializing");
AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load"); AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load");
AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking");
AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server");
@ -168,6 +170,7 @@ namespace Multiplayer
AZ::ConsoleFunctorFlags flags, AZ::ConsoleFunctorFlags flags,
AZ::ConsoleInvokedFrom invokedFrom AZ::ConsoleInvokedFrom invokedFrom
) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); })
, m_autonomousEntityReplicatorCreatedHandler([this]([[maybe_unused]] NetEntityId netEntityId) { OnAutonomousEntityReplicatorCreated(); })
{ {
AZ::Interface<IMultiplayer>::Register(this); AZ::Interface<IMultiplayer>::Register(this);
} }
@ -205,8 +208,23 @@ namespace Multiplayer
bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated) bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated)
{ {
InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer); if (port != sv_port)
return m_networkInterface->Listen(port); {
sv_port = port;
}
const uint16_t maxPort = sv_port + sv_portRange;
while (sv_port <= maxPort)
{
if (m_networkInterface->Listen(sv_port))
{
InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer);
return true;
}
AZLOG_WARN("Failed to start listening on port %u, port is in use?", static_cast<uint32_t>(sv_port));
sv_port = sv_port + 1;
}
return false;
} }
bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port) bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port)
@ -328,6 +346,11 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{ {
if (bg_multiplayerDebugDraw)
{
m_networkEntityManager.DebugDraw();
}
const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f)); const AZ::TimeMs deltaTimeMs = aznumeric_cast<AZ::TimeMs>(static_cast<int32_t>(deltaTime * 1000.0f));
const AZ::TimeMs serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs); const AZ::TimeMs serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs);
const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f; const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f;
@ -412,11 +435,6 @@ namespace Multiplayer
{ {
m_networkInterface->GetConnectionSet().VisitConnections(visitor); m_networkInterface->GetConnectionSet().VisitConnections(visitor);
} }
if (bg_multiplayerDebugDraw)
{
m_networkEntityManager.DebugDraw();
}
} }
int MultiplayerSystemComponent::GetTickOrder() int MultiplayerSystemComponent::GetTickOrder()
@ -487,17 +505,39 @@ namespace Multiplayer
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor); m_networkInterface->GetConnectionSet().VisitConnections(visitor);
return true; return true;
} }
} }
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str()); reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str());
// Hosts will spawn a new default player prefab for the user that just connected
if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
{
// We use a temporary userId over the clients address so we can maintain client lookups even in the event of wifi handoff
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(packet.GetTemporaryUserId());
EnableAutonomousControl(controlledEntity, connection->GetConnectionId());
ServerToClientConnectionData* connectionData = reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData());
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
connectionData->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
connectionData->SetControlledEntity(controlledEntity);
// If this is a migrate or rejoin, immediately ready the connection for updates
if (packet.GetTemporaryUserId() != 0)
{
connectionData->SetCanSendUpdates(true);
}
}
if (connection->SendReliablePacket(MultiplayerPackets::Accept(sv_map))) if (connection->SendReliablePacket(MultiplayerPackets::Accept(sv_map)))
{ {
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetDidHandshake(true); reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
if (packet.GetTemporaryUserId() == 0)
// Sync our console {
ConsoleReplicator consoleReplicator(connection); // Sync our console
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); }); ConsoleReplicator consoleReplicator(connection);
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
}
return true; return true;
} }
return false; return false;
@ -511,10 +551,26 @@ namespace Multiplayer
) )
{ {
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->SetDidHandshake(true); reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); if (m_temporaryUserIdentifier == 0)
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str()); {
AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str()); AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
}
else
{
// Bypass map loading and immediately ready the connection for updates
IConnectionData* connectionData = reinterpret_cast<IConnectionData*>(connection->GetUserData());
if (connectionData)
{
connectionData->SetCanSendUpdates(true);
// @nt: TODO - delete once dropped RPC problem fixed
// Connection has migrated, we are now waiting for the autonomous entity replicator to be created
connectionData->GetReplicationManager().AddAutonomousEntityReplicatorCreatedHandler(m_autonomousEntityReplicatorCreatedHandler);
}
}
m_serverAcceptanceReceivedEvent.Signal(); m_serverAcceptanceReceivedEvent.Signal();
return true; return true;
@ -637,13 +693,17 @@ namespace Multiplayer
// Store the temporary user identifier so we can transmit it with our next Connect packet // Store the temporary user identifier so we can transmit it with our next Connect packet
// The new server will use this to re-attach our set of autonomous entities // The new server will use this to re-attach our set of autonomous entities
m_temporaryUserIdentifier = packet.GetTemporaryUserIdentifier();
// Disconnect our existing server connection // Disconnect our existing server connection
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); }; auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor); m_networkInterface->GetConnectionSet().VisitConnections(visitor);
AZLOG_INFO("Migrating to new server shard"); AZLOG_INFO("Migrating to new server shard");
m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId()); m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId());
m_networkInterface->Connect(packet.GetRemoteServerAddress()); if (m_networkInterface->Connect(packet.GetRemoteServerAddress()) == AzNetworking::InvalidConnectionId)
{
AZLOG_ERROR("Failed to connect to new host during client migration event");
}
return true; return true;
} }
@ -673,7 +733,7 @@ namespace Multiplayer
providerTicket = m_pendingConnectionTickets.front(); providerTicket = m_pendingConnectionTickets.front();
m_pendingConnectionTickets.pop(); m_pendingConnectionTickets.pop();
} }
connection->SendReliablePacket(MultiplayerPackets::Connect(0, providerTicket.c_str())); connection->SendReliablePacket(MultiplayerPackets::Connect(0, m_temporaryUserIdentifier, providerTicket.c_str()));
} }
else else
{ {
@ -681,29 +741,10 @@ namespace Multiplayer
m_connectionAcquiredEvent.Signal(datum); m_connectionAcquiredEvent.Signal(datum);
} }
// Hosts will spawn a new default player prefab for the user that just connected
if (GetAgentType() == MultiplayerAgentType::ClientServer if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer) || GetAgentType() == MultiplayerAgentType::DedicatedServer)
{ {
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab(); connection->SetUserData(new ServerToClientConnectionData(connection, *this));
for (auto& netEntity : entityList)
{
if (netEntity.Exists())
{
netEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId());
}
netEntity.Activate();
}
NetworkEntityHandle controlledEntity;
if (entityList.size() > 0)
{
controlledEntity = entityList[0];
}
connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity));
AZStd::unique_ptr<IReplicationWindow> window = AZStd::make_unique<ServerToClientReplicationWindow>(controlledEntity, connection);
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window));
} }
else else
{ {
@ -725,9 +766,9 @@ namespace Multiplayer
void MultiplayerSystemComponent::OnDisconnect(AzNetworking::IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint) void MultiplayerSystemComponent::OnDisconnect(AzNetworking::IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint)
{ {
const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remote host disconnected"; const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remotely disconnected";
AZStd::string reasonString = ToString(reason); AZStd::string reasonString = ToString(reason);
AZLOG_INFO("%s due to %s from remote address: %s", endpointString, reasonString.c_str(), connection->GetRemoteAddress().GetString().c_str()); AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str());
// The client is disconnecting // The client is disconnecting
if (GetAgentType() == MultiplayerAgentType::Client) if (GetAgentType() == MultiplayerAgentType::Client)
@ -809,16 +850,8 @@ namespace Multiplayer
// Spawn the default player for this host since the host is also a player (not a dedicated server) // Spawn the default player for this host since the host is also a player (not a dedicated server)
if (m_agentType == MultiplayerAgentType::ClientServer) if (m_agentType == MultiplayerAgentType::ClientServer)
{ {
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab(); NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(0);
EnableAutonomousControl(controlledEntity, AzNetworking::InvalidConnectionId);
for (NetworkEntityHandle controlledEntity : entityList)
{
if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent())
{
controlledEntityNetBindComponent->SetAllowAutonomy(true);
}
controlledEntity.Activate();
}
} }
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
@ -869,9 +902,9 @@ namespace Multiplayer
handler.Connect(m_shutdownEvent); handler.Connect(m_shutdownEvent);
} }
void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId)
{ {
m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); m_notifyClientMigrationEvent.Signal(connectionId, hostId, userIdentifier, lastClientInputId, controlledEntityId);
} }
void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
@ -925,6 +958,22 @@ namespace Multiplayer
return m_filterEntityManager; return m_filterEntityManager;
} }
void MultiplayerSystemComponent::RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId)
{
m_playerRejoinData[temporaryUserIdentifier] = controlledEntityId;
}
void MultiplayerSystemComponent::CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId)
{
IConnection* connection = m_networkInterface->GetConnectionSet().GetConnection(connectionId);
if (connection != nullptr) // Make sure the player has not disconnected since the start of migration
{
// Tell the client who to join
MultiplayerPackets::ClientMigration clientMigration(publicHostId, temporaryUserIdentifier, migratedClientInputId);
connection->SendReliablePacket(clientMigration);
}
}
void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value) void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value)
{ {
m_spawnNetboundEntities = value; m_spawnNetboundEntities = value;
@ -1055,6 +1104,13 @@ namespace Multiplayer
m_cvarCommands.PushBackItem(AZStd::move(replicateString)); m_cvarCommands.PushBackItem(AZStd::move(replicateString));
} }
void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated()
{
m_autonomousEntityReplicatorCreatedHandler.Disconnect();
//m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 });
m_clientMigrationEndEvent.Signal();
}
void MultiplayerSystemComponent::ExecuteConsoleCommandList(IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands) void MultiplayerSystemComponent::ExecuteConsoleCommandList(IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands)
{ {
AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get(); AZ::IConsole* console = AZ::Interface<AZ::IConsole>::Get();
@ -1066,19 +1122,69 @@ namespace Multiplayer
} }
} }
INetworkEntityManager::EntityList MultiplayerSystemComponent::SpawnDefaultPlayerPrefab() NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier)
{ {
const auto node = m_playerRejoinData.find(temporaryUserIdentifier);
if (node != m_playerRejoinData.end())
{
return m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second);
}
PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str())); PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast<AZ::CVarFixedString>(sv_defaultPlayerSpawnAsset).c_str()));
INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate); INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate);
return entityList; for (NetworkEntityHandle subEntity : entityList)
{
subEntity.Activate();
}
NetworkEntityHandle controlledEntity;
if (!entityList.empty())
{
controlledEntity = entityList[0];
}
return controlledEntity;
}
void MultiplayerSystemComponent::EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId)
{
if (!entityHandle.Exists())
{
AZLOG_WARN("Attempting to enable autonomous control for an invalid entity");
return;
}
entityHandle.GetNetBindComponent()->SetOwningConnectionId(connectionId);
if (connectionId == InvalidConnectionId)
{
entityHandle.GetNetBindComponent()->SetAllowAutonomy(true);
}
auto* hierarchyComponent = entityHandle.FindComponent<NetworkHierarchyRootComponent>();
if (hierarchyComponent != nullptr)
{
for (AZ::Entity* subEntity : hierarchyComponent->GetHierarchicalEntities())
{
NetworkEntityHandle subEntityHandle = NetworkEntityHandle(subEntity);
NetBindComponent* subEntityNetBindComponent = subEntityHandle.GetNetBindComponent();
if (subEntityNetBindComponent != nullptr)
{
subEntityNetBindComponent->SetOwningConnectionId(connectionId);
if (connectionId == InvalidConnectionId)
{
subEntityNetBindComponent->SetAllowAutonomy(true);
}
}
}
}
} }
void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{ {
if (!AZ::Interface<IMultiplayer>::Get()->StartHosting(sv_port, sv_isDedicated)) if (!AZ::Interface<IMultiplayer>::Get()->StartHosting(sv_port, sv_isDedicated))
{ {
AZLOG_ERROR("Failed to start listening on port %u, port is in use?", static_cast<uint32_t>(sv_port)); AZLOG_ERROR("Failed to start listening on any allocated port");
} }
} }
AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to");

@ -123,7 +123,7 @@ namespace Multiplayer
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override;
void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override; void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override;
void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) override;
void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override;
void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override; AZ::TimeMs GetCurrentHostTimeMs() const override;
@ -132,6 +132,8 @@ namespace Multiplayer
INetworkEntityManager* GetNetworkEntityManager() override; INetworkEntityManager* GetNetworkEntityManager() override;
void SetFilterEntityManager(IFilterEntityManager* entityFilter) override; void SetFilterEntityManager(IFilterEntityManager* entityFilter) override;
IFilterEntityManager* GetFilterEntityManager() override; IFilterEntityManager* GetFilterEntityManager() override;
void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) override;
void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) override;
void SetShouldSpawnNetworkEntities(bool value) override; void SetShouldSpawnNetworkEntities(bool value) override;
bool GetShouldSpawnNetworkEntities() const override; bool GetShouldSpawnNetworkEntities() const override;
//! @} //! @}
@ -145,9 +147,11 @@ namespace Multiplayer
void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds);
void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom);
void OnAutonomousEntityReplicatorCreated();
void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector<Multiplayer::LongNetworkString, 32>& commands);
INetworkEntityManager::EntityList SpawnDefaultPlayerPrefab(); NetworkEntityHandle SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier);
void EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId);
AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session");
AzNetworking::INetworkInterface* m_networkInterface = nullptr; AzNetworking::INetworkInterface* m_networkInterface = nullptr;
@ -170,12 +174,16 @@ namespace Multiplayer
ClientMigrationEndEvent m_clientMigrationEndEvent; ClientMigrationEndEvent m_clientMigrationEndEvent;
NotifyClientMigrationEvent m_notifyClientMigrationEvent; NotifyClientMigrationEvent m_notifyClientMigrationEvent;
NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; NotifyEntityMigrationEvent m_notifyEntityMigrationEvent;
AZ::Event<NetEntityId>::Handler m_autonomousEntityReplicatorCreatedHandler;
AZStd::queue<AZStd::string> m_pendingConnectionTickets; AZStd::queue<AZStd::string> m_pendingConnectionTickets;
AZStd::unordered_map<uint64_t, NetEntityId> m_playerRejoinData;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 };
HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0); HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0);
uint64_t m_temporaryUserIdentifier = 0; // Used in the event of a migration or rejoin
double m_serverSendAccumulator = 0.0; double m_serverSendAccumulator = 0.0;
float m_renderBlendFactor = 0.0f; float m_renderBlendFactor = 0.0f;
float m_tickFactor = 0.0f; float m_tickFactor = 0.0f;

@ -47,6 +47,9 @@ namespace Multiplayer
, m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); }) , m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); })
, m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); }) , m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); })
{ {
// Set up our remote host identifier, by default we use the IP address of the remote host
m_remoteHostId = connection.GetRemoteAddress();
// Our max payload size is whatever is passed in, minus room for a udp packetheader // Our max payload size is whatever is passed in, minus room for a udp packetheader
m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead; m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead;
@ -62,12 +65,10 @@ namespace Multiplayer
networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler);
} }
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); if (m_updateMode == Mode::LocalServerToRemoteServer)
} {
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler);
void EntityReplicationManager::SetRemoteHostId(const HostId& hostId) }
{
m_remoteHostId = hostId;
} }
const HostId& EntityReplicationManager::GetRemoteHostId() const const HostId& EntityReplicationManager::GetRemoteHostId() const
@ -258,8 +259,8 @@ namespace Multiplayer
{ {
AZLOG_WARN AZLOG_WARN
( (
"Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d", "Serializing extremely large entity (%llu) - MaxPayload: %d NeededSize %d",
aznumeric_cast<uint32_t>(replicator->GetEntityHandle().GetNetEntityId()), aznumeric_cast<AZ::u64>(replicator->GetEntityHandle().GetNetEntityId()),
m_maxPayloadSize, m_maxPayloadSize,
nextMessageSize nextMessageSize
); );
@ -364,15 +365,29 @@ namespace Multiplayer
const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole());
// Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client
bool changedLocalRole(false); bool changedLocalRole(false);
if (AZ::Entity* localEnt = entityReplicator->GetEntityHandle().GetEntity()) NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent();
if (netBindComponent != nullptr)
{ {
NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent();
AZ_Assert(netBindComponent != nullptr, "No NetBindComponent");
changedLocalRole = (netBindComponent->GetNetEntityRole() != entityReplicator->GetBoundLocalNetworkRole()); changedLocalRole = (netBindComponent->GetNetEntityRole() != entityReplicator->GetBoundLocalNetworkRole());
} }
if (changedRemoteRole || changedLocalRole) if (changedRemoteRole || changedLocalRole)
{ {
const AZ::u64 intEntityId = static_cast<AZ::u64>(netBindComponent->GetNetEntityId());
const char* entityName = entityReplicator->GetEntityHandle().GetEntity()->GetName().c_str();
if (changedLocalRole)
{
const char* oldRoleString = GetEnumString(entityReplicator->GetRemoteNetworkRole());
const char* newRoleString = GetEnumString(remoteNetworkRole);
AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed local role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString);
}
if (changedRemoteRole)
{
const char* oldRoleString = GetEnumString(entityReplicator->GetBoundLocalNetworkRole());
const char* newRoleString = GetEnumString(netBindComponent->GetNetEntityRole());
AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed remote role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString);
}
// If we changed roles, we need to reset everything // If we changed roles, we need to reset everything
if (!entityReplicator->IsMarkedForRemoval()) if (!entityReplicator->IsMarkedForRemoval())
{ {
@ -387,8 +402,8 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_RepDeletes, NET_RepDeletes,
"Reinited replicator for %u from remote host %s role %d", "Reinited replicator for netEntityId %llu from remote host %s role %d",
entityHandle.GetNetEntityId(), static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(), GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole) aznumeric_cast<int32_t>(remoteNetworkRole)
); );
@ -404,8 +419,8 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_RepDeletes, NET_RepDeletes,
"Added replicator for %u from remote host %s role %d", "Added replicator for netEntityId %llu from remote host %s role %d",
entityHandle.GetNetEntityId(), static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(), GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole) aznumeric_cast<int32_t>(remoteNetworkRole)
); );
@ -413,7 +428,7 @@ namespace Multiplayer
} }
else else
{ {
AZLOG_ERROR("Failed to add entity replicator, entity does not exist, entity id %u", entityHandle.GetNetEntityId()); AZLOG_ERROR("Failed to add entity replicator, entity does not exist, netEntityId %llu", static_cast<AZ::u64>(entityHandle.GetNetEntityId()));
AZ_Assert(false, "Failed to add entity replicator, entity does not exist"); AZ_Assert(false, "Failed to add entity replicator, entity does not exist");
} }
return entityReplicator; return entityReplicator;
@ -502,24 +517,20 @@ namespace Multiplayer
{ {
if (entityReplicator->IsMarkedForRemoval()) if (entityReplicator->IsMarkedForRemoval())
{ {
AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
} }
else if (entityReplicator->OwnsReplicatorLifetime()) else if (entityReplicator->OwnsReplicatorLifetime())
{ {
// This can occur if we migrate entities quickly - if this is a replicator from C to A, A migrates to B, B then migrates to C, and A's delete replicator has not arrived at C // This can occur if we migrate entities quickly - if this is a replicator from C to A, A migrates to B, B then migrates to C, and A's delete replicator has not arrived at C
AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
} }
else else
{ {
shouldDeleteEntity = true; shouldDeleteEntity = true;
entityReplicator->MarkForRemoval(); entityReplicator->MarkForRemoval();
AZLOG(NET_RepDeletes, "Deleting replicater for entity id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Deleting replicater for entity id %llu remote host %s", static_cast<AZ::u64>(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str());
} }
} }
else
{
shouldDeleteEntity = updateMessage.GetTakeOwnership();
}
// Handle entity cleanup // Handle entity cleanup
if (shouldDeleteEntity) if (shouldDeleteEntity)
@ -529,17 +540,17 @@ namespace Multiplayer
{ {
if (updateMessage.GetWasMigrated()) if (updateMessage.GetWasMigrated())
{ {
AZLOG(NET_RepDeletes, "Leaving id %u using timeout remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Leaving id %llu using timeout remote host %s", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
} }
else else
{ {
AZLOG(NET_RepDeletes, "Deleting entity id %u remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Deleting entity id %llu remote host %s", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
GetNetworkEntityManager()->MarkForRemoval(entity); GetNetworkEntityManager()->MarkForRemoval(entity);
} }
} }
else else
{ {
AZLOG(NET_RepDeletes, "Trying to delete entity id %u remote host %s, but it has been removed", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Trying to delete entity id %llu remote host %s, but it has been removed", static_cast<AZ::u64>(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
} }
} }
@ -583,9 +594,9 @@ namespace Multiplayer
NetBindComponent* netBindComponent = replicatorEntity.GetNetBindComponent(); NetBindComponent* netBindComponent = replicatorEntity.GetNetBindComponent();
AZ_Assert(netBindComponent != nullptr, "No NetBindComponent"); AZ_Assert(netBindComponent != nullptr, "No NetBindComponent");
if (createEntity) if (netBindComponent->GetOwningConnectionId() != invokingConnection->GetConnectionId())
{ {
// Always set our invoking connectionId for any newly created entities, since this connection now 'owns' them from a rewind perspective // Always ensure our owning connectionId is correct for correct rewind behaviour
netBindComponent->SetOwningConnectionId(invokingConnection->GetConnectionId()); netBindComponent->SetOwningConnectionId(invokingConnection->GetConnectionId());
} }
@ -595,10 +606,11 @@ namespace Multiplayer
AZ_Assert(localNetworkRole != NetEntityRole::Authority, "UpdateMessage trying to set local role to Authority, this should only happen via migration"); AZ_Assert(localNetworkRole != NetEntityRole::Authority, "UpdateMessage trying to set local role to Authority, this should only happen via migration");
AZLOG_INFO AZLOG_INFO
( (
"EntityReplicationManager: Changing network role on entity %u, old role %u new role %u", "EntityReplicationManager: Changing network role on entity %s(%llu), old role %s new role %s",
aznumeric_cast<uint32_t>(netEntityId), replicatorEntity.GetEntity()->GetName().c_str(),
aznumeric_cast<uint32_t>(netBindComponent->GetNetEntityRole()), aznumeric_cast<AZ::u64>(netEntityId),
aznumeric_cast<uint32_t>(localNetworkRole) GetEnumString(netBindComponent->GetNetEntityRole()),
GetEnumString(localNetworkRole)
); );
if (NetworkRoleHasController(localNetworkRole)) if (NetworkRoleHasController(localNetworkRole))
@ -708,9 +720,9 @@ namespace Multiplayer
AZLOG_WARN AZLOG_WARN
( (
"Dropping Packet and LocalServerToRemoteClient connection, unexpected packet " "Dropping Packet and LocalServerToRemoteClient connection, unexpected packet "
"LocalShard=%s EntityId=%u RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s", "LocalShard=%s EntityId=%llu RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s",
GetNetworkEntityManager()->GetHostId().GetString().c_str(), GetNetworkEntityManager()->GetHostId().GetString().c_str(),
aznumeric_cast<uint32_t>(entityReplicator->GetEntityHandle().GetNetEntityId()), aznumeric_cast<AZ::u64>(entityReplicator->GetEntityHandle().GetNetEntityId()),
aznumeric_cast<uint32_t>(entityReplicator->GetRemoteNetworkRole()), aznumeric_cast<uint32_t>(entityReplicator->GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityReplicator->GetBoundLocalNetworkRole()), aznumeric_cast<uint32_t>(entityReplicator->GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(entityReplicator->GetNetBindComponent()->GetNetEntityRole()), aznumeric_cast<uint32_t>(entityReplicator->GetNetBindComponent()->GetNetEntityRole()),
@ -760,13 +772,13 @@ namespace Multiplayer
result = UpdateValidationResult::DropMessage; result = UpdateValidationResult::DropMessage;
if (updateMessage.GetIsDelete()) if (updateMessage.GetIsDelete())
{ {
AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote host %s", AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %llu, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); (AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
} }
else else
{ {
AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote host %s", AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %llu, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); (AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
} }
} }
} }
@ -853,10 +865,10 @@ namespace Multiplayer
{ {
AZLOG_INFO AZLOG_INFO
( (
"EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %u has already been deleted", "EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %llu has already been deleted",
GetMultiplayerComponentRegistry()->GetComponentName(message.GetComponentId()), GetMultiplayerComponentRegistry()->GetComponentName(message.GetComponentId()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()), GetMultiplayerComponentRegistry()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()),
message.GetEntityId() static_cast<AZ::u64>(message.GetEntityId())
); );
return false; return false;
} }
@ -1113,7 +1125,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer) if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{ {
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId()); netBindComponent->NotifyServerMigration(GetRemoteHostId());
} }
bool didSucceed = true; bool didSucceed = true;
@ -1145,7 +1157,7 @@ namespace Multiplayer
AZ_Assert(didSucceed, "Failed to migrate entity from server"); AZ_Assert(didSucceed, "Failed to migrate entity from server");
m_sendMigrateEntityEvent.Signal(m_connection, message); m_sendMigrateEntityEvent.Signal(m_connection, message);
AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Migration packet sent %llu to remote host %s", static_cast<AZ::u64>(netEntityId), GetRemoteHostId().GetString().c_str());
// Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status // Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status
GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId()); GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId());
@ -1201,7 +1213,7 @@ namespace Multiplayer
// Change the role on the replicator // Change the role on the replicator
AddEntityReplicator(entityHandle, NetEntityRole::Server); AddEntityReplicator(entityHandle, NetEntityRole::Server);
AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote host %s", entityHandle.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); AZLOG(NET_RepDeletes, "Handle Migration %llu new authority from remote host %s", static_cast<AZ::u64>(entityHandle.GetNetEntityId()), GetRemoteHostId().GetString().c_str());
return true; return true;
} }

@ -103,8 +103,8 @@ namespace Multiplayer
AZ_Assert AZ_Assert
( (
m_boundLocalNetworkRole != m_remoteNetworkRole, m_boundLocalNetworkRole != m_remoteNetworkRole,
"Invalid configuration detected, bound local role must differ from remote network role Role: %d", "Invalid configuration detected, bound local role must differ from remote network role: %s",
aznumeric_cast<int32_t>(m_boundLocalNetworkRole) GetEnumString(m_boundLocalNetworkRole)
); );
if (RemoteManagerOwnsEntityLifetime()) if (RemoteManagerOwnsEntityLifetime())
@ -176,7 +176,6 @@ namespace Multiplayer
switch (GetBoundLocalNetworkRole()) switch (GetBoundLocalNetworkRole())
{ {
case NetEntityRole::Authority: case NetEntityRole::Authority:
{
if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous) if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous)
{ {
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
@ -189,10 +188,8 @@ namespace Multiplayer
{ {
m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
} }
} break;
break;
case NetEntityRole::Server: case NetEntityRole::Server:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority) if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{ {
m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent()); m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent());
@ -204,23 +201,21 @@ namespace Multiplayer
// Listen for these to forward the rpc along to the other Client replicators // Listen for these to forward the rpc along to the other Client replicators
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
} }
// NOTE: e_Autonomous is not connected to e_ServerProxy, it is always connected to an e_Authority else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous)
AZ_Assert(GetRemoteNetworkRole() != NetEntityRole::Autonomous, "Unexpected autonomous remote role") {
} // NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority
break; AZ_Assert(false, "Unexpected autonomous remote role")
}
break;
case NetEntityRole::Client: case NetEntityRole::Client:
{
// Nothing allowed, no Client to Server communication // Nothing allowed, no Client to Server communication
} break;
break;
case NetEntityRole::Autonomous: case NetEntityRole::Autonomous:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority) if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{ {
m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent()); m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent());
} }
} break;
break;
default: default:
AZ_Assert(false, "Unexpected network role"); AZ_Assert(false, "Unexpected network role");
} }
@ -252,22 +247,9 @@ namespace Multiplayer
if (entity->GetState() != AZ::Entity::State::Init) if (entity->GetState() != AZ::Entity::State::Init)
{ {
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%u)", GetEntityHandle().GetNetEntityId()); AZLOG_WARN("Trying to activate an entity that is not in the Init state (%llu)", static_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()));
} }
// First we need to make sure the transform component has been updated with the correct value prior to activation
// This is because vanilla az components may only depend on the transform component, not the multiplayer transform component
//if (auto* locationComponent = FindCommonComponent<LocationComponent::Common>(GetEntityHandle()))
//{
// AZ::Transform newTransform = locationComponent->GetTransform();
// auto* transformComponent = entity->FindComponent<AzFramework::TransformComponent>();
// if (transformComponent)
// {
// // We can't use EBus here since the TransFormBus does not get connected until the activate call below
// transformComponent->SetWorldTM(newTransform);
// }
//}
// Ugly, but this is the only time we need to call a non-const function on this entity
entity->Activate(); entity->Activate();
m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this); m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this);
@ -281,8 +263,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = m_netBindComponent; NetBindComponent* netBindComponent = m_netBindComponent;
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client; bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous; bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
if (isAuthority || isClient || isAutonomous) if (isAuthority || isClient || isAutonomous)
@ -296,10 +277,10 @@ namespace Multiplayer
bool EntityReplicator::OwnsReplicatorLifetime() const bool EntityReplicator::OwnsReplicatorLifetime() const
{ {
bool ret(false); bool ret(false);
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority if (GetBoundLocalNetworkRole() == NetEntityRole::Authority // Authority always owns lifetime
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server || (GetBoundLocalNetworkRole() == NetEntityRole::Server // Server also owns lifetime if the remote endpoint is a client of some form
&& (GetRemoteNetworkRole() == NetEntityRole::Client && (GetRemoteNetworkRole() == NetEntityRole::Client
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous))) || GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
{ {
ret = true; ret = true;
} }
@ -309,10 +290,9 @@ namespace Multiplayer
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
{ {
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server) bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority); && (GetRemoteNetworkRole() == NetEntityRole::Authority);
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client) bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
return isServer || isClient; return isServer || isClient;
} }
@ -429,10 +409,8 @@ namespace Multiplayer
if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>()) if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>())
{ {
const NetEntityId parentId = networkTransform->GetParentEntityId(); const NetEntityId parentId = networkTransform->GetParentEntityId();
/* // For root entities attached to a level, a network parent won't be set.
* For root entities attached to a level, a network parent won't be set. // In this case, this entity is the root entity of the hierarchy and it will be activated first.
* In this case, this entity is the root entity of the hierarchy and it will be activated first.
*/
if (parentId != InvalidNetEntityId) if (parentId != InvalidNetEntityId)
{ {
ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId); ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId);
@ -452,9 +430,9 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_HierarchyActivationInfo, NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - waiting on the parent %u", "Hierchical entity %s asking for activation - waiting on the parent %llu",
entity->GetName().c_str(), entity->GetName().c_str(),
aznumeric_cast<uint32_t>(parentId) aznumeric_cast<AZ::u64>(parentId)
); );
return false; return false;
} }
@ -472,19 +450,19 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_RepDeletes, NET_RepDeletes,
"Sending delete replicator id %u migrated %d to remote host %s", "Sending delete replicator id %llu migrated %d to remote host %s",
aznumeric_cast<uint32_t>(GetEntityHandle().GetNetEntityId()), aznumeric_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()),
WasMigrated() ? 1 : 0, WasMigrated() ? 1 : 0,
m_replicationManager.GetRemoteHostId().GetString().c_str() m_replicationManager.GetRemoteHostId().GetString().c_str()
); );
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated(), m_propertyPublisher->IsRemoteReplicatorEstablished()); return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated());
} }
NetBindComponent* netBindComponent = GetNetBindComponent(); NetBindComponent* netBindComponent = GetNetBindComponent();
//const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId()); NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId());
//if (sendSliceName) if (sendSliceName)
{ {
updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId()); updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId());
} }
@ -553,42 +531,33 @@ namespace Multiplayer
switch (entityRpcMessage.GetRpcDeliveryType()) switch (entityRpcMessage.GetRpcDeliveryType())
{ {
case RpcDeliveryType::AuthorityToClient: case RpcDeliveryType::AuthorityToClient:
{
if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)) if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous))
&& (GetRemoteNetworkRole() == NetEntityRole::Authority)) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
{ {
// We are a local client, and we are connected to server, aka AuthorityToClient // We are a local client, and we are connected to server, aka AuthorityToClient
result = RpcValidationResult::HandleRpc; result = RpcValidationResult::HandleRpc;
} }
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
{ {
// We are on a server, and we received this message from another server, therefore we should forward this to any connected clients // We are on a server, and we received this message from another server, therefore we should forward this to any connected clients
result = RpcValidationResult::ForwardToClient; result = RpcValidationResult::ForwardToClient;
} }
} break;
break;
case RpcDeliveryType::AuthorityToAutonomous: case RpcDeliveryType::AuthorityToAutonomous:
{ if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) && (GetRemoteNetworkRole() == NetEntityRole::Authority))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
{ {
// We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous // We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous
result = RpcValidationResult::HandleRpc; result = RpcValidationResult::HandleRpc;
} }
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
{ {
// We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player // We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player
// This can occur if we've recently migrated // This can occur if we've recently migrated
result = RpcValidationResult::ForwardToAutonomous; result = RpcValidationResult::ForwardToAutonomous;
} }
} break;
break;
case RpcDeliveryType::AutonomousToAuthority: case RpcDeliveryType::AutonomousToAuthority:
{ if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
{ {
if (IsMarkedForRemoval()) if (IsMarkedForRemoval())
{ {
@ -610,12 +579,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc; result = RpcValidationResult::HandleRpc;
} }
} }
} break;
break;
case RpcDeliveryType::ServerToAuthority: case RpcDeliveryType::ServerToAuthority:
{ if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
{ {
// if we're marked for removal, then we should forward to whomever now owns this entity // if we're marked for removal, then we should forward to whomever now owns this entity
if (IsMarkedForRemoval()) if (IsMarkedForRemoval())
@ -638,9 +604,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc; result = RpcValidationResult::HandleRpc;
} }
} }
break;
} }
break;
}
if (result == RpcValidationResult::DropRpcAndDisconnect) if (result == RpcValidationResult::DropRpcAndDisconnect)
{ {
bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server); bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server);
@ -654,30 +620,29 @@ namespace Multiplayer
{ {
AZLOG_ERROR AZLOG_ERROR
( (
"Dropping RPC and Connection EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", "Dropping RPC and Connection EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()), GetEnumString(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()), GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()), aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()), GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false" IsMarkedForRemoval() ? "true" : "false"
); );
} }
} }
if (result == RpcValidationResult::DropRpc) if (result == RpcValidationResult::DropRpc)
{ {
AZLOG AZLOG
( (
NET_Rpc, NET_Rpc,
"Dropping RPC EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", "Dropping RPC EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()), GetEnumString(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()), GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()), aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()), GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false" IsMarkedForRemoval() ? "true" : "false"
); );
@ -696,13 +661,12 @@ namespace Multiplayer
{ {
AZLOG_WARN AZLOG_WARN
( (
"Dropping RPC since entity deleted EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", "Dropping RPC since entity deleted EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()), GetEnumString(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()), GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()), aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()), GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false" IsMarkedForRemoval() ? "true" : "false"
); );
@ -740,23 +704,23 @@ namespace Multiplayer
case RpcValidationResult::DropRpcAndDisconnect: case RpcValidationResult::DropRpcAndDisconnect:
return false; return false;
case RpcValidationResult::ForwardToClient: case RpcValidationResult::ForwardToClient:
{ {
ScopedForwardingMessage forwarding(*this); ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage); m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
}
return true; return true;
}
case RpcValidationResult::ForwardToAutonomous: case RpcValidationResult::ForwardToAutonomous:
{ {
ScopedForwardingMessage forwarding(*this); ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage); m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
}
return true; return true;
}
case RpcValidationResult::ForwardToAuthority: case RpcValidationResult::ForwardToAuthority:
{ {
ScopedForwardingMessage forwarding(*this); ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage); m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
}
return true; return true;
}
default: default:
break; break;
} }

@ -33,8 +33,8 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_AuthTracker, NET_AuthTracker,
"AuthTracker: Removing timeout for networkEntityId %u from %s, new owner is %s", "AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str(), timeoutData->second.m_previousOwner.GetString().c_str(),
newOwner.GetString().c_str() newOwner.GetString().c_str()
); );
@ -48,8 +48,8 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_AuthTracker, NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u from %s to %s", "AuthTracker: Assigning networkEntityId %llu from %s to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
iter->second.back().GetString().c_str(), iter->second.back().GetString().c_str(),
newOwner.GetString().c_str() newOwner.GetString().c_str()
); );
@ -59,8 +59,8 @@ namespace Multiplayer
AZLOG AZLOG
( (
NET_AuthTracker, NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u to %s", "AuthTracker: Assigning networkEntityId %llu to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
newOwner.GetString().c_str() newOwner.GetString().c_str()
); );
} }
@ -87,7 +87,7 @@ namespace Multiplayer
} }
} }
AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %u from %s", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str()); AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %llu from %s", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str());
if (auto localEnt = entityHandle.GetEntity()) if (auto localEnt = entityHandle.GetEntity())
{ {
if (authorityStack.empty()) if (authorityStack.empty())
@ -114,14 +114,14 @@ namespace Multiplayer
} }
else else
{ {
AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %u", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId())); AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %llu", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()));
} }
} }
} }
} }
else else
{ {
AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %u", aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId())); AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %llu", aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()));
AZ_Assert(false, "AuthTracker: Remove authority called on entity that was never added"); AZ_Assert(false, "AuthTracker: Remove authority called on entity that was never added");
} }
} }
@ -205,8 +205,8 @@ namespace Multiplayer
{ {
AZLOG_ERROR AZLOG_ERROR
( (
"Timed out entity id %u during migration previous owner %s, removing it", "Timed out entity id %llu during migration previous owner %s, removing it",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()), aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str() timeoutData->second.m_previousOwner.GetString().c_str()
); );
m_networkEntityManager.MarkForRemoval(entityHandle); m_networkEntityManager.MarkForRemoval(entityHandle);

@ -18,21 +18,13 @@ namespace Multiplayer
{ {
ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* networkEntityTracker) ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* networkEntityTracker)
: m_entity(entity) : m_entity(entity)
, m_networkEntityTracker(networkEntityTracker) , m_networkEntityTracker((networkEntityTracker != nullptr) ? networkEntityTracker : GetNetworkEntityTracker())
{ {
if (m_networkEntityTracker == nullptr) AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid");
{ m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity);
m_networkEntityTracker = GetNetworkEntityTracker();
}
if (m_networkEntityTracker)
{
m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity);
}
if (entity) if (entity)
{ {
AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid");
m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(entity); m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(entity);
if (m_netBindComponent != nullptr) if (m_netBindComponent != nullptr)
{ {

@ -48,6 +48,20 @@ namespace Multiplayer
void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr<IEntityDomain> entityDomain) void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
{ {
m_hostId = hostId; m_hostId = hostId;
// Configure our vended NetEntityIds so that no two hosts generate the same NetEntityId
{
// Needs more thought
const uint64_t addrPortion = hostId.GetAddress(AzNetworking::ByteOrder::Host);
const uint64_t portPortion = hostId.GetPort(AzNetworking::ByteOrder::Host);
const uint64_t hostIdentifier = (portPortion << 32) | addrPortion;
const AZ::HashValue32 hostHash = AZ::TypeHash32(hostIdentifier);
NetEntityId hostEntityIdOffset = static_cast<NetEntityId>(hostHash) << 32;
m_nextEntityId &= NetEntityId{ 0x0000000000000000FFFFFFFFFFFFFFFF };
m_nextEntityId |= hostEntityIdOffset;
}
m_entityDomain = AZStd::move(entityDomain); m_entityDomain = AZStd::move(entityDomain);
m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true);
m_entityDomain->ActivateTracking(m_ownedEntities); m_entityDomain->ActivateTracking(m_ownedEntities);
@ -227,11 +241,19 @@ namespace Multiplayer
{ {
AZ::Entity* entity = it->second; AZ::Entity* entity = it->second;
NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity);
AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
entityBounds.Expand(AZ::Vector3(0.01f));
if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority)
{ {
const AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId()); debugDisplay->SetColor(AZ::Colors::Black);
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); debugDisplay->SetAlpha(0.5f);
}
else
{
debugDisplay->SetColor(AZ::Colors::DeepSkyBlue);
debugDisplay->SetAlpha(0.25f);
} }
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax());
} }
if (m_entityDomain != nullptr) if (m_entityDomain != nullptr)

@ -18,7 +18,6 @@ namespace Multiplayer
, m_entityId(rhs.m_entityId) , m_entityId(rhs.m_entityId)
, m_isDelete(rhs.m_isDelete) , m_isDelete(rhs.m_isDelete)
, m_wasMigrated(rhs.m_wasMigrated) , m_wasMigrated(rhs.m_wasMigrated)
, m_takeOwnership(rhs.m_takeOwnership)
, m_hasValidPrefabId(rhs.m_hasValidPrefabId) , m_hasValidPrefabId(rhs.m_hasValidPrefabId)
, m_prefabEntityId(rhs.m_prefabEntityId) , m_prefabEntityId(rhs.m_prefabEntityId)
, m_data(AZStd::move(rhs.m_data)) , m_data(AZStd::move(rhs.m_data))
@ -31,7 +30,6 @@ namespace Multiplayer
, m_entityId(rhs.m_entityId) , m_entityId(rhs.m_entityId)
, m_isDelete(rhs.m_isDelete) , m_isDelete(rhs.m_isDelete)
, m_wasMigrated(rhs.m_wasMigrated) , m_wasMigrated(rhs.m_wasMigrated)
, m_takeOwnership(rhs.m_takeOwnership)
, m_hasValidPrefabId(rhs.m_hasValidPrefabId) , m_hasValidPrefabId(rhs.m_hasValidPrefabId)
, m_prefabEntityId(rhs.m_prefabEntityId) , m_prefabEntityId(rhs.m_prefabEntityId)
{ {
@ -58,11 +56,10 @@ namespace Multiplayer
; ;
} }
NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated, bool takeOwnership) NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated)
: m_entityId(entityId) : m_entityId(entityId)
, m_isDelete(true) , m_isDelete(true)
, m_wasMigrated(wasMigrated) , m_wasMigrated(wasMigrated)
, m_takeOwnership(takeOwnership)
{ {
// this is a delete entity message c-tor // this is a delete entity message c-tor
} }
@ -73,7 +70,6 @@ namespace Multiplayer
m_entityId = rhs.m_entityId; m_entityId = rhs.m_entityId;
m_isDelete = rhs.m_isDelete; m_isDelete = rhs.m_isDelete;
m_wasMigrated = rhs.m_wasMigrated; m_wasMigrated = rhs.m_wasMigrated;
m_takeOwnership = rhs.m_takeOwnership;
m_hasValidPrefabId = rhs.m_hasValidPrefabId; m_hasValidPrefabId = rhs.m_hasValidPrefabId;
m_prefabEntityId = rhs.m_prefabEntityId; m_prefabEntityId = rhs.m_prefabEntityId;
m_data = AZStd::move(rhs.m_data); m_data = AZStd::move(rhs.m_data);
@ -86,7 +82,6 @@ namespace Multiplayer
m_entityId = rhs.m_entityId; m_entityId = rhs.m_entityId;
m_isDelete = rhs.m_isDelete; m_isDelete = rhs.m_isDelete;
m_wasMigrated = rhs.m_wasMigrated; m_wasMigrated = rhs.m_wasMigrated;
m_takeOwnership = rhs.m_takeOwnership;
m_hasValidPrefabId = rhs.m_hasValidPrefabId; m_hasValidPrefabId = rhs.m_hasValidPrefabId;
m_prefabEntityId = rhs.m_prefabEntityId; m_prefabEntityId = rhs.m_prefabEntityId;
if (rhs.m_data != nullptr) if (rhs.m_data != nullptr)
@ -104,7 +99,6 @@ namespace Multiplayer
&& (m_entityId == rhs.m_entityId) && (m_entityId == rhs.m_entityId)
&& (m_isDelete == rhs.m_isDelete) && (m_isDelete == rhs.m_isDelete)
&& (m_wasMigrated == rhs.m_wasMigrated) && (m_wasMigrated == rhs.m_wasMigrated)
&& (m_takeOwnership == rhs.m_takeOwnership)
&& (m_hasValidPrefabId == rhs.m_hasValidPrefabId) && (m_hasValidPrefabId == rhs.m_hasValidPrefabId)
&& (m_prefabEntityId == rhs.m_prefabEntityId)); && (m_prefabEntityId == rhs.m_prefabEntityId));
} }
@ -160,11 +154,6 @@ namespace Multiplayer
return m_wasMigrated; return m_wasMigrated;
} }
bool NetworkEntityUpdateMessage::GetTakeOwnership() const
{
return m_takeOwnership;
}
bool NetworkEntityUpdateMessage::GetHasValidPrefabId() const bool NetworkEntityUpdateMessage::GetHasValidPrefabId() const
{ {
return m_hasValidPrefabId; return m_hasValidPrefabId;
@ -210,17 +199,15 @@ namespace Multiplayer
serializer.Serialize(m_entityId, "EntityId"); serializer.Serialize(m_entityId, "EntityId");
// Use the upper 4 bits for boolean flags, and the lower 4 bits for the network role // Use the upper 4 bits for boolean flags, and the lower 4 bits for the network role
uint8_t networkTypeAndFlags = (m_isDelete ? 0x80 : 0x00) uint8_t networkTypeAndFlags = (m_isDelete ? 0x40 : 0x00)
| (m_wasMigrated ? 0x40 : 0x00) | (m_wasMigrated ? 0x20 : 0x00)
| (m_takeOwnership ? 0x20 : 0x00)
| (m_hasValidPrefabId ? 0x10 : 0x00) | (m_hasValidPrefabId ? 0x10 : 0x00)
| static_cast<uint8_t>(m_networkRole); | static_cast<uint8_t>(m_networkRole);
if (serializer.Serialize(networkTypeAndFlags, "TypeAndFlags")) if (serializer.Serialize(networkTypeAndFlags, "TypeAndFlags"))
{ {
m_isDelete = (networkTypeAndFlags & 0x80) == 0x80; m_isDelete = (networkTypeAndFlags & 0x40) == 0x40;
m_wasMigrated = (networkTypeAndFlags & 0x40) == 0x40; m_wasMigrated = (networkTypeAndFlags & 0x20) == 0x20;
m_takeOwnership = (networkTypeAndFlags & 0x20) == 0x20;
m_hasValidPrefabId = (networkTypeAndFlags & 0x10) == 0x10; m_hasValidPrefabId = (networkTypeAndFlags & 0x10) == 0x10;
m_networkRole = static_cast<NetEntityRole>(networkTypeAndFlags & 0x0F); m_networkRole = static_cast<NetEntityRole>(networkTypeAndFlags & 0x0F);
} }

@ -6,7 +6,7 @@
* *
*/ */
#include <Source/NetworkInput/NetworkInputArray.h> #include <Multiplayer/NetworkInput/NetworkInputArray.h>
#include <Multiplayer/NetworkEntity/INetworkEntityManager.h> #include <Multiplayer/NetworkEntity/INetworkEntityManager.h>
#include <AzNetworking/Serialization/ISerializer.h> #include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/DeltaSerializer.h> #include <AzNetworking/Serialization/DeltaSerializer.h>

@ -6,7 +6,7 @@
* *
*/ */
#include <Source/NetworkInput/NetworkInputChild.h> #include <Multiplayer/NetworkInput/NetworkInputChild.h>
#include <Multiplayer/IMultiplayer.h> #include <Multiplayer/IMultiplayer.h>
#include <AzNetworking/Serialization/ISerializer.h> #include <AzNetworking/Serialization/ISerializer.h>

@ -6,7 +6,7 @@
* *
*/ */
#include <Source/NetworkInput/NetworkInputHistory.h> #include <Multiplayer/NetworkInput/NetworkInputHistory.h>
namespace Multiplayer namespace Multiplayer
{ {

@ -6,7 +6,7 @@
* *
*/ */
#include <Source/NetworkInput/NetworkInputMigrationVector.h> #include <Multiplayer/NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/IMultiplayer.h> #include <Multiplayer/IMultiplayer.h>
#include <AzNetworking/Serialization/ISerializer.h> #include <AzNetworking/Serialization/ISerializer.h>

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

Loading…
Cancel
Save