Convert prefab to temp spawnable assets (#6179)

* Convert prefab to temp in-memory spawnable assets

Signed-off-by: chiyenteng <82238204+chiyenteng@users.noreply.github.com>
This commit is contained in:
chiyenteng
2021-12-10 10:55:01 -08:00
committed by GitHub
parent 3a4277395b
commit ca49eedc5f
55 changed files with 757 additions and 301 deletions
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/IO/Path/Path.h>
@@ -27,6 +28,7 @@ namespace AzToolsFramework
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
using CreateSpawnableResult = AZ::Outcome<AZ::Data::AssetId, AZStd::string>;
/**
* The primary purpose of this bus is to facilitate writing automated tests for prefabs.
@@ -93,6 +95,34 @@ namespace AzToolsFramework
* Returns the path to the prefab, or an empty path if the entity is owned by the level.
*/
virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
/**
* Convert a prefab on given file path with given name to in-memory spawnable asset.
* Returns the asset id of the produced spawnable if creation succeeded;
* on failure, it comes with an error message detailing the cause of the error.
*/
virtual CreateSpawnableResult CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName) = 0;
/**
* Remove in-memory spawnable asset with given name.
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) = 0;
/**
* Return whether an in-memory spawnable with given name exists or not.
*/
virtual bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const = 0;
/**
* Return an asset id of a spawnalbe with given name. Invalid asset id will be returned if the spawnable doesn't exist.
*/
virtual AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const = 0;
/**
* Remove all the in-memory spawnable assets.
*/
virtual void RemoveAllInMemorySpawnableAssets() = 0;
};
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
@@ -8,10 +8,16 @@
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework
{
namespace Prefab
@@ -31,13 +37,18 @@ namespace AzToolsFramework
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
->Event("CreateInMemorySpawnableAsset", &PrefabPublicRequests::CreateInMemorySpawnableAsset)
->Event("RemoveInMemorySpawnableAsset", &PrefabPublicRequests::RemoveInMemorySpawnableAsset)
->Event("HasInMemorySpawnableAsset", &PrefabPublicRequests::HasInMemorySpawnableAsset)
->Event("GetInMemorySpawnableAssetId", &PrefabPublicRequests::GetInMemorySpawnableAssetId)
->Event("RemoveAllInMemorySpawnableAssets", &PrefabPublicRequests::RemoveAllInMemorySpawnableAssets)
;
}
}
void PrefabPublicRequestHandler::Connect()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
m_prefabPublicInterface = AZ::Interface<PrefabPublicInterface>::Get();
AZ_Assert(m_prefabPublicInterface, "PrefabPublicRequestHandler - Could not retrieve instance of PrefabPublicInterface");
PrefabPublicRequestBus::Handler::BusConnect();
@@ -46,7 +57,7 @@ namespace AzToolsFramework
void PrefabPublicRequestHandler::Disconnect()
{
PrefabPublicRequestBus::Handler::BusDisconnect();
m_spawnableAssetContainer.Deactivate();
m_prefabPublicInterface = nullptr;
}
@@ -79,5 +90,63 @@ namespace AzToolsFramework
{
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
}
bool PrefabPublicRequestHandler::TryActivateSpawnableAssetContainer()
{
bool activated = m_spawnableAssetContainer.IsActivated();
if (!activated)
{
activated = m_spawnableAssetContainer.Activate(PrefabConversionUtils::IntegrationTests);
}
return activated;
}
CreateSpawnableResult PrefabPublicRequestHandler::CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName)
{
if (!TryActivateSpawnableAssetContainer())
{
return AZ::Failure(AZStd::string("Failed to activate Spawnable Asset Container"));
}
auto result = m_spawnableAssetContainer.CreateInMemorySpawnableAsset(prefabFilePath, spawnableName);
if (result.IsSuccess())
{
return AZ::Success(result.GetValue().GetId());
}
else
{
return AZ::Failure(result.TakeError());
}
}
PrefabOperationResult PrefabPublicRequestHandler::RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName)
{
auto result = m_spawnableAssetContainer.RemoveInMemorySpawnableAsset(spawnableName);
if (result.IsSuccess())
{
return AZ::Success();
}
else
{
return AZ::Failure(result.TakeError());
}
}
bool PrefabPublicRequestHandler::HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const
{
return m_spawnableAssetContainer.HasInMemorySpawnableAsset(spawnableName);
}
AZ::Data::AssetId PrefabPublicRequestHandler::GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const
{
return m_spawnableAssetContainer.GetInMemorySpawnableAssetId(spawnableName);
}
void PrefabPublicRequestHandler::RemoveAllInMemorySpawnableAssets()
{
m_spawnableAssetContainer.ClearAllInMemorySpawnableAssets();
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -12,6 +12,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -37,9 +38,18 @@ namespace AzToolsFramework
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
CreateSpawnableResult CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName) override;
PrefabOperationResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) override;
bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const override;
AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const override;
void RemoveAllInMemorySpawnableAssets() override;
private:
bool TryActivateSpawnableAssetContainer();
PrefabConversionUtils::InMemorySpawnableAssetContainer m_spawnableAssetContainer;
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,275 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
InMemorySpawnableAssetContainer::~InMemorySpawnableAssetContainer()
{
Deactivate();
}
bool InMemorySpawnableAssetContainer::Activate(AZStd::string_view stackProfile)
{
AZ_Assert(!IsActivated(),
"InMemorySpawnableAssetContainer - Unable to activate an instance of InMemorySpawnableAssetContainer as the instance is already active.");
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(m_prefabSystemComponentInterface, "InMemorySpawnableAssetContainer - Could not retrieve instance of PrefabSystemComponentInterface");
m_loaderInterface = AZ::Interface<Prefab::PrefabLoaderInterface>::Get();
AZ_Assert(m_loaderInterface, "InMemorySpawnableAssetContainer - Could not retrieve instance of PrefabLoaderInterface");
m_stockProfile = stackProfile;
return m_converter.LoadStackProfile(m_stockProfile);
}
void InMemorySpawnableAssetContainer::Deactivate()
{
ClearAllInMemorySpawnableAssets();
m_stockProfile = "";
m_loaderInterface = nullptr;
m_prefabSystemComponentInterface = nullptr;
}
bool InMemorySpawnableAssetContainer::IsActivated() const
{
return m_converter.IsLoaded();
}
AZStd::string_view InMemorySpawnableAssetContainer::GetStockProfile() const
{
return m_stockProfile;
}
bool InMemorySpawnableAssetContainer::HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const
{
return m_spawnableAssets.find(spawnableName) != m_spawnableAssets.end();
}
AZ::Data::AssetId InMemorySpawnableAssetContainer::GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const
{
auto found = m_spawnableAssets.find(spawnableName);
if (found != m_spawnableAssets.end())
{
return found->second.m_spawnableAssetId;
}
else
{
return AZ::Data::AssetId();
}
}
auto InMemorySpawnableAssetContainer::RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) -> RemoveSpawnableResult
{
auto found = m_spawnableAssets.find(spawnableName);
if (found == m_spawnableAssets.end())
{
return AZ::Failure(AZStd::string::format("In-memory Spawnable '%.*s' doesn't exists.", AZ_STRING_ARG(spawnableName)));
}
for (auto& asset : found->second.m_assets)
{
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
}
m_spawnableAssets.erase(found);
return AZ::Success();
}
InMemorySpawnableAssetContainer::CreateSpawnableResult InMemorySpawnableAssetContainer::CreateInMemorySpawnableAsset(
AzToolsFramework::Prefab::TemplateId templateId, AZStd::string_view spawnableName, bool loadReferencedAssets)
{
if (!IsActivated())
{
return AZ::Failure(AZStd::string::format("Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(m_stockProfile)));
}
if (HasInMemorySpawnableAsset(spawnableName))
{
return AZ::Failure(AZStd::string::format("In-memory Spawnable '%.*s' already exists.", AZ_STRING_ARG(spawnableName)));
}
TemplateReference templateReference = m_prefabSystemComponentInterface->FindTemplate(templateId);
if (!templateReference.has_value())
{
return AZ::Failure(AZStd::string::format("Could not get Template DOM for given Template's id %llu .", templateId));
}
// Use a random uuid as this is only a temporary source.
PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(spawnableName, AZStd::move(copy));
m_converter.ProcessPrefab(context);
if (!context.HasCompletedSuccessfully() || context.GetProcessedObjects().empty())
{
return AZ::Failure(AZStd::string::format(
"Failed to convert the prefab into assets. Please confirm that the '%.*s' prefab processor stack is capable of producing a usable product asset.",
AZ_STRING_ARG(PrefabConversionUtils::IntegrationTests)));
}
static constexpr size_t NoTargetSpawnable = AZStd::numeric_limits<size_t>::max();
size_t targetSpawnableIndex = NoTargetSpawnable;
AZStd::vector<AZ::Data::AssetId> assetIds;
SpawnableAssetData spawnableAssetData;
AZStd::string rootProductId(spawnableName);
rootProductId += AzFramework::Spawnable::DotFileExtension;
// Create temporary assets from the processed data.
for (auto& product : context.GetProcessedObjects())
{
if (product.GetAssetType() == azrtti_typeid<AzFramework::Spawnable>() && product.GetId() == rootProductId)
{
targetSpawnableIndex = spawnableAssetData.m_assets.size();
}
AZ::Data::AssetInfo info;
info.m_assetId = product.GetAsset().GetId();
info.m_assetType = product.GetAssetType();
info.m_relativePath = product.GetId();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, info.m_assetId, info);
spawnableAssetData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default);
// Ensure the product asset is registered with the AssetManager
// Hold on to the returned asset to keep ref count alive until we assign it the latest data
AZ::Data::Asset<AZ::Data::AssetData> asset =
AZ::Data::AssetManager::Instance().FindOrCreateAsset(info.m_assetId, info.m_assetType, AZ::Data::AssetLoadBehavior::Default);
// Update the asset registered in the AssetManager with the data of our product from the Prefab Processor
AZ::Data::AssetManager::Instance().AssignAssetData(spawnableAssetData.m_assets.back());
}
if (targetSpawnableIndex == NoTargetSpawnable)
{
return AZ::Failure(AZStd::string::format("Failed to produce the target spawnable '%.*s'.", AZ_STRING_ARG(spawnableName)));
}
if (loadReferencedAssets)
{
for (auto& product : context.GetProcessedObjects())
{
LoadReferencedAssets(product.GetReferencedAssets());
}
}
auto& spawnableAssetDataAdded = m_spawnableAssets.emplace(spawnableName, spawnableAssetData).first->second;
spawnableAssetDataAdded.m_spawnableAssetId = spawnableAssetDataAdded.m_assets[targetSpawnableIndex].GetId();
return AZ::Success(spawnableAssetDataAdded.m_assets[targetSpawnableIndex]);
}
InMemorySpawnableAssetContainer::CreateSpawnableResult InMemorySpawnableAssetContainer::CreateInMemorySpawnableAsset(
AZStd::string_view prefabFilePath, AZStd::string_view spawnableName, bool loadReferencedAssets)
{
AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(prefabFilePath);
auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
if (templateId == InvalidTemplateId)
{
return AZ::Failure(AZStd::string::format("Template with source path '%.*s' is not found.", AZ_STRING_ARG(prefabFilePath)));
}
return CreateInMemorySpawnableAsset(templateId, spawnableName, loadReferencedAssets);
}
void InMemorySpawnableAssetContainer::ClearAllInMemorySpawnableAssets()
{
for (auto& [spawnableName, spawnableAssetData] : m_spawnableAssets)
{
for (auto& asset : spawnableAssetData.m_assets)
{
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
}
}
m_spawnableAssets.clear();
}
InMemorySpawnableAssetContainer::SpawnableAssets&& InMemorySpawnableAssetContainer::MoveAllInMemorySpawnableAssets()
{
return AZStd::move(m_spawnableAssets);
}
const InMemorySpawnableAssetContainer::SpawnableAssets& InMemorySpawnableAssetContainer::GetAllInMemorySpawnableAssets() const
{
return m_spawnableAssets;
}
void InMemorySpawnableAssetContainer::LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
{
// Start our loads on all assets by calling GetAsset from the AssetManager
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
{
continue;
}
AZ::Data::AssetId assetId = asset.GetId();
AZ::Data::AssetType assetType = asset.GetType();
asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior);
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
}
// For all Preload assets we block until they're ready
// We do this as a separate pass so that we don't interrupt queuing up all other asset loads
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior != AZ::Data::AssetLoadBehavior::PreLoad)
{
continue;
}
asset.BlockUntilLoadComplete();
if (asset.IsError())
{
AZ_Error("Prefab", false, "Asset with id %s failed to preload while entering game mode",
asset.GetId().ToString<AZStd::string>().c_str());
continue;
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,69 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
namespace AzToolsFramework::Prefab
{
class PrefabSystemComponentInterface;
class PrefabLoaderInterface;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class InMemorySpawnableAssetContainer
{
public:
AZ_CLASS_ALLOCATOR(InMemorySpawnableAssetContainer, AZ::SystemAllocator, 0);
using Assets = AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>;
using CreateSpawnableResult = AZ::Outcome<AZ::Data::Asset<AZ::Data::AssetData>&, AZStd::string>;
using RemoveSpawnableResult = AZ::Outcome<void, AZStd::string>;
struct SpawnableAssetData
{
Assets m_assets;
AZ::Data::AssetId m_spawnableAssetId;
};
using SpawnableAssets = AZStd::unordered_map<AZStd::string, SpawnableAssetData>;
~InMemorySpawnableAssetContainer();
bool Activate(AZStd::string_view stackProfile);
void Deactivate();
bool IsActivated() const;
AZStd::string_view GetStockProfile() const;
CreateSpawnableResult CreateInMemorySpawnableAsset(
AZStd::string_view prefabFilePath, AZStd::string_view spawnableName, bool loadReferencedAssets = false);
CreateSpawnableResult CreateInMemorySpawnableAsset(
AzToolsFramework::Prefab::TemplateId templateId, AZStd::string_view spawnableName, bool loadReferencedAssets = false);
RemoveSpawnableResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName);
AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const;
bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const;
void ClearAllInMemorySpawnableAssets();
SpawnableAssets&& MoveAllInMemorySpawnableAssets();
const SpawnableAssets& GetAllInMemorySpawnableAssets() const;
private:
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
SpawnableAssets m_spawnableAssets;
PrefabConversionUtils::PrefabConversionPipeline m_converter;
AZStd::string_view m_stockProfile;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
PrefabLoaderInterface* m_loaderInterface = nullptr;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,20 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
inline static constexpr AZStd::string_view PlayInEditor = "PlayInEditor";
inline static constexpr AZStd::string_view IntegrationTests = "IntegrationTests";
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils