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
@@ -15,6 +15,7 @@
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -47,7 +48,7 @@ namespace AzToolsFramework
//! Get all Assets generated by Prefab processing when entering Play-In Editor mode (Ctrl+G)
//! /return The vector of Assets generated by Prefab processing
virtual const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() = 0;
virtual const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& GetPlayInEditorAssetData() const = 0;
virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
@@ -25,6 +25,7 @@
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework
{
@@ -379,9 +380,9 @@ namespace AzToolsFramework
return m_rootInstance ? m_rootInstance->GetTemplateId() : Prefab::InvalidTemplateId;
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData()
const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() const
{
return m_playInEditorData.m_assets;
return m_playInEditorData.m_assetsCache.GetAllInMemorySpawnableAssets();
}
void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId)
@@ -405,65 +406,6 @@ namespace AzToolsFramework
m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback);
}
void PrefabEditorEntityOwnershipService::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 seperate 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;
}
}
}
void PrefabEditorEntityOwnershipService::StartPlayInEditor()
{
// This is a workaround until the replacement for GameEntityContext is done
@@ -471,110 +413,48 @@ namespace AzToolsFramework
if (m_rootInstance && !m_playInEditorData.m_isEnabled)
{
// Construct the runtime entities and products
Prefab::TemplateReference templateReference = m_prefabSystemComponent->FindTemplate(m_rootInstance->GetTemplateId());
if (templateReference.has_value())
// Construct the runtime entities and products
bool readyToCreateRootSpawnable = m_playInEditorData.m_assetsCache.IsActivated();
if (!readyToCreateRootSpawnable &&
!m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor))
{
bool converterLoaded = m_playInEditorData.m_converter.IsLoaded();
if (!converterLoaded)
{
converterLoaded = m_playInEditorData.m_converter.LoadStackProfile("PlayInEditor");
}
if (converterLoaded)
{
// Use a random uuid as this is only a temporary source.
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
Prefab::PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(DefaultMainSpawnableName, AZStd::move(copy));
m_playInEditorData.m_converter.ProcessPrefab(context);
if (context.HasCompletedSuccessfully() && !context.GetProcessedObjects().empty())
{
static constexpr size_t NoRootSpawnable = AZStd::numeric_limits<size_t>::max();
size_t rootSpawnableIndex = NoRootSpawnable;
// Create temporary assets from the processed data.
for (auto& product : context.GetProcessedObjects())
{
if (product.GetAssetType() == AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid() &&
product.GetId() ==
AZStd::string::format("%s.%s", DefaultMainSpawnableName, AzFramework::Spawnable::FileExtension))
{
rootSpawnableIndex = m_playInEditorData.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);
m_playInEditorData.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(m_playInEditorData.m_assets.back());
}
for (auto& product : context.GetProcessedObjects())
{
LoadReferencedAssets(product.GetReferencedAssets());
}
// make sure that PRE_NOTIFY assets get their notify before we activate, so that we can preserve the order of
// (load asset) -> (notify) -> (init) -> (activate)
AZ::Data::AssetManager::Instance().DispatchEvents();
if (rootSpawnableIndex != NoRootSpawnable)
{
m_playInEditorData.m_entities.Reset(m_playInEditorData.m_assets[rootSpawnableIndex]);
m_playInEditorData.m_entities.SpawnAllEntities();
}
else
{
AZ_Error("Prefab", false,
"Processing of the level prefab failed to produce a root spawnable while entering game mode. "
"Unable to fully enter game mode.");
return;
}
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(
&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesStarted);
}
else
{
AZ_Error("Prefab", false,
"Failed to convert the prefab into assets. "
"Confirm that the 'PlayInEditor' prefab processor stack is capable of producing a useable product asset.");
return;
}
}
else
{
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key 'PlayInEditor'.");
return;
}
m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
m_playInEditorData.m_deactivatedEntities.push_back(entity.get());
}
return true;
});
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(Prefab::PrefabConversionUtils::PlayInEditor));
return;
}
}
m_playInEditorData.m_isEnabled = true;
auto createRootSpawnableResult = m_playInEditorData.m_assetsCache.CreateInMemorySpawnableAsset(m_rootInstance->GetTemplateId(), DefaultMainSpawnableName, true);
if (createRootSpawnableResult.IsSuccess())
{
// make sure that PRE_NOTIFY assets get their notify before we activate, so that we can preserve the order of
// (load asset) -> (notify) -> (init) -> (activate)
AZ::Data::AssetManager::Instance().DispatchEvents();
m_playInEditorData.m_entities.Reset(createRootSpawnableResult.GetValue());
m_playInEditorData.m_entities.SpawnAllEntities();
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(
&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesStarted);
}
else
{
AZ_Error("Prefab", false, "Failed to create the root spawnable due to the error: '%.*s'", AZ_STRING_ARG(createRootSpawnableResult.GetError()));
return;
}
m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
m_playInEditorData.m_deactivatedEntities.push_back(entity.get());
}
return true;
});
m_playInEditorData.m_isEnabled = true;
}
}
void PrefabEditorEntityOwnershipService::StopPlayInEditor()
@@ -588,7 +468,7 @@ namespace AzToolsFramework
m_playInEditorData.m_entities.DespawnAllEntities();
m_playInEditorData.m_entities.Alert(
[assets = AZStd::move(m_playInEditorData.m_assets),
[allSpawnableAssetData = m_playInEditorData.m_assetsCache.MoveAllInMemorySpawnableAssets(),
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]([[maybe_unused]] uint32_t generation) mutable
{
auto end = deactivatedEntities.rend();
@@ -598,11 +478,10 @@ namespace AzToolsFramework
(*it)->Activate();
}
for (auto& asset : assets)
for (auto& [spawnableName, spawnableAssetData] : allSpawnableAssetData)
{
if (asset)
for (auto& asset : spawnableAssetData.m_assets)
{
// Explicitly release because this needs to happen before the asset is unregistered.
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
@@ -14,7 +14,7 @@
#include <AzFramework/Spawnable/SpawnableEntitiesContainer.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -176,8 +176,7 @@ namespace AzToolsFramework
private:
struct PlayInEditorData
{
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline m_converter;
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_assets;
AzToolsFramework::Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer m_assetsCache;
AZStd::vector<AZ::Entity*> m_deactivatedEntities;
AzFramework::SpawnableEntitiesContainer m_entities;
bool m_isEnabled{ false };
@@ -195,14 +194,12 @@ namespace AzToolsFramework
Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
Prefab::TemplateId GetRootPrefabTemplateId() override;
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() override;
const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& GetPlayInEditorAssetData() const override;
//////////////////////////////////////////////////////////////////////////
void OnEntityRemoved(AZ::EntityId entityId);
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
ValidateEntitiesCallback m_validateEntitiesCallback;
@@ -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
@@ -720,10 +720,13 @@ set(FILES
Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.cpp
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.cpp
Prefab/Spawnable/InMemorySpawnableAssetContainer.h
Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp
Prefab/Spawnable/PrefabCatchmentProcessor.h
Prefab/Spawnable/PrefabCatchmentProcessor.cpp
Prefab/Spawnable/PrefabConversionPipeline.h
Prefab/Spawnable/PrefabConversionPipeline.cpp
Prefab/Spawnable/PrefabConverterStackProfileNames.h
Prefab/Spawnable/ProcesedObjectStore.h
Prefab/Spawnable/ProcesedObjectStore.cpp
Prefab/Spawnable/PrefabProcessor.h