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:
+2
-1
@@ -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;
|
||||
|
||||
+45
-166
@@ -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());
|
||||
|
||||
+4
-7
@@ -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>;
|
||||
|
||||
+73
-4
@@ -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
|
||||
|
||||
+275
@@ -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
|
||||
+69
@@ -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
|
||||
+20
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user