Browse Source

More bug fixes and updates for type validation during serialization

Signed-off-by: kberg-amzn <[email protected]>
kberg-amzn 3 years ago
parent
commit
1eaf092e84
38 changed files with 506 additions and 148 deletions
  1. 2 0
      Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp
  2. 23 5
      Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h
  3. 4 4
      Code/Framework/AzNetworking/AzNetworking/Serialization/DeltaSerializer.cpp
  4. 4 4
      Code/Framework/AzNetworking/AzNetworking/Serialization/DeltaSerializer.h
  5. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp
  6. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h
  7. 4 7
      Code/Framework/AzNetworking/AzNetworking/Serialization/ISerializer.h
  8. 5 21
      Code/Framework/AzNetworking/AzNetworking/Serialization/ISerializer.inl
  9. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkInputSerializer.cpp
  10. 3 3
      Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkInputSerializer.h
  11. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkOutputSerializer.cpp
  12. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkOutputSerializer.h
  13. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.cpp
  14. 2 2
      Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.h
  15. 3 3
      Code/Framework/AzNetworking/AzNetworking/Serialization/TrackChangedSerializer.h
  16. 4 4
      Code/Framework/AzNetworking/AzNetworking/Serialization/TrackChangedSerializer.inl
  17. 84 0
      Code/Framework/AzNetworking/AzNetworking/Serialization/TypeValidatingSerializer.h
  18. 250 0
      Code/Framework/AzNetworking/AzNetworking/Serialization/TypeValidatingSerializer.inl
  19. 1 1
      Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp
  20. 2 0
      Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake
  21. 4 4
      Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp
  22. 2 0
      Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja
  23. 4 1
      Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h
  24. 17 0
      Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h
  25. 11 4
      Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h
  26. 0 3
      Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h
  27. 0 3
      Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h
  28. 1 0
      Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputArray.h
  29. 2 5
      Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp
  30. 2 2
      Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp
  31. 5 7
      Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp
  32. 7 11
      Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp
  33. 0 1
      Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp
  34. 3 4
      Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp
  35. 0 2
      Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp
  36. 1 1
      Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp
  37. 42 32
      Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp
  38. 2 2
      Gems/Multiplayer/Code/Tests/MockInterfaces.h

+ 2 - 0
Code/Framework/AzNetworking/AzNetworking/Framework/NetworkingSystemComponent.cpp

@@ -17,6 +17,8 @@
 
 namespace AzNetworking
 {
+    AZ_CVAR(bool, net_validateSerializedTypes, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Validate that all serialized types are correct");
+
     void NetworkingSystemComponent::Reflect(AZ::ReflectContext* context)
     {
         if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))

+ 23 - 5
Code/Framework/AzNetworking/AzNetworking/Serialization/AzContainerSerializers.h

@@ -22,6 +22,8 @@
 #include <AzCore/Math/Transform.h>
 #include <AzCore/Math/Frustum.h>
 #include <AzCore/Math/Aabb.h>
+#include <AzCore/Name/Name.h>
+#include <AzCore/Name/NameDictionary.h>
 #include <limits>
 
 namespace AzNetworking
@@ -63,7 +65,7 @@ namespace AzNetworking
         }
     };
 
-    // fixed size array
+    // Fixed size array
     template <typename TYPE, AZStd::size_t Size>
     struct SerializeAzContainer<AZStd::array<TYPE, Size>>
     {
@@ -82,7 +84,7 @@ namespace AzNetworking
         }
     };
 
-    // fixed_unordered_map
+    // Fixed unordered map
     template <typename Key, typename MappedType, AZStd::size_t FixedNumBuckets, AZStd::size_t FixedNumElements, class Hasher, class EqualKey>
     struct SerializeAzContainer<AZStd::fixed_unordered_map<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>>
     {
@@ -121,7 +123,7 @@ namespace AzNetworking
         }
     };
 
-    // fixed_unordered_multimap
+    // Fixed unordered multimap
     template <typename Key, typename MappedType, AZStd::size_t FixedNumBuckets, AZStd::size_t FixedNumElements, class Hasher, class EqualKey>
     struct SerializeAzContainer<AZStd::fixed_unordered_multimap<Key, MappedType, FixedNumBuckets, FixedNumElements, Hasher, EqualKey>>
     {
@@ -160,7 +162,7 @@ namespace AzNetworking
         }
     };
 
-    // multimap
+    // Multimap
     template <class Key, class MappedType, class Compare, class Allocator>
     struct SerializeAzContainer<AZStd::multimap<Key, MappedType, Compare, Allocator>>
     {
@@ -214,7 +216,7 @@ namespace AzNetworking
         }
     };
 
-    // fixed_string
+    // Fixed string
     template <AZStd::size_t MaxElementCount>
     struct SerializeAzContainer<AZStd::fixed_string<MaxElementCount>>
     {
@@ -336,4 +338,20 @@ namespace AzNetworking
             return serializer.IsValid();
         }
     };
+
+    template <>
+    struct SerializeObjectHelper<AZ::Name>
+    {
+        static bool SerializeObject(ISerializer& serializer, AZ::Name& value)
+        {
+            AZ::Name::Hash nameHash = value.GetHash();
+            bool result = serializer.Serialize(nameHash, "NameHash");
+
+            if (result && serializer.GetSerializerMode() == SerializerMode::WriteToObject)
+            {
+                value = AZ::NameDictionary::Instance().FindName(nameHash);
+            }
+            return result;
+        }
+    };
 }

+ 4 - 4
Code/Framework/AzNetworking/AzNetworking/Serialization/DeltaSerializer.cpp

@@ -160,12 +160,12 @@ namespace AzNetworking
         return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
     }
 
-    bool DeltaSerializerCreate::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool DeltaSerializerCreate::BeginObject([[maybe_unused]] const char* name)
     {
         return true;
     }
 
-    bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name)
     {
         return true;
     }
@@ -339,12 +339,12 @@ namespace AzNetworking
         return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
     }
 
