Переглянути джерело

Perf spawn test level

Signed-off-by: AMZN-Olex <[email protected]>
AMZN-Olex 3 роки тому
батько
коміт
a8de0198b0

+ 1 - 0
Gem/Code/CMakeLists.txt

@@ -6,6 +6,7 @@
 #
 
 ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
+#o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${gem_restricted_path} ${gem_path} ${gem_parent_relative_path})
 
 ly_add_target(
     NAME MultiplayerSample.Static STATIC

+ 40 - 0
Gem/Code/Include/NetworkPrefabSpawnerInterface.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <AzCore/Math/Transform.h>
+#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
+
+namespace MultiplayerSample
+{
+    using PrefabSpawnCallbackBeforeActivation = AZStd::function<void(
+        AZStd::shared_ptr<AzFramework::EntitySpawnTicket>,
+        AzFramework::SpawnableEntityContainerView)>;
+
+    using PrefabSpawnCallback = AZStd::function<void(
+        AZStd::shared_ptr<AzFramework::EntitySpawnTicket>,
+        AzFramework::SpawnableConstEntityContainerView)>;
+
+    struct PrefabCallbacks
+    {
+        PrefabSpawnCallbackBeforeActivation m_beforeActivateCallback;
+        PrefabSpawnCallback m_onActivateCallback;
+    };
+
+    class NetworkPrefabSpawnerRequests
+    {
+    public:
+        AZ_RTTI(RecastO3DEGemRequests, "{82e5cfb5-6a1a-4bd1-b48d-cd817474d611}");
+        virtual ~NetworkPrefabSpawnerRequests() = default;
+
+        virtual void SpawnPrefab(const AZ::Transform& worldTm, const char* assetPath, PrefabCallbacks callbacks) = 0;
+        virtual void SpawnPrefabAsset(const AZ::Transform& worldTm, const AZ::Data::Asset<AzFramework::Spawnable>& asset, PrefabCallbacks callbacks) = 0;
+        virtual void SpawnDefaultPrefab(const AZ::Transform& worldTm, PrefabCallbacks callbacks) = 0;
+    };
+
+    class NetworkPrefabSpawnerTraits
+        : public AZ::ComponentBus
+    {
+    };
+
+    using NetworkPrefabSpawnerRequestBus = AZ::EBus<NetworkPrefabSpawnerRequests, NetworkPrefabSpawnerTraits>;
+    using NetworkPrefabSpawnerInterface = AZ::Interface<NetworkPrefabSpawnerRequests>;
+}

+ 285 - 0
Gem/Code/Source/Components/PerfTest/NetworkPrefabSpawnerComponent.cpp

@@ -0,0 +1,285 @@
+
+#include "NetworkPrefabSpawnerComponent.h"
+
+#include <AzCore/Asset/AssetManagerBus.h>
+#include <AzCore/Asset/AssetSerializer.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
+
+namespace MultiplayerSample
+{
+    void NetworkPrefabSpawnerComponent::Reflect(AZ::ReflectContext* reflection)
+    {
+        if (const auto serializationContext = azrtti_cast<AZ::SerializeContext*>(reflection))
+        {
+            serializationContext->Class<NetworkPrefabSpawnerComponent, Component>()
+                ->Field("Default Prefab", &NetworkPrefabSpawnerComponent::m_defaultSpawnableAsset)
+                ->Version(1);
+
+            if (const auto editContext = serializationContext->GetEditContext())
+            {
+                editContext->Class<NetworkPrefabSpawnerComponent>("Network Prefab Spawner",
+                    "Handles spawning of prefabs")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "MultiplayerSample")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(nullptr, &NetworkPrefabSpawnerComponent::m_defaultSpawnableAsset, "Default Prefab", "Default prefab to spawn upon request.")
+                    ;
+            }
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::Activate()
+    {
+        if (NetworkPrefabSpawnerInterface::Get() == nullptr)
+        {
+            NetworkPrefabSpawnerInterface::Register(this);
+        }
+
+        NetworkPrefabSpawnerRequestBus::Handler::BusConnect(GetEntityId());
+
+        // preload
+        if (m_defaultSpawnableAsset.GetId().IsValid())
+        {
+            m_defaultSpawnableAsset.QueueLoad();
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::Deactivate()
+    {
+        if (NetworkPrefabSpawnerInterface::Get() == this)
+        {
+            NetworkPrefabSpawnerInterface::Unregister(this);
+        }
+
+        NetworkPrefabSpawnerRequestBus::Handler::BusDisconnect();
+        AZ::Data::AssetBus::MultiHandler::BusDisconnect();
+    }
+
+    void NetworkPrefabSpawnerComponent::SpawnDefaultPrefab(const AZ::Transform& worldTm, PrefabCallbacks callbacks)
+    {
+        AssetItem newAsset;
+        newAsset.m_pathToAsset = m_defaultSpawnableAsset.GetHint().c_str();
+        newAsset.m_spawnableAsset = m_defaultSpawnableAsset;
+        AZ::Data::AssetBus::MultiHandler::BusConnect(m_defaultSpawnableAsset.GetId());
+
+        if (newAsset.m_spawnableAsset.IsReady() == false)
+        {
+            newAsset.m_spawnableAsset.QueueLoad();
+        }
+
+        m_assetMap.emplace(newAsset.m_spawnableAsset.GetId(), newAsset);
+
+        const SpawnRequest request{ newAsset.m_spawnableAsset.GetId(), worldTm, AZStd::move(callbacks) };
+        if (newAsset.m_spawnableAsset.IsReady())
+        {
+            CreateInstance(request, &newAsset);
+        }
+        else
+        {
+            m_requests.push_back(request);
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::SpawnPrefab(const AZ::Transform& worldTm, const char* assetPath, PrefabCallbacks callbacks)
+    {
+        const AZ::Data::AssetId assetId = GetSpawnableAssetId(assetPath);
+
+        const SpawnRequest request{ assetId, worldTm, AZStd::move(callbacks) };
+        auto foundAsset = m_assetMap.find(assetId);
+        if (foundAsset != m_assetMap.end())
+        {
+            if (foundAsset->second.m_spawnableAsset.IsReady())
+            {
+                CreateInstance(request, &foundAsset->second);
+            }
+            else
+            {
+                m_requests.push_back(request);
+            }
+        }
+        else
+        {
+            AssetItem newAsset;
+            newAsset.m_pathToAsset = assetPath;
+            newAsset.m_spawnableAsset.Create(assetId, false);
+            AZ::Data::AssetBus::MultiHandler::BusConnect(assetId);
+            newAsset.m_spawnableAsset.QueueLoad();
+            m_assetMap.emplace(assetId, newAsset);
+
+            if (newAsset.m_spawnableAsset.IsReady())
+            {
+                CreateInstance(request, &newAsset);
+            }
+            else
+            {
+                m_requests.push_back(request);
+            }
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::SpawnPrefabAsset(const AZ::Transform& worldTm,
+        const AZ::Data::Asset<AzFramework::Spawnable>& asset, PrefabCallbacks callbacks)
+    {
+        AssetItem newAsset;
+        newAsset.m_pathToAsset = asset.GetHint().c_str();
+        newAsset.m_spawnableAsset = asset;
+        AZ::Data::AssetBus::MultiHandler::BusConnect(asset.GetId());
+
+        if (newAsset.m_spawnableAsset.IsReady() == false)
+        {
+            newAsset.m_spawnableAsset.QueueLoad();
+        }
+
+        m_assetMap.emplace(newAsset.m_spawnableAsset.GetId(), newAsset);
+
+        const SpawnRequest request{ newAsset.m_spawnableAsset.GetId(), worldTm, AZStd::move(callbacks) };
+        if (newAsset.m_spawnableAsset.IsReady())
+        {
+            CreateInstance(request, &newAsset);
+        }
+        else
+        {
+            m_requests.push_back(request);
+        }
+    }
+
+    AZ::Data::AssetId NetworkPrefabSpawnerComponent::GetSpawnableAssetId(const char* assetPath) const
+    {
+        if (assetPath)
+        {
+            AZ::Data::AssetId assetId;
+            AZ::Data::AssetCatalogRequestBus::BroadcastResult(
+                assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, assetPath,
+                AZ::Data::s_invalidAssetType, false);
+            if (assetId.IsValid())
+            {
+                return assetId;
+            }
+        }
+
+        return {};
+    }
+
+    void NetworkPrefabSpawnerComponent::CreateInstance(const SpawnRequest& request, const AssetItem* asset)
+    {
+        AZ::Transform world = request.m_whereToSpawn;
+
+        if (asset)
+        {
+            auto ticket = AZStd::make_shared<AzFramework::EntitySpawnTicket>(asset->m_spawnableAsset);
+
+            auto preSpawnCallback = [world, request, ticket]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableEntityContainerView view)
+            {
+                const AZ::Entity* rootEntity = *view.begin();
+                if (AzFramework::TransformComponent* entityTransform = rootEntity->FindComponent<AzFramework::TransformComponent>())
+                {
+                    entityTransform->SetWorldTM(world);
+                }
+
+                if (request.m_callbacks.m_beforeActivateCallback)
+                {
+                    request.m_callbacks.m_beforeActivateCallback(ticket, view);
+                }
+            };
+
+            auto onSpawnedCallback = [request, ticket]([[maybe_unused]] AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableConstEntityContainerView view)
+            {
+                if (request.m_callbacks.m_onActivateCallback)
+                {
+                    request.m_callbacks.m_onActivateCallback(ticket, view);
+                }
+            };
+
+            if (ticket->IsValid())
+            {
+                AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
+                optionalArgs.m_preInsertionCallback = AZStd::move(preSpawnCallback);
+                optionalArgs.m_completionCallback = AZStd::move(onSpawnedCallback);
+                AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
+            }
+            else
+            {
+                AZ_Assert(ticket->IsValid(), "Unable to instantiate spawnable asset");
+            }
+        }
+        else
+        {
+            AZ_Assert(asset, "AssetMap didn't contain the asset id for prefab spawning");
+
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
+    {
+        const AZ::Data::AssetId assetId = asset.GetId();
+        AZ::Data::AssetBus::MultiHandler::BusDisconnect(assetId);
+
+        const auto foundAsset = m_assetMap.find(assetId);
+        if (foundAsset != m_assetMap.end())
+        {
+            for (auto requestIterator = m_requests.begin(); requestIterator < m_requests.end(); /*iterating inside the loop body*/)
+            {
+                const SpawnRequest& request = *requestIterator;
+
+                if (request.m_assetIdToSpawn == assetId)
+                {
+                    CreateInstance(request, &foundAsset->second);
+                    requestIterator = m_requests.erase(requestIterator);
+                }
+                else
+                {
+                    ++requestIterator;
+                }
+            }
+        }
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetCanceled(AZ::Data::AssetId assetId)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetId.ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetContainerReady(AZ::Data::Asset<AZ::Data::AssetData> assetData)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> assetData)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetMoved(AZ::Data::Asset<AZ::Data::AssetData> assetData, [[maybe_unused]] void* oldDataPointer)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetPreReload(AZ::Data::Asset<AZ::Data::AssetData> assetData)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> assetData)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> assetData)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetSaved(AZ::Data::Asset<AZ::Data::AssetData> assetData, [[maybe_unused]] bool isSuccessful)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetData.GetId().ToString<AZStd::string>().c_str());
+    }
+
+    void NetworkPrefabSpawnerComponent::OnAssetUnloaded(const AZ::Data::AssetId assetId, [[maybe_unused]] const AZ::Data::AssetType asset)
+    {
+        AZ_Printf("TEST", "%s %s", __FUNCTION__, assetId.ToString<AZStd::string>().c_str());
+    }
+}

+ 72 - 0
Gem/Code/Source/Components/PerfTest/NetworkPrefabSpawnerComponent.h

@@ -0,0 +1,72 @@
+#pragma once
+
+#include <NetworkPrefabSpawnerInterface.h>
+#include <AzCore/Component/Component.h>
+#include <AzFramework/Spawnable/Spawnable.h>
+#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
+
+namespace MultiplayerSample
+{
+    /**
+     * \brief Can spawn prefabs using C++ API.
+     * Does not keep track of instances. The user should save a copy of the ticket using callbacks in @PrefabCallbacks.
+     */
+    class NetworkPrefabSpawnerComponent
+        : public AZ::Component
+        , public NetworkPrefabSpawnerRequestBus::Handler
+        , public AZ::Data::AssetBus::MultiHandler
+    {
+    public:
+        AZ_COMPONENT(NetworkPrefabSpawnerComponent, "{7E48961B-7E39-4FBC-95E4-74B712229E9B}", Component);
+        
+        static void Reflect(AZ::ReflectContext* reflection);
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+        {
+            provided.push_back(AZ_CRC_CE("NetworkPrefabSpawnerService"));
+        }
+
+        void Activate() override;
+        void Deactivate() override;
+
+        // NetworkPrefabSpawnerRequestBus
+        void SpawnPrefab(const AZ::Transform& worldTm, const char* assetPath, PrefabCallbacks callbacks) override;
+        void SpawnPrefabAsset(const AZ::Transform& worldTm, const AZ::Data::Asset<AzFramework::Spawnable>& asset, PrefabCallbacks callbacks) override;
+        void SpawnDefaultPrefab(const AZ::Transform& worldTm, PrefabCallbacks callbacks) override;
+
+        // AssetBus
+        void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetCanceled(AZ::Data::AssetId assetId) override;
+        void OnAssetContainerReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetMoved(AZ::Data::Asset<AZ::Data::AssetData> asset, void* oldDataPointer) override;
+        void OnAssetPreReload(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetReloadError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
+        void OnAssetSaved(AZ::Data::Asset<AZ::Data::AssetData> asset, bool isSuccessful) override;
+        void OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) override;
+
+    private:
+        AZ::Data::Asset<AzFramework::Spawnable> m_defaultSpawnableAsset;
+
+        AZ::Data::AssetId GetSpawnableAssetId(const char* assetPath) const;
+
+        struct AssetItem
+        {
+            AZStd::string m_pathToAsset;
+            AZ::Data::Asset<AzFramework::Spawnable> m_spawnableAsset;
+        };
+        AZStd::unordered_map<AZ::Data::AssetId, AssetItem> m_assetMap;
+
+        struct SpawnRequest
+        {
+            AZ::Data::AssetId m_assetIdToSpawn;
+            AZ::Transform m_whereToSpawn = AZ::Transform::CreateIdentity();
+            PrefabCallbacks m_callbacks;
+        };
+
+        AZStd::vector<SpawnRequest> m_requests;
+
+        AZStd::vector<AZStd::shared_ptr<AzFramework::EntitySpawnTicket>> m_instanceTickets;
+        void CreateInstance(const SpawnRequest& request, const AssetItem* asset);
+    };
+}

+ 68 - 0
Gem/Code/Source/Components/PerfTest/NetworkTestComponent.cpp

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <Components/PerfTest/NetworkTestComponent.h>
+#include <Multiplayer/IMultiplayer.h>
+#include <Multiplayer/Components/NetBindComponent.h>
+
+namespace MultiplayerSample
+{
+    void NetworkTestComponent::Reflect(AZ::ReflectContext* context)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serializeContext)
+        {
+            serializeContext->Class<NetworkTestComponent, AZ::Component>()
+                ->Field("Enable Movement", &NetworkTestComponent::m_enableMotion)
+                ->Field("Oscillator Amplitude", &NetworkTestComponent::m_oscillatorAmplitude)
+                ->Field("Oscillator Speed", &NetworkTestComponent::m_oscillatorSpeedFactor)
+                ->Version(1);
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                using namespace AZ::Edit;
+                editContext->Class<NetworkTestComponent>("Network Test Helper",
+                    "Various helpful test tools and behaviors to test multiplayer logic and performance.")
+                    ->ClassElement(ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "MultiplayerSample")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(nullptr, &NetworkTestComponent::m_enableMotion, "Enabled", "enabled oscillation along Z axis")
+                    ->DataElement(nullptr, &NetworkTestComponent::m_oscillatorAmplitude, "Oscillator Amplitude", "amplitude along Z axis")
+                    ->DataElement(nullptr, &NetworkTestComponent::m_oscillatorSpeedFactor, "Oscillator Speed", "speed factor along Z axis")
+                    ;
+            }
+        }
+    }
+
+    void NetworkTestComponent::Activate()
+    {
+        if (const Multiplayer::NetBindComponent* netBindComponent = GetEntity()->FindComponent<Multiplayer::NetBindComponent>())
+        {
+            if (netBindComponent->IsNetEntityRoleAuthority())
+            {
+                AZ::TickBus::Handler::BusConnect();
+                m_startTranslation = GetEntity()->GetTransform()->GetWorldTranslation();
+            }
+        }
+    }
+
+    void NetworkTestComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void NetworkTestComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        m_accumulatedTime += deltaTime;
+
+        AZ::Vector3 copy = m_startTranslation;
+        copy.SetZ(copy.GetZ() + AZStd::sin(m_accumulatedTime * m_oscillatorSpeedFactor) * m_oscillatorAmplitude);
+        GetEntity()->GetTransform()->SetWorldTranslation(copy);
+    }
+}

+ 49 - 0
Gem/Code/Source/Components/PerfTest/NetworkTestComponent.h

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+
+namespace MultiplayerSample
+{
+    //! @class NetworkTestComponent
+    class NetworkTestComponent final
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(MultiplayerSample::NetworkTestComponent, "{7FAA74C4-5A35-4602-95D9-E83DE9EC7B01}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+        {
+            provided.push_back(AZ_CRC_CE("NetBindService"));
+        }
+
+        //! AZ::Component overrides.
+        //! @{
+        void Activate() override;
+        void Deactivate() override;
+        //! }@
+
+        //! AZ::TickBus overrides.
+        //! @{
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        //! }@
+
+    private:
+        bool m_enableMotion = false;
+        float m_oscillatorAmplitude = 1.f;
+        float m_oscillatorSpeedFactor = 1.f;
+
+        float m_accumulatedTime = 0.f;
+        AZ::Vector3 m_startTranslation = AZ::Vector3::CreateZero();
+    };
+}

