Merge pull request #682 from aws-lumberyard-dev/MultiplayerPipeline
Added support for parent-child networked entities
This commit is contained in:
@@ -84,6 +84,7 @@ namespace AzFramework
|
||||
};
|
||||
|
||||
using EntitySpawnCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using EntityPreInsertionCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableEntityContainerView)>;
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket&)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket&, SpawnableConstEntityContainerView)>;
|
||||
@@ -110,7 +111,8 @@ namespace AzFramework
|
||||
//! @param completionCallback Optional callback that's called when spawning entities has completed. This can be called from
|
||||
//! a different thread than the one that made the function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) = 0;
|
||||
virtual void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) = 0;
|
||||
//! Spawn instances of some entities in the spawnable.
|
||||
//! @param ticket Stores the results of the call. Use this ticket to spawn additional entities or to despawn them.
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
@@ -118,7 +120,7 @@ namespace AzFramework
|
||||
//! a different thread than the one that made this function call. The returned list of entities contains all the newly
|
||||
//! created entities.
|
||||
virtual void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntitySpawnCallback completionCallback = {}) = 0;
|
||||
EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) = 0;
|
||||
//! Removes all entities in the provided list from the environment.
|
||||
//! @param ticket The ticket previously used to spawn entities with.
|
||||
//! @param completionCallback Optional callback that's called when despawning entities has completed. This can be called from
|
||||
|
||||
@@ -14,17 +14,20 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/parallel/scoped_lock.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <AzFramework/Entity/GameEntityContextBus.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesManager.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback)
|
||||
void SpawnableEntitiesManager::SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback,
|
||||
EntitySpawnCallback completionCallback)
|
||||
{
|
||||
SpawnAllEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
@@ -32,13 +35,15 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntitySpawnCallback completionCallback)
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
EntityPreInsertionCallback preInsertionCallback, EntitySpawnCallback completionCallback)
|
||||
{
|
||||
SpawnEntitiesCommand queueEntry;
|
||||
queueEntry.m_ticket = &ticket;
|
||||
queueEntry.m_entityIndices = AZStd::move(entityIndices);
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_preInsertionCallback = AZStd::move(preInsertionCallback);
|
||||
{
|
||||
AZStd::scoped_lock queueLock(m_pendingRequestQueueMutex);
|
||||
queueEntry.m_ticketId = GetTicketPayload<Ticket>(ticket).m_nextTicketId++;
|
||||
@@ -205,6 +210,7 @@ namespace AzFramework
|
||||
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
clone->SetId(AZ::Entity::MakeId());
|
||||
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, clone);
|
||||
return clone;
|
||||
}
|
||||
@@ -214,23 +220,79 @@ namespace AzFramework
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
|
||||
{
|
||||
size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size();
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesSize = entities.size();
|
||||
ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize);
|
||||
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize);
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
for(size_t i=0; i<entitiesSize; ++i)
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = entitiesToSpawn.size();
|
||||
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// TEMP: To be replaced by IdUtils::Remapper
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
EntityIdMap templateToCloneIdMap;
|
||||
// \TEMP
|
||||
|
||||
// Clone the entities from Spawnable
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[i], serializeContext));
|
||||
ticket.m_spawnedEntityIndices.push_back(i);
|
||||
const AZ::Entity& entityTemplate = *entitiesToSpawn[i];
|
||||
|
||||
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
clone->SetId(AZ::Entity::MakeId());
|
||||
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
|
||||
// TEMP: To be replaced by IdUtils::Remapper
|
||||
templateToCloneIdMap[entityTemplate.GetId()] = clone->GetId();
|
||||
|
||||
// Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here.
|
||||
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
|
||||
AZ::EntityId parentId = transformComponent->GetParentId();
|
||||
if (parentId.IsValid())
|
||||
{
|
||||
auto it = templateToCloneIdMap.find(parentId);
|
||||
if (it != templateToCloneIdMap.end())
|
||||
{
|
||||
transformComponent->SetParentRelative(it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning(
|
||||
"SpawnableEntitiesManager", false, "Entity %s doesn't have the parent entity %s present in the spawnable",
|
||||
clone->GetName().c_str(), parentId.ToString().data());
|
||||
}
|
||||
}
|
||||
// \TEMP
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(*request.m_ticket, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(),
|
||||
[](AZ::Entity* entity)
|
||||
{
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity);
|
||||
});
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
@@ -249,24 +311,56 @@ namespace AzFramework
|
||||
Ticket& ticket = GetTicketPayload<Ticket>(*request.m_ticket);
|
||||
if (ticket.m_spawnable.IsReady() && request.m_ticketId == ticket.m_currentTicketId)
|
||||
{
|
||||
size_t spawnedEntitiesCount = ticket.m_spawnedEntities.size();
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
const Spawnable::EntityList& entities = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesSize = entities.size();
|
||||
ticket.m_spawnedEntities.reserve(ticket.m_spawnedEntities.size() + entitiesSize);
|
||||
ticket.m_spawnedEntityIndices.reserve(ticket.m_spawnedEntityIndices.size() + entitiesSize);
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = request.m_entityIndices.size();
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
for (size_t index : request.m_entityIndices)
|
||||
{
|
||||
ticket.m_spawnedEntities.push_back(SpawnSingleEntity(*entities[index], serializeContext));
|
||||
ticket.m_spawnedEntityIndices.push_back(index);
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
const AZ::Entity& entityTemplate = *entitiesToSpawn[index];
|
||||
|
||||
AZ::Entity* clone = serializeContext.CloneObject(&entityTemplate);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
clone->SetId(AZ::Entity::MakeId());
|
||||
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(
|
||||
*request.m_ticket,
|
||||
SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
AZStd::for_each(ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end(),
|
||||
[](AZ::Entity* entity)
|
||||
{
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, entity);
|
||||
});
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(*request.m_ticket, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesCount, ticket.m_spawnedEntities.end()));
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
m_onSpawnedEvent.Signal(ticket.m_spawnable);
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace AzFramework
|
||||
// The following functions are thread safe
|
||||
//
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, EntitySpawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices,
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, EntityPreInsertionCallback preInsertionCallback = {}, EntitySpawnCallback completionCallback = {}) override;
|
||||
void SpawnEntities(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, EntityPreInsertionCallback preInsertionCallback = {},
|
||||
EntitySpawnCallback completionCallback = {}) override;
|
||||
void DespawnAllEntities(EntitySpawnTicket& ticket, EntityDespawnCallback completionCallback = {}) override;
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace AzFramework
|
||||
struct SpawnAllEntitiesCommand
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
};
|
||||
@@ -97,6 +98,7 @@ namespace AzFramework
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
EntitySpawnTicket* m_ticket;
|
||||
uint32_t m_ticketId;
|
||||
};
|
||||
|
||||
@@ -59,6 +59,26 @@ ly_add_target(
|
||||
)
|
||||
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_add_target(
|
||||
NAME Multiplayer.Tools.Static STATIC
|
||||
NAMESPACE Gem
|
||||
FILES_CMAKE
|
||||
multiplayer_tools_files.cmake
|
||||
COMPILE_DEFINITIONS
|
||||
PUBLIC
|
||||
MULTIPLAYER_TOOLS
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
Source
|
||||
${pal_source_dir}
|
||||
PUBLIC
|
||||
Include
|
||||
BUILD_DEPENDENCIES
|
||||
PUBLIC
|
||||
AZ::AzToolsFramework
|
||||
Gem::Multiplayer.Static
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME Multiplayer.Tools MODULE
|
||||
@@ -74,8 +94,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
Include
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzToolsFramework
|
||||
Gem::Multiplayer.Static
|
||||
Gem::Multiplayer.Tools.Static
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
@@ -145,6 +164,30 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_googletest(
|
||||
NAME Gem::Multiplayer.Tests
|
||||
)
|
||||
|
||||
if (PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
ly_add_target(
|
||||
NAME Multiplayer.Tools.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE Gem
|
||||
FILES_CMAKE
|
||||
multiplayer_tools_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Tests
|
||||
Source
|
||||
.
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
AZ::AzTestShared
|
||||
AZ::AzToolsFrameworkTestCommon
|
||||
Gem::Multiplayer.Tools.Static
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME Gem::Multiplayer.Tools.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
ly_add_target(
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Name/Name.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
//! @class INetworkSpawnableLibrary
|
||||
//! @brief The interface for managing network spawnables.
|
||||
class INetworkSpawnableLibrary
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(INetworkSpawnableLibrary, "{A3CF809C-6C1D-4B43-B2C4-3901B5DE1ABE}");
|
||||
|
||||
virtual ~INetworkSpawnableLibrary() = default;
|
||||
virtual void BuildSpawnablesList() = 0;
|
||||
virtual void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) = 0;
|
||||
virtual AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) = 0;
|
||||
virtual AZ::Data::AssetId GetAssetIdByName(AZ::Name name) = 0;
|
||||
};
|
||||
}
|
||||
@@ -78,6 +78,12 @@ namespace Multiplayer
|
||||
const AZ::Transform& transform
|
||||
) = 0;
|
||||
|
||||
//! Configures new networked entity
|
||||
//! @param netEntity the entity to setup
|
||||
//! @param prefabEntryId the name of the spawnable the entity originated from
|
||||
//! @param netEntityRole the net role the entity should be setup for
|
||||
virtual void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) = 0;
|
||||
|
||||
//! Returns an ConstEntityPtr for the provided entityId.
|
||||
//! @param netEntityId the netEntityId to get an ConstEntityPtr for
|
||||
//! @return the requested ConstEntityPtr
|
||||
|
||||
+3
-3
@@ -555,10 +555,10 @@ namespace Multiplayer
|
||||
{
|
||||
replicatorEntity = entityList[0];
|
||||
}
|
||||
|
||||
AZ_Assert(replicatorEntity != nullptr, "Failed to create entity from prefab %s", prefabEntityId.m_prefabName.GetCStr());
|
||||
if (replicatorEntity == nullptr)
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "There should be exactly one created entity out of prefab %s, index %d. Got: %d",
|
||||
prefabEntityId.m_prefabName.GetCStr(), prefabEntityId.m_entityOffset, entityList.size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,18 +34,15 @@ namespace Multiplayer
|
||||
: m_networkEntityAuthorityTracker(*this)
|
||||
, m_removeEntitiesEvent([this] { RemoveEntities(); }, AZ::Name("NetworkEntityManager remove entities event"))
|
||||
, m_updateEntityDomainEvent([this] { UpdateEntityDomain(); }, AZ::Name("NetworkEntityManager update entity domain event"))
|
||||
, m_onSpawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnSpawned(spawnable); })
|
||||
, m_onDespawnedHandler([this](AZ::Data::Asset<AzFramework::Spawnable> spawnable) { this->OnDespawned(spawnable); })
|
||||
{
|
||||
AZ::Interface<INetworkEntityManager>::Register(this);
|
||||
AzFramework::RootSpawnableNotificationBus::Handler::BusConnect();
|
||||
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->AddOnSpawnedHandler(m_onSpawnedHandler);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->AddOnDespawnedHandler(m_onDespawnedHandler);
|
||||
}
|
||||
|
||||
NetworkEntityManager::~NetworkEntityManager()
|
||||
{
|
||||
AzFramework::RootSpawnableNotificationBus::Handler::BusDisconnect();
|
||||
AZ::Interface<INetworkEntityManager>::Unregister(this);
|
||||
}
|
||||
|
||||
void NetworkEntityManager::Initialize(HostId hostId, AZStd::unique_ptr<IEntityDomain> entityDomain)
|
||||
@@ -331,17 +328,41 @@ namespace Multiplayer
|
||||
const AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
|
||||
size_t entitiesSize = entities.size();
|
||||
|
||||
using EntityIdMap = AZStd::unordered_map<AZ::EntityId, AZ::EntityId>;
|
||||
EntityIdMap originalToCloneIdMap;
|
||||
|
||||
for (size_t i = 0; i < entitiesSize; ++i)
|
||||
{
|
||||
AZ::Entity* clone = serializeContext->CloneObject(entities[i].get());
|
||||
AZ::Entity* originalEntity = entities[i].get();
|
||||
AZ::Entity* clone = serializeContext->CloneObject(originalEntity);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
|
||||
clone->SetId(AZ::Entity::MakeId());
|
||||
|
||||
originalToCloneIdMap[originalEntity->GetId()] = clone->GetId();
|
||||
|
||||
NetBindComponent* netBindComponent = clone->FindComponent<NetBindComponent>();
|
||||
if (netBindComponent != nullptr)
|
||||
{
|
||||
// Update TransformComponent parent Id. It is guaranteed for the entities array to be sorted from parent->child here.
|
||||
auto* transformComponent = clone->FindComponent<AzFramework::TransformComponent>();
|
||||
AZ::EntityId parentId = transformComponent->GetParentId();
|
||||
if (parentId.IsValid())
|
||||
{
|
||||
auto it = originalToCloneIdMap.find(parentId);
|
||||
if (it != originalToCloneIdMap.end())
|
||||
{
|
||||
transformComponent->SetParentRelative(it->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("NetworkEntityManager", false, "Entity %s doesn't have the parent entity %s present in network.spawnable",
|
||||
clone->GetName().c_str(), parentId.ToString().data());
|
||||
}
|
||||
}
|
||||
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetPrefabNameFromAssetId(spawnable.GetId());
|
||||
prefabEntityId.m_prefabName = m_networkPrefabLibrary.GetSpawnableNameFromAssetId(spawnable.GetId());
|
||||
prefabEntityId.m_entityOffset = aznumeric_cast<uint32_t>(i);
|
||||
|
||||
const NetEntityId netEntityId = NextId();
|
||||
@@ -469,57 +490,19 @@ namespace Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable)
|
||||
void NetworkEntityManager::SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole)
|
||||
{
|
||||
AzFramework::Spawnable* spawnableData = spawnable.GetAs<AzFramework::Spawnable>();
|
||||
const auto& entityList = spawnableData->GetEntities();
|
||||
if (entityList.size() == 0)
|
||||
auto* netBindComponent = netEntity->FindComponent<NetBindComponent>();
|
||||
|
||||
if (netBindComponent)
|
||||
{
|
||||
AZ_Error("NetworkEntityManager", false, "OnSpawned: Spawnable %s doesn't have any entities.",
|
||||
spawnable.GetHint().c_str());
|
||||
return;
|
||||
const NetEntityId netEntityId = NextId();
|
||||
netBindComponent->PreInit(netEntity, prefabEntityId, netEntityId, netEntityRole);
|
||||
}
|
||||
|
||||
const auto& rootEntity = entityList[0];
|
||||
auto* spawnableHolder = rootEntity->FindComponent<NetworkSpawnableHolderComponent>();
|
||||
if (!spawnableHolder)
|
||||
else
|
||||
{
|
||||
// Root entity doesn't have NetworkSpawnableHolderComponent. It means there's no corresponding network spawnable.
|
||||
return;
|
||||
AZ_Error("NetworkEntityManager", false, "SetupNetEntity called for an entity with no NetBindComponent. Entity: %s",
|
||||
netEntity->GetName().c_str());
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> netSpawnableAsset = spawnableHolder->GetNetworkSpawnableAsset();
|
||||
AzFramework::Spawnable* netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
|
||||
if (!netSpawnable)
|
||||
{
|
||||
// TODO: Temp sync load until JsonSerialization of loadBehavior is fixed.
|
||||
netSpawnableAsset = AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(
|
||||
netSpawnableAsset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(netSpawnableAsset);
|
||||
|
||||
netSpawnable = netSpawnableAsset.GetAs<AzFramework::Spawnable>();
|
||||
}
|
||||
|
||||
if (!netSpawnable)
|
||||
{
|
||||
AZ_Error("NetworkEntityManager", false, "OnRootSpawnableAssigned: Net spawnable doesn't have any data.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto* multiplayer = GetMultiplayer();
|
||||
|
||||
const auto agentType = multiplayer->GetAgentType();
|
||||
const bool spawnImmediately =
|
||||
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
|
||||
|
||||
if (spawnImmediately)
|
||||
{
|
||||
CreateEntitiesImmediate(*netSpawnable, NetEntityRole::Authority);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkEntityManager::OnDespawned([[maybe_unused]]AZ::Data::Asset<AzFramework::Spawnable> spawnable)
|
||||
{
|
||||
// TODO: Remove entities instantiated from the spawnable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ namespace Multiplayer
|
||||
const AZ::Transform& transform
|
||||
) override;
|
||||
|
||||
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
|
||||
|
||||
uint32_t GetEntityCount() const override;
|
||||
NetworkEntityHandle AddEntityToEntityMap(NetEntityId netEntityId, AZ::Entity* entity) override;
|
||||
void MarkForRemoval(const ConstNetworkEntityHandle& entityHandle) override;
|
||||
@@ -93,9 +95,6 @@ namespace Multiplayer
|
||||
void RemoveEntities();
|
||||
NetEntityId NextId();
|
||||
|
||||
void OnSpawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
|
||||
void OnDespawned(AZ::Data::Asset<AzFramework::Spawnable> spawnable);
|
||||
|
||||
NetworkEntityTracker m_networkEntityTracker;
|
||||
NetworkEntityAuthorityTracker m_networkEntityAuthorityTracker;
|
||||
MultiplayerComponentRegistry m_multiplayerComponentRegistry;
|
||||
@@ -123,8 +122,5 @@ namespace Multiplayer
|
||||
DeferredRpcMessages m_localDeferredRpcMessages;
|
||||
|
||||
NetworkSpawnableLibrary m_networkPrefabLibrary;
|
||||
|
||||
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onSpawnedHandler;
|
||||
AZ::Event<AZ::Data::Asset<AzFramework::Spawnable>>::Handler m_onDespawnedHandler;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,20 +14,23 @@
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
NetworkSpawnableLibrary::NetworkSpawnableLibrary()
|
||||
{
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Register(this);
|
||||
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
NetworkSpawnableLibrary::~NetworkSpawnableLibrary()
|
||||
{
|
||||
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
|
||||
AZ::Interface<INetworkSpawnableLibrary>::Unregister(this);
|
||||
}
|
||||
|
||||
void NetworkSpawnableLibrary::BuildPrefabsList()
|
||||
void NetworkSpawnableLibrary::BuildSpawnablesList()
|
||||
{
|
||||
auto enumerateCallback = [this](const AZ::Data::AssetId id, const AZ::Data::AssetInfo& info)
|
||||
{
|
||||
@@ -50,10 +53,10 @@ namespace Multiplayer
|
||||
|
||||
void NetworkSpawnableLibrary::OnCatalogLoaded([[maybe_unused]] const char* catalogFile)
|
||||
{
|
||||
BuildPrefabsList();
|
||||
BuildSpawnablesList();
|
||||
}
|
||||
|
||||
AZ::Name NetworkSpawnableLibrary::GetPrefabNameFromAssetId(AZ::Data::AssetId assetId)
|
||||
AZ::Name NetworkSpawnableLibrary::GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId)
|
||||
{
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
|
||||
@@ -12,30 +12,31 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <Multiplayer/INetworkSpawnableLibrary.h>
|
||||
#include <AzFramework/Asset/AssetCatalogBus.h>
|
||||
#include <AzCore/Name/Name.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
/// Implementation of the network prefab library interface.
|
||||
class NetworkSpawnableLibrary final
|
||||
: private AzFramework::AssetCatalogEventBus::Handler
|
||||
: public INetworkSpawnableLibrary
|
||||
, private AzFramework::AssetCatalogEventBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(NetworkSpawnableLibrary, "{65E15F33-E893-49C2-A8E2-B6A8A6EF31E0}", INetworkSpawnableLibrary);
|
||||
|
||||
NetworkSpawnableLibrary();
|
||||
~NetworkSpawnableLibrary();
|
||||
|
||||
void BuildPrefabsList();
|
||||
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id);
|
||||
/// INetworkSpawnableLibrary overrides.
|
||||
void BuildSpawnablesList() override;
|
||||
void ProcessSpawnableAsset(const AZStd::string& relativePath, AZ::Data::AssetId id) override;
|
||||
AZ::Name GetSpawnableNameFromAssetId(AZ::Data::AssetId assetId) override;
|
||||
AZ::Data::AssetId GetAssetIdByName(AZ::Name name) override;
|
||||
|
||||
/// AssetCatalogEventBus overrides.
|
||||
void OnCatalogLoaded(const char* catalogFile) override;
|
||||
|
||||
AZ::Name GetPrefabNameFromAssetId(AZ::Data::AssetId assetId);
|
||||
AZ::Data::AssetId GetAssetIdByName(AZ::Name name);
|
||||
|
||||
private:
|
||||
AZStd::unordered_map<AZ::Name, AZ::Data::AssetId> m_spawnables;
|
||||
AZStd::unordered_map<AZ::Data::AssetId, AZ::Name> m_spawnablesReverseLookup;
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
*/
|
||||
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <Multiplayer/IMultiplayer.h>
|
||||
#include <Multiplayer/INetworkSpawnableLibrary.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -21,15 +26,91 @@ namespace Multiplayer
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
|
||||
->Version(1);
|
||||
->Version(1)
|
||||
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
|
||||
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
|
||||
}
|
||||
}
|
||||
|
||||
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
|
||||
{
|
||||
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
if (!spawnable)
|
||||
{
|
||||
asset =
|
||||
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
|
||||
|
||||
spawnable = asset.GetAs<AzFramework::Spawnable>();
|
||||
}
|
||||
|
||||
return spawnable;
|
||||
}
|
||||
|
||||
|
||||
void NetBindMarkerComponent::Activate()
|
||||
{
|
||||
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
|
||||
const bool spawnImmediately =
|
||||
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
|
||||
|
||||
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
|
||||
{
|
||||
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->GetWorldTM();
|
||||
auto preInsertionCallback =
|
||||
[worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()]
|
||||
(AzFramework::EntitySpawnTicket&, AzFramework::SpawnableEntityContainerView entities)
|
||||
{
|
||||
if (entities.size() == 1)
|
||||
{
|
||||
AZ::Entity* netEntity = *entities.begin();
|
||||
|
||||
auto* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
transformComponent->SetWorldTM(worldTm);
|
||||
|
||||
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
|
||||
PrefabEntityId prefabEntityId;
|
||||
prefabEntityId.m_prefabName = spawnableName;
|
||||
prefabEntityId.m_entityOffset = netEntityIndex;
|
||||
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size());
|
||||
}
|
||||
};
|
||||
|
||||
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(m_netSpawnTicket, {m_netEntityIndex}, preInsertionCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::Deactivate()
|
||||
{
|
||||
if(m_netSpawnTicket.IsValid())
|
||||
{
|
||||
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
|
||||
}
|
||||
}
|
||||
|
||||
size_t NetBindMarkerComponent::GetNetEntityIndex() const
|
||||
{
|
||||
return m_netEntityIndex;
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex)
|
||||
{
|
||||
m_netEntityIndex = netEntityIndex;
|
||||
}
|
||||
|
||||
void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
|
||||
{
|
||||
m_networkSpawnableAsset = networkSpawnableAsset;
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
|
||||
{
|
||||
return m_networkSpawnableAsset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@@ -34,6 +37,15 @@ namespace Multiplayer
|
||||
void Deactivate() override;
|
||||
//! @}
|
||||
|
||||
size_t GetNetEntityIndex() const;
|
||||
void SetNetEntityIndex(size_t val);
|
||||
|
||||
void SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset);
|
||||
AZ::Data::Asset<AzFramework::Spawnable> GetNetworkSpawnableAsset() const;
|
||||
|
||||
private:
|
||||
AZ::Data::Asset<AzFramework::Spawnable> m_networkSpawnableAsset{AZ::Data::AssetLoadBehavior::PreLoad};
|
||||
size_t m_netEntityIndex = 0;
|
||||
AzFramework::EntitySpawnTicket m_netSpawnTicket;
|
||||
};
|
||||
} // namespace Multiplayer
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Multiplayer
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void NetworkPrefabProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab;
|
||||
@@ -113,29 +114,35 @@ namespace Multiplayer
|
||||
|
||||
AZStd::unique_ptr<Instance> networkInstance(aznew Instance());
|
||||
|
||||
for (auto entityId : networkedEntityIds)
|
||||
{
|
||||
AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release();
|
||||
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
|
||||
networkSpawnableAsset.Create(networkSpawnable->GetId());
|
||||
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
|
||||
for (size_t entityIndex = 0; entityIndex < networkedEntityIds.size(); ++entityIndex)
|
||||
{
|
||||
AZ::EntityId entityId = networkedEntityIds[entityIndex];
|
||||
|
||||
AZ::Entity* netEntity = sourceInstance->DetachEntity(entityId).release();
|
||||
// Net entity will need a new ID to avoid IDs collision
|
||||
netEntity->SetId(AZ::Entity::MakeId());
|
||||
networkInstance->AddEntity(*netEntity);
|
||||
|
||||
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(netEntity->GetName());
|
||||
// Use the old ID for the breadcrumb entity to keep parent-child relationship in the original spawnable
|
||||
AZ::Entity* breadcrumbEntity = aznew AZ::Entity(entityId, netEntity->GetName());
|
||||
breadcrumbEntity->SetRuntimeActiveByDefault(netEntity->IsRuntimeActiveByDefault());
|
||||
breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
|
||||
|
||||
NetBindMarkerComponent* netBindMarkerComponent = breadcrumbEntity->CreateComponent<NetBindMarkerComponent>();
|
||||
// Each spawnable has a root meta-data entity at position 0, so starting net indices from 1
|
||||
netBindMarkerComponent->SetNetEntityIndex(entityIndex + 1);
|
||||
netBindMarkerComponent->SetNetworkSpawnableAsset(networkSpawnableAsset);
|
||||
AzFramework::TransformComponent* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
|
||||
breadcrumbEntity->CreateComponent<AzFramework::TransformComponent>(*transformComponent);
|
||||
|
||||
// TODO: Configure NetBindMarkerComponent to refer to the net entity
|
||||
|
||||
sourceInstance->AddEntity(*breadcrumbEntity);
|
||||
}
|
||||
|
||||
// Add net spawnable asset holder
|
||||
{
|
||||
AZ::Data::AssetId assetId = networkSpawnable->GetId();
|
||||
AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset;
|
||||
networkSpawnableAsset.Create(assetId);
|
||||
networkSpawnableAsset.SetAutoLoadBehavior(AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
|
||||
EntityOptionalReference containerEntityRef = sourceInstance->GetContainerEntity();
|
||||
if (containerEntityRef.has_value())
|
||||
{
|
||||
@@ -175,6 +182,9 @@ namespace Multiplayer
|
||||
(*it)->InvalidateDependencies();
|
||||
(*it)->EvaluateDependencies();
|
||||
}
|
||||
|
||||
SpawnableUtils::SortEntitiesByTransformHierarchy(*networkSpawnable);
|
||||
|
||||
context.GetProcessedObjects().push_back(AZStd::move(object));
|
||||
}
|
||||
else
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
#include <AzQtComponents/Utilities/QtPluginPaths.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzTest/GemTestEnvironment.h>
|
||||
#include <QApplication>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Source/Pipeline/NetBindMarkerComponent.h>
|
||||
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
|
||||
#include <UnitTest/ToolsTestApplication.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
class MultiplayerToolsTestEnvironment : public AZ::Test::GemTestEnvironment
|
||||
{
|
||||
AZ::ComponentApplication* CreateApplicationInstance() override
|
||||
{
|
||||
return aznew UnitTest::ToolsTestApplication("MultiplayerToolsTest");
|
||||
}
|
||||
|
||||
void AddGemsAndComponents() override
|
||||
{
|
||||
AZStd::vector<AZ::ComponentDescriptor*> descriptors({
|
||||
NetBindComponent::CreateDescriptor(),
|
||||
NetBindMarkerComponent::CreateDescriptor(),
|
||||
NetworkSpawnableHolderComponent::CreateDescriptor()
|
||||
});
|
||||
|
||||
AddComponentDescriptors(descriptors);
|
||||
}
|
||||
};
|
||||
} // namespace UnitTest
|
||||
|
||||
// Required to support running integration tests with Qt
|
||||
AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleMock(&argc, argv);
|
||||
AzQtComponents::PrepareQtPaths();
|
||||
QApplication app(argc, argv);
|
||||
AZ::Test::printUnusedParametersWarning(argc, argv);
|
||||
AZ::Test::addTestEnvironments({new Multiplayer::MultiplayerToolsTestEnvironment});
|
||||
int result = RUN_ALL_TESTS();
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzFramework/Components/TransformComponent.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <Prefab/PrefabDomTypes.h>
|
||||
#include <Prefab/Spawnable/PrefabProcessorContext.h>
|
||||
#include <Multiplayer/Components/NetBindComponent.h>
|
||||
#include <Source/Pipeline/NetworkPrefabProcessor.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class PrefabProcessingTestFixture : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
static void ConvertEntitiesToPrefab(const AZStd::vector<AZ::Entity*>& entities, AzToolsFramework::Prefab::PrefabDom& prefabDom)
|
||||
{
|
||||
auto* prefabSystem = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(prefabSystem->CreatePrefab(entities, {}, "test/path"));
|
||||
ASSERT_TRUE(sourceInstance);
|
||||
|
||||
auto& prefabTemplateDom = prefabSystem->FindTemplateDom(sourceInstance->GetTemplateId());
|
||||
prefabDom.CopyFrom(prefabTemplateDom, prefabDom.GetAllocator());
|
||||
}
|
||||
|
||||
static AZ::Entity* CreateSourceEntity(const char* name, bool networked, const AZ::Transform& tm, AZ::Entity* parent = nullptr)
|
||||
{
|
||||
AZ::Entity* entity = aznew AZ::Entity(name);
|
||||
auto* transformComponent = entity->CreateComponent<AzFramework::TransformComponent>();
|
||||
|
||||
if (parent)
|
||||
{
|
||||
transformComponent->SetParent(parent->GetId());
|
||||
transformComponent->SetLocalTM(tm);
|
||||
}
|
||||
else
|
||||
{
|
||||
transformComponent->SetWorldTM(tm);
|
||||
}
|
||||
|
||||
if(networked)
|
||||
{
|
||||
entity->CreateComponent<Multiplayer::NetBindComponent>();
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrefabProcessingTestFixture, NetworkPrefabProcessor_ProcessPrefabTwoEntities_NetEntityGoesToNetSpawnable)
|
||||
{
|
||||
using AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext;
|
||||
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
|
||||
// Create test entities: 1 networked and 1 static
|
||||
const AZStd::string staticEntityName = "static_floor";
|
||||
entities.emplace_back(CreateSourceEntity(staticEntityName.c_str(), false, AZ::Transform::CreateIdentity()));
|
||||
|
||||
const AZStd::string netEntityName = "networked_entity";
|
||||
entities.emplace_back(CreateSourceEntity(netEntityName.c_str(), true, AZ::Transform::CreateIdentity()));
|
||||
|
||||
// Convert the entities into prefab. Note: This will transfer the ownership of AZ::Entity* into Prefab
|
||||
AzToolsFramework::Prefab::PrefabDom prefabDom;
|
||||
ConvertEntitiesToPrefab(entities, prefabDom);
|
||||
|
||||
// Add the prefab into the Prefab Processor Context
|
||||
const AZStd::string prefabName = "testPrefab";
|
||||
PrefabProcessorContext prefabProcessorContext{AZ::Uuid::CreateRandom()};
|
||||
prefabProcessorContext.AddPrefab(prefabName, AZStd::move(prefabDom));
|
||||
|
||||
// Request NetworkPrefabProcessor to process the prefab
|
||||
Multiplayer::NetworkPrefabProcessor processor;
|
||||
processor.Process(prefabProcessorContext);
|
||||
|
||||
// Validate results
|
||||
EXPECT_TRUE(prefabProcessorContext.HasCompletedSuccessfully());
|
||||
|
||||
// Should be 1 networked spawnable
|
||||
const auto& processedObjects = prefabProcessorContext.GetProcessedObjects();
|
||||
EXPECT_EQ(processedObjects.size(), 1);
|
||||
|
||||
// Verify the name and the type of the spawnable asset
|
||||
const AZ::Data::AssetData& spawnableAsset = processedObjects[0].GetAsset();
|
||||
EXPECT_EQ(prefabName + ".network.spawnable", processedObjects[0].GetId());
|
||||
EXPECT_EQ(spawnableAsset.GetType(), azrtti_typeid<AzFramework::Spawnable>());
|
||||
|
||||
// Verify we have only the networked entity in the network spawnable and not the static one
|
||||
const AzFramework::Spawnable* netSpawnable = azrtti_cast<const AzFramework::Spawnable*>(&spawnableAsset);
|
||||
const AzFramework::Spawnable::EntityList& entityList = netSpawnable->GetEntities();
|
||||
auto countEntityCallback = [](const auto& name)
|
||||
{
|
||||
return [name](const auto& entity)
|
||||
{
|
||||
return entity->GetName() == name;
|
||||
};
|
||||
};
|
||||
|
||||
EXPECT_EQ(0, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(staticEntityName)));
|
||||
EXPECT_EQ(1, AZStd::count_if(entityList.begin(), entityList.end(), countEntityCallback(netEntityName)));
|
||||
}
|
||||
|
||||
} // namespace UnitTest
|
||||
@@ -22,6 +22,7 @@ set(FILES
|
||||
Include/Multiplayer/ConnectionData/IConnectionData.h
|
||||
Include/Multiplayer/EntityDomains/IEntityDomain.h
|
||||
Include/Multiplayer/NetworkEntity/INetworkEntityManager.h
|
||||
Include/Multiplayer/INetworkSpawnableLibrary.h
|
||||
Include/Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h
|
||||
Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h
|
||||
Include/Multiplayer/NetworkEntity/NetworkEntityHandle.h
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Tests/MainTools.cpp
|
||||
Tests/PrefabProcessingTests.cpp
|
||||
)
|
||||
Reference in New Issue
Block a user