-    bool DeltaSerializerApply::BeginObject([[maybe_unused]] const char *name, [[maybe_unused]] const char* typeName)
+    bool DeltaSerializerApply::BeginObject([[maybe_unused]] const char *name)
     {
         return true;
     }
 
-    bool DeltaSerializerApply::EndObject([[maybe_unused]] const char *name, [[maybe_unused]] const char* typeName)
+    bool DeltaSerializerApply::EndObject([[maybe_unused]] const char *name)
     {
         return true;
     }

+ 4 - 4
Code/Framework/AzNetworking/AzNetworking/Serialization/DeltaSerializer.h

@@ -75,8 +75,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;
@@ -134,8 +134,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.cpp

@@ -113,12 +113,12 @@ namespace AzNetworking
         return true;
     }
 
-    bool HashSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool HashSerializer::BeginObject([[maybe_unused]] const char* name)
     {
         return true;
     }
 
-    bool HashSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool HashSerializer::EndObject([[maybe_unused]] const char* name)
     {
         return true;
     }

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/HashSerializer.h

@@ -40,8 +40,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 4 - 7
Code/Framework/AzNetworking/AzNetworking/Serialization/ISerializer.h

@@ -160,22 +160,19 @@ namespace AzNetworking
         //! Serialize interface for deducing whether or not TYPE is an enum or an object.
         //! @param value    object instance to serialize
         //! @param name     string name of the object
-        //! @param typeInfo basic type information for the value being serialized
         //! @return boolean true for success, false for serialization failure
         template <typename TYPE>
         bool Serialize(TYPE& value, const char* name);
 
         //! Begins serializing an object.
         //! @param name     string name of the object
-        //! @param typeInfo basic type information for the value being serialized
-        //! @return Result. In the case of Skip, Serialize is not called.
-        virtual bool BeginObject(const char* name, const char* typeName) = 0;
+        //! @return boolean true on success, false for failure
+        virtual bool BeginObject(const char* name) = 0;
 
         //! Ends serializing an object.
         //! @param name     string name of the object
-        //! @param typeInfo basic type information for the value being serialized
-        //! @return boolean true for success, false for serialization failure
-        virtual bool EndObject(const char* name, const char* typeName) = 0;
+        //! @return boolean true on success, false for failure
+        virtual bool EndObject(const char* name) = 0;
 
         //! Returns a pointer to the internal serialization buffer.
         //! @return pointer to the internal serialization buffer

+ 5 - 21
Code/Framework/AzNetworking/AzNetworking/Serialization/ISerializer.inl

@@ -14,8 +14,6 @@
 #include <AzCore/std/typetraits/is_enum.h>
 #include <AzCore/RTTI/TypeInfoSimple.h>
 #include <AzCore/RTTI/TypeSafeIntegral.h>
-#include <AzCore/Name/Name.h>
-#include <AzCore/Name/NameDictionary.h>
 
 namespace AzNetworking
 {
@@ -110,24 +108,25 @@ namespace AzNetworking
         return SerializeHelper<IsEnum, IsTypeSafeIntegral>::Serialize(*this, value, name);
     }
 
-    // SerializeHelper for objects and structures
+    // Helper for objects and structures
     template <>
     struct ISerializer::SerializeHelper<false, false>
     {
         template <typename TYPE>
         static bool Serialize(ISerializer& serializer, TYPE& value, const char* name)
         {
-            if (serializer.BeginObject(name, "Type name unknown"))
+            if (serializer.BeginObject(name))
             {
                 if (SerializeObjectHelper<TYPE>::SerializeObject(serializer, value))
                 {
-                    return serializer.EndObject(name, "Type name unknown");
+                    return serializer.EndObject(name);
                 }
             }
             return false;
         }
     };
 
+    // Helper for enums
     template <>
     struct ISerializer::SerializeHelper<true, false>
     {
@@ -153,6 +152,7 @@ namespace AzNetworking
         }
     };
 
+    // Helper for type-safe integrals
     template <>
     struct ISerializer::SerializeHelper<true, true>
     {
@@ -171,22 +171,6 @@ namespace AzNetworking
             return true;
         }
     };
-
-    template<>
-    struct SerializeObjectHelper<AZ::Name>
-    {
-        static bool SerializeObject(ISerializer& serializer, AZ::Name& value)
-        {
-            AZ::Name::Hash nameHash = value.GetHash();
-            bool result = serializer.Serialize(nameHash, "NameHash");
-
-            if (result && serializer.GetSerializerMode() == SerializerMode::WriteToObject)
-            {
-                value = AZ::NameDictionary::Instance().FindName(nameHash);
-            }
-            return result;
-        }
-    };
 }
 
 #include <AzNetworking/Serialization/AzContainerSerializers.h>

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkInputSerializer.cpp

@@ -97,12 +97,12 @@ namespace AzNetworking
         return SerializeBoundedValue<uint32_t>(0, bufferCapacity, outSize) && SerializeBytes(reinterpret_cast<uint8_t*>(buffer), outSize);
     }
 
-    bool NetworkInputSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool NetworkInputSerializer::BeginObject([[maybe_unused]] const char* name)
     {
         return true;
     }
 
-    bool NetworkInputSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool NetworkInputSerializer::EndObject([[maybe_unused]] const char* name)
     {
         return true;
     }

+ 3 - 3
Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkInputSerializer.h

@@ -14,7 +14,7 @@ namespace AzNetworking
 {
     //! @class NetworkInputSerializer
     //! @brief Input serializer for writing an object model into a bytestream.
-    class NetworkInputSerializer final
+    class NetworkInputSerializer
         : public ISerializer
     {
     public:
@@ -45,8 +45,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkOutputSerializer.cpp

@@ -102,12 +102,12 @@ namespace AzNetworking
         return SerializeBoundedValue<uint32_t>(0, bufferCapacity, outSize) && SerializeBytes(reinterpret_cast<uint8_t*>(buffer), outSize);
     }
 
-    bool NetworkOutputSerializer::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool NetworkOutputSerializer::BeginObject([[maybe_unused]] const char* name)
     {
         return true;
     }
 
-    bool NetworkOutputSerializer::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
+    bool NetworkOutputSerializer::EndObject([[maybe_unused]] const char* name)
     {
         return true;
     }

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/NetworkOutputSerializer.h

@@ -51,8 +51,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.cpp

@@ -91,7 +91,7 @@ namespace AzNetworking
         return false;
     }
 