+ 100 - 0
Gem/Code/Source/Components/PerfTest/NetworkTestSpawnerComponent.cpp

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <Components/PerfTest/NetworkTestSpawnerComponent.h>
+#include <Multiplayer/IMultiplayer.h>
+#include <Multiplayer/Components/NetBindComponent.h>
+
+#include "NetworkPrefabSpawnerComponent.h"
+
+#pragma optimize("", off)
+
+namespace MultiplayerSample
+{
+    void NetworkTestSpawnerComponent::Reflect(AZ::ReflectContext* context)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serializeContext)
+        {
+            serializeContext->Class<NetworkTestSpawnerComponent, AZ::Component>()
+                ->Field("Enabled", &NetworkTestSpawnerComponent::m_enabled)
+                ->Field("Max Objects", &NetworkTestSpawnerComponent::m_maximumLiveCount)
+                ->Field("Spawn Period", &NetworkTestSpawnerComponent::m_spawnPeriod)
+                ->Version(1);
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                using namespace AZ::Edit;
+                editContext->Class<NetworkTestSpawnerComponent>("Network Prefab Spawn Tester",
+                    "Various helpful test tools and behaviors to test multiplayer logic and performance.")
+                    ->ClassElement(ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "MultiplayerSample")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(nullptr, &NetworkTestSpawnerComponent::m_enabled, "Enabled", "Enables spawning of test prefabs")
+                    ->DataElement(nullptr, &NetworkTestSpawnerComponent::m_spawnPeriod, "Spawn Period", "How often to spawn new prefab instance, in seconds")
+                    ->DataElement(nullptr, &NetworkTestSpawnerComponent::m_maximumLiveCount, "Max Objects", 
+                        "Maximum objects to keep alive, will delete older objects when the count goes above this value.")
+                    ;
+            }
+        }
+    }
+
+    void NetworkTestSpawnerComponent::Activate()
+    {
+        if (const Multiplayer::NetBindComponent* netBindComponent = GetEntity()->FindComponent<Multiplayer::NetBindComponent>())
+        {
+            if (netBindComponent->IsNetEntityRoleAuthority())
+            {
+                AZ::TickBus::Handler::BusConnect();
+                
+                m_currentCount = 0;
+                m_accumulatedTime = 0.f;
+                m_sinceLastSpawn = 0.f;
+            }
+        }
+    }
+
+    void NetworkTestSpawnerComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void NetworkTestSpawnerComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        m_accumulatedTime += deltaTime;
+
+        if (m_accumulatedTime > m_spawnPeriod)
+        {
+            m_accumulatedTime = 0.f;
+
+            if (NetworkPrefabSpawnerComponent* spawner = GetEntity()->FindComponent<NetworkPrefabSpawnerComponent>())
+            {
+                AZ::Transform t = GetEntity()->GetTransform()->GetWorldTM();
+
+                PrefabCallbacks callbacks;
+                callbacks.m_onActivateCallback = [this](
+                    AZStd::shared_ptr<AzFramework::EntitySpawnTicket>&& ticket,
+                    [[maybe_unused]] AzFramework::SpawnableConstEntityContainerView view)
+                {
+                    m_spawnedObjects.push_back(move(ticket));
+                };
+
+                spawner->SpawnDefaultPrefab(t, callbacks);
+            }
+
+            m_currentCount++;
+
+            if (m_currentCount > m_maximumLiveCount)
+            {
+                m_spawnedObjects.pop_front();
+                m_currentCount--;
+            }
+        }
+    }
+}

