From 5acdc4059531f2aeab472369ce4f5c1214bf147e Mon Sep 17 00:00:00 2001 From: karlberg Date: Fri, 14 May 2021 14:24:33 -0700 Subject: [PATCH 1/6] Many fixes for external gem multiplayer components and component network inputs, fixes an uninitialized variable resulting in continual desyncs, restructures our public includes to match the directory structure of source, allows autogen artefacts to be included by external gems, allowing for external multiplayer components to interact with multiplayer gem components with no extra code --- .../Serialization/HashSerializer.h | 2 +- .../LocalPredictionPlayerInputComponent.h | 2 +- .../{ => Components}/MultiplayerComponent.h | 8 ++- .../MultiplayerComponentRegistry.h | 12 ++++- .../{ => Components}/MultiplayerController.h | 12 +++-- .../{ => Components}/NetBindComponent.h | 13 +++-- .../Components/NetworkTransformComponent.h | 0 .../{ => ConnectionData}/IConnectionData.h | 2 +- .../{ => EntityDomains}/IEntityDomain.h | 2 +- .../Code/Include/Multiplayer/IMultiplayer.h | 27 ++-------- .../Multiplayer/INetworkPlayerSpawner.h | 18 ------- .../EntityReplication}/ReplicationRecord.h | 0 .../INetworkEntityManager.h | 2 +- .../{ => NetworkEntity}/NetworkEntityHandle.h | 2 +- .../NetworkEntityHandle.inl | 0 .../NetworkEntityRpcMessage.h | 0 .../NetworkEntityUpdateMessage.h | 0 .../IMultiplayerComponentInput.h | 2 +- .../{ => NetworkInput}/NetworkInput.h | 14 ++--- .../{ => NetworkTime}/INetworkTime.h | 0 .../{ => NetworkTime}/RewindableObject.h | 4 +- .../{ => NetworkTime}/RewindableObject.inl | 0 .../IReplicationWindow.h | 2 +- .../AutoGen/AutoComponentTypes_Source.jinja | 8 ++- .../Source/AutoGen/AutoComponent_Common.jinja | 54 +++++++++++++++---- .../Source/AutoGen/AutoComponent_Header.jinja | 39 ++++++++------ .../Source/AutoGen/AutoComponent_Source.jinja | 51 +++++++++++++----- ...tionPlayerInputComponent.AutoComponent.xml | 6 +-- .../AutoGen/Multiplayer.AutoPackets.xml | 6 +-- ...etworkTransformComponent.AutoComponent.xml | 2 +- .../LocalPredictionPlayerInputComponent.cpp | 16 +++--- .../Components/MultiplayerComponent.cpp | 23 ++++++-- .../MultiplayerComponentRegistry.cpp | 8 ++- .../Components/MultiplayerController.cpp | 15 ++++-- .../Source/Components/NetBindComponent.cpp | 41 ++++++++++---- .../Components/NetworkTransformComponent.cpp | 4 +- .../ClientToServerConnectionData.h | 2 +- .../ServerToClientConnectionData.h | 2 +- .../Debug/MultiplayerDebugSystemComponent.cpp | 15 +++--- .../EntityDomains/FullOwnershipEntityDomain.h | 2 +- .../Code/Source/MultiplayerGem.cpp | 2 +- .../MultiplayerStats.cpp | 0 .../Source/MultiplayerSystemComponent.cpp | 22 +------- .../Code/Source/MultiplayerSystemComponent.h | 4 -- .../EntityReplicationManager.cpp | 17 +++--- .../EntityReplicationManager.h | 10 ++-- .../EntityReplication/EntityReplicator.cpp | 6 +-- .../EntityReplication/EntityReplicator.h | 4 +- .../EntityReplication/PropertyPublisher.h | 2 +- .../EntityReplication/PropertySubscriber.cpp | 2 +- .../EntityReplication/ReplicationRecord.cpp | 2 +- .../NetworkEntityAuthorityTracker.cpp | 4 +- .../NetworkEntity/NetworkEntityHandle.cpp | 8 +-- .../NetworkEntity/NetworkEntityManager.cpp | 2 +- .../NetworkEntity/NetworkEntityManager.h | 8 +-- .../NetworkEntity/NetworkEntityRpcMessage.cpp | 2 +- .../NetworkEntity/NetworkEntityTracker.cpp | 2 +- .../NetworkEntity/NetworkEntityTracker.h | 2 +- .../NetworkEntityUpdateMessage.cpp | 2 +- .../Code/Source/NetworkInput/NetworkInput.cpp | 23 ++++---- .../Source/NetworkInput/NetworkInputArray.cpp | 2 +- .../Source/NetworkInput/NetworkInputArray.h | 4 +- .../Source/NetworkInput/NetworkInputChild.h | 2 +- .../Source/NetworkInput/NetworkInputHistory.h | 2 +- .../NetworkInputMigrationVector.h | 4 +- .../Code/Source/NetworkTime/NetworkTime.cpp | 2 +- .../Code/Source/NetworkTime/NetworkTime.h | 2 +- .../Pipeline/NetworkPrefabProcessor.cpp | 2 +- .../NullReplicationWindow.h | 2 +- .../ServerToClientReplicationWindow.cpp | 2 +- .../ServerToClientReplicationWindow.h | 4 +- .../Code/Tests/RewindableObjectTests.cpp | 2 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 43 ++++++++------- 73 files changed, 348 insertions(+), 267 deletions(-) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/Components/LocalPredictionPlayerInputComponent.h (98%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => Components}/MultiplayerComponent.h (96%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => Components}/MultiplayerComponentRegistry.h (83%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => Components}/MultiplayerController.h (92%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => Components}/NetBindComponent.h (93%) rename Gems/Multiplayer/Code/{Source => Include/Multiplayer}/Components/NetworkTransformComponent.h (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => ConnectionData}/IConnectionData.h (97%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => EntityDomains}/IEntityDomain.h (96%) delete mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/INetworkPlayerSpawner.h rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity/EntityReplication}/ReplicationRecord.h (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity}/INetworkEntityManager.h (99%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity}/NetworkEntityHandle.h (99%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity}/NetworkEntityHandle.inl (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity}/NetworkEntityRpcMessage.h (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkEntity}/NetworkEntityUpdateMessage.h (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkInput}/IMultiplayerComponentInput.h (94%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkInput}/NetworkInput.h (85%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkTime}/INetworkTime.h (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkTime}/RewindableObject.h (97%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => NetworkTime}/RewindableObject.inl (100%) rename Gems/Multiplayer/Code/Include/Multiplayer/{ => ReplicationWindows}/IReplicationWindow.h (96%) rename Gems/Multiplayer/Code/{Include/Multiplayer => Source}/MultiplayerStats.cpp (100%) diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h index 2f86d1aafe..1cc44cf5bd 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h @@ -56,6 +56,6 @@ namespace AzNetworking private: - AZ::HashValue64 m_hash; + AZ::HashValue64 m_hash = AZ::HashValue64{ 0 }; }; } diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h similarity index 98% rename from Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h index 924fd78391..7a6105a7e3 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h similarity index 96% rename from Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponent.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h index 29348a698a..6f410dd97c 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h @@ -15,7 +15,8 @@ #include #include #include -#include +#include +#include #include #include @@ -62,7 +63,10 @@ namespace Multiplayer //! @} NetEntityId GetNetEntityId() const; - NetEntityRole GetNetEntityRole() const; + bool IsAuthority() const; + bool IsAutonomous() const; + bool IsServer() const; + bool IsClient() const; ConstNetworkEntityHandle GetEntityHandle() const; NetworkEntityHandle GetEntityHandle(); void MarkDirty(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponentRegistry.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h similarity index 83% rename from Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponentRegistry.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h index d06362ed4b..d3064d87dd 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerComponentRegistry.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponentRegistry.h @@ -14,7 +14,8 @@ #include #include -#include +#include +#include namespace Multiplayer { @@ -22,13 +23,15 @@ namespace Multiplayer { public: using PropertyNameLookupFunction = AZStd::function; - using RpcNameLookupFunction = AZStd::function; + using RpcNameLookupFunction = AZStd::function; + using AllocComponentInputFunction = AZStd::function()>; struct ComponentData { AZ::Name m_gemName; AZ::Name m_componentName; PropertyNameLookupFunction m_componentPropertyNameLookupFunction; RpcNameLookupFunction m_componentRpcNameLookupFunction; + AllocComponentInputFunction m_allocComponentInputFunction; }; //! Registers a multiplayer component with the multiplayer system. @@ -36,6 +39,11 @@ namespace Multiplayer //! @return the NetComponentId assigned to this particular component NetComponentId RegisterMultiplayerComponent(const ComponentData& componentData); + //! Allocates a new component input for the provided netComponentId. + //! @param netComponentId the NetComponentId to allocate a component input for + //! @return pointer to the allocated component input, caller assumes ownership + AZStd::unique_ptr AllocateComponentInput(NetComponentId netComponentId); + //! Returns the gem name associated with the provided NetComponentId. //! @param netComponentId the NetComponentId to return the gem name of //! @return the name of the gem that contains the requested component diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerController.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerController.h similarity index 92% rename from Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerController.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerController.h index 89c47c40d4..a8017ef9d1 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerController.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerController.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer @@ -47,9 +47,13 @@ namespace Multiplayer //! @return the networkId for the entity that owns this controller NetEntityId GetNetEntityId() const; - //! Returns the networkRole for the entity that owns this controller. - //! @return the networkRole for the entity that owns this controller - NetEntityRole GetNetEntityRole() const; + //! Returns true if this controller has authority. + //! @return boolean true if this controller has authority + bool IsAuthority() const; + + //! Returns true if this controller has autonomy (can locally predict). + //! @return boolean true if this controller has autonomy + bool IsAutonomous() const; //! Returns the raw AZ::Entity pointer for the entity that owns this controller. //! @return the raw AZ::Entity pointer for the entity that owns this controller diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h similarity index 93% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetBindComponent.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 464333e3b2..257e7cab4b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -20,10 +20,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -63,12 +63,16 @@ namespace Multiplayer NetEntityRole GetNetEntityRole() const; bool IsAuthority() const; + bool IsAutonomous() const; + bool IsServer() const; + bool IsClient() const; bool HasController() const; NetEntityId GetNetEntityId() const; const PrefabEntityId& GetPrefabEntityId() const; ConstNetworkEntityHandle GetEntityHandle() const; NetworkEntityHandle GetEntityHandle(); + void SetAllowAutonomy(bool value); MultiplayerComponentInputVector AllocateComponentInputs(); bool IsProcessingInput() const; void CreateInput(NetworkInput& networkInput, float deltaTime); @@ -155,6 +159,7 @@ namespace Multiplayer bool m_isProcessingInput = false; bool m_isMigrationDataValid = false; bool m_needsToBeStopped = false; + bool m_allowAutonomy = false; // Set to true for the hosts controlled entity friend class NetworkEntityManager; friend class EntityReplicationManager; diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h similarity index 100% rename from Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.h rename to Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IConnectionData.h b/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h similarity index 97% rename from Gems/Multiplayer/Code/Include/Multiplayer/IConnectionData.h rename to Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h index 39fdb61435..1fb3003c7b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IConnectionData.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/ConnectionData/IConnectionData.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IEntityDomain.h b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h similarity index 96% rename from Gems/Multiplayer/Code/Include/Multiplayer/IEntityDomain.h rename to Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h index 70215612b0..dd7a11bb4a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IEntityDomain.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/EntityDomains/IEntityDomain.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 4931fb167f..6a615465c2 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -15,8 +15,9 @@ #include #include #include -#include -#include +#include +#include +#include #include namespace AzNetworking @@ -101,28 +102,6 @@ namespace Multiplayer //! @return pointer to the network entity manager instance bound to this multiplayer instance virtual INetworkEntityManager* GetNetworkEntityManager() = 0; - //! Returns the gem name associated with the provided component index. - //! @param netComponentId the componentId to return the gem name of - //! @return the name of the gem that contains the requested component - virtual const char* GetComponentGemName(NetComponentId netComponentId) const = 0; - - //! Returns the component name associated with the provided component index. - //! @param netComponentId the componentId to return the component name of - //! @return the name of the component - virtual const char* GetComponentName(NetComponentId netComponentId) const = 0; - - //! Returns the property name associated with the provided component index and property index. - //! @param netComponentId the component index to return the property name of - //! @param propertyIndex the index of the network property to return the property name of - //! @return the name of the network property - virtual const char* GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const = 0; - - //! Returns the Rpc name associated with the provided component index and rpc index. - //! @param netComponentId the componentId to return the property name of - //! @param rpcIndex the index of the rpc to return the rpc name of - //! @return the name of the requested rpc - virtual const char* GetComponentRpcName(NetComponentId netComponentId, RpcIndex rpcIndex) const = 0; - //! Retrieve the stats object bound to this multiplayer instance. //! @return the stats object bound to this multiplayer instance MultiplayerStats& GetStats() { return m_stats; } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkPlayerSpawner.h b/Gems/Multiplayer/Code/Include/Multiplayer/INetworkPlayerSpawner.h deleted file mode 100644 index f50d60e82d..0000000000 --- a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkPlayerSpawner.h +++ /dev/null @@ -1,18 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -namespace Multiplayer -{ - -} diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationRecord.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/ReplicationRecord.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h similarity index 99% rename from Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h index 17224e64cb..72dbe201e5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/INetworkEntityManager.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityHandle.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h similarity index 99% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityHandle.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h index 813589fac6..21d4ae9c62 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityHandle.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h @@ -138,4 +138,4 @@ namespace Multiplayer }; } -#include +#include diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityHandle.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityHandle.inl rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityRpcMessage.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityRpcMessage.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityUpdateMessage.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntityUpdateMessage.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerComponentInput.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h similarity index 94% rename from Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerComponentInput.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h index b26feadc4f..13faff3a7e 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerComponentInput.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h @@ -28,7 +28,7 @@ namespace Multiplayer { public: virtual ~IMultiplayerComponentInput() = default; - virtual NetComponentId GetComponentId() const = 0; + virtual NetComponentId GetNetComponentId() const = 0; virtual bool Serialize(AzNetworking::ISerializer& serializer) = 0; }; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h similarity index 85% rename from Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h index 9c6d2ce66a..b2e0134ea9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInput.h @@ -12,9 +12,9 @@ #pragma once -#include -#include -#include +#include +#include +#include #include namespace Multiplayer @@ -57,15 +57,15 @@ namespace Multiplayer IMultiplayerComponentInput* FindComponentInput(NetComponentId componentId); template - const InputType* FindInput() const + const InputType* FindComponentInput() const { - return static_cast(FindInput(InputType::s_Type)); + return static_cast(FindComponentInput(InputType::s_netComponentId)); } template - InputType* FindInput() + InputType* FindComponentInput() { - return static_cast(FindInput(InputType::s_Type)); + return static_cast(FindComponentInput(InputType::s_netComponentId)); } private: diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/INetworkTime.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/INetworkTime.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/INetworkTime.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/RewindableObject.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h similarity index 97% rename from Gems/Multiplayer/Code/Include/Multiplayer/RewindableObject.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h index 9e1655aec7..d5b7d563ab 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/RewindableObject.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include #include #include @@ -115,4 +115,4 @@ namespace AZ AZ_TYPE_INFO_TEMPLATE(Multiplayer::RewindableObject, "{B2937B44-FEE1-4277-B1E0-863DE76D363F}", AZ_TYPE_INFO_TYPENAME, AZ_TYPE_INFO_AUTO); } -#include +#include diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/RewindableObject.inl b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/RewindableObject.inl rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkTime/RewindableObject.inl diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IReplicationWindow.h b/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h similarity index 96% rename from Gems/Multiplayer/Code/Include/Multiplayer/IReplicationWindow.h rename to Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h index d0192e8aa4..5d90fc286b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IReplicationWindow.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/ReplicationWindows/IReplicationWindow.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja index 453d74c907..7bb4bdcc2c 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponentTypes_Source.jinja @@ -1,6 +1,6 @@ #include -#include -#include +#include +#include {% for Component in dataFiles %} {% set ComponentDerived = Component.attrib['OverrideComponent']|booleanTrue %} {% set ControllerDerived = Component.attrib['OverrideController']|booleanTrue %} @@ -38,7 +38,11 @@ namespace {{ Namespace }} componentData.m_componentName = AZ::Name("{{ Component.attrib['Name'] }}"); componentData.m_componentPropertyNameLookupFunction = {{ ComponentBaseName }}::GetNetworkPropertyName; componentData.m_componentRpcNameLookupFunction = {{ ComponentBaseName }}::GetRpcName; + componentData.m_allocComponentInputFunction = {{ ComponentBaseName }}::AllocateComponentInput; {{ ComponentBaseName }}::s_netComponentId = multiplayerComponentRegistry->RegisterMultiplayerComponent(componentData); +{% if NetworkInputCount > 0 %} + {{ ComponentName }}NetworkInput::s_netComponentId = {{ ComponentBaseName }}::s_netComponentId; +{% endif %} stats.ReserveComponentStats({{ ComponentBaseName }}::s_netComponentId, static_cast({{ NetworkPropertyCount }}), static_cast({{ RpcCount }})); } {% endfor %} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index d211b933c5..d187a151fd 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -207,11 +207,11 @@ namespace {{ Component.attrib['Namespace'] }} public: AZ_MULTIPLAYER_COMPONENT({{ Component.attrib['Namespace'] }}::{{ ComponentName }}, s_{{ LowerFirst(ComponentName) }}ConcreteUuid, {{ Component.attrib['Namespace'] }}::{{ ComponentNameBase }}); - static void Reflect([[maybe_unused]] AZ::ReflectContext* context); + static void Reflect(AZ::ReflectContext* context); - void OnInit() override {} - void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} - void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} + void OnInit() override; + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; {{ DeclareRpcHandlers(Component, 'Authority', 'Client', true)|indent(8) }} }; @@ -222,15 +222,15 @@ namespace {{ Component.attrib['Namespace'] }} : public {{ ControllerNameBase }} { public: - {{ ControllerName }}({{ ComponentName }}& parent) : {{ ControllerNameBase }}(parent) {} + {{ ControllerName }}({{ ComponentName }}& parent); - void OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} - void OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) override {} + void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; + void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; {% if NetworkInputCount > 0 %} //! Common input processing logic for the NetworkInput. //! @param input input structure to process //! @param deltaTime amount of time to integrate the provided inputs over - void ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) override {} + void ProcessInput(Multiplayer::NetworkInput& input, float deltaTime) override; {%endif %} {{ DeclareRpcHandlers(Component, 'Server', 'Authority', true)|indent(8) }} {{ DeclareRpcHandlers(Component, 'Client', 'Authority', true)|indent(8) }} @@ -239,10 +239,12 @@ namespace {{ Component.attrib['Namespace'] }} }; {% endif %} } -{% if ComponentDerived %} /// Place in your .cpp +#include <{{ Component.attrib['OverrideInclude'] }}> + namespace {{ Component.attrib['Namespace'] }} { +{% if ComponentDerived %} void {{ ComponentName }}::{{ ComponentName }}::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -251,7 +253,41 @@ namespace {{ Component.attrib['Namespace'] }} serializeContext->Class<{{ ComponentName }}, {{ ComponentNameBase }}>() ->Version(1); } + {{ ComponentNameBase }}::Reflect(context); } + + void {{ ComponentName }}::OnInit() + { + } + + void {{ ComponentName }}::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + } + + void {{ ComponentName }}::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + } + +{% endif %} +{% if ControllerDerived %} + {{ ControllerName }}::{{ ControllerName }}({{ ComponentName }}& parent) + : {{ ControllerNameBase }}(parent) + { + } + + void {{ ControllerName }}::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + } + + void {{ ControllerName }}::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) + { + } +{% if NetworkInputCount > 0 %} + + void {{ ControllerName }}::ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) + { + } +{% endif %} } {% endif %} */ diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index c22945c983..0d4e7146a0 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -7,25 +7,25 @@ {% macro DeclareNetworkPropertyGetter(Property) %} {% set PropertyName = UpperFirst(Property.attrib['Name']) %} {% if Property.attrib['Container'] == 'Array' %} -{% if Property.attrib['GenerateEventBindings']|booleanTrue %} -void {{ PropertyName }}AddEvent(AZ::Event::Handler& handler); -{% endif %} const AZStd::array<{% if Property.attrib['IsRewindable']|booleanTrue %}Multiplayer::RewindableObject<{% endif %}{{ Property.attrib['Type'] }}{% if Property.attrib['IsRewindable']|booleanTrue %}, Multiplayer::k_RewindHistorySize>{% endif %}, {{ Property.attrib['Count'] }}> &Get{{ PropertyName }}Array() const; const {{ Property.attrib['Type'] }} &Get{{ PropertyName }}(int32_t index) const; -{% elif Property.attrib['Container'] == 'Vector' %} {% if Property.attrib['GenerateEventBindings']|booleanTrue %} void {{ PropertyName }}AddEvent(AZ::Event::Handler& handler); -void {{ PropertyName }}SizeChangedAddEvent(AZ::Event::Handler& handler); {% endif %} +{% elif Property.attrib['Container'] == 'Vector' %} const AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}> &Get{{ PropertyName }}Vector() const; const {{ Property.attrib['Type'] }} &Get{{ PropertyName }}(int32_t index) const; const {{ Property.attrib['Type'] }} &{{ PropertyName }}GetBack() const; uint32_t {{ PropertyName }}GetSize() const; +{% if Property.attrib['GenerateEventBindings']|booleanTrue %} +void {{ PropertyName }}AddEvent(AZ::Event::Handler& handler); +void {{ PropertyName }}SizeChangedAddEvent(AZ::Event::Handler& handler); +{% endif %} {% else %} +const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const; {% if Property.attrib['GenerateEventBindings']|booleanTrue %} void {{ PropertyName }}AddEvent(AZ::Event<{{ Property.attrib['Type'] }}>::Handler& handler); {% endif %} -const {{ Property.attrib['Type'] }}& Get{{ PropertyName }}() const; {% endif %} {% endmacro %} {# @@ -221,14 +221,14 @@ AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] } #include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include {% call(Include) AutoComponentMacros.ParseIncludes(Component) %} #include <{{ Include.attrib['File'] }}> {% endcall %} @@ -323,17 +323,19 @@ namespace {{ Component.attrib['Namespace'] }} }; {% if NetworkInputCount > 0 %} - class NetworkInput + class {{ ComponentName }}NetworkInput : public Multiplayer::IMultiplayerComponentInput { public: - Multiplayer::NetComponentId GetComponentId() const override; - INetworkInput& operator=(const INetworkInput& rhs) override; - bool Serialize(AzNetworking::ISerializer& serializer); + Multiplayer::NetComponentId GetNetComponentId() const override; + bool Serialize(AzNetworking::ISerializer& serializer) override; {% call(Input) AutoComponentMacros.ParseNetworkInputs(Component) %} {{ Input.attrib['Type'] }} m_{{ LowerFirst(Input.attrib['Name']) }} = {{ Input.attrib['Type'] }}({{ Input.attrib['Init'] }}); {% endcall %} + + static Multiplayer::NetComponentId s_netComponentId; + friend void RegisterMultiplayerComponents(); }; {% endif %} @@ -415,6 +417,8 @@ namespace {{ Component.attrib['Namespace'] }} static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static AZStd::unique_ptr AllocateComponentInput(); + {{ ComponentBaseName }}() = default; ~{{ ComponentBaseName }}() override = default; @@ -428,6 +432,7 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Server', false)|indent(8) -}} + {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Autonomous', false)|indent(8) -}} {{ DeclareNetworkPropertyGetters(Component, 'Authority', 'Client', false)|indent(8) }} {{ DeclareArchetypePropertyGetters(Component)|indent(8) -}} {{ DeclareRpcInvocations(Component, 'Server', 'Authority', false)|indent(8) }} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 200d38910b..516e6a05e8 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -476,7 +476,7 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re {%- if networkPropertyCount.update({'value': networkPropertyCount.value + 1}) %}{% endif -%} {% endcall %} {% if networkPropertyCount.value > 0 %} - MultiplayerStats& stats = GetMultiplayer()->GetStats(); + Multiplayer::MultiplayerStats& stats = Multiplayer::GetMultiplayer()->GetStats(); // We modify the record if we are writing an update so that we don't notify for a change that really didn't change the value (just a duplicated send from the server) [[maybe_unused]] bool modifyRecord = serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject; {% call(Property) AutoComponentMacros.ParseNetworkProperties(Component, ReplicateFrom, ReplicateTo) %} @@ -492,9 +492,9 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re if (deltaRecord.AnySet()) { {% if Property.attrib['Container'] == 'Vector' %} - NovaNet::SerializableFixedSizeVectorDeltaStruct<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}> deltaStruct(m_{{ LowerFirst(Property.attrib['Name']) }}, deltaRecord); + Multiplayer::SerializableFixedSizeVectorDeltaStruct<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}> deltaStruct(m_{{ LowerFirst(Property.attrib['Name']) }}, deltaRecord); {% else %} - NovaNet::SerializableFixedSizeArrayDeltaStruct<{% if Property.attrib['IsRewindable']|booleanTrue %}Multiplayer::RewindableObject<{% endif %}{{ Property.attrib['Type'] }}{% if Property.attrib['IsRewindable']|booleanTrue %}, Multiplayer::RewindHistorySize>{% endif %}, {{ Property.attrib['Count'] }}> deltaStruct(m_{{ Property.attrib['Name'] }}, deltaRecord); + Multiplayer::SerializableFixedSizeArrayDeltaStruct<{% if Property.attrib['IsRewindable']|booleanTrue %}Multiplayer::RewindableObject<{% endif %}{{ Property.attrib['Type'] }}{% if Property.attrib['IsRewindable']|booleanTrue %}, Multiplayer::RewindHistorySize>{% endif %}, {{ Property.attrib['Count'] }}> deltaStruct(m_{{ Property.attrib['Name'] }}, deltaRecord); {% endif %} serializer.Serialize(deltaStruct, "{{ UpperFirst(Property.attrib['Name']) }}"); } @@ -509,7 +509,7 @@ bool {{ ClassName }}::Serialize{{ AutoComponentMacros.GetNetPropertiesSetName(Re m_{{ LowerFirst(Property.attrib['Name']) }}, "{{ Property.attrib['Name'] }}", GetNetComponentId(), - static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::NetworkProperties::{{ UpperFirst(Property.attrib['Name']) }}), + static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::NetworkProperties::{{ UpperFirst(Property.attrib['Name']) }}), stats ); {% endif %} @@ -902,8 +902,8 @@ m_{{ LowerFirst(Property.attrib['Name']) }} = m_{{ LowerFirst(Property.attrib['N #include #include #include -#include -#include +#include +#include {% if ComponentDerived or ControllerDerived %} #include <{{ Component.attrib['OverrideInclude'] }}> {% endif %} @@ -916,6 +916,9 @@ m_{{ LowerFirst(Property.attrib['Name']) }} = m_{{ LowerFirst(Property.attrib['N namespace {{ Component.attrib['Namespace'] }} { Multiplayer::NetComponentId {{ UpperFirst(ComponentBaseName) }}::s_netComponentId = Multiplayer::InvalidNetComponentId; +{% if NetworkInputCount > 0 %} + Multiplayer::NetComponentId {{ ComponentName }}NetworkInput::s_netComponentId = Multiplayer::InvalidNetComponentId; +{% endif %} namespace {{ UpperFirst(Component.attrib['Name']) }}Internal { @@ -1051,6 +1054,21 @@ namespace {{ Component.attrib['Namespace'] }} {{ GenerateModelReplicationRecordPredictableBits(Component, ClassType, 'Autonomous', 'Authority')|indent(8) }} } +{% if NetworkInputCount > 0 %} + Multiplayer::NetComponentId {{ ComponentName }}NetworkInput::GetNetComponentId() const + { + return {{ ComponentName }}NetworkInput::s_netComponentId; + } + + bool {{ ComponentName }}NetworkInput::Serialize(AzNetworking::ISerializer& serializer) + { +{% call(Input) AutoComponentMacros.ParseNetworkInputs(Component) %} + serializer.Serialize(m_{{ LowerFirst(Input.attrib['Name']) }}, "{{ UpperFirst(Input.attrib['Name']) }}"); +{% endcall %} + return serializer.IsValid(); + } + +{% endif %} {{ ControllerBaseName }}::{{ ControllerBaseName }}({{ ComponentName }}& parent) : MultiplayerController(parent) { @@ -1107,10 +1125,10 @@ namespace {{ Component.attrib['Namespace'] }} {{ DefineRpcInvocations(Component, ControllerBaseName, 'Authority', 'Client', true)|indent(4) }} {% for Service in Component.iter('ComponentRelation') %} {% if (Service.attrib['HasController']|booleanTrue) and (Service.attrib['Constraint'] != 'Incompatible') %} - {{ Service.attrib['Name'] }}Controller* {{ ControllerBaseName }}::Get{{ Service.attrib['Name'] }}Controller() + {{ Service.attrib['Namespace'] }}::{{ Service.attrib['Name'] }}Controller* {{ ControllerBaseName }}::Get{{ Service.attrib['Name'] }}Controller() { - MultiplayerComponent* controllerComponent = GetParent().Get{{ Service.attrib['Name'] }}(); - return static_cast<{{ Service.attrib['Name'] }}Controller*>(controllerComponent->GetController()); + Multiplayer::MultiplayerComponent* controllerComponent = GetParent().Get{{ Service.attrib['Name'] }}(); + return static_cast<{{ Service.attrib['Namespace'] }}::{{ Service.attrib['Name'] }}Controller*>(controllerComponent->GetController()); } {% endif %} @@ -1164,7 +1182,7 @@ namespace {{ Component.attrib['Namespace'] }} void {{ ComponentBaseName }}::{{ ComponentBaseName }}::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { - provided.push_back(AZ_CRC_CE("{{ ComponentName }}Service")); + provided.push_back(AZ_CRC_CE("{{ ComponentName }}")); } void {{ ComponentBaseName }}::{{ ComponentBaseName }}::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) @@ -1184,12 +1202,21 @@ namespace {{ Component.attrib['Namespace'] }} void {{ ComponentBaseName }}::{{ ComponentBaseName }}::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { - incompatible.push_back(AZ_CRC_CE("{{ ComponentName }}Service")); + incompatible.push_back(AZ_CRC_CE("{{ ComponentName }}")); {% call(ComponentService) ParseComponentServiceNames(Component, ClassType, 'Incompatible') %} incompatible.push_back(AZ_CRC_CE("{{ ComponentService }}")); {% endcall %} } + AZStd::unique_ptr {{ ComponentBaseName }}::AllocateComponentInput() + { +{% if NetworkInputCount > 0 %} + return AZStd::make_unique<{{ ComponentName }}NetworkInput>(); +{% else %} + return nullptr; +{% endif %} + } + void {{ ComponentBaseName }}::Init() { if (m_netBindComponent == nullptr) @@ -1408,6 +1435,7 @@ namespace {{ Component.attrib['Namespace'] }} } {% endif %} +{% endfor %} const char* {{ ComponentBaseName }}::GetNetworkPropertyName([[maybe_unused]] Multiplayer::PropertyIndex propertyIndex) { {% if NetworkPropertyCount > 0 %} @@ -1437,6 +1465,5 @@ namespace {{ Component.attrib['Namespace'] }} {% endif %} return "Unknown Rpc"; } -{% endfor %} } {% endfor %} diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index a5a7e8decd..94bdac2b5d 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -5,13 +5,13 @@ Namespace="Multiplayer" OverrideComponent="true" OverrideController="true" - OverrideInclude="Source/Components/LocalPredictionPlayerInputComponent.h" + OverrideInclude="Multiplayer/Components/LocalPredictionPlayerInputComponent.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - + - + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 1260075cba..2f934979b1 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -3,9 +3,9 @@ - - - + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml index e76ac75edc..96653a607c 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkTransformComponent.AutoComponent.xml @@ -5,7 +5,7 @@ Namespace="Multiplayer" OverrideComponent="true" OverrideController="true" - OverrideInclude="Source/Components/NetworkTransformComponent.h" + OverrideInclude="Multiplayer/Components/NetworkTransformComponent.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index b57c465df2..46136fcde9 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include @@ -81,12 +81,7 @@ namespace Multiplayer , m_migrateStartHandler([this](ClientInputId migratedInputId) { OnMigrateStart(migratedInputId); }) , m_migrateEndHandler([this]() { OnMigrateEnd(); }) { - if (GetNetEntityRole() == NetEntityRole::Autonomous) - { - m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true); - parent.GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler); - parent.GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler); - } + ; } void LocalPredictionPlayerInputComponentController::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -96,6 +91,13 @@ namespace Multiplayer m_allowMigrateClientInput = true; m_serverMigrateFrameId = GetNetworkTime()->GetHostFrameId(); } + + if (IsAutonomous()) + { + m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true); + GetParent().GetNetBindComponent()->AddEntityMigrationStartEventHandler(m_migrateStartHandler); + GetParent().GetNetBindComponent()->AddEntityMigrationEndEventHandler(m_migrateEndHandler); + } } void LocalPredictionPlayerInputComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp index ae6fc50f5a..8542288b23 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponent.cpp @@ -10,8 +10,8 @@ * */ -#include -#include +#include +#include #include namespace Multiplayer @@ -46,9 +46,24 @@ namespace Multiplayer return m_netBindComponent ? m_netBindComponent->GetNetEntityId() : InvalidNetEntityId; } - NetEntityRole MultiplayerComponent::GetNetEntityRole() const + bool MultiplayerComponent::IsAuthority() const { - return m_netBindComponent ? m_netBindComponent->GetNetEntityRole() : NetEntityRole::InvalidRole; + return m_netBindComponent ? m_netBindComponent->IsAuthority() : false; + } + + bool MultiplayerComponent::IsAutonomous() const + { + return m_netBindComponent ? m_netBindComponent->IsAutonomous() : false; + } + + bool MultiplayerComponent::IsServer() const + { + return m_netBindComponent ? m_netBindComponent->IsServer() : false; + } + + bool MultiplayerComponent::IsClient() const + { + return m_netBindComponent ? m_netBindComponent->IsClient() : false; } ConstNetworkEntityHandle MultiplayerComponent::GetEntityHandle() const diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp index 648b28633e..7e7cbfc480 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerComponentRegistry.cpp @@ -10,7 +10,7 @@ * */ -#include +#include namespace Multiplayer { @@ -21,6 +21,12 @@ namespace Multiplayer return netComponentId; } + AZStd::unique_ptr MultiplayerComponentRegistry::AllocateComponentInput(NetComponentId netComponentId) + { + const ComponentData& componentData = GetMultiplayerComponentData(netComponentId); + return AZStd::move(componentData.m_allocComponentInputFunction()); + } + const char* MultiplayerComponentRegistry::GetComponentGemName(NetComponentId netComponentId) const { const ComponentData& componentData = GetMultiplayerComponentData(netComponentId); diff --git a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp index 9b8f41d5bc..b0bafccf79 100644 --- a/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp +++ b/Gems/Multiplayer/Code/Source/Components/MultiplayerController.cpp @@ -10,9 +10,9 @@ * */ -#include -#include -#include +#include +#include +#include namespace Multiplayer { @@ -27,9 +27,14 @@ namespace Multiplayer return m_owner.GetNetEntityId(); } - NetEntityRole MultiplayerController::GetNetEntityRole() const + bool MultiplayerController::IsAuthority() const { - return GetNetBindComponent()->GetNetEntityRole(); + return GetNetBindComponent() ? GetNetBindComponent()->IsAuthority() : false; + } + + bool MultiplayerController::IsAutonomous() const + { + return GetNetBindComponent() ? GetNetBindComponent()->IsAutonomous() : false; } AZ::Entity* MultiplayerController::GetEntity() const diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 6dc661415e..6449f57e8e 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -10,13 +10,13 @@ * */ -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -110,6 +110,22 @@ namespace Multiplayer return (m_netEntityRole == NetEntityRole::Authority); } + bool NetBindComponent::IsAutonomous() const + { + return (m_netEntityRole == NetEntityRole::Autonomous) + || (m_netEntityRole == NetEntityRole::Authority) && m_allowAutonomy; + } + + bool NetBindComponent::IsServer() const + { + return (m_netEntityRole == NetEntityRole::Server); + } + + bool NetBindComponent::IsClient() const + { + return (m_netEntityRole == NetEntityRole::Client); + } + bool NetBindComponent::HasController() const { return (m_netEntityRole == NetEntityRole::Authority) @@ -136,14 +152,21 @@ namespace Multiplayer return m_netEntityHandle; } + void NetBindComponent::SetAllowAutonomy(bool value) + { + // This flag allows a player host to autonomously control their player entity, even though the entity is in an authority role + m_allowAutonomy = value; + } + MultiplayerComponentInputVector NetBindComponent::AllocateComponentInputs() { MultiplayerComponentInputVector componentInputs; const size_t multiplayerComponentSize = m_multiplayerInputComponentVector.size(); for (size_t i = 0; i < multiplayerComponentSize; ++i) { - // TODO: ComponentInput factory, needs multiplayer component architecture and autogen - AZStd::unique_ptr componentInput = nullptr; // ComponentInputFactory(multiplayerComponent->GetComponentId()); + const NetComponentId netComponentId = m_multiplayerInputComponentVector[i]->GetNetComponentId(); + AZStd::unique_ptr componentInput = AZStd::move(GetMultiplayerComponentRegistry()->AllocateComponentInput(netComponentId)); + if (componentInput != nullptr) { componentInputs.emplace_back(AZStd::move(componentInput)); diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 607e2813ec..0cc4cb131e 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include @@ -96,7 +96,7 @@ namespace Multiplayer void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm) { - if (GetNetEntityRole() == NetEntityRole::Authority) + if (IsAuthority()) { SetRotation(worldTm.GetRotation()); SetTranslation(worldTm.GetTranslation()); diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h index 449ffafe45..2e7be47842 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ClientToServerConnectionData.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 6274a6ba31..faa11bc225 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 1ae4bffd07..4ae7c3fdfe 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -138,7 +138,7 @@ namespace Multiplayer void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId) { - IMultiplayer* multiplayer = AZ::Interface::Get(); + MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); { const MultiplayerStats::Metric metric = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId); float callsPerSecond = 0.0f; @@ -150,7 +150,7 @@ namespace Multiplayer for (AZStd::size_t index = 0; index < componentStats.m_propertyUpdatesSent.size(); ++index) { const PropertyIndex propertyIndex = aznumeric_cast(index); - const char* propertyName = multiplayer->GetComponentPropertyName(netComponentId, propertyIndex); + const char* propertyName = componentRegistry->GetComponentPropertyName(netComponentId, propertyIndex); const MultiplayerStats::Metric& subMetric = componentStats.m_propertyUpdatesSent[index]; callsPerSecond = 0.0f; bytesPerSecond = 0.0f; @@ -172,7 +172,7 @@ namespace Multiplayer for (AZStd::size_t index = 0; index < componentStats.m_propertyUpdatesRecv.size(); ++index) { const PropertyIndex propertyIndex = aznumeric_cast(index); - const char* propertyName = multiplayer->GetComponentPropertyName(netComponentId, propertyIndex); + const char* propertyName = componentRegistry->GetComponentPropertyName(netComponentId, propertyIndex); const MultiplayerStats::Metric& subMetric = componentStats.m_propertyUpdatesRecv[index]; callsPerSecond = 0.0f; bytesPerSecond = 0.0f; @@ -194,7 +194,7 @@ namespace Multiplayer for (AZStd::size_t index = 0; index < componentStats.m_rpcsSent.size(); ++index) { const RpcIndex rpcIndex = aznumeric_cast(index); - const char* rpcName = multiplayer->GetComponentRpcName(netComponentId, rpcIndex); + const char* rpcName = componentRegistry->GetComponentRpcName(netComponentId, rpcIndex); const MultiplayerStats::Metric& subMetric = componentStats.m_rpcsSent[index]; callsPerSecond = 0.0f; bytesPerSecond = 0.0f; @@ -216,7 +216,7 @@ namespace Multiplayer for (AZStd::size_t index = 0; index < componentStats.m_rpcsRecv.size(); ++index) { const RpcIndex rpcIndex = aznumeric_cast(index); - const char* rpcName = multiplayer->GetComponentRpcName(netComponentId, rpcIndex); + const char* rpcName = componentRegistry->GetComponentRpcName(netComponentId, rpcIndex); const MultiplayerStats::Metric& subMetric = componentStats.m_rpcsRecv[index]; callsPerSecond = 0.0f; bytesPerSecond = 0.0f; @@ -238,6 +238,7 @@ namespace Multiplayer if (ImGui::Begin("Multiplayer Stats", &m_displayMultiplayerStats, ImGuiWindowFlags_None)) { IMultiplayer* multiplayer = AZ::Interface::Get(); + MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); const Multiplayer::MultiplayerStats& stats = multiplayer->GetStats(); ImGui::Text("Multiplayer operating in %s mode", GetEnumString(multiplayer->GetAgentType())); ImGui::Text("Total networked entities: %llu", aznumeric_cast(stats.m_entityCount)); @@ -267,8 +268,8 @@ namespace Multiplayer { const NetComponentId netComponentId = aznumeric_cast(index); using StringLabel = AZStd::fixed_string<128>; - const StringLabel gemName = multiplayer->GetComponentGemName(netComponentId); - const StringLabel componentName = multiplayer->GetComponentName(netComponentId); + const StringLabel gemName = componentRegistry->GetComponentGemName(netComponentId); + const StringLabel componentName = componentRegistry->GetComponentName(netComponentId); const StringLabel label = gemName + "::" + componentName; if (DrawComponentRow(label.c_str(), stats, netComponentId)) { diff --git a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h index 3bf6eb554f..d8d264dc34 100644 --- a/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h +++ b/Gems/Multiplayer/Code/Source/EntityDomains/FullOwnershipEntityDomain.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp index aef3e546ad..14bb71bcd5 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerGem.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.cpp b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp similarity index 100% rename from Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.cpp rename to Gems/Multiplayer/Code/Source/MultiplayerStats.cpp diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 80a09d7d48..a10bbf8a1a 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -576,26 +576,6 @@ namespace Multiplayer return &m_networkEntityManager; } - const char* MultiplayerSystemComponent::GetComponentGemName(NetComponentId netComponentId) const - { - return GetMultiplayerComponentRegistry()->GetComponentGemName(netComponentId); - } - - const char* MultiplayerSystemComponent::GetComponentName(NetComponentId netComponentId) const - { - return GetMultiplayerComponentRegistry()->GetComponentName(netComponentId); - } - - const char* MultiplayerSystemComponent::GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const - { - return GetMultiplayerComponentRegistry()->GetComponentPropertyName(netComponentId, propertyIndex); - } - - const char* MultiplayerSystemComponent::GetComponentRpcName(NetComponentId netComponentId, RpcIndex rpcIndex) const - { - return GetMultiplayerComponentRegistry()->GetComponentRpcName(netComponentId, rpcIndex); - } - void MultiplayerSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { const MultiplayerStats& stats = GetStats(); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index ba59a82eae..6bedd0599b 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -94,10 +94,6 @@ namespace Multiplayer AZ::TimeMs GetCurrentHostTimeMs() const override; INetworkTime* GetNetworkTime() override; INetworkEntityManager* GetNetworkEntityManager() override; - const char* GetComponentGemName(NetComponentId netComponentId) const override; - const char* GetComponentName(NetComponentId netComponentId) const override; - const char* GetComponentPropertyName(NetComponentId netComponentId, PropertyIndex propertyIndex) const override; - const char* GetComponentRpcName(NetComponentId netComponentId, RpcIndex rpcIndex) const override; //! @} //! Console commands. diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 286090ca74..e47d142be8 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -15,13 +15,13 @@ #include #include #include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -828,12 +828,11 @@ namespace Multiplayer { if (entityReplicator == nullptr) { - IMultiplayer* multiplayer = GetMultiplayer(); AZLOG_INFO ( "EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %u has already been deleted", - multiplayer->GetComponentName(message.GetComponentId()), - multiplayer->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentName(message.GetComponentId()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()), message.GetEntityId() ); return false; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h index 50a4ad43d4..083413e19e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -13,11 +13,11 @@ #pragma once #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 15293d518d..f84487080b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -16,11 +16,11 @@ #include #include #include -#include #include #include -#include -#include +#include +#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h index 3587c28975..ced665abf3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.h @@ -18,8 +18,8 @@ #include #include #include -#include -#include +#include +#include namespace AzNetworking { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.h b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.h index be8ac1b65b..fb23adfbd5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace AzNetworking diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp index 4994884364..b74c8af081 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertySubscriber.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp index 6aa6c10b11..41cc86aaee 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp @@ -10,7 +10,7 @@ * */ -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index ecfd416380..f30b7ee9e0 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -11,8 +11,8 @@ */ #include -#include -#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 0dd7292d25..3ca56f01b3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -10,10 +10,10 @@ * */ -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index 28b72abf25..6f3fdf2b23 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h index e763e7ebca..3291f2c38d 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.h @@ -18,10 +18,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp index d58c192162..f636fb6447 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp index 42104e79fd..2dac90deee 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h index 34f5d03f2f..1b4b7f15f1 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityTracker.h @@ -13,7 +13,7 @@ #pragma once #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp index 5ece0c7157..3fe5a497cb 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp @@ -10,7 +10,7 @@ * */ -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index eafd4375e7..7b9cba5a93 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -10,8 +10,10 @@ * */ -#include -#include +#include +#include +#include +#include #include #include @@ -111,13 +113,12 @@ namespace Multiplayer // This happens when deserializing a non-delta'd input command // However in the delta serializer case, we use the previous input as our initial value // which will have the NetworkInputs setup and therefore won't write out the componentId - NetComponentId componentId = m_componentInputs[i] ? m_componentInputs[i]->GetComponentId() : InvalidNetComponentId; + NetComponentId componentId = m_componentInputs[i] ? m_componentInputs[i]->GetNetComponentId() : InvalidNetComponentId; serializer.Serialize(componentId, "ComponentType"); // Create a new input if we don't have one or the types do not match - if ((m_componentInputs[i] == nullptr) || (componentId != m_componentInputs[i]->GetComponentId())) + if ((m_componentInputs[i] == nullptr) || (componentId != m_componentInputs[i]->GetNetComponentId())) { - // TODO: ComponentInput factory, needs multiplayer component architecture and autogen - m_componentInputs[i] = nullptr; // ComponentInputFactory(componentId); + m_componentInputs[i] = AZStd::move(GetMultiplayerComponentRegistry()->AllocateComponentInput(componentId)); } if (!m_componentInputs[i]) { @@ -135,7 +136,7 @@ namespace Multiplayer // We assume that the order of the network inputs is fixed between the server and client for (auto& componentInput : m_componentInputs) { - NetComponentId componentId = componentInput->GetComponentId(); + NetComponentId componentId = componentInput->GetNetComponentId(); serializer.Serialize(componentId, "ComponentId"); serializer.Serialize(*componentInput, "ComponentInput"); } @@ -148,7 +149,7 @@ namespace Multiplayer // linear search since we expect to have very few components for (auto& componentInput : m_componentInputs) { - if (componentInput->GetComponentId() == componentId) + if (componentInput->GetNetComponentId() == componentId) { return componentInput.get(); } @@ -169,10 +170,10 @@ namespace Multiplayer m_componentInputs.resize(rhs.m_componentInputs.size()); for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i) { - if (m_componentInputs[i] == nullptr || m_componentInputs[i]->GetComponentId() != rhs.m_componentInputs[i]->GetComponentId()) + const NetComponentId rhsComponentId = rhs.m_componentInputs[i]->GetNetComponentId(); + if (m_componentInputs[i] == nullptr || m_componentInputs[i]->GetNetComponentId() != rhsComponentId) { - // TODO: ComponentInput factory, needs multiplayer component architecture and autogen - m_componentInputs[i] = nullptr; // ComponentInputFactory(rhs.m_componentInputs[i]->GetComponentId()); + m_componentInputs[i] = AZStd::move(GetMultiplayerComponentRegistry()->AllocateComponentInput(rhsComponentId)); } *m_componentInputs[i] = *rhs.m_componentInputs[i]; } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp index 82e5cea0c4..d95310e4b5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h index d5cbcbbed3..b67ce79112 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h @@ -12,8 +12,8 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h index fa4ab1e4e9..e81bce0210 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h index c5f0a70fd3..76eeebf73a 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h index 454cef4e0a..e5f8fdf648 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h @@ -12,8 +12,8 @@ #pragma once -#include -#include +#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp index d991e59d05..98ece0a8cc 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.cpp @@ -11,8 +11,8 @@ */ #include -#include #include +#include #include namespace Multiplayer diff --git a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h index ff2da0f759..f714e046b3 100644 --- a/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h +++ b/Gems/Multiplayer/Code/Source/NetworkTime/NetworkTime.h @@ -12,7 +12,7 @@ #pragma once -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp index 805a982506..4661df7564 100644 --- a/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp +++ b/Gems/Multiplayer/Code/Source/Pipeline/NetworkPrefabProcessor.cpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h index 5cb9c0de70..f633a35a14 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/NullReplicationWindow.h @@ -12,7 +12,7 @@ #pragma once -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index bf370c1952..d048476cc0 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -11,7 +11,7 @@ */ #include -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index 25fbfd481d..fc143a7cc4 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -13,8 +13,8 @@ #pragma once #include -#include -#include +#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp index f614dc2690..4596e3b35c 100644 --- a/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp +++ b/Gems/Multiplayer/Code/Tests/RewindableObjectTests.cpp @@ -10,8 +10,8 @@ * */ -#include #include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 26909cbfd3..7b335854fa 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -10,33 +10,34 @@ # set(FILES - Include/Multiplayer/IConnectionData.h - Include/Multiplayer/IEntityDomain.h Include/Multiplayer/IMultiplayer.h - Include/Multiplayer/IMultiplayerComponentInput.h - Include/Multiplayer/INetworkEntityManager.h - Include/Multiplayer/INetworkPlayerSpawner.h - Include/Multiplayer/INetworkTime.h - Include/Multiplayer/IReplicationWindow.h - Include/Multiplayer/MultiplayerComponent.h - Include/Multiplayer/MultiplayerController.h - Include/Multiplayer/MultiplayerComponentRegistry.h - Include/Multiplayer/MultiplayerStats.cpp Include/Multiplayer/MultiplayerStats.h Include/Multiplayer/MultiplayerTypes.h - Include/Multiplayer/NetBindComponent.h - Include/Multiplayer/NetworkEntityRpcMessage.h - Include/Multiplayer/NetworkEntityUpdateMessage.h - Include/Multiplayer/NetworkEntityHandle.h - Include/Multiplayer/NetworkEntityHandle.inl - Include/Multiplayer/NetworkInput.h - Include/Multiplayer/ReplicationRecord.h - Include/Multiplayer/RewindableObject.h - Include/Multiplayer/RewindableObject.inl + Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h + Include/Multiplayer/Components/MultiplayerComponent.h + Include/Multiplayer/Components/MultiplayerController.h + Include/Multiplayer/Components/MultiplayerComponentRegistry.h + Include/Multiplayer/Components/NetBindComponent.h + Include/Multiplayer/Components/NetworkTransformComponent.h + Include/Multiplayer/ConnectionData/IConnectionData.h + Include/Multiplayer/EntityDomains/IEntityDomain.h + Include/Multiplayer/NetworkEntity/INetworkEntityManager.h + Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h + Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h + Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h + Include/Multiplayer/NetworkEntity/NetworkEntityHandle.inl + Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h + Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h + Include/Multiplayer/NetworkInput/NetworkInput.h + Include/Multiplayer/NetworkTime/INetworkTime.h + Include/Multiplayer/NetworkTime/RewindableObject.h + Include/Multiplayer/NetworkTime/RewindableObject.inl + Include/Multiplayer/ReplicationWindows/IReplicationWindow.h Source/Multiplayer_precompiled.cpp Source/Multiplayer_precompiled.h Source/MultiplayerSystemComponent.cpp Source/MultiplayerSystemComponent.h + Source/MultiplayerStats.cpp Source/AutoGen/AutoComponent_Header.jinja Source/AutoGen/AutoComponent_Source.jinja Source/AutoGen/AutoComponent_Common.jinja @@ -46,13 +47,11 @@ set(FILES Source/AutoGen/Multiplayer.AutoPackets.xml Source/AutoGen/NetworkTransformComponent.AutoComponent.xml Source/Components/LocalPredictionPlayerInputComponent.cpp - Source/Components/LocalPredictionPlayerInputComponent.h Source/Components/MultiplayerComponent.cpp Source/Components/MultiplayerController.cpp Source/Components/MultiplayerComponentRegistry.cpp Source/Components/NetBindComponent.cpp Source/Components/NetworkTransformComponent.cpp - Source/Components/NetworkTransformComponent.h Source/ConnectionData/ClientToServerConnectionData.cpp Source/ConnectionData/ClientToServerConnectionData.h Source/ConnectionData/ClientToServerConnectionData.inl From bb3142278e2bb90049f5ccb06a846a49724a190e Mon Sep 17 00:00:00 2001 From: karlberg Date: Fri, 14 May 2021 15:03:11 -0700 Subject: [PATCH 2/6] Fixes an example comment error in multiplayer component jinjas --- .../Code/Source/AutoGen/AutoComponent_Common.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index d187a151fd..4279c26cf2 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -282,14 +282,14 @@ namespace {{ Component.attrib['Namespace'] }} void {{ ControllerName }}::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) { } -{% if NetworkInputCount > 0 %} +{% if NetworkInputCount > 0 %} void {{ ControllerName }}::ProcessInput([[maybe_unused]] Multiplayer::NetworkInput& input, [[maybe_unused]] float deltaTime) { } +{% endif %} {% endif %} } -{% endif %} */ {% else %} // NOTE: From 61c24ee265f4f7d8fdf69785ac900f7ed88da214 Mon Sep 17 00:00:00 2001 From: karlberg Date: Fri, 14 May 2021 15:22:57 -0700 Subject: [PATCH 3/6] Removing tab in cmake file --- Gems/Multiplayer/Code/multiplayer_files.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 7b335854fa..8dd7c07689 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -37,7 +37,7 @@ set(FILES Source/Multiplayer_precompiled.h Source/MultiplayerSystemComponent.cpp Source/MultiplayerSystemComponent.h - Source/MultiplayerStats.cpp + Source/MultiplayerStats.cpp Source/AutoGen/AutoComponent_Header.jinja Source/AutoGen/AutoComponent_Source.jinja Source/AutoGen/AutoComponent_Common.jinja From 0b35d278336b73402cab4855c965ae0e333c2e94 Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Sat, 15 May 2021 16:16:49 -0500 Subject: [PATCH 4/6] Added support to the AZ Console to be notified when the Settings Registry modifies a particular path (#691) * Updated the SettingsRegistry CommandLineArgumentSettings delimiter function to no longer work on a character basis, but on a line basis. This provides more control by the user to determine how to the argument into the JSON pointer and JSON value parts Added function o the Settings Registry to specify the JSON Apply Patch settings to use when performing a JSON Patch/Merge Patch operation Fixed the SettingsRegistryImpl::CommandLineArgument function to properly set unsigned 64-bit values into the SettingsRegistry by checking the ERRNO of strtoll and strtoull * Updated the reporting of the JSON Patching operations to supply a JSON pointer of the patched element to the Issue Reporting callback when the patch operation is successful. This allows using the Issue Reporting callback as a notification system when field is updated during a patch operation * Added support to the AZ Console to be able to run Console Commands based on notifications from the Settings Registry when a field underneath the "/Amazon/AzCore/Runtime/ConsoleCommands" object is modified. This takes advantage of the Settings Registry RegisterNotifier API to determine when a field is modified as well as the JSON Merger JsonApplyPatchSettings Issue Reporting Callback to determine when a field is modified or updated. As a Side Note also fixed an issue with the AZ Console incorrectly converting unsigned 64-bit types using strtoll * Making the Console constructor which accepts an AZ::SettingsRegistryInterface explicit * Updating string format calls which use *.s for formatting string_views, to use the AZ_STRING_ARG macro * Addressed typos in comments around the SettingsRegistry AZ Console functions * Fixed the SettingsRegistryTest that look for an empty value * clang 6.0.0 constexpr build fix. For some reason clang cannot make a constexpr AZStd::string_view out of a constexpr AZStd::fixed_string despite there being a valid constexpr operator AZStd::string_view * Mac build fix * SettingsRegistryTest.MergeSettingsFolder_ConflictingSpecializations_ReportsErrorAndReturnsFalse test fix on Mac * Updated the LoadSettingsFile test to validate running a console with 0 arguments Replace the static_cast in the ConsoleTypeHelpers.inl code to convert a str to long long with an aznumeric_cast * Added printf logging to the ConsoleCommandKeyNotificationHandler to determine if the console commands are being performed on the Jenkins Linux node * Fixed Dangling string_view reference in the ConsoleCommandKeyNotificationHandler that was causing command execution from a file to fail. Renamed the second TestFreeFunc function in the ConsoleTests.cpp to validate thath the first TestFreeFunc function is being tested * Updated the Component Application AZ Console to use the SettingsRegistry as the backend when loading config and Settings Rgistry json files --- .../AzCore/Component/ComponentApplication.cpp | 2 +- .../AzCore/AzCore/Console/Console.cpp | 214 +++++++++++--- .../Framework/AzCore/AzCore/Console/Console.h | 6 + .../AzCore/Console/ConsoleTypeHelpers.inl | 10 +- .../AzCore/AzCore/Console/IConsole.h | 10 + .../AzCore/Serialization/Json/JsonMerger.cpp | 60 +++- .../AzCore/Serialization/Json/JsonMerger.h | 5 + .../AzCore/Settings/SettingsRegistry.cpp | 24 ++ .../AzCore/AzCore/Settings/SettingsRegistry.h | 32 ++- .../AzCore/Settings/SettingsRegistryImpl.cpp | 160 +++++------ .../AzCore/Settings/SettingsRegistryImpl.h | 10 +- .../Settings/SettingsRegistryMergeUtils.cpp | 49 +--- .../UnitTest/Mocks/MockSettingsRegistry.h | 3 + .../AzCore/Tests/Console/ConsoleTests.cpp | 265 +++++++++++++++++- .../SettingsRegistryConsoleUtilsTests.cpp | 12 +- .../AzCore/Tests/SettingsRegistryTests.cpp | 42 +-- .../AzFramework/Application/Application.cpp | 2 - 17 files changed, 659 insertions(+), 247 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index c93f825a2c..8a170f5d89 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -477,7 +477,7 @@ namespace AZ m_console = AZ::Interface::Get(); if (m_console == nullptr) { - m_console = aznew AZ::Console(); + m_console = aznew AZ::Console(*m_settingsRegistry); AZ::Interface::Register(m_console); m_ownsConsole = true; m_console->LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); diff --git a/Code/Framework/AzCore/AzCore/Console/Console.cpp b/Code/Framework/AzCore/AzCore/Console/Console.cpp index 788a127830..3cb33b564a 100644 --- a/Code/Framework/AzCore/AzCore/Console/Console.cpp +++ b/Code/Framework/AzCore/AzCore/Console/Console.cpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -43,6 +45,12 @@ namespace AZ { } + Console::Console(AZ::SettingsRegistryInterface& settingsRegistryInterface) + : Console() + { + RegisterCommandInvokerWithSettingsRegistry(settingsRegistryInterface); + } + Console::~Console() { // on console destruction relink the console functors back to the deferred head @@ -111,51 +119,51 @@ namespace AZ void Console::ExecuteConfigFile(AZStd::string_view configFileName) { - IO::FixedMaxPath filePathFixed = configFileName; - if (AZ::IO::FileIOBase* fileIOBase = AZ::IO::FileIOBase::GetInstance()) + auto settingsRegistry = AZ::SettingsRegistry::Get(); + // If the config file is a settings registry file use the SettingsRegistryInterface MergeSettingsFile function + // otherwise use the SettingsRegistryMergeUtils MergeSettingsToRegistry_ConfigFile function to merge an INI-style + // file to the settings registry + AZ::IO::PathView configFile(configFileName); + if (configFile.Extension() == ".setreg") { - fileIOBase->ResolvePath(filePathFixed, configFileName); + settingsRegistry->MergeSettingsFile(configFile.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch); } - - IO::SystemFile file; - if (!file.Open(filePathFixed.c_str(), AZ::IO::SystemFile::SF_OPEN_READ_ONLY)) + else if (configFile.Extension() == ".setregpatch") { - AZLOG_ERROR("Failed to load '%s'. File could not be opened.", filePathFixed.c_str()); - return; + settingsRegistry->MergeSettingsFile(configFile.Native(), AZ::SettingsRegistryInterface::Format::JsonPatch); } - const IO::SizeType length = file.Length(); - if (length == 0) + else { - AZLOG_ERROR("Failed to load '%s'. File is empty.", filePathFixed.c_str()); - return; - } - file.Seek(0, IO::SystemFile::SF_SEEK_BEGIN); - AZStd::string fileBuffer; - fileBuffer.resize(length); - IO::SizeType bytesRead = file.Read(length, fileBuffer.data()); - file.Close(); - // Resize again just in case bytesRead is less than length for some reason - fileBuffer.resize(bytesRead); - - AZLOG_INFO("Loading config file %s", filePathFixed.c_str()); - - AZStd::vector separatedCommands; - auto BreakCommandsByLine = [&separatedCommands](AZStd::string_view token) - { - separatedCommands.emplace_back(token); - }; - StringFunc::TokenizeVisitor(fileBuffer, BreakCommandsByLine, "\n\r"); - - for (const auto& commandView : separatedCommands) - { - ConsoleCommandContainer commandArgsView; - auto ConvertCommandStringToArray = [&commandArgsView](AZStd::string_view token) + AZ::SettingsRegistryMergeUtils::ConfigParserSettings configParserSettings; + configParserSettings.m_registryRootPointerPath = "/Amazon/AzCore/Runtime/ConsoleCommands"; + configParserSettings.m_commandLineSettings.m_delimiterFunc = [](AZStd::string_view line) { - commandArgsView.emplace_back(token); + SettingsRegistryInterface::CommandLineArgumentSettings::JsonPathValue pathValue; + AZStd::string_view parsedLine = line; + + // Splits the line based on the or + if (auto path = AZ::StringFunc::TokenizeNext(parsedLine, "=:"); path.has_value()) + { + pathValue.m_path = AZ::StringFunc::StripEnds(*path); + pathValue.m_value = AZ::StringFunc::StripEnds(parsedLine); + } + // If the value is empty, then the line either contained an equal sign followed only by whitespace or the line was empty + // 1. line="testInit=", pathValue.m_path="testInit", pathValue.m_value="" + // 2. line="testInit 1", pathValue.m_path="testInit 1", pathValue.m_value="" + // Therefore the path is split the path on whitespace in order to retrieve a value + if (pathValue.m_value.empty()) + { + parsedLine = pathValue.m_path; + if (auto path = AZ::StringFunc::TokenizeNext(parsedLine, " \t"); path.has_value()) + { + pathValue.m_path = AZ::StringFunc::StripEnds(*path); + pathValue.m_value = AZ::StringFunc::StripEnds(parsedLine); + } + + } + return pathValue; }; - constexpr AZStd::string_view commandSeparators = " ="; - StringFunc::TokenizeVisitor(commandView, ConvertCommandStringToArray, commandSeparators); - PerformCommand(commandArgsView, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null); + AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ConfigFile(*settingsRegistry, configFile.Native(), configParserSettings); } } @@ -447,4 +455,134 @@ namespace AZ return result; } + + struct ConsoleCommandKeyNotificationHandler + { + ConsoleCommandKeyNotificationHandler(AZ::SettingsRegistryInterface& registry, Console& console) + : m_settingsRegistry(registry) + , m_console(console) + { + } + + // Responsible for using the Json Serialization Issue Callback system + // to determine when a JSON Patch or JSON Merge Patch modifies a value + // at a path underneath the IConsole::ConsoleRootCommandKey JSON pointer + JsonSerializationResult::ResultCode operator()(AZStd::string_view message, + JsonSerializationResult::ResultCode result, AZStd::string_view path) + { + AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; + if (result.GetTask() == JsonSerializationResult::Tasks::Merge + && result.GetProcessing() == JsonSerializationResult::Processing::Completed + && inputKey.IsRelativeTo(consoleRootCommandKey)) + { + if (auto type = m_settingsRegistry.GetType(path); type != SettingsRegistryInterface::Type::NoType) + { + operator()(path, type); + } + } + + // This is the default issue reporting, that logs using the warning category + if (result.GetProcessing() != JsonSerializationResult::Processing::Completed) + { + scratchBuffer.append(message.begin(), message.end()); + scratchBuffer.append("\n Reason: "); + result.AppendToString(scratchBuffer, path); + scratchBuffer.append("."); + AZ_Warning("JSON Serialization", false, "%s", scratchBuffer.c_str()); + + scratchBuffer.clear(); + } + return result; + } + + void operator()(AZStd::string_view path, SettingsRegistryInterface::Type type) + { + using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; + + AZ::IO::PathView consoleRootCommandKey{ IConsole::ConsoleRootCommandKey, AZ::IO::PosixPathSeparator }; + AZ::IO::PathView inputKey{ path, AZ::IO::PosixPathSeparator }; + if (inputKey.IsRelativeTo(consoleRootCommandKey)) + { + FixedValueString command = inputKey.LexicallyRelative(consoleRootCommandKey).Native(); + ConsoleCommandContainer commandArgs; + // Argument string which stores the value from the Settings Registry long enough + // to pass into the PerformCommand. The ConsoleCommandContainer stores string_views + // and therefore doesn't own the memory. + FixedValueString commandArgString; + + if (type == SettingsRegistryInterface::Type::String) + { + if (m_settingsRegistry.Get(commandArgString, path)) + { + auto ConvertCommandArgumentToArray = [&commandArgs](AZStd::string_view token) + { + commandArgs.emplace_back(token); + }; + constexpr AZStd::string_view commandSeparators = " \t\n\r"; + StringFunc::TokenizeVisitor(commandArgString, ConvertCommandArgumentToArray, commandSeparators); + } + } + else if (type == SettingsRegistryInterface::Type::Boolean) + { + bool commandArgBool{}; + if (m_settingsRegistry.Get(commandArgBool, path)) + { + commandArgString = commandArgBool ? "true" : "false"; + commandArgs.emplace_back(commandArgString); + } + } + else if (type == SettingsRegistryInterface::Type::Integer) + { + // Try converting to a signed 64-bit number first and then an unsigned 64-bit number + AZ::s64 commandArgInt{}; + AZ::u64 commandArgUInt{}; + if (m_settingsRegistry.Get(commandArgInt, path)) + { + AZStd::to_string(commandArgString, commandArgInt); + commandArgs.emplace_back(commandArgString); + } + else if (m_settingsRegistry.Get(commandArgUInt, path)) + { + AZStd::to_string(commandArgString, commandArgUInt); + commandArgs.emplace_back(commandArgString); + } + } + else if (type == SettingsRegistryInterface::Type::FloatingPoint) + { + double commandArgFloat{}; + if (m_settingsRegistry.Get(commandArgFloat, path)) + { + AZStd::to_string(commandArgString, commandArgFloat); + commandArgs.emplace_back(commandArgString); + } + } + CVarFixedString commandTrace(command); + for (AZStd::string_view commandArg : commandArgs) + { + commandTrace.push_back(' '); + commandTrace += commandArg; + } + + m_console.PerformCommand(command, commandArgs, ConsoleSilentMode::NotSilent, ConsoleInvokedFrom::AzConsole, ConsoleFunctorFlags::Null, ConsoleFunctorFlags::Null); + } + } + + AZ::Console& m_console; + AZ::SettingsRegistryInterface& m_settingsRegistry; + AZStd::string scratchBuffer; + }; + + void Console::RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) + { + // Make sure the there is a JSON object at the path of AZ::IConsole::ConsoleRootCommandKey + // So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects) + settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } }}})", + SettingsRegistryInterface::Format::JsonMergePatch); + m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this }); + + JsonApplyPatchSettings applyPatchSettings; + applyPatchSettings.m_reporting = ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this }; + settingsRegistry.SetApplyPatchSettings(applyPatchSettings); + } } diff --git a/Code/Framework/AzCore/AzCore/Console/Console.h b/Code/Framework/AzCore/AzCore/Console/Console.h index 1c9c6897f6..ba614824fe 100644 --- a/Code/Framework/AzCore/AzCore/Console/Console.h +++ b/Code/Framework/AzCore/AzCore/Console/Console.h @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -29,6 +30,9 @@ namespace AZ AZ_CLASS_ALLOCATOR(Console, AZ::OSAllocator, 0); Console(); + //! Constructor overload which registers a notifier with the Settings Registry that will execute + //! a console command whenever a key is set under the AZ::IConsole::ConsoleCommandRootKey JSON object + explicit Console(AZ::SettingsRegistryInterface& settingsRegistry); ~Console() override; //! IConsole interface @@ -67,6 +71,7 @@ namespace AZ void RegisterFunctor(ConsoleFunctorBase* functor) override; void UnregisterFunctor(ConsoleFunctorBase* functor) override; void LinkDeferredFunctors(ConsoleFunctorBase*& deferredHead) override; + void RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) override; //! @} private: @@ -96,6 +101,7 @@ namespace AZ ConsoleFunctorBase* m_head; using CommandMap = AZStd::unordered_map>; CommandMap m_commands; + AZ::SettingsRegistryInterface::NotifyEventHandler m_consoleCommandKeyHandler; friend class ConsoleFunctorBase; }; diff --git a/Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl b/Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl index 0257564d43..18ed27e517 100644 --- a/Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl +++ b/Code/Framework/AzCore/AzCore/Console/ConsoleTypeHelpers.inl @@ -148,7 +148,15 @@ namespace AZ { AZ::CVarFixedString convertCandidate{ arguments.front() }; char* endPtr = nullptr; - MAX_TYPE value = static_cast(strtoll(convertCandidate.c_str(), &endPtr, 0)); + MAX_TYPE value; + if constexpr (AZStd::is_unsigned_v) + { + value = aznumeric_cast(strtoull(convertCandidate.c_str(), &endPtr, 0)); + } + else + { + value = aznumeric_cast(strtoll(convertCandidate.c_str(), &endPtr, 0)); + } if (endPtr == convertCandidate.c_str()) { diff --git a/Code/Framework/AzCore/AzCore/Console/IConsole.h b/Code/Framework/AzCore/AzCore/Console/IConsole.h index 9f5ede7f4c..b976f3a9db 100644 --- a/Code/Framework/AzCore/AzCore/Console/IConsole.h +++ b/Code/Framework/AzCore/AzCore/Console/IConsole.h @@ -22,8 +22,10 @@ namespace AZ { + class SettingsRegistryInterface; class CommandLine; + //! @class IConsole //! A simple console class for providing text based variable and process interaction. class IConsole @@ -33,6 +35,8 @@ namespace AZ using FunctorVisitor = AZStd::function; + inline static constexpr AZStd::string_view ConsoleRootCommandKey = "/Amazon/AzCore/Runtime/ConsoleCommands"; + IConsole() = default; virtual ~IConsole() = default; @@ -145,6 +149,12 @@ namespace AZ //! Returns the AZ::Event<> invoked whenever a console command could not be found. DispatchCommandNotFoundEvent& GetDispatchCommandNotFoundEvent(); + //! Register a notification event handler with the Settings Registry + //! That is responsible for updating console commands whenever + //! a key is found underneath the "/Amazon/AzCore/Runtime/ConsoleCommands" JSON entry + //! @param Settings Registry reference to register notifier with + virtual void RegisterCommandInvokerWithSettingsRegistry(AZ::SettingsRegistryInterface& settingsRegistry) = 0; + AZ_DISABLE_COPY_MOVE(IConsole); protected: diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp index e359888da7..de9aa70362 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp @@ -11,13 +11,17 @@ */ #include +#include #include #include #include +#include #include namespace AZ { + using ReporterString = AZStd::fixed_string<1024>; + JsonSerializationResult::ResultCode JsonMerger::ApplyPatch(rapidjson::Value& target, rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch, JsonApplyPatchSettings& settings) @@ -105,8 +109,7 @@ namespace AZ } else { - AZ::OSString message = AZ::OSString::format(R"(Unknown operation "%.*s".)", - aznumeric_cast(operationName.length()), operationName.data()); + auto message = ReporterString::format(R"(Unknown operation "%.*s".)", AZ_STRING_ARG(operationName)); return settings.m_reporting(message.c_str(), ResultCode(Tasks::Merge, Outcomes::Unknown), element); } @@ -131,6 +134,14 @@ namespace AZ JsonSerializationResult::ResultCode JsonMerger::ApplyMergePatch(rapidjson::Value& target, rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch, JsonApplyPatchSettings& settings) + { + StackedString element(StackedString::Format::JsonPointer); + return ApplyMergePatchInternal(target, allocator, patch, settings, element); + } + + JsonSerializationResult::ResultCode JsonMerger::ApplyMergePatchInternal(rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch, + JsonApplyPatchSettings& settings, StackedString& element) { using namespace JsonSerializationResult; @@ -150,14 +161,18 @@ namespace AZ { if (targetField != target.MemberEnd()) { - result.Combine(ApplyMergePatch(targetField->value, allocator, field.value, settings)); + ScopedStackedString fieldNameScope{ element, + AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) }; + result.Combine(ApplyMergePatchInternal(targetField->value, allocator, field.value, settings, element)); } else { rapidjson::Value name; name.CopyFrom(field.name, allocator, true); rapidjson::Value value; - result.Combine(ApplyMergePatch(value, allocator, field.value, settings)); + ScopedStackedString fieldNameScope{ element, + AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) }; + result.Combine(ApplyMergePatchInternal(value, allocator, field.value, settings, element)); target.AddMember(AZStd::move(name), AZStd::move(value), allocator); } } @@ -165,7 +180,14 @@ namespace AZ { if (targetField != target.MemberEnd()) { + ScopedStackedString fieldNameScope{ element, + AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) }; + AZStd::string_view jsonPath = element.Get(); + target.RemoveMember(targetField); + result.Combine(settings.m_reporting(ReporterString::format( + R"(Successfully removed member from "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)), + ResultCode(Tasks::Merge, Outcomes::Success), element)); } } else @@ -173,6 +195,12 @@ namespace AZ if (targetField != target.MemberEnd()) { targetField->value.CopyFrom(field.value, allocator, true); + + ScopedStackedString fieldNameScope{ element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) }; + AZStd::string_view jsonPath = element.Get(); + result.Combine(settings.m_reporting(ReporterString::format( + R"(Successfully updated JSON field "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)), + ResultCode(Tasks::Merge, Outcomes::Success), element)); } else { @@ -181,6 +209,12 @@ namespace AZ name.CopyFrom(field.name, allocator, true); value.CopyFrom(field.value, allocator, true); target.AddMember(AZStd::move(name), AZStd::move(value), allocator); + + ScopedStackedString fieldNameScope{ element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()) }; + AZStd::string_view jsonPath = element.Get(); + result.Combine(settings.m_reporting(ReporterString::format( + R"(Successfully added JSON field "%.*s" using JSON Merge Patch)", AZ_STRING_ARG(jsonPath)), + ResultCode(Tasks::Merge, Outcomes::Success), element)); } } } @@ -190,7 +224,7 @@ namespace AZ target.CopyFrom(patch, allocator, true); } result.Combine(settings.m_reporting("Successfully applied patch to target using JSON Merge Patch.", - ResultCode(Tasks::Merge, Outcomes::Success), StackedString(StackedString::Format::JsonPointer))); + ResultCode(Tasks::Merge, Outcomes::Success), element)); return result; } @@ -268,9 +302,11 @@ namespace AZ const rapidjson::Pointer::Token* const tokens = path.GetTokens(); if (path.GetTokenCount() == 0) { + rapidjson::StringBuffer pointerPathString; + path.Stringify(pointerPathString); target = AZStd::move(newValue); return settings.m_reporting(R"(Successfully applied "add" operation.)", - ResultCode(Tasks::Merge, Outcomes::Success), element); + ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString()); } rapidjson::Pointer parent = rapidjson::Pointer(tokens, path.GetTokenCount() - 1); @@ -342,8 +378,10 @@ namespace AZ ResultCode(Tasks::Merge, Outcomes::TypeMismatch), element); } + rapidjson::StringBuffer pointerPathString; + path.Stringify(pointerPathString); return settings.m_reporting(R"(Successfully applied "add" operation.)", - ResultCode(Tasks::Merge, Outcomes::Success), element); + ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString()); } JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Remove(rapidjson::Value& target, const rapidjson::Pointer& path, @@ -393,8 +431,10 @@ namespace AZ ResultCode(Tasks::Merge, Outcomes::TypeMismatch), element); } + rapidjson::StringBuffer pointerPathString; + path.Stringify(pointerPathString); return settings.m_reporting(R"(Successfully applied "remove" operation.)", - ResultCode(Tasks::Merge, Outcomes::Success), element); + ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString()); } JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Replace(rapidjson::Value& target, @@ -420,8 +460,10 @@ namespace AZ memberValue->CopyFrom(value->value, allocator); + rapidjson::StringBuffer pointerPathString; + path.Stringify(pointerPathString); return settings.m_reporting(R"(Successfully applied "replace" operation.)", - ResultCode(Tasks::Merge, Outcomes::Success), element); + ResultCode(Tasks::Merge, Outcomes::Success), pointerPathString.GetString()); } JsonSerializationResult::ResultCode JsonMerger::ApplyPatch_Move(rapidjson::Value& target, diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.h index a33bac117b..127897c6ff 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.h @@ -42,6 +42,11 @@ namespace AZ rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch, JsonApplyPatchSettings& settings); + //! Implementation of the JSON Merge Patch algorithm: https://tools.ietf.org/html/rfc7386 + static JsonSerializationResult::ResultCode ApplyMergePatchInternal(rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& patch, + JsonApplyPatchSettings& settings, StackedString& element); + //! Function to create JSON Merge Patches: https://tools.ietf.org/html/rfc7386 static JsonSerializationResult::ResultCode CreateMergePatch(rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const rapidjson::Value& source, diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.cpp index 609eb03a2c..f6b804494e 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.cpp @@ -88,4 +88,28 @@ namespace AZ { return index < m_names.size() ? m_names[index] : AZStd::string_view(); } + + SettingsRegistryInterface::CommandLineArgumentSettings::CommandLineArgumentSettings() + { + m_delimiterFunc = [](AZStd::string_view line) -> JsonPathValue + { + constexpr AZStd::string_view CommandLineArgumentDelimiters{ "=:" }; + JsonPathValue pathValue; + pathValue.m_value = line; + + // Splits the line on the first delimiter and stores that in the pathValue.m_path variable + // The StringFunc::TokenizeNext function updates the pathValue.m_value parameter in place + // to contain all the text after the first delimiter + // So if pathValue.m_value="foo = Hello Ice Cream=World:17", the call to TokenizeNext would + // split the value as follows + // pathValue.m_path = "foo" + // pathValue.m_value = "Hello Ice Cream=World:17" + if (auto path = AZ::StringFunc::TokenizeNext(pathValue.m_value, CommandLineArgumentDelimiters); path.has_value()) + { + pathValue.m_path = AZ::StringFunc::StripEnds(*path); + } + pathValue.m_value = AZ::StringFunc::StripEnds(pathValue.m_value); + return pathValue; + }; + } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h index 768841cc09..2d9b0d83e5 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistry.h @@ -26,6 +26,7 @@ namespace AZ { + struct JsonApplyPatchSettings; //! The Settings Registry is the central storage for global settings. Having application-wide settings //! stored in a central location allows different tools such as command lines, consoles, configuration //! files, etc. to work in a universal way. @@ -260,21 +261,20 @@ namespace AZ virtual bool Remove(AZStd::string_view path) = 0; //! Structure which contains configuration settings for how to parse a single command line argument - //! It supports supplying a functor for determining if a character is a delimiter + //! It supports supplying a functor for splitting a line into JSON path and JSON value struct CommandLineArgumentSettings { - inline static constexpr AZStd::string_view CommandLineArgumentDelimiters{ "=:"}; - CommandLineArgumentSettings() + struct JsonPathValue { - m_delimiterFunc = [](const char delimiter) -> bool - { - return CommandLineArgumentDelimiters.find_first_of(delimiter) != AZStd::string_view::npos; - }; - } - - //! Callback function which is invoked to determine whether a delimiter has been found - //! return value of true indicates that a delimiter has been found - using DelimiterFunc = AZStd::function; + AZStd::string_view m_path; + AZStd::string_view m_value; + }; + + CommandLineArgumentSettings(); + + //! Callback function which is invoked to determine how to split a command line argument + //! into a JSON path and a JSON value + using DelimiterFunc = AZStd::function; DelimiterFunc m_delimiterFunc; }; //! Merges a single command line argument into the settings registry. Command line arguments @@ -322,6 +322,14 @@ namespace AZ //! @return True if the registry folder was successfully merged, otherwise false. virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations, AZStd::string_view platform = {}, AZStd::string_view rootKey = "", AZStd::vector* scratchBuffer = nullptr) = 0; + + //! Stores the settings structure which is used when merging settings to the Settings Registry + //! using JSON Merge Patch or JSON Merge Patch. + //! The settings contain an issue reporting callback which can be used to track patching process. + //! Potential application of the reporting callback could be to update a UI whenever a key receives an updated value + //! @param applyPatchSettings The ApplyPatchSettings which are using during JSON Merging + virtual void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) = 0; + virtual void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) = 0; }; inline SettingsRegistryInterface::Visitor::~Visitor() = default; diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp index 9ea76817af..2421c75be3 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -419,9 +420,6 @@ namespace AZ bool SettingsRegistryImpl::MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey, const CommandLineArgumentSettings& commandLineSettings) { - const char* front = argument.begin(); - const char* back = argument.end(); - if (!commandLineSettings.m_delimiterFunc) { AZ_Error("SettingsRegistry", false, @@ -429,87 +427,40 @@ namespace AZ aznumeric_cast(argument.size()), argument.data()); return false; } - const char* split = AZStd::find_if(front, back, commandLineSettings.m_delimiterFunc); - if (split == front || // There is no key - split == (back-1) || // There is no value - split == back) // Split character not found. + + auto [key, value] = commandLineSettings.m_delimiterFunc(argument); + if (key.empty()) { + // They key where to set the JSON value cannot be empty + // The value of the JSON can be though + // This is so that a key can be set to empty string using "/KeyPath=" return false; } - const char* keyStart = front; - while (std::isspace(*keyStart)) // This is safe because it will eventually stop on = + // Prepend the rootKey as an anchor to the argument key + SettingsRegistryInterface::FixedValueString keyPath{ rootKey.ends_with('/') + ? rootKey.substr(0, rootKey.size() - 1) + : rootKey }; + // Append the JSON reference token prefix of '/' to the keyPath + if (!key.starts_with('/')) { - keyStart++; + keyPath.push_back('/'); } - if (keyStart == split) // Key is just white spaces + if ((key.size() + keyPath.size()) > keyPath.max_size()) { + // The key portion is longer than the FixedValueString max size that can be stored + // This limitation is arbitrary, if an AZStd::string is used or if the C++17 std::to_chars + // function is used, there wouldn't need to be a limitation return false; } - const char* keyEnd = split; - while (std::isspace(*--keyEnd)); - keyEnd++; + keyPath += key; + key = keyPath; - char buffer[MaxJsonPathLength]; - AZStd::string_view key; - bool keyHasDivider = *keyStart == '/'; - if (!rootKey.empty()) + if (value.empty()) { - bool rootKeyHasDivider = (rootKey[rootKey.length() - 1]) == '/'; - size_t count; - if (!rootKeyHasDivider && !keyHasDivider) - { - count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s/%.*s", - aznumeric_cast(rootKey.length()), rootKey.data(), - aznumeric_cast(keyEnd - keyStart), keyStart); - } - else if (rootKeyHasDivider && keyHasDivider) - { - count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s%.*s", - aznumeric_cast(rootKey.length()) - 1, rootKey.data(), - aznumeric_cast(keyEnd - keyStart), keyStart); - } - else - { - count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "%.*s%.*s", - aznumeric_cast(rootKey.length()), rootKey.data(), - aznumeric_cast(keyEnd - keyStart), keyStart); - } - if (count >= AZ_ARRAY_SIZE(buffer) - 1) - { - return false; - } - key = AZStd::string_view(buffer, count); - } - else if (!keyHasDivider) - { - size_t count = azsnprintf(buffer, AZ_ARRAY_SIZE(buffer), "/%.*s", - aznumeric_cast(keyEnd - keyStart), keyStart); - if (count >= AZ_ARRAY_SIZE(buffer) - 1) - { - return false; - } - key = AZStd::string_view(buffer, count); - } - else - { - key = AZStd::string_view(keyStart, keyEnd); + return Set(key, value); } - const char* valueStart = split + 1; - while (std::isspace(*valueStart) && valueStart < back) - { - valueStart++; - } - if (valueStart == back) - { - return false; // The value is empty - } - const char* valueEnd = back; - while (std::isspace(*(--valueEnd))); - valueEnd++; - - AZStd::string_view value(valueStart, valueEnd); if (value == "true") { return Set(key, true); @@ -519,23 +470,35 @@ namespace AZ return Set(key, false); } - if (value.length() - 1 >= MaxCommandLineArgumentLength) + SettingsRegistryInterface::FixedValueString valueString; + if (value.size() > valueString.max_size()) { + // The value portion is longer than the FixedValueString max size that can be stored + // This limitation is arbitrary, if an AZStd::string is used or if the C++17 std::to_chars + // function is used, there wouldn't need to be a limitation return false; } - char argumentString[MaxCommandLineArgumentLength]; - snprintf(argumentString, AZ_ARRAY_SIZE(argument), "%.*s", aznumeric_cast(value.length()), value.data()); - char* argumentStringEnd = argumentString + value.length(); + valueString = value; + const char* valueStringEnd = valueString.c_str() + valueString.size(); + errno = 0; char* convertEnd = nullptr; - s64 intValue = strtoll(argumentString, &convertEnd, 0); - if (convertEnd == argumentStringEnd) + s64 intValue = strtoll(valueString.c_str(), &convertEnd, 0); + if (errno != ERANGE && convertEnd == valueStringEnd) { return Set(key, intValue); } + errno = 0; convertEnd = nullptr; - double floatingPointValue = strtod(argumentString, &convertEnd); - if (convertEnd == argumentStringEnd) + u64 uintValue = strtoull(valueString.c_str(), &convertEnd, 0); + if (errno != ERANGE && convertEnd == valueStringEnd) + { + return Set(key, uintValue); + } + errno = 0; + convertEnd = nullptr; + double floatingPointValue = strtod(valueString.c_str(), &convertEnd); + if (errno != ERANGE && convertEnd == valueStringEnd) { return Set(key, floatingPointValue); } @@ -611,7 +574,7 @@ namespace AZ } else { - if (MaxFilePathLength < path.length() + 1) + if (AZ::IO::MaxPathLength < path.length() + 1) { AZ_Error("Settings Registry", false, R"(Path "%.*s" is too long. Either make sure that the provided path is terminated or use a shorter path.)", @@ -623,10 +586,8 @@ namespace AZ .AddMember(StringRef("Path"), AZStd::move(pathValue), m_settings.GetAllocator()); return false; } - char filePath[MaxFilePathLength]; - azstrncpy(filePath, AZ_ARRAY_SIZE(filePath), path.data(), path.length()); - filePath[path.length()] = 0; - result = MergeSettingsFileInternal(filePath, format, rootKey, *scratchBuffer); + AZ::IO::FixedMaxPathString filePath(path); + result = MergeSettingsFileInternal(filePath.c_str(), format, rootKey, *scratchBuffer); } scratchBuffer->clear(); @@ -660,7 +621,7 @@ namespace AZ additionalSpaceRequired += AZ_ARRAY_SIZE(PlatformFolder) + platform.length() + 2; // +2 for the two slashes. } - if (path.length() + additionalSpaceRequired > MaxFilePathLength) + if (path.length() + additionalSpaceRequired > AZ::IO::MaxPathLength) { AZ_Error("Settings Registry", false, "Folder path for the Setting Registry is too long: %.*s", static_cast(path.size()), path.data()); @@ -673,7 +634,7 @@ namespace AZ RegistryFileList fileList; scratchBuffer->clear(); - AZStd::fixed_string folderPath{ path }; + AZ::IO::FixedMaxPathString folderPath{ path }; constexpr AZStd::string_view pathSeparators{ AZ_CORRECT_AND_WRONG_DATABASE_SEPARATOR }; if (pathSeparators.find_first_of(folderPath.back()) == AZStd::string_view::npos) { @@ -926,7 +887,7 @@ namespace AZ // Sort by the name first so the registry file gets applied with all its specializations. if (lhs.m_tags[0] != rhs.m_tags[0]) { - return strcmp(lhs.m_relativePath, rhs.m_relativePath) < 0; + return lhs.m_relativePath < rhs.m_relativePath; } // Then sort by size first so the files with the fewest specializations get applied first. @@ -956,14 +917,14 @@ namespace AZ } collisionFound = true; - AZ_Error("Settings Registry", false, R"(Two registry files point to the same specialization: "%s" and "%s")", - lhs.m_relativePath, rhs.m_relativePath); + AZ_Error("Settings Registry", false, R"(Two registry files in "%.*s" point to the same specialization: "%s" and "%s")", + AZ_STRING_ARG(folderPath), lhs.m_relativePath.c_str(), rhs.m_relativePath.c_str()); historyPointer.Create(m_settings, m_settings.GetAllocator()).SetObject() .AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator()) .AddMember(StringRef("Path"), Value(folderPath.data(), aznumeric_caster(folderPath.length()), m_settings.GetAllocator()), m_settings.GetAllocator()) - .AddMember(StringRef("File1"), Value(lhs.m_relativePath, m_settings.GetAllocator()), m_settings.GetAllocator()) - .AddMember(StringRef("File2"), Value(rhs.m_relativePath, m_settings.GetAllocator()), m_settings.GetAllocator()); + .AddMember(StringRef("File1"), Value(lhs.m_relativePath.c_str(), m_settings.GetAllocator()), m_settings.GetAllocator()) + .AddMember(StringRef("File2"), Value(rhs.m_relativePath.c_str(), m_settings.GetAllocator()), m_settings.GetAllocator()); return false; } @@ -1036,9 +997,9 @@ namespace AZ // thats the name tag. AZStd::sort(AZStd::next(output.m_tags.begin()), output.m_tags.end()); - if (filePathSize < AZ_ARRAY_SIZE(output.m_relativePath)) + if (filePathSize < output.m_relativePath.max_size()) { - azstrcpy(output.m_relativePath, AZ_ARRAY_SIZE(output.m_relativePath), filename); + output.m_relativePath = filename; return true; } else @@ -1145,7 +1106,7 @@ namespace AZ JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge); if (rootKey.empty()) { - mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach); + mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings); } else { @@ -1153,7 +1114,7 @@ namespace AZ if (root.IsValid()) { Value& rootValue = root.Create(m_settings, m_settings.GetAllocator()); - mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach); + mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings); } else { @@ -1180,4 +1141,13 @@ namespace AZ return true; } + + void SettingsRegistryImpl::SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) + { + m_applyPatchSettings = applyPatchSettings; + } + void SettingsRegistryImpl::GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) + { + applyPatchSettings = m_applyPatchSettings; + } } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.h index 0cd5da131a..a80c12534b 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -35,9 +36,6 @@ namespace AZ AZ_CLASS_ALLOCATOR(SettingsRegistryImpl, AZ::OSAllocator, 0); AZ_RTTI(AZ::SettingsRegistryImpl, "{E9C34190-F888-48CA-83C9-9F24B4E21D72}", AZ::SettingsRegistryInterface); - static constexpr size_t MaxFilePathLength = AZ_MAX_PATH_LEN; - static constexpr size_t MaxJsonPathLength = 1024; - static constexpr size_t MaxCommandLineArgumentLength = 1024; static constexpr size_t MaxRegistryFolderEntries = 128; SettingsRegistryImpl(); @@ -80,11 +78,14 @@ namespace AZ bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations, AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector* scratchBuffer = nullptr) override; + void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override; + void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override; + private: using TagList = AZStd::fixed_vector; struct RegistryFile { - char m_relativePath[MaxFilePathLength]{ 0 }; + AZ::IO::FixedMaxPathString m_relativePath; TagList m_tags; bool m_isPatch{ false }; bool m_isPlatformFile{ false }; @@ -109,5 +110,6 @@ namespace AZ rapidjson::Document m_settings; JsonSerializerSettings m_serializationSettings; JsonDeserializerSettings m_deserializationSettings; + JsonApplyPatchSettings m_applyPatchSettings; }; } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index bd73162498..82bf1db484 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -340,46 +340,6 @@ namespace AZ::SettingsRegistryMergeUtils return sectionName; } - // Encodes a key, value delimited line such that the entire "key" can be stored as a single - // JSON Pointer key by escaping the tilde(~) and forward slash(/) - template - static AZStd::fixed_string EncodeLineForJsonPointer(AZStd::string_view token, - const AZ::SettingsRegistryInterface::CommandLineArgumentSettings::DelimiterFunc& delimiterFunc) - { - if (!delimiterFunc) - { - // Since the delimiter function is not valid, return the token unchanged - return AZStd::fixed_string{ token }; - } - // Iterate over the line and escape the '~' and '/' values - AZStd::fixed_string encodedToken; - size_t chIndex = 0; - for (; chIndex < token.size(); ++chIndex) - { - const char ch = token[chIndex]; - if (delimiterFunc(ch)) - { - // If the delimiter is found, this indicates that the end of the key has been found - break; - } - switch (ch) - { - case '~': - encodedToken += "~0"; - break; - case '/': - encodedToken += "~1"; - break; - default: - encodedToken += ch; - } - } - - // Copy over the rest of the post delimited line to the encoded token - encodedToken.append(token.data() + chIndex, token.data() + token.size()); - return encodedToken; - } - void QuerySpecializationsFromRegistry(SettingsRegistryInterface& registry, SettingsRegistryInterface::Specializations& specializations) { // Append any specializations stored in the registry @@ -499,14 +459,7 @@ namespace AZ::SettingsRegistryMergeUtils } } - // Check if the "key" portion of the line has '~' or '/' as the SettingsRegistry uses JSON Pointer - // to set the "value" portion. Those characters need to be escaped with ~0 and ~1 respectively - // to allow them to be embedded in a single json key - // Iterate over the line and escape the '~' and '/' values - AZStd::fixed_string escapedLine = EncodeLineForJsonPointer(line, - configParserSettings.m_commandLineSettings.m_delimiterFunc); - - registry.MergeCommandLineArgument(escapedLine, currentJsonPointerPath, configParserSettings.m_commandLineSettings); + registry.MergeCommandLineArgument(line, currentJsonPointerPath, configParserSettings.m_commandLineSettings); // Skip past the newline character if found frontIter = lineEndIter + (foundNewLine ? 1 : 0); diff --git a/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockSettingsRegistry.h b/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockSettingsRegistry.h index f4abbd9867..447b38e553 100644 --- a/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockSettingsRegistry.h +++ b/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockSettingsRegistry.h @@ -54,6 +54,9 @@ namespace AZ MOCK_METHOD5( MergeSettingsFolder, bool(AZStd::string_view, const Specializations&, AZStd::string_view, AZStd::string_view, AZStd::vector*)); + + MOCK_METHOD1(SetApplyPatchSettings, void(const JsonApplyPatchSettings&)); + MOCK_METHOD1(GetApplyPatchSettings, void(JsonApplyPatchSettings&)); }; } // namespace AZ diff --git a/Code/Framework/AzCore/Tests/Console/ConsoleTests.cpp b/Code/Framework/AzCore/Tests/Console/ConsoleTests.cpp index e56ae53646..5f3debca90 100644 --- a/Code/Framework/AzCore/Tests/Console/ConsoleTests.cpp +++ b/Code/Framework/AzCore/Tests/Console/ConsoleTests.cpp @@ -13,24 +13,25 @@ #include #include #include - +#include +#include namespace AZ { using namespace UnitTest; - AZ_CVAR(bool, testBool, false, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(char, testChar, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(int8_t, testInt8, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(int16_t, testInt16, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(int32_t, testInt32, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(int64_t, testInt64, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(uint8_t, testUInt8, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(bool, testBool, false, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(char, testChar, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(int8_t, testInt8, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(int16_t, testInt16, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(int32_t, testInt32, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(int64_t, testInt64, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(uint8_t, testUInt8, 0, nullptr, ConsoleFunctorFlags::Null, ""); AZ_CVAR(uint16_t, testUInt16, 0, nullptr, ConsoleFunctorFlags::Null, ""); AZ_CVAR(uint32_t, testUInt32, 0, nullptr, ConsoleFunctorFlags::Null, ""); AZ_CVAR(uint64_t, testUInt64, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, testFloat, 0, nullptr, ConsoleFunctorFlags::Null, ""); - AZ_CVAR(double, testDouble, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(float, testFloat, 0, nullptr, ConsoleFunctorFlags::Null, ""); + AZ_CVAR(double, testDouble, 0, nullptr, ConsoleFunctorFlags::Null, ""); AZ_CVAR(AZ::CVarFixedString, testString, "default", nullptr, ConsoleFunctorFlags::Null, ""); @@ -189,7 +190,7 @@ namespace AZ TEST_F(ConsoleTests, CVar_GetSetTest_Vector2) { - testVec2 = AZ::Vector2{ 0.0f, 0.0f}; + testVec2 = AZ::Vector2{ 0.0f, 0.0f }; TestCVarHelper(testVec2, "testVec2", "testVec2 1 1", "testVec2 asdf", AZ::Vector2(100, 100), AZ::Vector2(0, 0), AZ::Vector2(1, 1)); } @@ -350,3 +351,245 @@ namespace AZ } } } + + +namespace ConsoleSettingsRegistryTests +{ + //! ConfigFile MergeUtils Test + struct ConfigFileParams + { + AZStd::string_view m_testConfigFileName; + AZStd::string_view m_testConfigContents; + }; + class ConsoleSettingsRegistryFixture + : public UnitTest::ScopedAllocatorSetupFixture + , public ::testing::WithParamInterface + { + public: + void SetUp() override + { + m_registry = AZStd::make_unique(); + // Store off the old global settings registry to restore after each test + m_oldSettingsRegistry = AZ::SettingsRegistry::Get(); + if (m_oldSettingsRegistry != nullptr) + { + AZ::SettingsRegistry::Unregister(m_oldSettingsRegistry); + } + AZ::SettingsRegistry::Register(m_registry.get()); + + // Create a TestFile in the Test Directory + m_testFolder = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "ConsoleTestFolder"; + auto configFileParams = GetParam(); + CreateTestFile(m_testFolder / configFileParams.m_testConfigFileName, configFileParams.m_testConfigContents); + } + + void TearDown() override + { + // Remove the Test Directory + DeleteFolderRecursive(m_testFolder); + + // Restore the old global settings registry + AZ::SettingsRegistry::Unregister(m_registry.get()); + if (m_oldSettingsRegistry != nullptr) + { + AZ::SettingsRegistry::Register(m_oldSettingsRegistry); + m_oldSettingsRegistry = {}; + } + m_registry.reset(); + } + + void TestClassFunc(const AZ::ConsoleCommandContainer& someStrings) + { + m_stringArgCount = someStrings.size(); + } + + AZ_CONSOLEFUNC(ConsoleSettingsRegistryFixture, TestClassFunc, AZ::ConsoleFunctorFlags::Null, ""); + + static void DeleteFolderRecursive(const AZ::IO::PathView& path) + { + auto callback = [&path](AZStd::string_view filename, bool isFile) -> bool + { + if (isFile) + { + auto filePath = AZ::IO::FixedMaxPath(path) / filename; + AZ::IO::SystemFile::Delete(filePath.c_str()); + } + else + { + if (filename != "." && filename != "..") + { + auto folderPath = AZ::IO::FixedMaxPath(path) / filename; + DeleteFolderRecursive(folderPath); + } + } + return true; + }; + auto searchPath = AZ::IO::FixedMaxPath(path) / "*"; + AZ::IO::SystemFile::FindFiles(searchPath.c_str(), callback); + AZ::IO::SystemFile::DeleteDir(AZ::IO::FixedMaxPathString(path.Native()).c_str()); + } + + static bool CreateTestFile(const AZ::IO::FixedMaxPath& testPath, AZStd::string_view content) + { + AZ::IO::SystemFile file; + if (!file.Open(testPath.c_str(), AZ::IO::SystemFile::OpenMode::SF_OPEN_CREATE + | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) + { + AZ_Assert(false, "Unable to open test file for writing: %s", testPath.c_str()); + return false; + } + + if (file.Write(content.data(), content.size()) != content.size()) + { + AZ_Assert(false, "Unable to write content to test file: %s", testPath.c_str()); + return false; + } + + return true; + } + + protected: + size_t m_stringArgCount{}; + AZStd::unique_ptr m_registry; + AZ::IO::FixedMaxPath m_testFolder; + + private: + AZ::SettingsRegistryInterface* m_oldSettingsRegistry{}; + }; + + static bool s_consoleFreeFunctionInvoked = false; + static void TestSettingsRegistryFreeFunc(const AZ::ConsoleCommandContainer& someStrings) + { + EXPECT_TRUE(someStrings.empty()); + s_consoleFreeFunctionInvoked = true; + } + + AZ_CONSOLEFREEFUNC(TestSettingsRegistryFreeFunc, AZ::ConsoleFunctorFlags::Null, ""); + + TEST_P(ConsoleSettingsRegistryFixture, Console_AbleToLoadSettingsFile_Successfully) + { + AZ::Console testConsole(*m_registry); + testConsole.LinkDeferredFunctors(AZ::ConsoleFunctorBase::GetDeferredHead()); + AZ::Interface::Register(&testConsole); + AZ_CVAR_SCOPED(int32_t, testInit, 0, nullptr, AZ::ConsoleFunctorFlags::Null, ""); + s_consoleFreeFunctionInvoked = false; + testInit = {}; + AZ::testChar = {}; + AZ::testBool = {}; + AZ::testInt8 = {}; + AZ::testInt16 = {}; + AZ::testInt32 = {}; + AZ::testInt64 = {}; + AZ::testUInt8 = {}; + AZ::testUInt16 = {}; + AZ::testUInt32 = {}; + AZ::testUInt64 = {}; + AZ::testFloat= {}; + AZ::testDouble = {}; + AZ::testString = {}; + + auto configFileParams = GetParam(); + auto testFilePath = m_testFolder / configFileParams.m_testConfigFileName; + EXPECT_TRUE(AZ::IO::SystemFile::Exists(testFilePath.c_str())); + testConsole.ExecuteConfigFile(testFilePath.Native()); + EXPECT_TRUE(s_consoleFreeFunctionInvoked); + EXPECT_EQ(3, testInit); + EXPECT_TRUE(static_cast(AZ::testBool)); + EXPECT_EQ('Q', AZ::testChar); + EXPECT_EQ(24, AZ::testInt8); + EXPECT_EQ(-32, AZ::testInt16); + EXPECT_EQ(41, AZ::testInt32); + EXPECT_EQ(-51, AZ::testInt64); + EXPECT_EQ(3, AZ::testUInt8); + EXPECT_EQ(5, AZ::testUInt16); + EXPECT_EQ(6, AZ::testUInt32); + EXPECT_EQ(0xFFFF'FFFF'FFFF'FFFF, AZ::testUInt64); + EXPECT_FLOAT_EQ(1.0f, AZ::testFloat); + EXPECT_DOUBLE_EQ(2, AZ::testDouble); + EXPECT_STREQ("Stable", static_cast(AZ::testString).c_str()); + EXPECT_EQ(3, m_stringArgCount); + AZ::Interface::Unregister(&testConsole); + } + + + static constexpr AZStd::string_view UserINIStyleContent = + R"( + testInit = 3 + testBool true + testChar Q + testInt8 24 + testInt16 -32 + testInt32 41 + testInt64 -51 + testUInt8 3 + testUInt16 5 + testUInt32 6 + testUInt64 18446744073709551615 + testFloat 1.0 + testDouble 2 + testString Stable + ConsoleSettingsRegistryFixture.testClassFunc Foo Bar Baz + TestSettingsRegistryFreeFunc + )"; + + static constexpr AZStd::string_view UserJsonMergePatchContent = + R"( + { + "Amazon": { + "AzCore": { + "Runtime": { + "ConsoleCommands": { + "testInit": 3, + "testBool": true, + "testChar": "Q", + "testInt8": 24, + "testInt16": -32, + "testInt32": 41, + "testInt64": -51, + "testUInt8": 3, + "testUInt16": 5, + "testUInt32": 6, + "testUInt64": 18446744073709551615, + "testFloat": 1.0, + "testDouble": 2, + "testString": "Stable", + "ConsoleSettingsRegistryFixture.testClassFunc": "Foo Bar Baz", + "TestSettingsRegistryFreeFunc": "" + } + } + } + } + } + )"; + static constexpr AZStd::string_view UserJsonPatchContent = + R"( + [ + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInit", "value": 3 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testBool", "value": true }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testChar", "value": "Q" }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt8", "value": 24 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt16", "value": -32 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt32", "value": 41 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testInt64", "value": -51 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt8", "value": 3 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt16", "value": 5 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt32", "value": 6 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testUInt64", "value": 18446744073709551615 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testFloat", "value": 1.0 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testDouble", "value": 2 }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/testString", "value": "Stable" }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/ConsoleSettingsRegistryFixture.testClassFunc", "value": "Foo Bar Baz" }, + { "op": "add", "path": "/Amazon/AzCore/Runtime/ConsoleCommands/TestSettingsRegistryFreeFunc", "value": "" } + ] + )"; + + INSTANTIATE_TEST_CASE_P( + ExecuteCommandFromSettingsFile, + ConsoleSettingsRegistryFixture, + ::testing::Values( + ConfigFileParams{"user.cfg", UserINIStyleContent}, + ConfigFileParams{"user.setreg", UserJsonMergePatchContent}, + ConfigFileParams{"user.setregpatch", UserJsonPatchContent} + ) + ); +} diff --git a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryConsoleUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryConsoleUtilsTests.cpp index fb9b1a4aa4..c18e83ab63 100644 --- a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryConsoleUtilsTests.cpp +++ b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryConsoleUtilsTests.cpp @@ -55,7 +55,7 @@ namespace SettingsRegistryConsoleUtilsTests { constexpr const char* settingsKey = "/TestKey"; constexpr const char* expectedValue = "TestValue"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{ AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) }; EXPECT_TRUE(testConsole.PerformCommand(AZ::SettingsRegistryConsoleUtils::SettingsRegistrySet, { settingsKey, expectedValue })); @@ -69,7 +69,7 @@ namespace SettingsRegistryConsoleUtilsTests { constexpr const char* settingsKey = "/TestKey"; constexpr const char* expectedValue = "TestValue"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); // Scopes the console functor handle so that it destructs and unregisters the console functors { @@ -89,7 +89,7 @@ namespace SettingsRegistryConsoleUtilsTests constexpr const char* settingsKey2 = "/TestKey2"; constexpr const char* expectedValue = R"(TestValue)"; constexpr const char* expectedValue2 = R"(Hello World)"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{ AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) }; @@ -109,7 +109,7 @@ namespace SettingsRegistryConsoleUtilsTests constexpr const char* settingsKey2 = "/TestKey2"; constexpr const char* expectedValue = R"(TestValue)"; constexpr const char* expectedValue2 = R"(Hello World)"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); // Add settings to settings registry EXPECT_TRUE(m_registry->Set(settingsKey, expectedValue)); @@ -137,7 +137,7 @@ namespace SettingsRegistryConsoleUtilsTests constexpr const char* settingsKey2 = "/TestKey2"; constexpr const char* expectedValue = R"(TestValue)"; constexpr const char* expectedValue2 = R"(Hello World)"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{ AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) }; @@ -195,7 +195,7 @@ namespace SettingsRegistryConsoleUtilsTests constexpr const char* SettingsKey2 = "TestKey2"; constexpr const char* ExpectedValue = R"(TestValue)"; constexpr const char* ExpectedValue2 = R"(Hello World)"; - AZ::Console testConsole; + AZ::Console testConsole(*m_registry); AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle handle{ AZ::SettingsRegistryConsoleUtils::RegisterAzConsoleCommands(*m_registry, testConsole) }; diff --git a/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp b/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp index f7651664bd..fc07db5b52 100644 --- a/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp +++ b/Code/Framework/AzCore/Tests/SettingsRegistryTests.cpp @@ -1228,27 +1228,33 @@ namespace SettingsRegistryTests TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyIsTooLong_ReturnsFalse) { - AZStd::string argument = AZStd::string::format("Te%*cst=Value", aznumeric_cast(AZ::SettingsRegistryImpl::MaxJsonPathLength), ' '); + constexpr int LongKeySize = 1024; + AZStd::string argument = AZStd::string::format("Te%*cst=Value", LongKeySize, ' '); EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, {}, {})); } TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyIsTooLongWithDivider_ReturnsFalse) { - AZStd::string argument = AZStd::string::format("/Te%*cst=Value", aznumeric_cast(AZ::SettingsRegistryImpl::MaxJsonPathLength), ' '); + constexpr int LongKeySize = 1024; + AZStd::string argument = AZStd::string::format("/Te%*cst=Value", LongKeySize, ' '); EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, "/Path", {})); } TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsTooLong_ReturnsFalse) { - AZStd::string argument = AZStd::string::format("Test=Val%*cue", aznumeric_cast(AZ::SettingsRegistryImpl::MaxCommandLineArgumentLength), ' '); + constexpr int LongValueSize = 1024; + AZStd::string argument = AZStd::string::format("Test=Val%*cue", LongValueSize, ' '); EXPECT_FALSE(m_registry->MergeCommandLineArgument(argument, {}, {})); EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/Test")); } - TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingValue_ReturnsFalse) + TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingValue_ReturnsEmptyString) { - EXPECT_FALSE(m_registry->MergeCommandLineArgument("Test=", {}, {})); - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/Test")); + EXPECT_TRUE(m_registry->MergeCommandLineArgument("Test=", {}, {})); + EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Test")); + AZ::SettingsRegistryInterface::FixedValueString value; + EXPECT_TRUE(m_registry->Get(value, "/Test")); + EXPECT_TRUE(value.empty()); } TEST_F(SettingsRegistryTest, MergeCommandLineArgument_MissingKey_ReturnsFalse) @@ -1271,9 +1277,13 @@ namespace SettingsRegistryTests EXPECT_FALSE(m_registry->MergeCommandLineArgument(" =Value", {}, {})); } - TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsSpaces_ReturnsFalse) + TEST_F(SettingsRegistryTest, MergeCommandLineArgument_ValueIsSpaces_ReturnsEmptyString) { - EXPECT_FALSE(m_registry->MergeCommandLineArgument("Key= ", {}, {})); + EXPECT_TRUE(m_registry->MergeCommandLineArgument("Key= ", {}, {})); + EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Key")); + AZ::SettingsRegistryInterface::FixedValueString value; + EXPECT_TRUE(m_registry->Get(value, "/Key")); + EXPECT_TRUE(value.empty()); } TEST_F(SettingsRegistryTest, MergeCommandLineArgument_KeyAndValueAreSpaces_ReturnsFalse) @@ -1367,9 +1377,8 @@ namespace SettingsRegistryTests TEST_F(SettingsRegistryTest, MergeSettingsFile_PathAsSubStringThatsTooLong_ReturnsFalse) { - char path[AZ::SettingsRegistryImpl::MaxFilePathLength + 1]; - memset(path, '1', sizeof(path)); - AZStd::string_view subPath(path, AZ::SettingsRegistryImpl::MaxFilePathLength); + constexpr AZStd::fixed_string path(AZ::IO::MaxPathLength + 1, '1'); + const AZStd::string_view subPath(path); AZ_TEST_START_TRACE_SUPPRESSION; bool result = m_registry->MergeSettingsFile(subPath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {}, nullptr); @@ -1719,8 +1728,7 @@ namespace SettingsRegistryTests TEST_F(SettingsRegistryTest, MergeSettingsFolder_PathTooLong_ReportsErrorAndReturnsFalse) { - char path[AZ::SettingsRegistryImpl::MaxFilePathLength + 1]{}; - memset(path, 'a', AZ_ARRAY_SIZE(path)); + constexpr AZStd::fixed_string path(AZ::IO::MaxPathLength + 1, 'a'); AZ_TEST_START_TRACE_SUPPRESSION; bool result = m_registry->MergeSettingsFolder(path, { "editor", "test" }, {}, nullptr); @@ -1741,7 +1749,7 @@ namespace SettingsRegistryTests m_testFolder->push_back(AZ_CORRECT_DATABASE_SEPARATOR); *m_testFolder += AZ::SettingsRegistryInterface::RegistryFolder; bool result = m_registry->MergeSettingsFolder(*m_testFolder, { "editor", "test" }, {}, nullptr); - AZ_TEST_STOP_TRACE_SUPPRESSION(2); + EXPECT_GT(::UnitTest::TestRunner::Instance().StopAssertTests(), 0); EXPECT_FALSE(result); EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/0")); // Folder and specialization settings. @@ -1751,11 +1759,5 @@ namespace SettingsRegistryTests EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/Path")); EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/File1")); EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/1/File2")); - - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2")); - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/Error")); - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/Path")); - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/File1")); - EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/2/File2")); } } // namespace SettingsRegistryTests diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index dc6ae684cf..e02892de4e 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -236,8 +236,6 @@ namespace AzFramework // Archive classes relies on the FileIOBase DirectInstance to close // files properly m_directFileIO.reset(); - - // The AZ::Console skips destruction and always leaks to allow it to be used in static memory } void Application::Start(const Descriptor& descriptor, const StartupParameters& startupParameters) From c6ea0c0a46c2d6df0bc20734440735349af073e3 Mon Sep 17 00:00:00 2001 From: karlberg Date: Sat, 15 May 2021 15:10:17 -0700 Subject: [PATCH 5/6] Various local prediction and input processing related fixes --- .../Serialization/HashSerializer.cpp | 4 +- .../LocalPredictionPlayerInputComponent.h | 3 +- .../Components/MultiplayerComponent.h | 1 + .../Multiplayer/Components/NetBindComponent.h | 1 + .../NetworkInput/IMultiplayerComponentInput.h | 1 + .../Source/AutoGen/AutoComponent_Header.jinja | 4 +- .../Source/AutoGen/AutoComponent_Source.jinja | 16 ++++ .../LocalPredictionPlayerInputComponent.cpp | 93 ++++++++----------- .../Source/Components/NetBindComponent.cpp | 8 ++ .../Source/MultiplayerSystemComponent.cpp | 1 + .../Code/Source/NetworkInput/NetworkInput.cpp | 5 +- .../Source/NetworkInput/NetworkInputArray.cpp | 11 --- .../Source/NetworkInput/NetworkInputArray.h | 4 - 13 files changed, 78 insertions(+), 74 deletions(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp index c7f47cbc04..479299a87b 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp @@ -22,7 +22,9 @@ namespace AzNetworking AZ::HashValue32 HashSerializer::GetHash() const { // Just truncate the upper bits - return static_cast(m_hash); + const AZ::HashValue32 lower = static_cast(m_hash); + const AZ::HashValue32 upper = static_cast(m_hash >> 32); + return lower ^ upper; } SerializerMode HashSerializer::GetSerializerMode() const diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h index 7a6105a7e3..c69623a9e9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -102,7 +102,8 @@ namespace Multiplayer AZ::TimeMs m_lastInputReceivedTimeMs = AZ::TimeMs{ 0 }; AZ::TimeMs m_lastCorrectionSentTimeMs = AZ::TimeMs{ 0 }; - ClientInputId m_clientInputId = ClientInputId{ 0 }; + ClientInputId m_clientInputId = ClientInputId{ 0 }; // Clients incrementing inputId + ClientInputId m_lastClientInputId = ClientInputId{ 0 }; // Last inputId processed by the server ClientInputId m_lastCorrectionInputId = ClientInputId{ 0 }; ClientInputId m_lastMigratedInputId = ClientInputId{ 0 }; // Used to resend inputs that were queued during a migration event HostFrameId m_serverMigrateFrameId = InvalidHostFrameId; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h index 6f410dd97c..19689171c5 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h @@ -71,6 +71,7 @@ namespace Multiplayer NetworkEntityHandle GetEntityHandle(); void MarkDirty(); + virtual void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) = 0; virtual NetComponentId GetNetComponentId() const = 0; virtual bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, NetEntityRole netEntityRole, NetworkEntityRpcMessage& rpcMessage) = 0; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 257e7cab4b..41503396fb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -72,6 +72,7 @@ namespace Multiplayer ConstNetworkEntityHandle GetEntityHandle() const; NetworkEntityHandle GetEntityHandle(); + void SetOwningConnectionId(AzNetworking::ConnectionId connectionId); void SetAllowAutonomy(bool value); MultiplayerComponentInputVector AllocateComponentInputs(); bool IsProcessingInput() const; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h index 13faff3a7e..7af1bf3f60 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h @@ -30,6 +30,7 @@ namespace Multiplayer virtual ~IMultiplayerComponentInput() = default; virtual NetComponentId GetNetComponentId() const = 0; virtual bool Serialize(AzNetworking::ISerializer& serializer) = 0; + virtual IMultiplayerComponentInput& operator= (const IMultiplayerComponentInput&) { return *this; } }; using MultiplayerComponentInputVector = AZStd::vector>; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja index 0d4e7146a0..8eede21452 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Header.jinja @@ -329,6 +329,7 @@ namespace {{ Component.attrib['Namespace'] }} public: Multiplayer::NetComponentId GetNetComponentId() const override; bool Serialize(AzNetworking::ISerializer& serializer) override; + Multiplayer::IMultiplayerComponentInput& operator =(const Multiplayer::IMultiplayerComponentInput& rhs) override; {% call(Input) AutoComponentMacros.ParseNetworkInputs(Component) %} {{ Input.attrib['Type'] }} m_{{ LowerFirst(Input.attrib['Name']) }} = {{ Input.attrib['Type'] }}({{ Input.attrib['Init'] }}); @@ -439,6 +440,7 @@ namespace {{ Component.attrib['Namespace'] }} //! MultiplayerComponent interface //! @{ + void SetOwningConnectionId(AzNetworking::ConnectionId connectionId) override; Multiplayer::NetComponentId GetNetComponentId() const override; bool HandleRpcMessage(AzNetworking::IConnection* invokingConnection, Multiplayer::NetEntityRole remoteRole, Multiplayer::NetworkEntityRpcMessage& rpcMessage) override; bool SerializeStateDeltaMessage(Multiplayer::ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) override; @@ -519,7 +521,7 @@ namespace {{ Component.attrib['Namespace'] }} //! Archetype Properties {{ DeclareArchetypePropertyVars(Component)|indent(8) }} {% call(Type, Name) AutoComponentMacros.ParseComponentServiceTypeAndName(Component) %} - {{ Type }}* {{ Name }} = nullptr; + {{ Type }}* {{ Name }} = nullptr; {% endcall %} static Multiplayer::NetComponentId s_netComponentId; diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 516e6a05e8..3b3fa817cd 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -1068,6 +1068,13 @@ namespace {{ Component.attrib['Namespace'] }} return serializer.IsValid(); } + Multiplayer::IMultiplayerComponentInput& {{ ComponentName }}NetworkInput::operator =([[maybe_unused]] const Multiplayer::IMultiplayerComponentInput& rhs) + { + AZ_Assert(s_netComponentId == rhs.GetNetComponentId(), "AttachNetSystemComponent was not called on the owning NetworkInput"); + *this = *static_cast(&rhs); + return *this; + } + {% endif %} {{ ControllerBaseName }}::{{ ControllerBaseName }}({{ ComponentName }}& parent) : MultiplayerController(parent) @@ -1278,6 +1285,15 @@ namespace {{ Component.attrib['Namespace'] }} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', false)|indent(4) -}} {{ DefineRpcInvocations(Component, ComponentBaseName, 'Server', 'Authority', true)|indent(4) }} + void {{ ComponentBaseName }}::SetOwningConnectionId([[maybe_unused]] AzNetworking::ConnectionId connectionId) + { +{% for Property in Component.iter('NetworkProperty') %} +{% if Property.attrib['IsRewindable']|booleanTrue %} + m_{{ LowerFirst(Property.attrib['Name']) }}.SetOwningConnectionId(connectionId); +{% endif %} +{% endfor %} + } + Multiplayer::NetComponentId {{ ComponentBaseName }}::GetNetComponentId() const { return s_netComponentId; diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 46136fcde9..10a0d17e73 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -113,73 +113,57 @@ namespace Multiplayer [[maybe_unused]] const AzNetworking::PacketEncodingBuffer& clientState ) { - // After receiving the first input from the client, start the update event to check for slow hacking - if (!m_updateBankedTimeEvent.IsScheduled()) - { - m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true); - } - if (invokingConnection == nullptr) { // Discard any input messages that were locally dispatched or sent by disconnected clients return; } + const ClientInputId clientInputId = inputArray[0].GetClientInputId(); + if (clientInputId <= m_lastClientInputId) + { + AZLOG(NET_Prediction, "Discarding old or out of order move input (current: %u, received %u)", + aznumeric_cast(m_lastClientInputId), aznumeric_cast(clientInputId)); + return; + } + + // After receiving the first input from the client, start the update event to check for slow hacking + if (!m_updateBankedTimeEvent.IsScheduled()) + { + m_updateBankedTimeEvent.Enqueue(sv_InputUpdateTimeMs, true); + } + const AZ::TimeMs currentTimeMs = AZ::GetElapsedTimeMs(); const double clientInputRateSec = static_cast(static_cast(cl_InputRateMs)) / 1000.0; m_lastInputReceivedTimeMs = currentTimeMs; // Keep track of last inputs received, also allows us to update frame ids m_lastInputReceived = inputArray; - - // Figure out which index from the input array we want - // we start at the oldest input that has not been processed - int32_t inputArrayIndex = -1; - for (int32_t i = NetworkInputArray::MaxElements - 1; i >= 0; --i) - { - // Find an input that is newer than the last one we processed - if (m_lastInputReceived[i].GetClientInputId() > GetLastInputId()) - { - inputArrayIndex = i; - break; - } - } - - if (inputArrayIndex < 0) - { - AZLOG - ( - NET_Prediction, - "Discarding old or out of order move input (current: %u, received %u)", - aznumeric_cast(GetLastInputId()), - aznumeric_cast(m_lastInputReceived[0].GetClientInputId()) - ); - return; - } - - bool lostInput = false; - if (GetLastInputId() < inputArray.GetPreviousInputId()) - { - // last move id processed is older than the previous input id, we missed some input packets - lostInput = true; - } - SetLastInputId(m_lastInputReceived[0].GetClientInputId()); // Set this variable in case of migration - while (inputArrayIndex >= 0) + while (m_lastClientInputId < clientInputId) { - NetworkInput& input = m_lastInputReceived[inputArrayIndex]; + ++m_lastClientInputId; + + // Figure out which index from the input array we want + // If we have skipped an id, check if it was sent to us in the array. If we have lost too many, just use the oldest one in the array + const uint32_t deltaFrameId = aznumeric_cast(clientInputId - m_lastClientInputId); // always >= 0 because of while loop check + const uint32_t inputArrayIdx = AZStd::min(deltaFrameId, NetworkInputArray::MaxElements - 1); + const bool lostInput = deltaFrameId >= NetworkInputArray::MaxElements; // For logging only + + NetworkInput &input = m_lastInputReceived[inputArrayIdx]; + input.SetClientInputId(m_lastClientInputId); // Anticheat, if we're receiving too many inputs, and fall outside our variable latency input window // Discard move input events, client may be speed hacking if (m_clientBankedTime < sv_MaxBankTimeWindowSec) { m_clientBankedTime = AZStd::min(m_clientBankedTime + clientInputRateSec, (double)sv_MaxBankTimeWindowSec); // clamp to boundary - { ScopedAlterTime scopedTime(input.GetHostFrameId(), input.GetHostTimeMs(), invokingConnection->GetConnectionId()); GetNetBindComponent()->ProcessInput(input, static_cast(clientInputRateSec)); } + if (lostInput) { AZLOG(NET_Prediction, "InputLost InputId=%u", aznumeric_cast(input.GetClientInputId())); @@ -193,7 +177,6 @@ namespace Multiplayer { AZLOG(NET_Prediction, "Dropped InputId=%u", aznumeric_cast(input.GetClientInputId())); } - --inputArrayIndex; } if (sv_EnableCorrections && (currentTimeMs - m_lastCorrectionSentTimeMs > sv_MinCorrectionTimeMs)) @@ -205,6 +188,14 @@ namespace Multiplayer const AZ::HashValue32 localAuthorityHash = hashSerializer.GetHash(); + AZLOG + ( + NET_Prediction, + "Hash values for ProcessInput: client=%u, server=%u", + aznumeric_cast(stateHash), + aznumeric_cast(localAuthorityHash) + ); + if (stateHash != localAuthorityHash) { // Produce correction for client @@ -542,21 +533,15 @@ namespace Multiplayer m_inputHistory.PopFront(); } - const size_t inputHistorySize = m_inputHistory.Size(); + const int64_t inputHistorySize = aznumeric_cast(m_inputHistory.Size()); // Form the rest of the input array using the n most recent elements in the history buffer // NOTE: inputArray[0] has already been initialized hence start at i = 1 - for (uint32_t i = 1; i < NetworkInputArray::MaxElements; ++i) + for (int64_t i = 1; i < aznumeric_cast(NetworkInputArray::MaxElements); ++i) { - if (i < inputHistorySize) - { - inputArray[i] = m_inputHistory[inputHistorySize - 1 - i]; - } - else // History is too small? - { - // Plug in the most recent input - inputArray[i] = input; - } + // Clamp to oldest element if history is too small + const int64_t historyIndex = AZStd::max(inputHistorySize - 1 - i, 0); + inputArray[i] = m_inputHistory[historyIndex]; } // Send the input to server (only when we are not migrating) diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 6449f57e8e..e48d0b0d09 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -152,6 +152,14 @@ namespace Multiplayer return m_netEntityHandle; } + void NetBindComponent::SetOwningConnectionId(AzNetworking::ConnectionId connectionId) + { + for (MultiplayerComponent* multiplayerComponent : m_multiplayerInputComponentVector) + { + multiplayerComponent->SetOwningConnectionId(connectionId); + } + } + void NetBindComponent::SetAllowAutonomy(bool value) { // This flag allows a player host to autonomously control their player entity, even though the entity is in an authority role diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index a10bbf8a1a..723fada6d1 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -448,6 +448,7 @@ namespace Multiplayer if (entityList.size() > 0) { controlledEntity = entityList[0]; + controlledEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); } if (connection->GetUserData() == nullptr) // Only add user data if the connect event handler has not already done so diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index 7b9cba5a93..ba87849047 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -9,7 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ - +#pragma optimize ("", off) #include #include #include @@ -166,6 +166,7 @@ namespace Multiplayer void NetworkInput::CopyInternal(const NetworkInput& rhs) { m_inputId = rhs.m_inputId; + m_hostFrameId = rhs.m_hostFrameId; m_hostTimeMs = rhs.m_hostTimeMs; m_componentInputs.resize(rhs.m_componentInputs.size()); for (int32_t i = 0; i < rhs.m_componentInputs.size(); ++i) @@ -175,7 +176,7 @@ namespace Multiplayer { m_componentInputs[i] = AZStd::move(GetMultiplayerComponentRegistry()->AllocateComponentInput(rhsComponentId)); } - *m_componentInputs[i] = *rhs.m_componentInputs[i]; + *(m_componentInputs[i]) = *(rhs.m_componentInputs[i]); } m_wasAttached = rhs.m_wasAttached; } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp index d95310e4b5..e736cd4da2 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp @@ -48,16 +48,6 @@ namespace Multiplayer return m_inputs[index].m_networkInput; } - void NetworkInputArray::SetPreviousInputId(ClientInputId previousInputId) - { - m_previousInputId = previousInputId; - } - - ClientInputId NetworkInputArray::GetPreviousInputId() const - { - return m_previousInputId; - } - bool NetworkInputArray::Serialize(AzNetworking::ISerializer& serializer) { // Always serialize the full first element @@ -102,7 +92,6 @@ namespace Multiplayer } } } - serializer.Serialize(m_previousInputId, "PreviousInputId"); return true; } } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h index b67ce79112..293fb18928 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h @@ -33,9 +33,6 @@ namespace Multiplayer NetworkInput& operator[](uint32_t index); const NetworkInput& operator[](uint32_t index) const; - void SetPreviousInputId(ClientInputId previousInputId); - ClientInputId GetPreviousInputId() const; - bool Serialize(AzNetworking::ISerializer& serializer); private: @@ -49,6 +46,5 @@ namespace Multiplayer ConstNetworkEntityHandle m_owner; AZStd::array m_inputs; - ClientInputId m_previousInputId; }; } From e0ea9e6224e02dab83dc57914242533f12528ec3 Mon Sep 17 00:00:00 2001 From: karlberg Date: Sat, 15 May 2021 15:29:22 -0700 Subject: [PATCH 6/6] Removing debug code --- Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp index ba87849047..2589f52d87 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp @@ -9,7 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ -#pragma optimize ("", off) + #include #include #include