-    bool StringifySerializer::BeginObject(const char* name, const char*)
+    bool StringifySerializer::BeginObject(const char* name)
     {
         m_prefixSizeStack.push_back(m_prefix.size());
         m_prefix += name;
@@ -99,7 +99,7 @@ namespace AzNetworking
         return true;
     }
 
-    bool StringifySerializer::EndObject(const char*, const char*)
+    bool StringifySerializer::EndObject(const char*)
     {
         m_prefix.resize(m_prefixSizeStack.back());
         m_prefixSizeStack.pop_back();

+ 2 - 2
Code/Framework/AzNetworking/AzNetworking/Serialization/StringifySerializer.h

@@ -42,8 +42,8 @@ namespace AzNetworking
         bool Serialize(float& value, const char* name, float minValue, float maxValue) override;
         bool Serialize(double& value, const char* name, double minValue, double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char* name, const char* typeName) override;
-        bool EndObject(const char* name, const char* typeName) override;
+        bool BeginObject(const char* name) override;
+        bool EndObject(const char* name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 3 - 3
Code/Framework/AzNetworking/AzNetworking/Serialization/TrackChangedSerializer.h

@@ -16,7 +16,7 @@ namespace AzNetworking
     //! @class TrackChangedSerializer
     //! @brief Output serializer that tracks if it actually writes changes to memory or not.
     template <typename BASE_TYPE>
-    class TrackChangedSerializer final
+    class TrackChangedSerializer
         : public BASE_TYPE
     {
     public:
@@ -41,8 +41,8 @@ namespace AzNetworking
         bool Serialize(   float& value, const char* name,    float minValue,    float maxValue) override;
         bool Serialize(  double& value, const char* name,   double minValue,   double maxValue) override;
         bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
-        bool BeginObject(const char *name, const char* typeName) override;
-        bool EndObject(const char *name, const char* typeName) override;
+        bool BeginObject(const char *name) override;
+        bool EndObject(const char *name) override;
 
         const uint8_t* GetBuffer() const override;
         uint32_t GetCapacity() const override;

+ 4 - 4
Code/Framework/AzNetworking/AzNetworking/Serialization/TrackChangedSerializer.inl

@@ -146,15 +146,15 @@ namespace AzNetworking
     }
 
     template <typename BASE_TYPE>
-    bool TrackChangedSerializer<BASE_TYPE>::BeginObject(const char* name, const char* typeName)
+    bool TrackChangedSerializer<BASE_TYPE>::BeginObject(const char* name)
     {
-        return BASE_TYPE::BeginObject(name, typeName);
+        return BASE_TYPE::BeginObject(name);
     }
 
     template <typename BASE_TYPE>
-    bool TrackChangedSerializer<BASE_TYPE>::EndObject(const char* name, const char* typeName)
+    bool TrackChangedSerializer<BASE_TYPE>::EndObject(const char* name)
     {
-        return BASE_TYPE::EndObject(name, typeName);
+        return BASE_TYPE::EndObject(name);
     }
 
     template <typename BASE_TYPE>

+ 84 - 0
Code/Framework/AzNetworking/AzNetworking/Serialization/TypeValidatingSerializer.h

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzNetworking/Serialization/ISerializer.h>
+
+namespace AzNetworking
+{
+    enum class ValidateSerializeType : uint8_t
+    {
+        Bool,
+        Char,
+        Int8,
+        Int16,
+        Int32,
+        Int64,
+        Uint8,
+        Uint16,
+        Uint32,
+        Uint64,
+        Float,
+        Double,
+        ByteArray,
+        ObjectStart,
+        ObjectEnd
+    };
+
+    const char* GetEnumString(ValidateSerializeType value);
+
+    //! @class SerializerTypeValidator
+    //! @brief A helper that can be used by any serializer to inject type information into the serialized data.
+    template <typename BASE_TYPE>
+    class TypeValidatingSerializer final
+        : public BASE_TYPE
+    {
+    public:
+
+        //! Constructor.
+        //! @param buffer         output buffer to read from
+        //! @param bufferCapacity capacity of the buffer in bytes
+        TypeValidatingSerializer(uint8_t* buffer, uint32_t bufferCapacity);
+        TypeValidatingSerializer(const uint8_t* buffer, uint32_t bufferCapacity);
+
+        // ISerializer interfaces
+        SerializerMode GetSerializerMode() const override;
+        bool Serialize(bool& value, const char* name) override;
+        bool Serialize(char& value, const char* name, char minValue, char maxValue) override;
+        bool Serialize(int8_t& value, const char* name, int8_t minValue, int8_t maxValue) override;
+        bool Serialize(int16_t& value, const char* name, int16_t minValue, int16_t maxValue) override;
+        bool Serialize(int32_t& value, const char* name, int32_t minValue, int32_t maxValue) override;
+        bool Serialize(int64_t& value, const char* name, int64_t minValue, int64_t maxValue) override;
+        bool Serialize(uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue) override;
+        bool Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue) override;
+        bool Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue) override;
+        bool Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue) override;
+        bool Serialize(float& value, const char* name, float minValue, float maxValue) override;
+        bool Serialize(double& value, const char* name, double minValue, double maxValue) override;
+        bool SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name) override;
+        bool BeginObject(const char* name) override;
+        bool EndObject(const char* name) override;
+
+        const uint8_t* GetBuffer() const override;
+        uint32_t GetCapacity() const override;
+        uint32_t GetSize() const override;
+        void ClearTrackedChangesFlag() override;
+        bool GetTrackedChangesFlag() const override;
+        // ISerializer interfaces
+
+    private:
+
+        bool Validate(const char* name, ValidateSerializeType type);
+
+        bool m_enabled = false;
+        bool m_validating = false;
+    };
+}
+
+#include <AzNetworking/Serialization/TypeValidatingSerializer.inl>