+ 55 - 0
Gem/Code/Source/Components/PerfTest/NetworkTestSpawnerComponent.h

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
+
+namespace MultiplayerSample
+{
+    //! @class NetworkTestSpawnerComponent
+    class NetworkTestSpawnerComponent final
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(MultiplayerSample::NetworkTestSpawnerComponent, "{4F8A554C-99F3-4DDB-8313-3B2FD5F78843}");
+
+        static void Reflect(AZ::ReflectContext* context);
+        
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+        {
+            required.push_back(AZ_CRC_CE("NetBindService"));
+            required.push_back(AZ_CRC_CE("NetworkPrefabSpawnerService"));
+        }
+
+        //! AZ::Component overrides.
+        //! @{
+        void Activate() override;
+        void Deactivate() override;
+        //! }@
+
+        //! AZ::TickBus overrides.
+        //! @{
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        //! }@
+
+    private:
+        bool m_enabled = false;
+        int m_maximumLiveCount = 0;
+        float m_spawnPeriod = 1.f;
+
+        int m_currentCount = 0;
+
+        float m_accumulatedTime = 0.f;
+        float m_sinceLastSpawn = 0.f;
+
+        AZStd::deque<AZStd::shared_ptr<AzFramework::EntitySpawnTicket>> m_spawnedObjects;
+    };
+}

