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
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.
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_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 COMMIT_ID=HEAD
```
4. In the same Command Prompt window, Deploy the CDK applications for AWS gems by running deploy_cdk_applications.cmd.
## 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(Cylinder);
REFLECT_SHAPETYPE_ENUM_VALUE(PhysicsAsset);
REFLECT_SHAPETYPE_ENUM_VALUE(Heightfield);
#undef REFLECT_SHAPETYPE_ENUM_VALUE
}
@ -305,4 +306,125 @@ namespace Physics
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
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/HeightfieldProviderBus.h>
namespace Physics
{
/// 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.
PhysicsAsset, ///< Shapes configured in the asset.
CookedMesh, ///< Stores a blob of mesh data cooked for the specific engine.
Heightfield ///< Interacts with the physics system heightfield
};
class ShapeConfiguration
@ -196,4 +200,52 @@ namespace Physics
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

@ -132,6 +132,10 @@ namespace Physics
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.
/// @param nativeMeshObject Pointer to the mesh object.
virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0;

@ -107,6 +107,7 @@ namespace Physics
PhysicsAssetShapeConfiguration::Reflect(context);
NativeShapeConfiguration::Reflect(context);
CookedMeshShapeConfiguration::Reflect(context);
HeightfieldShapeConfiguration::Reflect(context);
AzPhysics::SystemInterface::Reflect(context);
AzPhysics::Scene::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(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_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_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");
static uint32_t GetChildNodeCount()
{
@ -25,14 +24,12 @@ namespace AzFramework
return (bg_octreeUseQuadtree) ? QuadtreeNodeChildCount : OctreeNodeChildCount;
}
OctreeNode::OctreeNode(const AZ::Aabb& bounds)
: m_bounds(bounds)
{
;
}
OctreeNode::OctreeNode(OctreeNode&& rhs)
: m_bounds(rhs.m_bounds)
, m_parent(rhs.m_parent)
@ -46,7 +43,6 @@ namespace AzFramework
}
}
OctreeNode& OctreeNode::operator=(OctreeNode&& rhs)
{
m_bounds = rhs.m_bounds;
@ -63,7 +59,6 @@ namespace AzFramework
return *this;
}
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");
@ -98,7 +93,6 @@ namespace AzFramework
}
}
void OctreeNode::Update(OctreeScene& octreeScene, VisibilityEntry* entry)
{
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)
{
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
{
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
{
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
{
EnumerateHelper(frustum, callback);
if (AZ::ShapeIntersection::Overlaps(frustum, m_bounds))
{
EnumerateHelper(frustum, callback);
}
}
void OctreeNode::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
{
// Invoke the callback for the current node
@ -190,25 +188,21 @@ namespace AzFramework
}
}
const AZStd::vector<VisibilityEntry*>& OctreeNode::GetEntries() const
{
return m_entries;
}
OctreeNode* OctreeNode::GetChildren() const
{
return m_children;
}
bool OctreeNode::IsLeaf() const
{
return m_children == nullptr;
}
void OctreeNode::TryMerge(OctreeScene& octreeScene)
{
if (IsLeaf())
@ -236,7 +230,6 @@ namespace AzFramework
}
}
template <typename T>
void OctreeNode::EnumerateHelper(const T& boundingVolume, const IVisibilityScene::EnumerateCallback& callback) const
{
@ -262,7 +255,6 @@ namespace AzFramework
}
}
void OctreeNode::Split(OctreeScene& octreeScene)
{
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)
{
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)
{
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
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(aabb, callback);
}
void OctreeScene::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(sphere, callback);
}
void OctreeScene::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.Enumerate(frustum, callback);
}
void OctreeScene::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
m_root.EnumerateNoCull(callback);
}
uint32_t OctreeScene::GetEntryCount() const
{
return m_entryCount;
@ -421,26 +406,22 @@ namespace AzFramework
return m_nodeCount;
}
uint32_t OctreeScene::GetFreeNodeCount() const
{
// Each entry represents GetChildNodeCount() nodes
return aznumeric_cast<uint32_t>(m_freeOctreeNodes.size() * GetChildNodeCount());
}
uint32_t OctreeScene::GetPageCount() const
{
return aznumeric_cast<uint32_t>(m_nodeCache.size());
}
uint32_t OctreeScene::GetChildNodeCount() const
{
return AzFramework::GetChildNodeCount();
}
void OctreeScene::DumpStats()
{
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());
}
static inline uint32_t CreateNodeIndex(uint32_t page, uint32_t offset)
{
AZ_Assert(page <= 0xFFFF && offset <= 0xFFFF, "Out of range values passed to CreateNodeIndex");
return (page << 16) | offset;
}
static inline void ExtractPageAndOffsetFromIndex(uint32_t index, uint32_t& page, uint32_t& offset)
{
offset = index & 0x0000FFFF;
page = index >> 16;
}
uint32_t OctreeScene::AllocateChildNodes()
{
const uint32_t childCount = GetChildNodeCount();
@ -508,14 +486,12 @@ namespace AzFramework
return CreateNodeIndex(nextChildPage, nextChildOffset);
}
void OctreeScene::ReleaseChildNodes(uint32_t nodeIndex)
{
m_nodeCount -= GetChildNodeCount();
m_freeOctreeNodes.push(nodeIndex);
}
OctreeNode* OctreeScene::GetChildNodesAtIndex(uint32_t nodeIndex) const
{
uint32_t childPage;
@ -524,7 +500,6 @@ namespace AzFramework
return &(*m_nodeCache[childPage])[childOffset];
}
void OctreeSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
@ -534,19 +509,16 @@ namespace AzFramework
}
}
void OctreeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("OctreeService"));
}
void OctreeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("OctreeService"));
}
OctreeSystemComponent::OctreeSystemComponent()
{
AZ::Interface<IVisibilitySystem>::Register(this);
@ -555,7 +527,6 @@ namespace AzFramework
m_defaultScene = aznew OctreeScene(AZ::Name("DefaultVisibilityScene"));
}
OctreeSystemComponent::~OctreeSystemComponent()
{
AZ_Assert(m_scenes.empty(), "All IVisibilityScenes must be destroyed before shutdown");
@ -566,13 +537,11 @@ namespace AzFramework
AZ::Interface<IVisibilitySystem>::Unregister(this);
}
void OctreeSystemComponent::Activate()
{
;
}
void OctreeSystemComponent::Deactivate()
{
;
@ -591,7 +560,6 @@ namespace AzFramework
return newScene;
}
void OctreeSystemComponent::DestroyVisibilityScene(IVisibilityScene* visScene)
{
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());
}
IVisibilityScene* OctreeSystemComponent::FindVisibilityScene(const AZ::Name& sceneName)
{
for (OctreeScene* scene : m_scenes)
@ -619,7 +586,6 @@ namespace AzFramework
return nullptr;
}
void OctreeSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
for (OctreeScene* scene : m_scenes)

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

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

@ -53,7 +53,7 @@ namespace AzNetworking
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;
if (maxTimeouts < 0)
@ -103,7 +103,7 @@ namespace AzNetworking
// By this point, the item is definitely timed out
// Invoke the timeout function to see how to proceed
const TimeoutResult result = timeoutHandler.HandleTimeout(mapItem);
const TimeoutResult result = timeoutHandler(mapItem);
if (result == TimeoutResult::Refresh)
{
@ -122,4 +122,10 @@ namespace AzNetworking
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
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.
//! @param timeoutHandler listener instance to call back on for timeouts
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration

@ -13,6 +13,7 @@
#include <QIcon>
#include <QToolButton>
#include <QPropertyAnimation>
#include <QPainter>
namespace AzQtComponents
{
@ -27,6 +28,13 @@ namespace AzQtComponents
setAttribute(Qt::WA_ShowWithoutActivating);
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);
QIcon toastIcon;
@ -53,6 +61,13 @@ namespace AzQtComponents
m_ui->titleLabel->setText(toastConfiguration.m_title);
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_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()
{
QPoint globalCursorPos = QCursor::pos();

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

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

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

@ -177,4 +177,14 @@ namespace AzToolsFramework
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 UpdateToastPosition();
void SetOffset(const QPoint& offset);
void SetAnchorPoint(const QPointF& anchorPoint);
private:
ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration);
void DisplayQueuedNotification();

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

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

@ -61,6 +61,24 @@ QTabBar::tab:focus {
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) **************/
#formLineEditWidget,
@ -218,6 +236,10 @@ QTabBar::tab:focus {
color: #666666;
}
#verticalSeparatingLine {
color: #888888;
}
/************** Project Settings **************/
#projectSettings {
margin-top:42px;
@ -481,6 +503,14 @@ QProgressBar::chunk {
font-weight: 600;
}
#gemCatalogMenuButton {
qproperty-flat: true;
max-width:36px;
min-width:36px;
max-height:24px;
min-height:24px;
}
#GemCatalogCartOverlayGemDownloadHeader {
margin:0;
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);
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.
connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex)
{
@ -133,7 +135,7 @@ namespace O3DE::ProjectManager
}
else
{
emit GotoPreviousScreenRequest();
emit GoToPreviousScreenRequest();
}
}

@ -29,17 +29,17 @@ namespace O3DE::ProjectManager
topBarFrameWidget->setLayout(topBarHLayout);
QTabWidget* tabWidget = new QTabWidget();
tabWidget->setObjectName("engineTab");
tabWidget->tabBar()->setObjectName("engineTabBar");
tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_tabWidget = new QTabWidget();
m_tabWidget->setObjectName("engineTab");
m_tabWidget->tabBar()->setObjectName("engineTabBar");
m_tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_engineSettingsScreen = new EngineSettingsScreen();
m_gemRepoScreen = new GemRepoScreen();
tabWidget->addTab(m_engineSettingsScreen, tr("General"));
tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(tabWidget);
m_tabWidget->addTab(m_engineSettingsScreen, tr("General"));
m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(m_tabWidget);
vLayout->addWidget(topBarFrameWidget);
@ -61,4 +61,28 @@ namespace O3DE::ProjectManager
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

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

@ -8,12 +8,14 @@
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <AzCore/std/functional.h>
#include <TagWidget.h>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QLabel>
#include <QPushButton>
#include <QMenu>
#include <QProgressBar>
#include <TagWidget.h>
namespace O3DE::ProjectManager
{
@ -404,6 +406,28 @@ namespace O3DE::ProjectManager
CartButton* cartButton = new CartButton(gemModel, downloadController);
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()

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

@ -38,6 +38,8 @@ namespace O3DE::ProjectManager
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
vLayout->addWidget(m_headerWidget);
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
vLayout->addLayout(hLayout);
@ -64,6 +66,9 @@ namespace O3DE::ProjectManager
hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout);
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)
@ -84,6 +89,7 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject();
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
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)
{
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
@ -105,6 +177,7 @@ namespace O3DE::ProjectManager
}
m_gemModel->UpdateGemDependencies();
m_notificationsEnabled = false;
// Gather enabled gems for the given project.
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()));
}
m_notificationsEnabled = true;
}
else
{
@ -194,6 +269,27 @@ namespace O3DE::ProjectManager
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()
{
return ProjectManagerScreen::GemCatalog;

@ -10,6 +10,8 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h>
@ -41,9 +43,24 @@ namespace O3DE::ProjectManager
GemModel* GetGemModel() const { return m_gemModel; }
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:
void FillModel(const QString& projectPath);
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr;
@ -52,5 +69,6 @@ namespace O3DE::ProjectManager
QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr;
DownloadController* m_downloadController = nullptr;
bool m_notificationsEnabled = true;
};
} // namespace O3DE::ProjectManager

@ -10,6 +10,7 @@
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
namespace O3DE::ProjectManager
{
@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
AZ_Assert(gemModel, "Failed to obtain GemModel");
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
uint32_t numChangedDependencies = 0;
if (IsAdded(modelIndex))
{
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
{
// 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)
{
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)
@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
}
return result;
}
} // namespace O3DE::ProjectManager

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

@ -47,6 +47,14 @@ namespace O3DE::ProjectManager
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
virtual void NotifyCurrentScreen()
{
@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
signals:
void ChangeScreenRequest(ProjectManagerScreen screen);
void GotoPreviousScreenRequest();
void GoToPreviousScreenRequest();
void ResetScreenRequest(ProjectManagerScreen screen);
void NotifyCurrentProject(const QString& projectPath);
void NotifyBuildProject(const ProjectInfo& projectInfo);

@ -83,11 +83,28 @@ namespace O3DE::ProjectManager
bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit)
{
ScreenWidget* newScreen = nullptr;
const auto iterator = m_screenMap.find(screen);
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* newScreen = iterator.value();
if (currentScreen != newScreen)
{
@ -109,6 +126,11 @@ namespace O3DE::ProjectManager
newScreen->NotifyCurrentScreen();
if (iterator == m_screenMap.end())
{
newScreen->GoToScreen(screen);
}
return true;
}
}
@ -116,7 +138,7 @@ namespace O3DE::ProjectManager
return false;
}
bool ScreensCtrl::GotoPreviousScreen()
bool ScreensCtrl::GoToPreviousScreen()
{
if (!m_screenVisitOrder.isEmpty())
{
@ -171,7 +193,7 @@ namespace O3DE::ProjectManager
m_screenMap.insert(screen, newScreen);
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::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject);
connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject);

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

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

@ -404,18 +404,18 @@ namespace AZ
bool AzslCompiler::ParseSrgPopulateRootConstantData(const rapidjson::Document& input, RootConstantData& rootConstantData) const
{
if (input.HasMember("InlineConstantBuffer"))
if (input.HasMember("RootConstantBuffer"))
{
const rapidjson::Value& rootConstantBufferValue = input["InlineConstantBuffer"];
AZ_Assert(rootConstantBufferValue.IsObject(), "InlineConstantBuffer is not an object");
const rapidjson::Value& rootConstantBufferValue = input["RootConstantBuffer"];
AZ_Assert(rootConstantBufferValue.IsObject(), "RootConstantBuffer is not an object");
for (rapidjson::Value::ConstMemberIterator itr = rootConstantBufferValue.MemberBegin(); itr != rootConstantBufferValue.MemberEnd(); ++itr)
{
AZStd::string_view rootConstantBufferMemberName = itr->name.GetString();
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)
{
@ -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)
{
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;

@ -81,7 +81,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
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
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
@ -96,7 +96,7 @@ namespace AZ
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
// 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".
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_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
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
if (!azslc.ParseSrgPopulateRootConstantData(
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");
return AssetBuilderSDK::ProcessJobResult_Failed;

@ -561,7 +561,7 @@ namespace AZ
// Access the root constants reflection
if (!azslCompiler.ParseSrgPopulateRootConstantData(
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");
return false;

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

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

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

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

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

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

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

@ -61,7 +61,7 @@ namespace AtomToolsFramework
AZ::RPI::RenderPipelineDescriptor pipelineDesc;
pipelineDesc.m_mainViewTagName = "MainCamera";
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
// [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": [
{
"Name": "HairGlobalShapeConstraintsComputePass",

@ -12,7 +12,8 @@
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{ // used for copy from MSAA to regular RT
{
// used for copy from MSAA to regular RT
"Name": "RenderTargetInputOnly",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
@ -29,7 +30,7 @@
// If DepthLinear is not availbale - connect to another viewport (non MSAA) image.
{
"Name": "DepthLinearInput",
"SlotType": "InputOutput"
"SlotType": "Input"
},
{
"Name": "DepthLinear",
@ -71,6 +72,12 @@
}
}
],
"FallbackConnections": [
{
"Input": "DepthLinearInput",
"Output": "DepthLinear"
}
],
"PassRequests": [
{
"Name": "HairGlobalShapeConstraintsComputePass",
@ -257,7 +264,8 @@
"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?
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {
@ -340,7 +348,8 @@
"TemplateName": "HairShortCutResolveColorPassTemplate",
"Enabled": true,
"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?
"LocalSlot": "RenderTargetInputOutput",
"AttachmentRef": {

@ -144,25 +144,11 @@ namespace AZ
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);
if (desiredPass)
{
desiredPass->SetEnabled(enable);
}
*/
}
bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance<HairRenderObject> renderObject)
@ -184,15 +170,13 @@ namespace AZ
void HairFeatureProcessor::UpdateHairSkinning()
{
// Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..)
for (auto objIter = m_hairRenderObjects.begin(); objIter != m_hairRenderObjects.end(); ++objIter)
// Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..)
for (auto& hairRenderObject : m_hairRenderObjects)
{
if (!objIter->get()->IsEnabled())
if (hairRenderObject->IsEnabled())
{
return;
hairRenderObject->Update();
}
objIter->get()->Update();
}
}

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

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

@ -213,12 +213,12 @@ namespace EMotionFX
*/
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 float GetCurrentPlayTime(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetCurrentPlayTime(); }
MCORE_INLINE size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); }
MCORE_INLINE void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); }
size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); }
void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); }
virtual void SetPlaySpeed(AnimGraphInstance* animGraphInstance, float speedFactor) { FindOrCreateUniqueNodeData(animGraphInstance)->SetPlaySpeed(speedFactor); }
virtual float GetPlaySpeed(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetPlaySpeed(); }

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

@ -326,7 +326,7 @@ namespace EMotionFX
uniqueData->m_totalSeconds = 0.0f;
uniqueData->m_blendProgress = 0.0f;
m_targetNode->SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32);
m_targetNode->SetSyncIndex(animGraphInstance, InvalidIndex);
// Trigger action
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_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject));
MOCK_METHOD1(ReleaseNativeHeightfieldObject, void(void* nativeMeshObject));
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(CookConvexMeshToMemory, bool(const AZ::Vector3* vertices, AZ::u32 vertexCount, AZStd::vector<AZ::u8>& result));

@ -107,7 +107,16 @@ endif()
# Tests
################################################################################
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(
NAME GradientSignal.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
@ -122,6 +131,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest
Gem::GradientSignal.Static
Gem::LmbrCentral
Gem::GradientSignal.Mocks
)
ly_add_googletest(
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.
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:
// Set the Max Size and clear the container
void SetMaxSize(int size);
@ -99,6 +103,7 @@ namespace ImGui
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.
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_drawMostRecentValueText;
};

@ -147,6 +147,8 @@ namespace ImGui
float imGuiHistoWidgetHeight = m_collapsed ? histogramHeight : (histogramHeight - 15);
if (GetSize() > 0)
{
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, m_barLineColor.Value);
switch (m_viewType)
{
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));
break;
}
ImGui::PopStyleColor();
}

@ -15,12 +15,6 @@
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.
class AxisAlignedBoxShapeComponent
: public AZ::Component

@ -24,6 +24,12 @@ namespace LmbrCentral
/// Type ID for the BoxShapeConfig
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
class BoxShapeConfig
: public ShapeComponentConfig

@ -57,6 +57,14 @@ namespace Multiplayer
const AzNetworking::PacketEncodingBuffer& correction
) 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 boolean true if we're currently migrating from one host to another
bool IsMigrating() const;

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

@ -58,11 +58,10 @@ namespace Multiplayer
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
protected:
private:
//! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
@ -80,5 +79,8 @@ namespace Multiplayer
bool m_isHierarchyEnabled = true;
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 NetworkHierarchyRootComponentController;
friend class ServerToClientReplicationWindow;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
@ -61,10 +60,9 @@ namespace Multiplayer
bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer);
protected:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
void SetTopLevelHierarchyRootEntity(AZ::Entity* previousHierarchyRoot, AZ::Entity* newHierarchyRoot);
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
@ -81,16 +79,19 @@ namespace Multiplayer
//! Rebuilds hierarchy starting from this root component's entity.
void RebuildHierarchy();
//! @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.
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.
bool m_isHierarchyEnabled = true;
AzNetworking::ConnectionId m_previousOwningConnectionId = AzNetworking::InvalidConnectionId;
void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override;
friend class HierarchyBenchmarkBase;
};

@ -45,7 +45,7 @@ namespace Multiplayer
using ClientMigrationStartEvent = AZ::Event<ClientInputId>;
using ClientMigrationEndEvent = 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 ConnectionAcquiredEvent = AZ::Event<MultiplayerAgentDatum>;
using ServerAcceptanceReceivedEvent = AZ::Event<>;
@ -136,10 +136,12 @@ namespace Multiplayer
virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0;
//! Signals a NotifyClientMigrationEvent with the provided parameters.
//! @param hostId the host id of the host the client is migrating to
//! @param userIdentifier the user identifier the client will provide the new host to validate identity
//! @param lastClientInputId the last processed clientInputId by the current host
virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0;
//! @param connectionId the connection id of the client that is migrating
//! @param hostId the host id of the host the client is migrating to
//! @param userIdentifier the user identifier the client will provide the new host to validate identity
//! @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.
//! @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
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.
//! 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

@ -29,7 +29,7 @@ namespace Multiplayer
using HostId = AzNetworking::IpAddress;
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);
AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t);
@ -68,6 +68,7 @@ namespace Multiplayer
Server, // A simulated proxy on a server
Authority // An authoritative proxy on a server (full authority)
};
const char* GetEnumString(NetEntityRole value);
enum class ComponentSerializationType : uint8_t
{
@ -113,6 +114,24 @@ namespace Multiplayer
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)
: m_prefabName(name)
, m_entityOffset(entityOffset)

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

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

@ -13,9 +13,9 @@
<Include File="Multiplayer/MultiplayerTypes.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInput.h"/>
<Include File="Source/NetworkInput/NetworkInputArray.h"/>
<Include File="Source/NetworkInput/NetworkInputHistory.h"/>
<Include File="Source/NetworkInput/NetworkInputMigrationVector.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputArray.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputHistory.h"/>
<Include File="Multiplayer/NetworkInput/NetworkInputMigrationVector.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" />

@ -9,6 +9,7 @@
<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="uint64_t" Name="temporaryUserId" Init="0" />
<Member Type="Multiplayer::LongNetworkString" Name="ticket" />
</Packet>

@ -8,7 +8,7 @@
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
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" />

@ -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
{
return m_lastMigratedInputId != ClientInputId{ 0 };

@ -394,9 +394,9 @@ namespace Multiplayer
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)

@ -129,34 +129,48 @@ namespace Multiplayer
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 (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
if (m_rootEntity != newHierarchyRoot)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (m_rootEntity)
m_rootEntity = newHierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
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)
{
m_rootEntity = newRoot;
m_previousOwningConnectionId = GetNetBindComponent()->GetOwningConnectionId();
GetNetBindComponent()->SetOwningConnectionId(m_rootEntity->FindComponent<NetBindComponent>()->GetOwningConnectionId());
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
else
{
GetNetBindComponent()->SetOwningConnectionId(m_previousOwningConnectionId);
m_isHierarchyEnabled = false;
m_rootEntity = nullptr;
m_networkHierarchyLeaveEvent.Signal();
}
}
@ -203,11 +221,11 @@ namespace Multiplayer
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr);
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr, nullptr);
}
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))
{
SetRootForEntity(nullptr, childEntity);
SetRootForEntity(GetEntity(), nullptr, childEntity);
}
}
}
@ -209,7 +209,7 @@ namespace Multiplayer
auto [rootComponent, childComponent] = GetHierarchyComponents(parentEntity);
if (rootComponent == nullptr && childComponent == nullptr)
{
RebuildHierarchy();
SetRootForEntity(nullptr, nullptr, GetEntity());
}
else
{
@ -219,7 +219,7 @@ namespace Multiplayer
else
{
// Detached from parent
RebuildHierarchy();
SetRootForEntity(nullptr, nullptr, GetEntity());
}
}
@ -247,14 +247,14 @@ namespace Multiplayer
{
// This is a newly added entity to the network hierarchy.
hierarchyChanged = true;
SetRootForEntity(GetEntity(), currentEntity);
SetRootForEntity(nullptr, GetEntity(), currentEntity);
}
}
// These entities were removed since last rebuild.
for (const AZ::Entity* previousEntity : previousEntities)
{
SetRootForEntity(nullptr, previousEntity);
SetRootForEntity(GetEntity(), nullptr, previousEntity);
}
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);
if (hierarchyChildComponent)
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root);
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(previousKnownRoot, newRoot);
}
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 (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
if (newHierarchyRoot)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (hierarchyRoot)
if (m_rootEntity != newHierarchyRoot)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId());
controller->SetHierarchyRoot(netRootId);
m_rootEntity = newHierarchyRoot;
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);
}
}
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.
RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId)
{
NetworkHierarchyRootComponentBase::SetOwningConnectionId(connectionId);
if (IsHierarchicalChild() == false)
{
m_previousOwningConnectionId = connectionId;
}
}
NetworkHierarchyRootComponentController::NetworkHierarchyRootComponentController(NetworkHierarchyRootComponent& parent)
: NetworkHierarchyRootComponentControllerBase(parent)
{
@ -370,7 +391,7 @@ namespace Multiplayer
void NetworkHierarchyRootComponentController::CreateInput(Multiplayer::NetworkInput& input, float deltaTime)
{
NetworkHierarchyRootComponent& component = GetParent();
if(!component.IsHierarchicalRoot())
if (!component.IsHierarchicalRoot())
{
return;
}
@ -386,7 +407,7 @@ namespace Multiplayer
for (AZ::Entity* child : entities)
{
if(child == component.GetEntity())
if (child == component.GetEntity())
{
continue; // Avoid infinite recursion
}

@ -23,22 +23,16 @@ namespace Multiplayer
ServerToClientConnectionData::ServerToClientConnectionData
(
AzNetworking::IConnection* connection,
AzNetworking::IConnectionListener& connectionListener,
NetworkEntityHandle controlledEntity
AzNetworking::IConnectionListener& connectionListener
)
: m_connection(connection)
, m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); })
, m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); })
, m_controlledEntity(controlledEntity)
, m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId)
{
OnControlledEntityMigration(entityHandle, remoteHostId);
})
, 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.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs);
}
@ -54,6 +48,20 @@ namespace Multiplayer
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
{
return ConnectionDataType::ServerToClient;
@ -94,8 +102,7 @@ namespace Multiplayer
void ServerToClientConnectionData::OnControlledEntityMigration
(
[[maybe_unused]] const ConstNetworkEntityHandle& entityHandle,
[[maybe_unused]] const HostId& remoteHostId,
[[maybe_unused]] AzNetworking::ConnectionId connectionId
const HostId& remoteHostId
)
{
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
const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64();
const uint64_t temporaryUserIdentifier = AzNetworking::CryptoRand64();
// Tell the new host that a client is about to (re)join
GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId);
// Tell the client who to join
MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId);
GetConnection()->SendReliablePacket(clientMigration);
GetMultiplayer()->SendNotifyClientMigrationEvent(GetConnection()->GetConnectionId(), remoteHostId, temporaryUserIdentifier, migratedClientInputId, m_controlledEntity.GetNetEntityId());
// We need to send a MultiplayerPackets::ClientMigration packet to complete this process
// This happens inside MultiplayerSystemComponent, once we're certain the remote host has appropriately prepared
m_controlledEntity = NetworkEntityHandle();
m_canSendUpdates = false;

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

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

@ -74,7 +74,7 @@ namespace Multiplayer
{
ImGui::Text("%s", entity->GetId().ToString().c_str());
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::Text("%s", entity->GetName().c_str());
ImGui::NextColumn();

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

@ -8,6 +8,7 @@
#include <Multiplayer/MultiplayerConstants.h>
#include <Multiplayer/Components/MultiplayerComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <MultiplayerSystemComponent.h>
#include <ConnectionData/ClientToServerConnectionData.h>
#include <ConnectionData/ServerToClientConnectionData.h>
@ -76,6 +77,7 @@ namespace Multiplayer
"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, 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(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");
@ -168,6 +170,7 @@ namespace Multiplayer
AZ::ConsoleFunctorFlags flags,
AZ::ConsoleInvokedFrom invokedFrom
) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); })
, m_autonomousEntityReplicatorCreatedHandler([this]([[maybe_unused]] NetEntityId netEntityId) { OnAutonomousEntityReplicatorCreated(); })
{
AZ::Interface<IMultiplayer>::Register(this);
}
@ -205,8 +208,23 @@ namespace Multiplayer
bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated)
{
InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer);
return m_networkInterface->Listen(port);
if (port != sv_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)
@ -328,6 +346,11 @@ namespace Multiplayer
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 serverRateMs = static_cast<AZ::TimeMs>(sv_serverSendRateMs);
const float serverRateSeconds = static_cast<float>(serverRateMs) / 1000.0f;
@ -412,11 +435,6 @@ namespace Multiplayer
{
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
}
if (bg_multiplayerDebugDraw)
{
m_networkEntityManager.DebugDraw();
}
}
int MultiplayerSystemComponent::GetTickOrder()
@ -487,17 +505,39 @@ namespace Multiplayer
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
return true;
}
}
}
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)))
{
reinterpret_cast<ServerToClientConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
// Sync our console
ConsoleReplicator consoleReplicator(connection);
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
if (packet.GetTemporaryUserId() == 0)
{
// Sync our console
ConsoleReplicator consoleReplicator(connection);
AZ::Interface<AZ::IConsole>::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); });
}
return true;
}
return false;
@ -511,10 +551,26 @@ namespace Multiplayer
)
{
reinterpret_cast<ClientToServerConnectionData*>(connection->GetUserData())->SetDidHandshake(true);
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(commandString.c_str());
AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap();
AZ::Interface<AZ::IConsole>::Get()->PerformCommand(loadLevelString.c_str());
if (m_temporaryUserIdentifier == 0)
{
AZ::CVarFixedString commandString = "sv_map " + packet.GetMap();
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();
return true;
@ -637,13 +693,17 @@ namespace Multiplayer
// 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
m_temporaryUserIdentifier = packet.GetTemporaryUserIdentifier();
// Disconnect our existing server connection
auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); };
m_networkInterface->GetConnectionSet().VisitConnections(visitor);
AZLOG_INFO("Migrating to new server shard");
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;
}
@ -673,7 +733,7 @@ namespace Multiplayer
providerTicket = m_pendingConnectionTickets.front();
m_pendingConnectionTickets.pop();
}
connection->SendReliablePacket(MultiplayerPackets::Connect(0, providerTicket.c_str()));
connection->SendReliablePacket(MultiplayerPackets::Connect(0, m_temporaryUserIdentifier, providerTicket.c_str()));
}
else
{
@ -681,29 +741,10 @@ namespace Multiplayer
m_connectionAcquiredEvent.Signal(datum);
}
// Hosts will spawn a new default player prefab for the user that just connected
if (GetAgentType() == MultiplayerAgentType::ClientServer
|| GetAgentType() == MultiplayerAgentType::DedicatedServer)
{
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
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));
connection->SetUserData(new ServerToClientConnectionData(connection, *this));
}
else
{
@ -725,9 +766,9 @@ namespace Multiplayer
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);
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
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)
if (m_agentType == MultiplayerAgentType::ClientServer)
{
INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab();
for (NetworkEntityHandle controlledEntity : entityList)
{
if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent())
{
controlledEntityNetBindComponent->SetAllowAutonomy(true);
}
controlledEntity.Activate();
}
NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(0);
EnableAutonomousControl(controlledEntity, AzNetworking::InvalidConnectionId);
}
AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType));
@ -869,9 +902,9 @@ namespace Multiplayer
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)
@ -925,6 +958,22 @@ namespace Multiplayer
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)
{
m_spawnNetboundEntities = value;
@ -1055,6 +1104,13 @@ namespace Multiplayer
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)
{
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()));
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)
{
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");

@ -123,7 +123,7 @@ namespace Multiplayer
void AddSessionInitHandler(SessionInitEvent::Handler& handler) override;
void AddSessionShutdownHandler(SessionShutdownEvent::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 SendReadyForEntityUpdates(bool readyForEntityUpdates) override;
AZ::TimeMs GetCurrentHostTimeMs() const override;
@ -132,6 +132,8 @@ namespace Multiplayer
INetworkEntityManager* GetNetworkEntityManager() override;
void SetFilterEntityManager(IFilterEntityManager* entityFilter) 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;
bool GetShouldSpawnNetworkEntities() const override;
//! @}
@ -145,9 +147,11 @@ namespace Multiplayer
void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds);
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);
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");
AzNetworking::INetworkInterface* m_networkInterface = nullptr;
@ -170,12 +174,16 @@ namespace Multiplayer
ClientMigrationEndEvent m_clientMigrationEndEvent;
NotifyClientMigrationEvent m_notifyClientMigrationEvent;
NotifyEntityMigrationEvent m_notifyEntityMigrationEvent;
AZ::Event<NetEntityId>::Handler m_autonomousEntityReplicatorCreatedHandler;
AZStd::queue<AZStd::string> m_pendingConnectionTickets;
AZStd::unordered_map<uint64_t, NetEntityId> m_playerRejoinData;
AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 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;
float m_renderBlendFactor = 0.0f;
float m_tickFactor = 0.0f;

@ -47,6 +47,9 @@ namespace Multiplayer
, m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); })
, 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
m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead;
@ -62,12 +65,10 @@ namespace Multiplayer
networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler);
}
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler);
}
void EntityReplicationManager::SetRemoteHostId(const HostId& hostId)
{
m_remoteHostId = hostId;
if (m_updateMode == Mode::LocalServerToRemoteServer)
{
GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler);
}
}
const HostId& EntityReplicationManager::GetRemoteHostId() const
@ -258,8 +259,8 @@ namespace Multiplayer
{
AZLOG_WARN
(
"Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d",
aznumeric_cast<uint32_t>(replicator->GetEntityHandle().GetNetEntityId()),
"Serializing extremely large entity (%llu) - MaxPayload: %d NeededSize %d",
aznumeric_cast<AZ::u64>(replicator->GetEntityHandle().GetNetEntityId()),
m_maxPayloadSize,
nextMessageSize
);
@ -364,15 +365,29 @@ namespace Multiplayer
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
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());
}
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 (!entityReplicator->IsMarkedForRemoval())
{
@ -387,8 +402,8 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Reinited replicator for %u from remote host %s role %d",
entityHandle.GetNetEntityId(),
"Reinited replicator for netEntityId %llu from remote host %s role %d",
static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole)
);
@ -404,8 +419,8 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Added replicator for %u from remote host %s role %d",
entityHandle.GetNetEntityId(),
"Added replicator for netEntityId %llu from remote host %s role %d",
static_cast<AZ::u64>(entityHandle.GetNetEntityId()),
GetRemoteHostId().GetString().c_str(),
aznumeric_cast<int32_t>(remoteNetworkRole)
);
@ -413,7 +428,7 @@ namespace Multiplayer
}
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");
}
return entityReplicator;
@ -502,24 +517,20 @@ namespace Multiplayer
{
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())
{
// 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
{
shouldDeleteEntity = true;
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
if (shouldDeleteEntity)
@ -529,17 +540,17 @@ namespace Multiplayer
{
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
{
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);
}
}
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();
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());
}
@ -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");
AZLOG_INFO
(
"EntityReplicationManager: Changing network role on entity %u, old role %u new role %u",
aznumeric_cast<uint32_t>(netEntityId),
aznumeric_cast<uint32_t>(netBindComponent->GetNetEntityRole()),
aznumeric_cast<uint32_t>(localNetworkRole)
"EntityReplicationManager: Changing network role on entity %s(%llu), old role %s new role %s",
replicatorEntity.GetEntity()->GetName().c_str(),
aznumeric_cast<AZ::u64>(netEntityId),
GetEnumString(netBindComponent->GetNetEntityRole()),
GetEnumString(localNetworkRole)
);
if (NetworkRoleHasController(localNetworkRole))
@ -708,9 +720,9 @@ namespace Multiplayer
AZLOG_WARN
(
"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(),
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->GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(entityReplicator->GetNetBindComponent()->GetNetEntityRole()),
@ -760,13 +772,13 @@ namespace Multiplayer
result = UpdateValidationResult::DropMessage;
if (updateMessage.GetIsDelete())
{
AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %llu, sequence %d latest sequence %d from remote host %s",
(AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
}
else
{
AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote host %s",
updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %llu, sequence %d latest sequence %d from remote host %s",
(AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str());
}
}
}
@ -853,10 +865,10 @@ namespace Multiplayer
{
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()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()),
message.GetEntityId()
static_cast<AZ::u64>(message.GetEntityId())
);
return false;
}
@ -1113,7 +1125,7 @@ namespace Multiplayer
if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer)
{
netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId());
netBindComponent->NotifyServerMigration(GetRemoteHostId());
}
bool didSucceed = true;
@ -1145,7 +1157,7 @@ namespace Multiplayer
AZ_Assert(didSucceed, "Failed to migrate entity from server");
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
GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId());
@ -1201,7 +1213,7 @@ namespace Multiplayer
// Change the role on the replicator
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;
}

@ -103,8 +103,8 @@ namespace Multiplayer
AZ_Assert
(
m_boundLocalNetworkRole != m_remoteNetworkRole,
"Invalid configuration detected, bound local role must differ from remote network role Role: %d",
aznumeric_cast<int32_t>(m_boundLocalNetworkRole)
"Invalid configuration detected, bound local role must differ from remote network role: %s",
GetEnumString(m_boundLocalNetworkRole)
);
if (RemoteManagerOwnsEntityLifetime())
@ -176,7 +176,6 @@ namespace Multiplayer
switch (GetBoundLocalNetworkRole())
{
case NetEntityRole::Authority:
{
if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
@ -189,10 +188,8 @@ namespace Multiplayer
{
m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
}
}
break;
break;
case NetEntityRole::Server:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent());
@ -204,23 +201,21 @@ namespace Multiplayer
// Listen for these to forward the rpc along to the other Client replicators
m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent());
}
// NOTE: e_Autonomous is not connected to e_ServerProxy, it is always connected to an e_Authority
AZ_Assert(GetRemoteNetworkRole() != NetEntityRole::Autonomous, "Unexpected autonomous remote role")
}
break;
else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous)
{
// NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority
AZ_Assert(false, "Unexpected autonomous remote role")
}
break;
case NetEntityRole::Client:
{
// Nothing allowed, no Client to Server communication
}
break;
break;
case NetEntityRole::Autonomous:
{
if (GetRemoteNetworkRole() == NetEntityRole::Authority)
{
m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent());
}
}
break;
break;
default:
AZ_Assert(false, "Unexpected network role");
}
@ -252,22 +247,9 @@ namespace Multiplayer
if (entity->GetState() != AZ::Entity::State::Init)
{
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%u)", 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
AZLOG_WARN("Trying to activate an entity that is not in the Init state (%llu)", static_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()));
}
entity->Activate();
m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this);
@ -281,8 +263,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = m_netBindComponent;
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
if (isAuthority || isClient || isAutonomous)
@ -296,10 +277,10 @@ namespace Multiplayer
bool EntityReplicator::OwnsReplicatorLifetime() const
{
bool ret(false);
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server
if (GetBoundLocalNetworkRole() == NetEntityRole::Authority // Authority always owns lifetime
|| (GetBoundLocalNetworkRole() == NetEntityRole::Server // Server also owns lifetime if the remote endpoint is a client of some form
&& (GetRemoteNetworkRole() == NetEntityRole::Client
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
|| GetRemoteNetworkRole() == NetEntityRole::Autonomous)))
{
ret = true;
}
@ -309,10 +290,9 @@ namespace Multiplayer
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
{
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
return isServer || isClient;
}
@ -429,10 +409,8 @@ namespace Multiplayer
if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>())
{
const NetEntityId parentId = networkTransform->GetParentEntityId();
/*
* 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.
*/
// 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.
if (parentId != InvalidNetEntityId)
{
ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId);
@ -452,9 +430,9 @@ namespace Multiplayer
AZLOG
(
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(),
aznumeric_cast<uint32_t>(parentId)
aznumeric_cast<AZ::u64>(parentId)
);
return false;
}
@ -472,19 +450,19 @@ namespace Multiplayer
AZLOG
(
NET_RepDeletes,
"Sending delete replicator id %u migrated %d to remote host %s",
aznumeric_cast<uint32_t>(GetEntityHandle().GetNetEntityId()),
"Sending delete replicator id %llu migrated %d to remote host %s",
aznumeric_cast<AZ::u64>(GetEntityHandle().GetNetEntityId()),
WasMigrated() ? 1 : 0,
m_replicationManager.GetRemoteHostId().GetString().c_str()
);
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated(), m_propertyPublisher->IsRemoteReplicatorEstablished());
return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated());
}
NetBindComponent* netBindComponent = GetNetBindComponent();
//const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished();
NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId());
//if (sendSliceName)
if (sendSliceName)
{
updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId());
}
@ -553,42 +531,33 @@ namespace Multiplayer
switch (entityRpcMessage.GetRpcDeliveryType())
{
case RpcDeliveryType::AuthorityToClient:
{
if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous))
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
{
// We are a local client, and we are connected to server, aka AuthorityToClient
result = RpcValidationResult::HandleRpc;
}
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (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
result = RpcValidationResult::ForwardToClient;
}
}
break;
break;
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
result = RpcValidationResult::HandleRpc;
}
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Server))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (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
// This can occur if we've recently migrated
result = RpcValidationResult::ForwardToAutonomous;
}
}
break;
break;
case RpcDeliveryType::AutonomousToAuthority:
{
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous))
{
if (IsMarkedForRemoval())
{
@ -610,12 +579,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc;
}
}
}
break;
break;
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 (IsMarkedForRemoval())
@ -638,9 +604,9 @@ namespace Multiplayer
result = RpcValidationResult::HandleRpc;
}
}
break;
}
break;
}
if (result == RpcValidationResult::DropRpcAndDisconnect)
{
bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server);
@ -654,30 +620,29 @@ namespace Multiplayer
{
AZLOG_ERROR
(
"Dropping RPC and Connection EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC and Connection EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
}
}
if (result == RpcValidationResult::DropRpc)
{
AZLOG
(
NET_Rpc,
"Dropping RPC EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
@ -696,13 +661,12 @@ namespace Multiplayer
{
AZLOG_WARN
(
"Dropping RPC since entity deleted EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<uint32_t>(m_entityHandle.GetNetEntityId()),
aznumeric_cast<uint32_t>(GetBoundLocalNetworkRole()),
aznumeric_cast<uint32_t>(GetRemoteNetworkRole()),
"Dropping RPC since entity deleted EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s",
aznumeric_cast<AZ::u64>(m_entityHandle.GetNetEntityId()),
GetEnumString(GetBoundLocalNetworkRole()),
GetEnumString(GetRemoteNetworkRole()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcDeliveryType()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetComponentId()),
aznumeric_cast<uint32_t>(entityRpcMessage.GetRpcIndex()),
GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()),
entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false",
IsMarkedForRemoval() ? "true" : "false"
);
@ -740,23 +704,23 @@ namespace Multiplayer
case RpcValidationResult::DropRpcAndDisconnect:
return false;
case RpcValidationResult::ForwardToClient:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage);
}
return true;
}
case RpcValidationResult::ForwardToAutonomous:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage);
}
return true;
}
case RpcValidationResult::ForwardToAuthority:
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
{
ScopedForwardingMessage forwarding(*this);
m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage);
}
return true;
}
default:
break;
}

@ -33,8 +33,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Removing timeout for networkEntityId %u from %s, new owner is %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str(),
newOwner.GetString().c_str()
);
@ -48,8 +48,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u from %s to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Assigning networkEntityId %llu from %s to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
iter->second.back().GetString().c_str(),
newOwner.GetString().c_str()
);
@ -59,8 +59,8 @@ namespace Multiplayer
AZLOG
(
NET_AuthTracker,
"AuthTracker: Assigning networkEntityId %u to %s",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"AuthTracker: Assigning networkEntityId %llu to %s",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
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 (authorityStack.empty())
@ -114,14 +114,14 @@ namespace Multiplayer
}
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
{
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");
}
}
@ -205,8 +205,8 @@ namespace Multiplayer
{
AZLOG_ERROR
(
"Timed out entity id %u during migration previous owner %s, removing it",
aznumeric_cast<uint32_t>(entityHandle.GetNetEntityId()),
"Timed out entity id %llu during migration previous owner %s, removing it",
aznumeric_cast<AZ::u64>(entityHandle.GetNetEntityId()),
timeoutData->second.m_previousOwner.GetString().c_str()
);
m_networkEntityManager.MarkForRemoval(entityHandle);

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

@ -48,6 +48,20 @@ namespace Multiplayer
void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
{
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_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true);
m_entityDomain->ActivateTracking(m_ownedEntities);
@ -227,11 +241,19 @@ namespace Multiplayer
{
AZ::Entity* entity = it->second;
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)
{
const AZ::Aabb entityBounds = AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->GetEntityWorldBoundsUnion(entity->GetId());
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax());
debugDisplay->SetColor(AZ::Colors::Black);
debugDisplay->SetAlpha(0.5f);
}
else
{
debugDisplay->SetColor(AZ::Colors::DeepSkyBlue);
debugDisplay->SetAlpha(0.25f);
}
debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax());
}
if (m_entityDomain != nullptr)

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

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

@ -6,7 +6,7 @@
*
*/
#include <Source/NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/NetworkInput/NetworkInputMigrationVector.h>
#include <Multiplayer/IMultiplayer.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