From b04bfc459ff57c10ab21a4668c63586fcb67148a Mon Sep 17 00:00:00 2001 From: sconel Date: Mon, 10 May 2021 11:46:58 -0700 Subject: [PATCH] Asset Preload fix for json serialization and prefab game mode --- .../Registry/editorpreferences.setreg | 3 +- .../AzCore/AzCore/Asset/AssetCommon.h | 18 ++++---- .../AzCore/Asset/AssetJsonSerializer.cpp | 33 ++++++++++++- .../AzCore/Asset/AssetManagerComponent.cpp | 9 +++- .../AzCore/Serialization/SerializeContext.h | 2 - .../AzCore/AzCore/azcore_files.cmake | 2 + .../PrefabEditorEntityOwnershipService.cpp | 46 ++++++++++++++++++- .../Prefab/PrefabDomUtils.cpp | 45 ++++++++++++++++++ .../AzToolsFramework/Prefab/PrefabDomUtils.h | 11 +++++ .../Spawnable/PrefabCatchmentProcessor.cpp | 5 +- .../Prefab/Spawnable/ProcesedObjectStore.cpp | 10 ++++ .../Prefab/Spawnable/ProcesedObjectStore.h | 5 ++ .../Prefab/Spawnable/SpawnableUtils.cpp | 7 +-- .../Prefab/Spawnable/SpawnableUtils.h | 2 +- 14 files changed, 178 insertions(+), 20 deletions(-) diff --git a/AutomatedTesting/Registry/editorpreferences.setreg b/AutomatedTesting/Registry/editorpreferences.setreg index b338d6acad..d7afc9e0ae 100644 --- a/AutomatedTesting/Registry/editorpreferences.setreg +++ b/AutomatedTesting/Registry/editorpreferences.setreg @@ -1,7 +1,8 @@ { "Amazon": { "Preferences": { - "EnablePrefabSystem": false + "EnablePrefabSystem": true, + "EnablePrefabSystemWipFeatures": true } } } \ No newline at end of file diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index c8a245af68..c5cdbbf331 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -216,16 +217,14 @@ namespace AZ /** * Setting for each reference (Asset) to control loading of referenced assets during serialization. */ - enum class AssetLoadBehavior : u8 - { - PreLoad = 0, ///< Serializer will "Pre load" dependencies, asset containers may load in parallel but will not signal AssetReady - QueueLoad = 1, ///< Serializer will queue an asynchronous load of the referenced asset and return the object to the user. User code should use the \ref AZ::Data::AssetBus to monitor for when it's ready. - NoLoad = 2, ///< Serializer will load reference information, but asset loading will be left to the user. User code should call Asset::QueueLoad and use the \ref AZ::Data::AssetBus to monitor for when it's ready. - ///< AssetContainers will skip NoLoad dependencies - + AZ_ENUM_WITH_UNDERLYING_TYPE(AssetLoadBehavior, u8, + (PreLoad, 0), ///< Serializer will "Pre load" dependencies, asset containers may load in parallel but will not signal AssetReady + (QueueLoad, 1), ///< Serializer will queue an asynchronous load of the referenced asset and return the object to the user. User code should use the \ref AZ::Data::AssetBus to monitor for when it's ready. + (NoLoad, 2), ///< Serializer will load reference information, but asset loading will be left to the user. User code should call Asset::QueueLoad and use the \ref AZ::Data::AssetBus to monitor for when it's ready. + ///< AssetContainers will skip NoLoad dependencies Count, - Default = QueueLoad, - }; + (Default, QueueLoad) + ); struct AssetFilterInfo { @@ -1222,6 +1221,7 @@ namespace AZ } // namespace ProductDependencyInfo } // namespace Data + AZ_TYPE_INFO_SPECIALIZE(Data::AssetLoadBehavior, "{DAF9ECED-FEF3-4D7A-A220-8CFD6A5E6DA1}"); AZ_TYPE_INFO_TEMPLATE_WITH_NAME(AZ::Data::Asset, "Asset", "{C891BF19-B60C-45E2-BFD0-027D15DDC939}", AZ_TYPE_INFO_CLASS); } // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp index 767dadedf8..6968b51025 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -70,6 +71,17 @@ namespace AZ } } + { + const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior(); + const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ? + defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default; + + result.Combine( + ContinueStoringToJsonObjectField(outputValue, "loadBehavior", + &autoLoadBehavior, &defaultAutoLoadBehavior, + azrtti_typeid(), context)); + } + { ScopedContextPath subPathHint(context, "m_assetHint"); const AZStd::string* hint = &instance->GetHint(); @@ -100,6 +112,20 @@ namespace AZ AssetId id; JSR::ResultCode result(JSR::Tasks::ReadField); + SerializedAssetTracker** assetIdTracker = + context.GetMetadata().Find(); + + { + Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior(); + + result.Combine( + ContinueLoadingFromJsonObjectField(&loadBehavior, + azrtti_typeid(), + inputValue, "loadBehavior", context)); + + instance->SetAutoLoadBehavior(loadBehavior); + } + auto it = inputValue.FindMember("assetId"); if (it != inputValue.MemberEnd()) { @@ -107,7 +133,7 @@ namespace AZ result = ContinueLoading(&id, azrtti_typeid(), it->value, context); if (!id.m_guid.IsNull()) { - *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), AssetLoadBehavior::NoLoad); + *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior()); result.Combine(context.Report(result, "Successfully created Asset with id.")); @@ -142,6 +168,11 @@ namespace AZ "The asset hint is missing for Asset, so it will be left empty.")); } + if (assetIdTracker && *assetIdTracker) + { + (*assetIdTracker)->AddAsset(*instance); + } + bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip; bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults; AZStd::string_view message = diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManagerComponent.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetManagerComponent.cpp index d861614309..1075c6a931 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetManagerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetManagerComponent.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -24,6 +24,11 @@ namespace AZ { + namespace Data + { + AZ_ENUM_DEFINE_REFLECT_UTILITIES(AssetLoadBehavior); + } + //========================================================================= // AssetDatabaseComponent // [6/25/2012] @@ -99,6 +104,8 @@ namespace AZ if (SerializeContext* serializeContext = azrtti_cast(context)) { + AZ::Data::AssetLoadBehaviorReflect(*serializeContext); + serializeContext->RegisterGenericType>(); serializeContext->Class() diff --git a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h index e5bc76cd19..6806232337 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h @@ -14,8 +14,6 @@ #include -#include - #include #include diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 5357ed66a6..c6ad90200b 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -35,6 +35,8 @@ set(FILES Asset/AssetSerializer.h Asset/AssetTypeInfoBus.h Asset/AssetInternal/WeakAsset.h + Asset/SerializedAssetTracker.cpp + Asset/SerializedAssetTracker.h Casting/lossy_cast.h Casting/numeric_cast.h Component/Component.cpp diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp index 81233069a9..a4e9d207be 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -342,6 +343,45 @@ namespace AzToolsFramework m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback); } + void LoadReferencedAssets(AZStd::vector>& referencedAssets) + { + for (AZ::Data::Asset& asset : referencedAssets) + { + if (!asset.GetId().IsValid()) + { + 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(); + const bool blockingLoad = loadBehavior == AZ::Data::AssetLoadBehavior::PreLoad; + + asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior); + + if (!asset.GetId().IsValid()) + { + continue; + } + + if (blockingLoad) + { + asset.BlockUntilLoadComplete(); + + if (asset.IsError()) + { + continue; + } + } + } + } + void PrefabEditorEntityOwnershipService::StartPlayInEditor() { // This is a workaround until the replacement for GameEntityContext is done @@ -381,13 +421,15 @@ namespace AzToolsFramework rootSpawnableIndex = m_playInEditorData.m_assets.size(); } + LoadReferencedAssets(product.GetReferencedAssets()); + 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, product.GetAsset().GetId(), info); + &AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, info.m_assetId, info); m_playInEditorData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default); } @@ -398,6 +440,8 @@ namespace AzToolsFramework m_playInEditorData.m_entities.SpawnAllEntities(); } + AZ::Data::AssetManager::Instance().DispatchEvents(); + // This is a workaround until the replacement for GameEntityContext is done AzFramework::GameEntityContextEventBus::Broadcast( &AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesStarted); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp index d53aeaa241..ae1737f907 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp @@ -11,8 +11,10 @@ */ #include +#include #include #include + #include #include #include @@ -115,6 +117,49 @@ namespace AzToolsFramework return true; } + bool LoadInstanceFromPrefabDom( + Instance& instance, const PrefabDom& prefabDom, AZStd::vector>& loadedAssets, LoadInstanceFlags flags) + { + // When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will + // be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload + // is avoided. + AZ::Data::AssetManager::Instance().SuspendAssetRelease(); + + InstanceEntityIdMapper entityIdMapper; + entityIdMapper.SetLoadingInstance(instance); + if ((flags & LoadInstanceFlags::AssignRandomEntityId) == LoadInstanceFlags::AssignRandomEntityId) + { + entityIdMapper.SetEntityIdGenerationApproach(InstanceEntityIdMapper::EntityIdGenerationApproach::Random); + } + + AZ::Data::SerializedAssetTracker assetTracker; + + AZ::JsonDeserializerSettings settings; + // The InstanceEntityIdMapper is registered twice because it's used in several places during deserialization where one is + // specific for the InstanceEntityIdMapper and once for the generic JsonEntityIdMapper. Because the Json Serializer's meta + // data has strict typing and doesn't look for inheritance both have to be explicitly added so they're found both locations. + settings.m_metadata.Add(static_cast(&entityIdMapper)); + settings.m_metadata.Add(&entityIdMapper); + settings.m_metadata.Add(&assetTracker); + + AZ::JsonSerializationResult::ResultCode result = + AZ::JsonSerialization::Load(instance, prefabDom, settings); + + AZ::Data::AssetManager::Instance().ResumeAssetRelease(); + + if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted) + { + AZ_Error("Prefab", false, + "Failed to de-serialize Prefab Instance from Prefab DOM. " + "Unable to proceed."); + + return false; + } + + loadedAssets = assetTracker.GetTrackedAssets(); + return true; + } + bool LoadInstanceFromPrefabDom( Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadInstanceFlags flags) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h index c4b988e36f..1f000c2f70 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h @@ -13,6 +13,7 @@ #pragma once #include +#include #include #include @@ -67,6 +68,16 @@ namespace AzToolsFramework bool LoadInstanceFromPrefabDom( Instance& instance, const PrefabDom& prefabDom, LoadInstanceFlags flags = LoadInstanceFlags::None); + /** + * Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances. + * @param instance The Instance to load. + * @param prefabDom the prefabDom that will be used to load the Instance data. + * @param shouldClearContainers whether to clear containers in Instance while loading. + * @return bool on whether the operation succeeded. + */ + bool LoadInstanceFromPrefabDom( + Instance& instance, const PrefabDom& prefabDom, AZStd::vector>& loadedAssets, LoadInstanceFlags flags = LoadInstanceFlags::None); + /** * Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances. * @param instance The Instance to load. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp index 5d8f1f35e3..9ae795dbf1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.cpp @@ -20,6 +20,8 @@ #include #include +#include + namespace AzToolsFramework::Prefab::PrefabConversionUtils { void PrefabCatchmentProcessor::Process(PrefabProcessorContext& context) @@ -63,7 +65,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer)); AZ_Assert(spawnable, "Failed to create a new spawnable."); - bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab); + Prefab::PrefabDomUtils::PrintPrefabDomValue("Prefab used for spawnable", prefab); + bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab, object.GetReferencedAssets()); if (result) { AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp index ee45c258ff..78d1332a71 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.cpp @@ -56,6 +56,16 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils return *m_asset; } + AZStd::vector>& ProcessedObjectStore::GetReferencedAssets() + { + return m_referencedAssets; + } + + const AZStd::vector>& ProcessedObjectStore::GetReferencedAssets() const + { + return m_referencedAssets; + } + AZStd::unique_ptr ProcessedObjectStore::ReleaseAsset() { return AZStd::move(m_asset); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h index a24d1f24c5..48e0007516 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h @@ -48,6 +48,10 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils AZ::Data::AssetData& GetAsset(); AZStd::unique_ptr ReleaseAsset(); + AZStd::vector>& GetReferencedAssets(); + const AZStd::vector>& GetReferencedAssets() const; + + const AZStd::string& GetId() const; private: @@ -55,6 +59,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils SerializerFunction m_assetSerializer; AZStd::unique_ptr m_asset; + AZStd::vector> m_referencedAssets; AZStd::string m_uniqueId; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp index 713a5d8205..085758cda9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.cpp @@ -28,16 +28,17 @@ namespace AzToolsFramework::Prefab::SpawnableUtils AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom) { AzFramework::Spawnable spawnable; - [[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom); + AZStd::vector> referencedAssets; + [[maybe_unused]] bool result = CreateSpawnable(spawnable, prefabDom, referencedAssets); AZ_Assert(result, "Failed to Load Prefab Instance from given Prefab DOM while Spawnable creation."); return spawnable; } - bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom) + bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector>& referencedAssets) { Instance instance; - if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom, + if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom, referencedAssets, Prefab::PrefabDomUtils::LoadInstanceFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is // going to be used to create clones of the entities. { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h index 74cbbcb1e4..c30e5ea77a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h @@ -18,7 +18,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils { AzFramework::Spawnable CreateSpawnable(const PrefabDom& prefabDom); - bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom); + bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector>& referencedAssets); void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable); } // namespace AzToolsFramework::Prefab::SpawnableUtils