Merge pull request #7145 from aws-lumberyard-dev/Prefabs/PlayInEditorMissingAssets
Fixed missing assets when entering Play-In-Editor.
This commit is contained in:
@@ -92,6 +92,12 @@ namespace AZ::Data
|
||||
result.Combine(resultHint);
|
||||
}
|
||||
|
||||
if (SerializedAssetTracker* assetTracker = context.GetMetadata().Find<SerializedAssetTracker>();
|
||||
assetTracker != nullptr && result.GetProcessing() == JSR::Processing::Completed)
|
||||
{
|
||||
assetTracker->AddAsset(*instance);
|
||||
}
|
||||
|
||||
return context.Report(result,
|
||||
result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset<T>." : "Failed to store Asset<T>.");
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode JsonIssueReporter(AZStd::string& scratchBuffer,
|
||||
static AZ::JsonSerializationResult::ResultCode JsonIssueReporter(AZStd::string& scratchBuffer,
|
||||
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result, AZStd::string_view path)
|
||||
{
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
@@ -48,6 +48,66 @@ namespace AzToolsFramework
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool StoreInstanceInPrefabDom(
|
||||
const Instance& instance,
|
||||
PrefabDom& prefabDom,
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>* referencedAssets,
|
||||
StoreFlags flags)
|
||||
{
|
||||
InstanceEntityIdMapper entityIdMapper;
|
||||
entityIdMapper.SetStoringInstance(instance);
|
||||
|
||||
// Need to store the id mapper as both its type and its base type
|
||||
// Metadata is found by type id and we need access to both types at different levels (Instance, EntityId)
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
|
||||
settings.m_metadata.Add(&entityIdMapper);
|
||||
if (referencedAssets)
|
||||
{
|
||||
settings.m_metadata.Add(AZ::Data::SerializedAssetTracker{});
|
||||
}
|
||||
|
||||
if ((flags & StoreFlags::StripDefaultValues) != StoreFlags::StripDefaultValues)
|
||||
{
|
||||
settings.m_keepDefaults = true;
|
||||
}
|
||||
|
||||
if ((flags & StoreFlags::StoreLinkIds) != StoreFlags::None)
|
||||
{
|
||||
settings.m_metadata.Create<LinkIdMetadata>();
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](
|
||||
AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
|
||||
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
|
||||
{
|
||||
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
|
||||
};
|
||||
|
||||
settings.m_reporting = AZStd::move(issueReportingCallback);
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode result =
|
||||
AZ::JsonSerialization::Store(prefabDom, prefabDom.GetAllocator(), instance, settings);
|
||||
|
||||
if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false,
|
||||
"Failed to serialize prefab instance with source path %s. "
|
||||
"Unable to proceed.",
|
||||
instance.GetTemplateSourcePath().c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (referencedAssets)
|
||||
{
|
||||
*referencedAssets = AZStd::move(settings.m_metadata.Find<AZ::Data::SerializedAssetTracker>()->GetTrackedAssets());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
PrefabDomValueReference FindPrefabDomValue(PrefabDomValue& parentValue, const char* valueName)
|
||||
@@ -74,49 +134,16 @@ namespace AzToolsFramework
|
||||
|
||||
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreFlags flags)
|
||||
{
|
||||
InstanceEntityIdMapper entityIdMapper;
|
||||
entityIdMapper.SetStoringInstance(instance);
|
||||
return Internal::StoreInstanceInPrefabDom(instance, prefabDom, nullptr, flags);
|
||||
}
|
||||
|
||||
// Need to store the id mapper as both its type and its base type
|
||||
// Meta data is found by type id and we need access to both types at different levels (Instance, EntityId)
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
|
||||
settings.m_metadata.Add(&entityIdMapper);
|
||||
|
||||
if ((flags & StoreFlags::StripDefaultValues) != StoreFlags::StripDefaultValues)
|
||||
{
|
||||
settings.m_keepDefaults = true;
|
||||
}
|
||||
|
||||
if ((flags & StoreFlags::StoreLinkIds) != StoreFlags::None)
|
||||
{
|
||||
settings.m_metadata.Create<LinkIdMetadata>();
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer]
|
||||
(AZStd::string_view message, AZ::JsonSerializationResult::ResultCode result,
|
||||
AZStd::string_view path) -> AZ::JsonSerializationResult::ResultCode
|
||||
{
|
||||
return Internal::JsonIssueReporter(scratchBuffer, message, result, path);
|
||||
};
|
||||
|
||||
settings.m_reporting = AZStd::move(issueReportingCallback);
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode result =
|
||||
AZ::JsonSerialization::Store(prefabDom, prefabDom.GetAllocator(), instance, settings);
|
||||
|
||||
if (result.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZ_Error("Prefab", false,
|
||||
"Failed to serialize prefab instance with source path %s. "
|
||||
"Unable to proceed.",
|
||||
instance.GetTemplateSourcePath().c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool StoreInstanceInPrefabDom(
|
||||
const Instance& instance,
|
||||
PrefabDom& prefabDom,
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets,
|
||||
StoreFlags flags)
|
||||
{
|
||||
return Internal::StoreInstanceInPrefabDom(instance, prefabDom, &referencedAssets, flags);
|
||||
}
|
||||
|
||||
bool StoreEntityInPrefabDomFormat(const AZ::Entity& entity, Instance& owningInstance, PrefabDom& prefabDom, StoreFlags flags)
|
||||
|
||||
@@ -57,14 +57,28 @@ namespace AzToolsFramework
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(StoreFlags);
|
||||
|
||||
/**
|
||||
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates
|
||||
* @param instance The instance to store
|
||||
* @param prefabDom The prefabDom that will be used to store the Instance data
|
||||
* @param flags Controls behavior such as whether to store default values
|
||||
* @return bool on whether the operation succeeded
|
||||
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates.
|
||||
* @param instance The instance to store.
|
||||
* @param prefabDom The prefabDom that will be used to store the Instance data.
|
||||
* @param flags Controls behavior such as whether to store default values.
|
||||
* @return bool on whether the operation succeeded.
|
||||
*/
|
||||
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreFlags flags = StoreFlags::None);
|
||||
|
||||
/**
|
||||
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates.
|
||||
* @param instance The instance to store.
|
||||
* @param prefabDom The prefabDom that will be used to store the Instance data.
|
||||
* @param referencedAssets Collect a list of the assets that are referenced during storing.
|
||||
* @param flags Controls behavior such as whether to store default values.
|
||||
* @return bool on whether the operation succeeded.
|
||||
*/
|
||||
bool StoreInstanceInPrefabDom(
|
||||
const Instance& instance,
|
||||
PrefabDom& prefabDom,
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets,
|
||||
StoreFlags flags = StoreFlags::None);
|
||||
|
||||
/**
|
||||
* Stores a valid entity in Prefab Dom format.
|
||||
* @param entity The entity to store
|
||||
|
||||
+69
-46
@@ -6,12 +6,14 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
|
||||
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoader.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
@@ -161,13 +163,10 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
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());
|
||||
}
|
||||
LoadReferencedAssets(spawnableAssetData);
|
||||
}
|
||||
|
||||
auto& spawnableAssetDataAdded = m_spawnableAssets.emplace(spawnableName, spawnableAssetData).first->second;
|
||||
@@ -213,63 +212,87 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return m_spawnableAssets;
|
||||
}
|
||||
|
||||
void InMemorySpawnableAssetContainer::LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
|
||||
void InMemorySpawnableAssetContainer::LoadReferencedAssets(SpawnableAssetData& spawnable)
|
||||
{
|
||||
// Start our loads on all assets by calling GetAsset from the AssetManager
|
||||
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
|
||||
// Get the referenced assets directly from the product. This is done for two reasons:
|
||||
// 1. Avoids calls to the Asset Manager for assets that are already loaded.
|
||||
// 2. Gets the exact asset to load to avoid issues with assets that don't reload.
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>*> blockingAssets;
|
||||
|
||||
AZ::SerializeContext* sc = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(sc, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
AZ_Assert(sc, "Unable to locate Serialize Context while resolving asset references in the in-memory spawnable asset container.");
|
||||
|
||||
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : spawnable.m_assets)
|
||||
{
|
||||
if (!asset.GetId().IsValid())
|
||||
auto callback = [&blockingAssets](
|
||||
void* object, const AZ::SerializeContext::ClassData* classData,
|
||||
[[maybe_unused]]const AZ::SerializeContext::ClassElement* elementData) -> bool
|
||||
{
|
||||
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
|
||||
continue;
|
||||
}
|
||||
if (classData->m_typeId == AZ::GetAssetClassId())
|
||||
{
|
||||
auto asset = reinterpret_cast<AZ::Data::Asset<AZ::Data::AssetData>*>(object);
|
||||
|
||||
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
|
||||
if (!asset->GetId().IsValid())
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false,
|
||||
"Invalid asset found referenced in scene while entering game mode. The asset was stored in an instance of %s.",
|
||||
classData->m_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (asset->GetStatus() != AZ::Data::AssetData::AssetStatus::NotLoaded)
|
||||
{
|
||||
// Already loaded so no need to do anything.
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Data::AssetId assetId = asset.GetId();
|
||||
AZ::Data::AssetType assetType = asset.GetType();
|
||||
const AZ::Data::AssetLoadBehavior loadBehavior = asset->GetAutoLoadBehavior();
|
||||
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior);
|
||||
if (loadBehavior == AZ::Data::AssetLoadBehavior::PreLoad)
|
||||
{
|
||||
// Only assets that are preloaded need to be waited on.
|
||||
blockingAssets.push_back(asset);
|
||||
}
|
||||
if (!asset->QueueLoad())
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false, "Failed to queue asset '%s' (%s) of type '%s' for loading while entering game mode.",
|
||||
asset->GetHint().c_str(), asset->GetId().ToString<AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>>().c_str(),
|
||||
asset->GetType().ToString<AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>>().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!asset.GetId().IsValid())
|
||||
{
|
||||
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
AZ::SerializeContext::EnumerateInstanceCallContext enumerationContext(
|
||||
callback, nullptr, sc, AZ::SerializeContext::ENUM_ACCESS_FOR_READ, nullptr);
|
||||
sc->EnumerateInstance(&enumerationContext, asset.GetData(), asset.GetType(), nullptr, nullptr);
|
||||
}
|
||||
|
||||
// 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)
|
||||
for (AZ::Data::Asset<AZ::Data::AssetData>* asset : blockingAssets)
|
||||
{
|
||||
if (!asset.GetId().IsValid())
|
||||
asset->BlockUntilLoadComplete();
|
||||
|
||||
if (asset->IsError())
|
||||
{
|
||||
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());
|
||||
AZ_Error(
|
||||
"Prefab", false, "Asset '%s' (%s) of type '%s' failed to preload while entering game mode", asset->GetHint().c_str(),
|
||||
asset->GetId().ToString<AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>>().c_str(),
|
||||
asset->GetType().ToString<AZStd::fixed_string<AZ::Uuid::MaxStringBuffer>>().c_str());
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
|
||||
class InMemorySpawnableAssetContainer
|
||||
{
|
||||
public:
|
||||
@@ -58,8 +57,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
const SpawnableAssets& GetAllInMemorySpawnableAssets() const;
|
||||
|
||||
private:
|
||||
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
|
||||
|
||||
void LoadReferencedAssets(SpawnableAssetData& spawnable);
|
||||
|
||||
SpawnableAssets m_spawnableAssets;
|
||||
PrefabConversionUtils::PrefabConversionPipeline m_converter;
|
||||
AZStd::string_view m_stockProfile;
|
||||
|
||||
+21
-10
@@ -53,21 +53,14 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
const PrefabDom& PrefabDocument::GetDom() const
|
||||
{
|
||||
if (m_isDirty)
|
||||
{
|
||||
m_isDirty = !PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
|
||||
}
|
||||
RefreshPrefabDom();
|
||||
return m_dom;
|
||||
}
|
||||
|
||||
PrefabDom&& PrefabDocument::TakeDom()
|
||||
{
|
||||
if (m_isDirty)
|
||||
{
|
||||
[[maybe_unused]] bool storedSuccessfully = PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom);
|
||||
AZ_Assert(storedSuccessfully, "Failed to store Instance '%s' to PrefabDom.", m_name.c_str());
|
||||
m_isDirty = false;
|
||||
}
|
||||
RefreshPrefabDom();
|
||||
|
||||
// After the PrefabDom is moved an empty PrefabDom is left behind. This should be reflected in the Instance,
|
||||
// so reset it so it's empty as well.
|
||||
m_instance->Reset();
|
||||
@@ -126,11 +119,13 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabDocument::GetReferencedAssets()
|
||||
{
|
||||
RefreshPrefabDom();
|
||||
return m_referencedAssets;
|
||||
}
|
||||
|
||||
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabDocument::GetReferencedAssets() const
|
||||
{
|
||||
RefreshPrefabDom();
|
||||
return m_referencedAssets;
|
||||
}
|
||||
|
||||
@@ -158,4 +153,20 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabDocument::RefreshPrefabDom() const
|
||||
{
|
||||
if (m_isDirty)
|
||||
{
|
||||
m_referencedAssets.clear();
|
||||
if (PrefabDomUtils::StoreInstanceInPrefabDom(*m_instance, m_dom, m_referencedAssets))
|
||||
{
|
||||
m_isDirty = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Failed to store Instance '%s' to PrefabDom.", m_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
@@ -58,11 +58,13 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
private:
|
||||
bool ConstructInstanceFromPrefabDom(const PrefabDom& prefab);
|
||||
// Marked const so this function can be called from other const functions. It will only operate on mutable variables.
|
||||
void RefreshPrefabDom() const;
|
||||
|
||||
mutable PrefabDom m_dom;
|
||||
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> m_instance;
|
||||
AZStd::string m_name;
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_referencedAssets;
|
||||
mutable AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_referencedAssets;
|
||||
mutable bool m_isDirty{ false };
|
||||
};
|
||||
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
Reference in New Issue
Block a user