+ 6 - 0
Gem/Code/Source/MultiplayerSampleModule.cpp

@@ -8,6 +8,9 @@
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/Module/Module.h>
 #include <Components/ExampleFilteredEntityComponent.h>
+#include <Components/PerfTest/NetworkPrefabSpawnerComponent.h>
+#include <Components/PerfTest/NetworkTestComponent.h>
+#include <Components/PerfTest/NetworkTestSpawnerComponent.h>
 #include <Source/AutoGen/AutoComponentTypes.h>
 
 #include "MultiplayerSampleSystemComponent.h"
@@ -28,6 +31,9 @@ namespace MultiplayerSample
             m_descriptors.insert(m_descriptors.end(), {
                 MultiplayerSampleSystemComponent::CreateDescriptor(),
                 ExampleFilteredEntityComponent::CreateDescriptor(),
+                NetworkTestComponent::CreateDescriptor(),
+                NetworkPrefabSpawnerComponent::CreateDescriptor(),
+                NetworkTestSpawnerComponent::CreateDescriptor(),
             });
 
             CreateComponentDescriptors(m_descriptors);

+ 7 - 0
Gem/Code/multiplayersample_files.cmake

@@ -6,6 +6,7 @@
 #
 
 set(FILES
+    Include/NetworkPrefabSpawnerInterface.h
     Source/AutoGen/NetworkAiComponent.AutoComponent.xml
     Source/AutoGen/NetworkAnimationComponent.AutoComponent.xml
     Source/AutoGen/NetworkHealthComponent.AutoComponent.xml
@@ -31,6 +32,12 @@ set(FILES
     Source/Components/NetworkWeaponsComponent.h
     Source/Components/NetworkSimplePlayerCameraComponent.cpp
     Source/Components/NetworkSimplePlayerCameraComponent.h
+    Source/Components/PerfTest/NetworkPrefabSpawnerComponent.cpp
+    Source/Components/PerfTest/NetworkPrefabSpawnerComponent.h
+    Source/Components/PerfTest/NetworkTestComponent.cpp
+    Source/Components/PerfTest/NetworkTestComponent.h
+    Source/Components/PerfTest/NetworkTestSpawnerComponent.cpp
+    Source/Components/PerfTest/NetworkTestSpawnerComponent.h
     Source/Components/NetworkStressTestComponent.cpp
     Source/Components/NetworkStressTestComponent.h
     Source/Components/NetworkPlayerMovementComponent.cpp

+ 645 - 0
Levels/SpawningPerfTest/SpawningPerfTest.prefab

@@ -0,0 +1,645 @@
+{
+    "ContainerEntity": {
+        "Id": "Entity_[1146574390643]",
+        "Name": "Level",
+        "Components": {
+            "Component_[10641544592923449938]": {
+                "$type": "EditorInspectorComponent",
+                "Id": 10641544592923449938
+            },
+            "Component_[12039882709170782873]": {
+                "$type": "EditorOnlyEntityComponent",
+                "Id": 12039882709170782873
+            },
+            "Component_[12265484671603697631]": {
+                "$type": "EditorPendingCompositionComponent",
+                "Id": 12265484671603697631
+            },
+            "Component_[14126657869720434043]": {
+                "$type": "EditorEntitySortComponent",
+                "Id": 14126657869720434043,
+                "Child Entity Order": [
+                    "Entity_[1176639161715]",
+                    "Entity_[947961075516]",
+                    "Instance_[420009386512]/ContainerEntity"
+                ]
+            },
+            "Component_[15230859088967841193]": {
+                "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                "Id": 15230859088967841193,
+                "Parent Entity": ""
+            },
+            "Component_[16239496886950819870]": {
+                "$type": "EditorDisabledCompositionComponent",
+                "Id": 16239496886950819870
+            },
+            "Component_[5688118765544765547]": {
+                "$type": "EditorEntityIconComponent",
+                "Id": 5688118765544765547
+            },
+            "Component_[6545738857812235305]": {
+                "$type": "SelectionComponent",
+                "Id": 6545738857812235305
+            },
+            "Component_[7247035804068349658]": {
+                "$type": "EditorPrefabComponent",
+                "Id": 7247035804068349658
+            },
+            "Component_[9307224322037797205]": {
+                "$type": "EditorLockComponent",
+                "Id": 9307224322037797205
+            },
+            "Component_[9562516168917670048]": {
+                "$type": "EditorVisibilityComponent",
+                "Id": 9562516168917670048
+            }
+        }
+    },
+    "Entities": {
+        "Entity_[1155164325235]": {
+            "Id": "Entity_[1155164325235]",
+            "Name": "Sun",
+            "Components": {
+                "Component_[10440557478882592717]": {
+                    "$type": "SelectionComponent",
+                    "Id": 10440557478882592717
+                },
+                "Component_[13620450453324765907]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 13620450453324765907
+                },
+                "Component_[2134313378593666258]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 2134313378593666258
+                },
+                "Component_[234010807770404186]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 234010807770404186
+                },
+                "Component_[2970359110423865725]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 2970359110423865725
+                },
+                "Component_[3722854130373041803]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 3722854130373041803
+                },
+                "Component_[5992533738676323195]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 5992533738676323195
+                },
+                "Component_[7378860763541895402]": {
+                    "$type": "AZ::Render::EditorDirectionalLightComponent",
+                    "Id": 7378860763541895402,
+                    "Controller": {
+                        "Configuration": {
+                            "Intensity": 1.0,
+                            "CameraEntityId": "",
+                            "ShadowFilterMethod": 1
+                        }
+                    }
+                },
+                "Component_[7892834440890947578]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 7892834440890947578,
+                    "Parent Entity": "Entity_[1176639161715]",
+                    "Transform Data": {
+                        "Translate": [
+                            0.0,
+                            0.0,
+                            13.487043380737305
+                        ],
+                        "Rotate": [
+                            -76.13099670410156,
+                            -0.847000002861023,
+                            -15.8100004196167
+                        ]
+                    }
+                },
+                "Component_[8599729549570828259]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 8599729549570828259
+                },
+                "Component_[952797371922080273]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 952797371922080273
+                }
+            }
+        },
+        "Entity_[1159459292531]": {
+            "Id": "Entity_[1159459292531]",
+            "Name": "Ground",
+            "Components": {
+                "Component_[11701138785793981042]": {
+                    "$type": "SelectionComponent",
+                    "Id": 11701138785793981042
+                },
+                "Component_[12260880513256986252]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 12260880513256986252
+                },
+                "Component_[13711420870643673468]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 13711420870643673468
+                },
+                "Component_[138002849734991713]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 138002849734991713
+                },
+                "Component_[16578565737331764849]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 16578565737331764849,
+                    "Parent Entity": "Entity_[1176639161715]"
+                },
+                "Component_[16919232076966545697]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 16919232076966545697
+                },
+                "Component_[5182430712893438093]": {
+                    "$type": "EditorMaterialComponent",
+                    "Id": 5182430712893438093
+                },
+                "Component_[5675108321710651991]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 5675108321710651991,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}",
+                                    "subId": 277889906
+                                },
+                                "assetHint": "objects/groudplane/groundplane_512x512m.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[5681893399601237518]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 5681893399601237518
+                },
+                "Component_[592692962543397545]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 592692962543397545
+                },
+                "Component_[7090012899106946164]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 7090012899106946164
+                },
+                "Component_[9410832619875640998]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 9410832619875640998
+                }
+            }
+        },
+        "Entity_[1163754259827]": {
+            "Id": "Entity_[1163754259827]",
+            "Name": "Camera",
+            "Components": {
+                "Component_[11895140916889160460]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 11895140916889160460
+                },
+                "Component_[16880285896855930892]": {
+                    "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent",
+                    "Id": 16880285896855930892,
+                    "Controller": {
+                        "Configuration": {
+                            "Field of View": 55.0,
+                            "EditorEntityId": 9615948867337072083
+                        }
+                    }
+                },
+                "Component_[17187464423780271193]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 17187464423780271193
+                },
+                "Component_[17495696818315413311]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 17495696818315413311
+                },
+                "Component_[18086214374043522055]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 18086214374043522055,
+                    "Parent Entity": "Entity_[1176639161715]",
+                    "Transform Data": {
+                        "Translate": [
+                            -2.3000001907348633,
+                            -3.9368600845336914,
+                            1.0
+                        ],
+                        "Rotate": [
+                            -2.050307512283325,
+                            1.9552897214889526,
+                            -43.623355865478516
+                        ]
+                    }
+                },
+                "Component_[18387556550380114975]": {
+                    "$type": "SelectionComponent",
+                    "Id": 18387556550380114975
+                },
+                "Component_[2654521436129313160]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 2654521436129313160
+                },
+                "Component_[5265045084611556958]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 5265045084611556958
+                },
+                "Component_[7169798125182238623]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 7169798125182238623
+                },
+                "Component_[7255796294953281766]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 7255796294953281766,
+                    "m_template": {
+                        "$type": "FlyCameraInputComponent"
+                    }
+                },
+                "Component_[8866210352157164042]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 8866210352157164042
+                },
+                "Component_[9129253381063760879]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 9129253381063760879
+                }
+            }
+        },
+        "Entity_[1168049227123]": {
+            "Id": "Entity_[1168049227123]",
+            "Name": "Grid",
+            "Components": {
+                "Component_[11443347433215807130]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 11443347433215807130
+                },
+                "Component_[11779275529534764488]": {
+                    "$type": "SelectionComponent",
+                    "Id": 11779275529534764488
+                },
+                "Component_[14249419413039427459]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 14249419413039427459
+                },
+                "Component_[15448581635946161318]": {
+                    "$type": "AZ::Render::EditorGridComponent",
+                    "Id": 15448581635946161318,
+                    "Controller": {
+                        "Configuration": {
+                            "primarySpacing": 4.0,
+                            "primaryColor": [
+                                0.501960813999176,
+                                0.501960813999176,
+                                0.501960813999176
+                            ],
+                            "secondarySpacing": 0.5,
+                            "secondaryColor": [
+                                0.250980406999588,
+                                0.250980406999588,
+                                0.250980406999588
+                            ]
+                        }
+                    }
+                },
+                "Component_[1843303322527297409]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 1843303322527297409
+                },
+                "Component_[2390335684631149729]": {
+                    "$type": "EditorColliderComponent",
+                    "Id": 2390335684631149729,
+                    "ColliderConfiguration": {
+                        "Position": [
+                            0.0,
+                            0.0,
+                            -0.5
+                        ],
+                        "MaterialSelection": {
+                            "MaterialIds": [
+                                {}
+                            ]
+                        }
+                    },
+                    "ShapeConfiguration": {
+                        "ShapeType": 1,
+                        "Box": {
+                            "Configuration": [
+                                100.0,
+                                100.0,
+                                1.0
+                            ]
+                        }
+                    }
+                },
+                "Component_[380249072065273654]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 380249072065273654,
+                    "Parent Entity": "Entity_[1176639161715]"
+                },
+                "Component_[7476660583684339787]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 7476660583684339787
+                },
+                "Component_[7557626501215118375]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 7557626501215118375
+                },
+                "Component_[7984048488947365511]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 7984048488947365511
+                },
+                "Component_[8118181039276487398]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 8118181039276487398
+                },
+                "Component_[9189909764215270515]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 9189909764215270515
+                }
+            }
+        },
+        "Entity_[1176639161715]": {
+            "Id": "Entity_[1176639161715]",
+            "Name": "Atom Default Environment",
+            "Components": {
+                "Component_[10757302973393310045]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 10757302973393310045,
+                    "Parent Entity": "Entity_[1146574390643]"
+                },
+                "Component_[14505817420424255464]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 14505817420424255464,
+                    "ComponentOrderEntryArray": [
+                        {
+                            "ComponentId": 10757302973393310045
+                        }
+                    ]
+                },
+                "Component_[14988041764659020032]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 14988041764659020032
+                },
+                "Component_[15808690248755038124]": {
+                    "$type": "SelectionComponent",
+                    "Id": 15808690248755038124
+                },
+                "Component_[15900837685796817138]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 15900837685796817138
+                },
+                "Component_[3298767348226484884]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 3298767348226484884
+                },
+                "Component_[4076975109609220594]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 4076975109609220594
+                },
+                "Component_[5679760548946028854]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 5679760548946028854
+                },
+                "Component_[5855590796136709437]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 5855590796136709437,
+                    "Child Entity Order": [
+                        "Entity_[1155164325235]",
+                        "Entity_[1180934129011]",
+                        "Entity_[1168049227123]",
+                        "Entity_[1163754259827]",
+                        "Entity_[1159459292531]"
+                    ]
+                },
+                "Component_[9277695270015777859]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 9277695270015777859
+                }
+            }
+        },
+        "Entity_[1180934129011]": {
+            "Id": "Entity_[1180934129011]",
+            "Name": "Global Sky",
+            "Components": {
+                "Component_[11231930600558681245]": {
+                    "$type": "AZ::Render::EditorHDRiSkyboxComponent",
+                    "Id": 11231930600558681245,
+                    "Controller": {
+                        "Configuration": {
+                            "CubemapAsset": {
+                                "assetId": {
+                                    "guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}",
+                                    "subId": 1000
+                                },
+                                "assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage"
+                            }
+                        }
+                    }
+                },
+                "Component_[11980494120202836095]": {
+                    "$type": "SelectionComponent",
+                    "Id": 11980494120202836095
+                },
+                "Component_[1428633914413949476]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 1428633914413949476
+                },
+                "Component_[14936200426671614999]": {
+                    "$type": "AZ::Render::EditorImageBasedLightComponent",
+                    "Id": 14936200426671614999,
+                    "Controller": {
+                        "Configuration": {
+                            "diffuseImageAsset": {
+                                "assetId": {
+                                    "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}",
+                                    "subId": 3000
+                                },
+                                "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage"
+                            },
+                            "specularImageAsset": {
+                                "assetId": {
+                                    "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}",
+                                    "subId": 2000
+                                },
+                                "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage"
+                            }
+                        }
+                    }
+                },
+                "Component_[14994774102579326069]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 14994774102579326069
+                },
+                "Component_[15417479889044493340]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 15417479889044493340
+                },
+                "Component_[15826613364991382688]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 15826613364991382688
+                },
+                "Component_[1665003113283562343]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 1665003113283562343
+                },
+                "Component_[3704934735944502280]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 3704934735944502280
+                },
+                "Component_[5698542331457326479]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 5698542331457326479
+                },
+                "Component_[6644513399057217122]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 6644513399057217122,
+                    "Parent Entity": "Entity_[1176639161715]"
+                },
+                "Component_[931091830724002070]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 931091830724002070
+                }
+            }
+        },
+        "Entity_[947961075516]": {
+            "Id": "Entity_[947961075516]",
+            "Name": "Test Spawner",
+            "Components": {
+                "Component_[10963079133675343639]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 10963079133675343639,
+                    "Parent Entity": "Entity_[1146574390643]",
+                    "Transform Data": {
+                        "Translate": [
+                            7.0853471755981445,
+                            -3.1976943016052246,
+                            8.996732711791992
+                        ]
+                    }
+                },
+                "Component_[12075594064273029366]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 12075594064273029366
+                },
+                "Component_[13350504610562010348]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 13350504610562010348
+                },
+                "Component_[13660454289932738945]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 13660454289932738945
+                },
+                "Component_[14107901424544397859]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 14107901424544397859,
+                    "m_template": {
+                        "$type": "Multiplayer::NetworkTransformComponent"
+                    }
+                },
+                "Component_[15277657083426159426]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 15277657083426159426,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{6DE0E9A8-A1C7-5D0F-9407-4E627C1F223C}",
+                                    "subId": 284780167
+                                },
+                                "assetHint": "models/sphere.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[16096517290206289106]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 16096517290206289106
+                },
+                "Component_[16926976350439785536]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 16926976350439785536
+                },
+                "Component_[16930831424390780459]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 16930831424390780459
+                },
+                "Component_[1766674680697458302]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 1766674680697458302,
+                    "m_template": {
+                        "$type": "MultiplayerSample::NetworkTestSpawnerComponent",
+                        "Enabled": true,
+                        "Max Objects": 10
+                    }
+                },
+                "Component_[194809454864350389]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 194809454864350389
+                },
+                "Component_[3262769293713077991]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 3262769293713077991,
+                    "m_template": {
+                        "$type": "NetBindComponent"
+                    }
+                },
+                "Component_[4995577444629330118]": {
+                    "$type": "SelectionComponent",
+                    "Id": 4995577444629330118
+                },
+                "Component_[5153851167289802312]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 5153851167289802312
+                },
+                "Component_[891954839471865918]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 891954839471865918,
+                    "m_template": {
+                        "$type": "NetworkPrefabSpawnerComponent",
+                        "Default Prefab": {
+                            "assetId": {
+                                "guid": "{997E5003-6C9F-56BA-BB11-97A228F4B888}",
+                                "subId": 3333583332
+                            },
+                            "assetHint": "prefabs/test_net_object.network.spawnable"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "Instances": {
+        "Instance_[420009386512]": {
+            "Source": "Prefabs/Test_Net_Object.prefab",
+            "Patches": [
+                {
+                    "op": "replace",
+                    "path": "/ContainerEntity/Components/Component_[10875373432506593388]/Parent Entity",
+                    "value": "../Entity_[1146574390643]"
+                },
+                {
+                    "op": "replace",
+                    "path": "/ContainerEntity/Components/Component_[10875373432506593388]/Transform Data/Translate/0",
+                    "value": 2.2419509887695313
+                },
+                {
+                    "op": "replace",
+                    "path": "/ContainerEntity/Components/Component_[10875373432506593388]/Transform Data/Translate/1",
+                    "value": 3.4806900024414063
+                },
+                {
+                    "op": "replace",
+                    "path": "/ContainerEntity/Components/Component_[10875373432506593388]/Transform Data/Translate/2",
+                    "value": 3.210494041442871
+                },
+                {
+                    "op": "remove",
+                    "path": "/LinkId"
+                }
+            ]
+        }
+    }
+}

+ 12 - 0
Levels/SpawningPerfTest/_savebackup/2022-01-20 [09.42.00]/tags.txt

@@ -0,0 +1,12 @@
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0

+ 12 - 0
Levels/SpawningPerfTest/tags.txt

@@ -0,0 +1,12 @@
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0
+0,0,0,0,0,0

+ 219 - 0
Prefabs/Test_Net_Object.prefab

@@ -0,0 +1,219 @@
+{
+    "ContainerEntity": {
+        "Id": "ContainerEntity",
+        "Name": "Test_Net_Object",
+        "Components": {
+            "Component_[10191933821067884969]": {
+                "$type": "EditorPrefabComponent",
+                "Id": 10191933821067884969
+            },
+            "Component_[10875373432506593388]": {
+                "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                "Id": 10875373432506593388,
+                "Parent Entity": ""
+            },
+            "Component_[11273717943717484824]": {
+                "$type": "EditorEntityIconComponent",
+                "Id": 11273717943717484824
+            },
+            "Component_[11851438600995494736]": {
+                "$type": "EditorLockComponent",
+                "Id": 11851438600995494736
+            },
+            "Component_[11956544343143466658]": {
+                "$type": "EditorVisibilityComponent",
+                "Id": 11956544343143466658
+            },
+            "Component_[12257238983641842239]": {
+                "$type": "EditorOnlyEntityComponent",
+                "Id": 12257238983641842239
+            },
+            "Component_[4588600448743603559]": {
+                "$type": "EditorInspectorComponent",
+                "Id": 4588600448743603559
+            },
+            "Component_[6275754365988430753]": {
+                "$type": "EditorEntitySortComponent",
+                "Id": 6275754365988430753,
+                "Child Entity Order": [
+                    "Entity_[428599321104]"
+                ]
+            },
+            "Component_[6498525539371196468]": {
+                "$type": "EditorPendingCompositionComponent",
+                "Id": 6498525539371196468
+            },
+            "Component_[7418299210056750657]": {
+                "$type": "EditorDisabledCompositionComponent",
+                "Id": 7418299210056750657
+            },
+            "Component_[8238034828511115950]": {
+                "$type": "SelectionComponent",
+                "Id": 8238034828511115950
+            }
+        }
+    },
+    "Entities": {
+        "Entity_[428599321104]": {
+            "Id": "Entity_[428599321104]",
+            "Name": "Test Net Object",
+            "Components": {
+                "Component_[11149569005424291664]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 11149569005424291664
+                },
+                "Component_[11915541920127156560]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 11915541920127156560
+                },
+                "Component_[13213110774758686394]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 13213110774758686394
+                },
+                "Component_[14022225546352237038]": {
+                    "$type": "EditorRigidBodyComponent",
+                    "Id": 14022225546352237038,
+                    "Configuration": {
+                        "entityId": "",
+                        "Mass": 999.9999389648438,
+                        "Centre of mass offset": [
+                            0.0,
+                            0.0,
+                            0.5
+                        ],
+                        "Inertia tensor": {
+                            "roll": 0.0,
+                            "pitch": 0.0,
+                            "yaw": 0.0,
+                            "scale": 0.0059999991208314896
+                        }
+                    }
+                },
+                "Component_[15076020362360634866]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 15076020362360634866,
+                    "m_template": {
+                        "$type": "Multiplayer::NetworkTransformComponent"
+                    }
+                },
+                "Component_[15646219890037406725]": {
+                    "$type": "SelectionComponent",
+                    "Id": 15646219890037406725
+                },
+                "Component_[2148707257337573835]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 2148707257337573835,
+                    "ComponentOrderEntryArray": [
+                        {
+                            "ComponentId": 4278170607060799418
+                        },
+                        {
+                            "ComponentId": 4538817944860823080,
+                            "SortIndex": 1
+                        },
+                        {
+                            "ComponentId": 6520325419833207798,
+                            "SortIndex": 2
+                        },
+                        {
+                            "ComponentId": 14022225546352237038,
+                            "SortIndex": 3
+                        },
+                        {
+                            "ComponentId": 2512731683353439569,
+                            "SortIndex": 4
+                        },
+                        {
+                            "ComponentId": 15076020362360634866,
+                            "SortIndex": 5
+                        },
+                        {
+                            "ComponentId": 10163598543192033006,
+                            "SortIndex": 6
+                        }
+                    ]
+                },
+                "Component_[2286851697353605533]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 2286851697353605533
+                },
+                "Component_[2310691145575538810]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 2310691145575538810,
+                    "m_template": {
+                        "$type": "NetBindComponent"
+                    }
+                },
+                "Component_[2512731683353439569]": {
+                    "$type": "EditorColliderComponent",
+                    "Id": 2512731683353439569,
+                    "ColliderConfiguration": {
+                        "Position": [
+                            0.0,
+                            0.0,
+                            0.5
+                        ],
+                        "MaterialSelection": {
+                            "MaterialIds": [
+                                {}
+                            ]
+                        }
+                    },
+                    "ShapeConfiguration": {
+                        "ShapeType": 1
+                    }
+                },
+                "Component_[307488191766036503]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 307488191766036503
+                },
+                "Component_[3177549462510279351]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 3177549462510279351
+                },
+                "Component_[4278170607060799418]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 4278170607060799418,
+                    "Parent Entity": "ContainerEntity"
+                },
+                "Component_[4538817944860823080]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 4538817944860823080,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{8F19FB6D-DF18-5CC0-B921-6755089004D7}",
+                                    "subId": 275006192
+                                },
+                                "assetHint": "materialeditor/viewportmodels/beveledcube.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[6520325419833207798]": {
+                    "$type": "EditorMaterialComponent",
+                    "Id": 6520325419833207798,
+                    "Controller": {
+                        "Configuration": {
+                            "materials": {
+                                "{}": {
+                                    "MaterialAsset": {
+                                        "assetId": {
+                                            "guid": "{5BC13D95-377E-53EA-9A93-156384BE8B04}"
+                                        },
+                                        "assetHint": "materials/presets/pbr/metal_gold_matte.azmaterial"
+                                    }
+                                }
+                            }
+                        }
+                    }
+                },
+                "Component_[7570897283397398271]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 7570897283397398271
+                }
+            }
+        }
+    }
+}