+ 250 - 0
Code/Framework/AzNetworking/AzNetworking/Serialization/TypeValidatingSerializer.inl

@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzNetworking/Serialization/TypeValidatingSerializer.h>
+#include <AzCore/Console/IConsole.h>
+#include <AzCore/Utils/TypeHash.h>
+
+namespace AzNetworking
+{
+    inline const char* GetEnumString(ValidateSerializeType value)
+    {
+        switch (value)
+        {
+        case ValidateSerializeType::Bool:
+            return "bool";
+        case ValidateSerializeType::Char:
+            return "char";
+        case ValidateSerializeType::Int8:
+            return "int8_t";
+        case ValidateSerializeType::Int16:
+            return "int16_t";
+        case ValidateSerializeType::Int32:
+            return "int32_t";
+        case ValidateSerializeType::Int64:
+            return "int64_t";
+        case ValidateSerializeType::Uint8:
+            return "uint8_t";
+        case ValidateSerializeType::Uint16:
+            return "uint16_t";
+        case ValidateSerializeType::Uint32:
+            return "uint32_t";
+        case ValidateSerializeType::Uint64:
+            return "uint64_t";
+        case ValidateSerializeType::Float:
+            return "float";
+        case ValidateSerializeType::Double:
+            return "double";
+        case ValidateSerializeType::ByteArray:
+            return "byte-array";
+        case ValidateSerializeType::ObjectStart:
+            return "object-start";
+        case ValidateSerializeType::ObjectEnd:
+            return "object-end";
+        }
+        return "Unknown Type";
+    }
+
+    template <typename BASE_TYPE>
+    TypeValidatingSerializer<BASE_TYPE>::TypeValidatingSerializer(uint8_t* buffer, uint32_t bufferCapacity)
+        : BASE_TYPE(buffer, bufferCapacity)
+    {
+        AZ::Interface<AZ::IConsole>::Get()->GetCvarValue("net_validateSerializedTypes", m_enabled);
+    }
+
+    template <typename BASE_TYPE>
+    TypeValidatingSerializer<BASE_TYPE>::TypeValidatingSerializer(const uint8_t* buffer, uint32_t bufferCapacity)
+        : BASE_TYPE(const_cast<uint8_t*>(buffer), bufferCapacity)
+    {
+        AZ::Interface<AZ::IConsole>::Get()->GetCvarValue("net_validateSerializedTypes", m_enabled);
+    }
+
+    template <typename BASE_TYPE>
+    SerializerMode TypeValidatingSerializer<BASE_TYPE>::GetSerializerMode() const
+    {
+        return BASE_TYPE::GetSerializerMode();
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(bool& value, const char* name)
+    {
+        Validate(name, ValidateSerializeType::Bool);
+        return BASE_TYPE::Serialize(value, name);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(char& value, const char* name, char minValue, char maxValue)
+    {
+        Validate(name, ValidateSerializeType::Char);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(int8_t& value, const char* name, int8_t minValue, int8_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Int8);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(int16_t& value, const char* name, int16_t minValue, int16_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Int16);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(int32_t& value, const char* name, int32_t minValue, int32_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Int32);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(int64_t& value, const char* name, int64_t minValue, int64_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Int64);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(uint8_t& value, const char* name, uint8_t minValue, uint8_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Uint8);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(uint16_t& value, const char* name, uint16_t minValue, uint16_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Uint16);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(uint32_t& value, const char* name, uint32_t minValue, uint32_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Uint32);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(uint64_t& value, const char* name, uint64_t minValue, uint64_t maxValue)
+    {
+        Validate(name, ValidateSerializeType::Uint64);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(float& value, const char* name, float minValue, float maxValue)
+    {
+        Validate(name, ValidateSerializeType::Float);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Serialize(double& value, const char* name, double minValue, double maxValue)
+    {
+        Validate(name, ValidateSerializeType::Double);
+        return BASE_TYPE::Serialize(value, name, minValue, maxValue);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::SerializeBytes(uint8_t* buffer, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name)
+    {
+        Validate(name, ValidateSerializeType::ByteArray);
+        return BASE_TYPE::SerializeBytes(buffer, bufferCapacity, isString, outSize, name);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::BeginObject(const char* name)
+    {
+        Validate(name, ValidateSerializeType::ObjectStart);
+        return BASE_TYPE::BeginObject(name);
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::EndObject(const char* name)
+    {
+        Validate(name, ValidateSerializeType::ObjectEnd);
+        return BASE_TYPE::EndObject(name);
+    }
+
+    template <typename BASE_TYPE>
+    const uint8_t* TypeValidatingSerializer<BASE_TYPE>::GetBuffer() const
+    {
+        return BASE_TYPE::GetBuffer();
+    }
+
+    template <typename BASE_TYPE>
+    uint32_t TypeValidatingSerializer<BASE_TYPE>::GetCapacity() const
+    {
+        return BASE_TYPE::GetCapacity();
+    }
+
+    template <typename BASE_TYPE>
+    uint32_t TypeValidatingSerializer<BASE_TYPE>::GetSize() const
+    {
+        return BASE_TYPE::GetSize();
+    }
+
+    template <typename BASE_TYPE>
+    void TypeValidatingSerializer<BASE_TYPE>::ClearTrackedChangesFlag()
+    {
+        return BASE_TYPE::ClearTrackedChangesFlag();
+    }
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::GetTrackedChangesFlag() const
+    {
+        return BASE_TYPE::GetTrackedChangesFlag();
+    }
+
+    struct ScopedEnableValidation
+    {
+        ScopedEnableValidation(bool& validating)
+            : m_validating(validating)
+        {
+            m_validating = true;
+        }
+        ~ScopedEnableValidation()
+        {
+            m_validating = false;
+        }
+        bool& m_validating;
+    };
+
+    template <typename BASE_TYPE>
+    bool TypeValidatingSerializer<BASE_TYPE>::Validate(const char* name, ValidateSerializeType type)
+    {
+        if (m_enabled && !m_validating)
+        {
+            // This guards against recursion during name serialization
+            ScopedEnableValidation validateScope(m_validating);
+
+            AZ::CVarFixedString nameValue = name;
+            AzNetworking::SerializeAzContainer<AZ::CVarFixedString>::Serialize(*this, nameValue);
+            if (nameValue != name)
+            {
+                AZ_Assert(false, "Name validation failed during serialization, expected %s but encountered %s",
+                    nameValue.c_str(), name);
+                return false;
+            }
+
+            uint8_t typeValue = static_cast<uint8_t>(type);
+            BASE_TYPE::Serialize(typeValue, "Type", 0, AZStd::numeric_limits<uint8_t>::max());
+            if (typeValue != static_cast<uint8_t>(type))
+            {
+                AZ_Assert(false, "Type validation failed during serialization, expected %s but encountered %s",
+                    GetEnumString(type), GetEnumString(static_cast<ValidateSerializeType>(typeValue)));
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 1 - 1
Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp

@@ -129,7 +129,7 @@ namespace AzNetworking
     {
         if (m_connectionSet.GetConnection(remoteAddress) != nullptr)
         {
-            AZLOG_INFO("Attempting to connect to an endpoint that already has a connection");
+            AZLOG_INFO("Attempting to connect to an endpoint (%s) that already has a connection", remoteAddress.GetString().c_str());
             return InvalidConnectionId;
         }
 

+ 2 - 0
Code/Framework/AzNetworking/AzNetworking/aznetworking_files.cmake

@@ -66,6 +66,8 @@ set(FILES
     Serialization/StringifySerializer.h
     Serialization/TrackChangedSerializer.h
     Serialization/TrackChangedSerializer.inl
+    Serialization/TypeValidatingSerializer.h
+    Serialization/TypeValidatingSerializer.inl
     TcpTransport/TcpConnection.cpp
     TcpTransport/TcpConnection.h
     TcpTransport/TcpConnection.inl

+ 4 - 4
Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp

@@ -184,8 +184,8 @@ namespace UnitTest
 
         createSerializer.ClearTrackedChangesFlag(); //NO-OP
         EXPECT_FALSE(createSerializer.GetTrackedChangesFlag());
-        EXPECT_TRUE(createSerializer.BeginObject("CreateSerializer", "Begin"));
-        EXPECT_TRUE(createSerializer.EndObject("CreateSerializer", "End"));
+        EXPECT_TRUE(createSerializer.BeginObject("CreateSerializer"));
+        EXPECT_TRUE(createSerializer.EndObject("CreateSerializer"));
     }
 
     TEST_F(DeltaSerializerTests, DeltaArraySize)
@@ -217,7 +217,7 @@ namespace UnitTest
 
         applySerializer.ClearTrackedChangesFlag(); //NO-OP
         EXPECT_FALSE(applySerializer.GetTrackedChangesFlag());
-        EXPECT_TRUE(applySerializer.BeginObject("CreateSerializer", "Begin"));
-        EXPECT_TRUE(applySerializer.EndObject("CreateSerializer", "End"));
+        EXPECT_TRUE(applySerializer.BeginObject("CreateSerializer"));
+        EXPECT_TRUE(applySerializer.EndObject("CreateSerializer"));
     }
 }

+ 2 - 0
Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja

@@ -1731,6 +1731,7 @@ namespace {{ Component.attrib['Namespace'] }}
 
         {{ RecordName }} record = {{ RecordName }}::AttachRecord(replicationRecord);
 
+        serializer.BeginObject("{{ ComponentBaseName }}");
         if (replicationRecord.ContainsAuthorityToClientBits())
         {
             SerializeAuthorityToClientProperties(record, serializer);
@@ -1747,6 +1748,7 @@ namespace {{ Component.attrib['Namespace'] }}
         {
             SerializeAutonomousToAuthorityProperties(record, serializer);
         }
+        serializer.EndObject("{{ ComponentBaseName }}");
         return serializer.IsValid();
     }
 

+ 4 - 1
Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h

@@ -166,7 +166,10 @@ namespace Multiplayer
         ReplicationRecord m_predictableRecord = NetEntityRole::Autonomous;
         ReplicationRecord m_localNotificationRecord = NetEntityRole::InvalidRole;
         PrefabEntityId    m_prefabEntityId;
-        AZStd::unordered_map<NetComponentId, MultiplayerComponent*> m_multiplayerComponentMap;
+
+        // It is important that this component map be ordered, as we walk it to generate serialization ordering
+        AZStd::map<NetComponentId, MultiplayerComponent*> m_multiplayerComponentMap;
+
         AZStd::vector<MultiplayerComponent*> m_multiplayerSerializationComponentVector;
         AZStd::vector<MultiplayerComponent*> m_multiplayerInputComponentVector;
 

+ 17 - 0
Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h

@@ -11,6 +11,10 @@
 #include <AzCore/RTTI/RTTI.h>
 #include <AzNetworking/ConnectionLayer/IConnection.h>
 #include <AzNetworking/DataStructures/ByteBuffer.h>
+#include <AzNetworking/Serialization/NetworkInputSerializer.h>
+#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
+#include <AzNetworking/Serialization/TrackChangedSerializer.h>
+#include <AzNetworking/Serialization/TypeValidatingSerializer.h>
 #include <Multiplayer/NetworkEntity/IFilterEntityManager.h>
 #include <Multiplayer/Components/MultiplayerComponentRegistry.h>
 #include <Multiplayer/NetworkEntity/INetworkEntityManager.h>
@@ -24,6 +28,19 @@ namespace AzNetworking
 
 namespace Multiplayer
 {
+#ifdef AZ_RELEASE_BUILD
+    // Disable serializer type validation in release
+    using InputSerializer = AzNetworking::NetworkInputSerializer;
+    using OutputSerializer = AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer>;
+    using RpcInputSerializer = AzNetworking::NetworkInputSerializer;
+    using RpcOutputSerializer = AzNetworking::NetworkOutputSerializer;
+#else
+    using InputSerializer = AzNetworking::TypeValidatingSerializer<AzNetworking::NetworkInputSerializer>;
+    using OutputSerializer = AzNetworking::TypeValidatingSerializer<AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer>>;
+    using RpcInputSerializer = AzNetworking::TypeValidatingSerializer<AzNetworking::NetworkInputSerializer>;
+    using RpcOutputSerializer = AzNetworking::TypeValidatingSerializer<AzNetworking::NetworkOutputSerializer>;
+#endif
+
     //! Collection of types of Multiplayer Connections
     enum class MultiplayerAgentType
     {

+ 11 - 4
Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h

@@ -24,7 +24,16 @@ namespace Multiplayer
     static constexpr uint32_t RewindHistorySize = 128;
 
     //! The default blend factor for ScopedAlterTime
-    static constexpr float DefaultBlendFactor = 1.f;
+    static constexpr float DefaultBlendFactor = 1.0f;
+
+    //! The maximum number of entity updates we can stuff into a single update packet
+    static constexpr uint32_t MaxAggregateEntityMessages = 2048;
+
+    //! The maximum number of RPC's we can aggregate into a single packet
+    static constexpr uint32_t MaxAggregateRpcMessages = 1024;
+
+    //! The maximum number of netEntityIds we can stuff into a single reset packet
+    static constexpr uint32_t MaxAggregateEntityResets = 2048;
 
     using HostId = AzNetworking::IpAddress;
     static const HostId InvalidHostId = HostId();
@@ -32,10 +41,8 @@ namespace Multiplayer
     AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint64_t);
     static constexpr NetEntityId InvalidNetEntityId = static_cast<NetEntityId>(-1);
 
-    using NetEntityIdSet = AZStd::unordered_set<NetEntityId>;
+    using NetEntityIdSet = AZStd::set<NetEntityId>;
 
-    //! The maximum number of netEntityIds we can stuff into a single reset packet
-    static const uint32_t MaxAggregateEntityResets = 2048;
     using NetEntityIdsForReset = AZStd::fixed_vector<NetEntityId, MaxAggregateEntityResets>;
 
     AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t);

+ 0 - 3
Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h

@@ -16,9 +16,6 @@ namespace Multiplayer
 {
     struct IRpcParamStruct;
 
-    //! The maximum number of RPC's we can aggregate into a single packet
-    static constexpr uint32_t MaxAggregateRpcMessages = 1024;
-
     //! @class NetworkEntityRpcMessage
     //! @brief Remote procedure call data.
     class NetworkEntityRpcMessage

+ 0 - 3
Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h

@@ -15,9 +15,6 @@
 
 namespace Multiplayer
 {
-    //! The maximum number of entity updates we can stuff into a single update packet
-    static const uint32_t MaxAggregateEntityMessages = 2048;
-
     //! @class NetworkEntityUpdateMessage
     //! @brief Property replication packet.
     class NetworkEntityUpdateMessage

+ 1 - 0
Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputArray.h

@@ -37,6 +37,7 @@ namespace Multiplayer
         {
             Wrapper() : m_networkInput() {}
             Wrapper(const NetworkInput& networkInput) : m_networkInput(networkInput) {}
+            bool Serialize(AzNetworking::ISerializer& serializer) { return serializer.Serialize(m_networkInput, "Input"); }
             NetworkInput m_networkInput;
         };
 

+ 2 - 5
Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp

@@ -10,10 +10,7 @@
 #include <AzCore/Serialization/SerializeContext.h>
 #include <AzCore/Serialization/EditContext.h>
 #include <AzNetworking/Serialization/HashSerializer.h>
-#include <AzNetworking/Serialization/NetworkInputSerializer.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
 #include <AzNetworking/Serialization/StringifySerializer.h>
-#include <AzNetworking/Serialization/TrackChangedSerializer.h>
 #include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
 #include <Multiplayer/MultiplayerDebug.h>
 
@@ -265,7 +262,7 @@ namespace Multiplayer
                 // Produce correction for client
                 AzNetworking::PacketEncodingBuffer correction;
                 correction.Resize(correction.GetCapacity());
-                AzNetworking::NetworkInputSerializer serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetCapacity()));
+                InputSerializer serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetCapacity()));
 
                 // only deserialize if we have data (for client/server profile/debug mismatches)
                 if (correction.GetSize() > 0)
@@ -370,7 +367,7 @@ namespace Multiplayer
         m_lastCorrectionInputId = inputId;
 
         // Apply the correction
-        AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
+        OutputSerializer serializer(correction.GetBuffer(), static_cast<uint32_t>(correction.GetSize()));
         SerializeEntityCorrection(serializer);
         GetNetBindComponent()->NotifyCorrection();
 

+ 2 - 2
Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp

@@ -480,13 +480,13 @@ namespace Multiplayer
         stats.RecordEntitySerializeStart(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str());
 
         bool success = true;
+        serializer.BeginObject(GetEntity()->GetName().c_str());
         for (auto iter = m_multiplayerSerializationComponentVector.begin(); iter != m_multiplayerSerializationComponentVector.end(); ++iter)
         {
             success &= (*iter)->SerializeStateDeltaMessage(replicationRecord, serializer);
-
             stats.RecordComponentSerializeEnd(serializer.GetSerializerMode(), (*iter)->GetNetComponentId());
         }
-
+        serializer.EndObject(GetEntity()->GetName().c_str());
         stats.RecordEntitySerializeStop(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str());
 
         return success;

+ 5 - 7
Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp

@@ -20,9 +20,6 @@
 #include <AzNetworking/ConnectionLayer/IConnection.h>
 #include <AzNetworking/ConnectionLayer/IConnectionListener.h>
 #include <AzNetworking/PacketLayer/IPacketHeader.h>
-#include <AzNetworking/Serialization/NetworkInputSerializer.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
-#include <AzNetworking/Serialization/TrackChangedSerializer.h>
 #include <AzCore/Component/ComponentApplicationBus.h>
 #include <AzCore/Console/IConsole.h>
 #include <AzCore/Console/ILogger.h>
@@ -767,7 +764,7 @@ namespace Multiplayer
                         // This can happen when Shard A migrates an entity to Shard B, then shard B migrates the entity to Shard C, and Shard A tries to delete a replicator it had to Shard C (which has already made a new replicator for Shard A)
                         result = UpdateValidationResult::DropMessage;
                     }
-                    else if (entityReplicator->GetRemoteNetworkRole() != NetEntityRole::Authority) // We expect to the remote role to be NetEntityRole::Authority
+                    else if (entityReplicator->GetRemoteNetworkRole() != NetEntityRole::Authority) // We expect the remote role to be NetEntityRole::Authority
                     {
                         // This entity has migrated previously, and we haven't heard back that the remove was successful, so we can accept the message
                         AZ_Assert(entityReplicator->IsMarkedForRemoval() && entityReplicator->GetRemoteNetworkRole() == NetEntityRole::Server, "Unexpected server message is not Authority or Server");
@@ -834,7 +831,7 @@ namespace Multiplayer
             return HandleEntityDeleteMessage(entityReplicator, packetHeader, updateMessage);
         }
 
-        AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> outputSerializer(updateMessage.GetData()->GetBuffer(), static_cast<uint32_t>(updateMessage.GetData()->GetSize()));
+        OutputSerializer outputSerializer(updateMessage.GetData()->GetBuffer(), static_cast<uint32_t>(updateMessage.GetData()->GetSize()));
 
         PrefabEntityId prefabEntityId;
         if (updateMessage.GetHasValidPrefabId())
@@ -881,6 +878,7 @@ namespace Multiplayer
             {
                 if (!entityReplicator->HandleRpcMessage(invokingConnection, rpcMessage))
                 {
+                    AZ_Assert(false, "Failed processing RPC messages, disconnecting");
                     return false;
                 }
             }
@@ -1186,7 +1184,7 @@ namespace Multiplayer
                 // Send an update packet if it needs one
                 propPublisher->GenerateRecord();
                 bool needsNetworkPropertyUpdate = propPublisher->PrepareSerialization();
-                AzNetworking::NetworkInputSerializer inputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetCapacity()));
+                InputSerializer inputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetCapacity()));
                 if (needsNetworkPropertyUpdate)
                 {
                     // Write out entity state into the buffer
@@ -1214,7 +1212,7 @@ namespace Multiplayer
         {
             if (message.m_propertyUpdateData.GetSize() > 0)
             {
-                AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetSize()));
+                OutputSerializer outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetSize()));
                 if (!HandlePropertyChangeMessage
                 (
                     invokingConnection,

+ 7 - 11
Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp

@@ -21,9 +21,6 @@
 
 #include <AzNetworking/ConnectionLayer/IConnection.h>
 #include <AzNetworking/PacketLayer/IPacket.h>
-#include <AzNetworking/Serialization/ISerializer.h>
-#include <AzNetworking/Serialization/NetworkInputSerializer.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
 
 #include <AzCore/Component/ComponentApplicationBus.h>
 #include <AzCore/Console/IConsole.h>
@@ -353,17 +350,16 @@ namespace Multiplayer
 
     bool EntityReplicator::IsMarkedForRemoval() const
     {
-        bool ret(true);
         if (m_propertyPublisher)
         {
-            ret = m_propertyPublisher->IsDeleting();
+            return m_propertyPublisher->IsDeleting();
         }
         else if (m_propertySubscriber)
         {
-            AZ_Assert(m_propertySubscriber, "Expected to have at least a subscriber when deleting");
-            ret = m_propertySubscriber->IsDeleting();
+            return m_propertySubscriber->IsDeleting();
         }
-        return ret;
+        AZLOG_WARN("Encountered netentity marked for removal that is not properly bound");
+        return true;
     }
 
     void EntityReplicator::SetPendingRemoval(AZ::TimeMs pendingRemovalTimeMs)
@@ -394,16 +390,16 @@ namespace Multiplayer
 
     bool EntityReplicator::IsDeletionAcknowledged() const
     {
-        // we sent the delete message, make sure it gets there
+        // We sent the delete message, make sure it gets there
         if (m_propertyPublisher)
         {
             return m_propertyPublisher->IsDeleted();
         }
         else if (m_propertySubscriber)
         {
-            AZ_Assert(m_propertySubscriber, "Expected to have at least a subscriber when deleting");
             return m_propertySubscriber->IsDeleted();
         }
+        AZLOG_WARN("Encountered netentity marked for removal that is not properly bound");
         return true;
     }
 
@@ -491,7 +487,7 @@ namespace Multiplayer
             updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId());
         }
 
-        AzNetworking::NetworkInputSerializer inputSerializer(updateMessage.ModifyData().GetBuffer(), static_cast<uint32_t>(updateMessage.ModifyData().GetCapacity()));
+        InputSerializer inputSerializer(updateMessage.ModifyData().GetBuffer(), static_cast<uint32_t>(updateMessage.ModifyData().GetCapacity()));
         m_propertyPublisher->UpdateSerialization(inputSerializer);
         updateMessage.ModifyData().Resize(inputSerializer.GetSize());
 

+ 0 - 1
Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp

@@ -14,7 +14,6 @@
 #include <AzCore/Console/ILogger.h>
 #include <AzCore/EBus/IEventScheduler.h>
 #include <AzNetworking/Utilities/NetworkCommon.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
 
 namespace Multiplayer
 {

+ 3 - 4
Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityRpcMessage.cpp

@@ -7,8 +7,7 @@
  */
 
 #include <Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h>
-#include <AzNetworking/Serialization/NetworkInputSerializer.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
+#include <Multiplayer/IMultiplayer.h>
 #include <AzCore/Console/ILogger.h>
 
 namespace Multiplayer
@@ -135,7 +134,7 @@ namespace Multiplayer
             m_data = AZStd::make_unique<AzNetworking::PacketEncodingBuffer>();
         }
 
-        AzNetworking::NetworkInputSerializer serializer(m_data->GetBuffer(), static_cast<uint32_t>(m_data->GetCapacity()));
+        RpcInputSerializer serializer(m_data->GetBuffer(), static_cast<uint32_t>(m_data->GetCapacity()));
         if (params.Serialize(serializer))
         {
             m_data->Resize(serializer.GetSize());
@@ -154,7 +153,7 @@ namespace Multiplayer
             return false;
         }
 
-        AzNetworking::NetworkOutputSerializer serializer(m_data->GetBuffer(), static_cast<uint32_t>(m_data->GetSize()));
+        RpcOutputSerializer serializer(m_data->GetBuffer(), static_cast<uint32_t>(m_data->GetSize()));
         return outParams.Serialize(serializer);
     }
 

+ 0 - 2
Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp

@@ -7,8 +7,6 @@
  */
 
 #include <Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h>
-#include <AzNetworking/Serialization/NetworkInputSerializer.h>
-#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
 #include <AzCore/Console/ILogger.h>
 
 namespace Multiplayer

+ 1 - 1
Gems/Multiplayer/Code/Source/NetworkInput/NetworkInput.cpp

@@ -146,7 +146,7 @@ namespace Multiplayer
                 // 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]->GetNetComponentId() : InvalidNetComponentId;
-                serializer.Serialize(componentId, "ComponentType");
+                serializer.Serialize(componentId, "ComponentId");
                 // 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]->GetNetComponentId()))
                 {

+ 42 - 32
Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp

@@ -13,6 +13,8 @@
 
 namespace Multiplayer
 {
+    AZ_CVAR(bool, net_useInputDeltaSerialization, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, inputs will use delta-serialization to reduce RPC bandwidth");
+
     NetworkInputArray::NetworkInputArray()
         : m_owner()
         , m_inputs()
@@ -46,48 +48,56 @@ namespace Multiplayer
 
     bool NetworkInputArray::Serialize(AzNetworking::ISerializer& serializer)
     {
-        // Always serialize the full first element
-        if (!m_inputs[0].m_networkInput.Serialize(serializer))
-        {
-            return false;
-        }
-
-        // For each subsequent element
-        for (uint32_t i = 1; i < m_inputs.size(); ++i)
+        if (net_useInputDeltaSerialization)
         {
-            if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
+            // Use delta-serialization to compress input RPC bandwidth usage
+            // Always serialize the full first element
+            if (!m_inputs[0].m_networkInput.Serialize(serializer))
             {
-                AzNetworking::SerializerDelta deltaSerializer;
-                // Read out the delta
-                if (!deltaSerializer.Serialize(serializer))
-                {
-                    return false;
-                }
-                // Start with previous value
-                m_inputs[i].m_networkInput = m_inputs[i - 1].m_networkInput;
-                // Then apply delta
-                AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
-                if (!applySerializer.ApplyDelta(m_inputs[i].m_networkInput))
-                {
-                    return false;
-                }
+                return false;
             }
-            else
+
+            // For each subsequent element
+            for (uint32_t i = 1; i < m_inputs.size(); ++i)
             {
-                AzNetworking::SerializerDelta deltaSerializer;
-                // Create the delta
-                AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
-                if (!createSerializer.CreateDelta(m_inputs[i - 1].m_networkInput, m_inputs[i].m_networkInput))
+                if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
                 {
-                    return false;
+                    AzNetworking::SerializerDelta deltaSerializer;
+                    // Read out the delta
+                    if (!deltaSerializer.Serialize(serializer))
+                    {
+                        return false;
+                    }
+                    // Start with previous value
+                    m_inputs[i].m_networkInput = m_inputs[i - 1].m_networkInput;
+                    // Then apply delta
+                    AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
+                    if (!applySerializer.ApplyDelta(m_inputs[i].m_networkInput))
+                    {
+                        return false;
+                    }
                 }
-                // Then write out the delta
-                if (!deltaSerializer.Serialize(serializer))
+                else
                 {
-                    return false;
+                    AzNetworking::SerializerDelta deltaSerializer;
+                    // Create the delta
+                    AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
+                    if (!createSerializer.CreateDelta(m_inputs[i - 1].m_networkInput, m_inputs[i].m_networkInput))
+                    {
+                        return false;
+                    }
+                    // Then write out the delta
+                    if (!deltaSerializer.Serialize(serializer))
+                    {
+                        return false;
+                    }
                 }
             }
         }
+        else
+        {
+            return serializer.Serialize(m_inputs, "InputArray");
+        }
         return true;
     }
 }

+ 2 - 2
Gems/Multiplayer/Code/Tests/MockInterfaces.h

@@ -170,8 +170,8 @@ namespace UnitTest
         MOCK_METHOD4(Serialize, bool (float&, const char*, float, float));
         MOCK_METHOD4(Serialize, bool (double&, const char*, double, double));
         MOCK_METHOD5(SerializeBytes, bool (uint8_t*, uint32_t, bool, uint32_t&, const char*));
-        MOCK_METHOD2(BeginObject, bool (const char*, const char*));
-        MOCK_METHOD2(EndObject, bool (const char*, const char*));
+        MOCK_METHOD2(BeginObject, bool (const char*));
+        MOCK_METHOD2(EndObject, bool (const char*));
         MOCK_CONST_METHOD0(GetBuffer, const uint8_t* ());
         MOCK_CONST_METHOD0(GetCapacity, uint32_t ());
         MOCK_CONST_METHOD0(GetSize, uint32_t ());