[LYN-7064] Hot Loading of Procedural Prefabs (#4923)
* Prefab hotloading - load and maintain asset references Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Working hotloading Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Clean up code, add comments Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add missing includes Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add event for template removal. Clear ProceduralPrefabSystemComponent lookup when templates are removed. Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Add unit test. Fix missing include, make sure source name is set Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix missing include Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix missing include Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Update comments to make ebus event behavior more clear Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix missing include Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix missing includes Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix missing include Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix temp directory usage which doesn't work on linux Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix temp directory usage which doesn't work on linux Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Use AZ::IO::Path for comparison. Print paths on failure Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com> * Fix unit test - register asset with catalog so it can be looked up later Signed-off-by: amzn-mike <80125227+amzn-mike@users.noreply.github.com>
This commit is contained in:
@@ -69,6 +69,15 @@ namespace UnitTest
|
||||
return numAssertsFailed;
|
||||
}
|
||||
|
||||
void ResetSuppressionSettingsToDefault()
|
||||
{
|
||||
m_suppressErrors = true;
|
||||
m_suppressWarnings = true;
|
||||
m_suppressAsserts = true;
|
||||
m_suppressOutput = true;
|
||||
m_suppressPrintf = true;
|
||||
}
|
||||
|
||||
bool m_isAssertTest;
|
||||
bool m_suppressErrors = true;
|
||||
bool m_suppressWarnings = true;
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
#include <AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h>
|
||||
#include <Prefab/ProceduralPrefabSystemComponent.h>
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
|
||||
@@ -272,6 +273,7 @@ namespace AzToolsFramework
|
||||
azrtti_typeid<ReadOnlyEntitySystemComponent>(),
|
||||
azrtti_typeid<SliceMetadataEntityContextComponent>(),
|
||||
azrtti_typeid<Prefab::PrefabSystemComponent>(),
|
||||
azrtti_typeid<Prefab::ProceduralPrefabSystemComponent>(),
|
||||
azrtti_typeid<EditorEntityFixupComponent>(),
|
||||
azrtti_typeid<Components::EditorComponentAPIComponent>(),
|
||||
azrtti_typeid<Components::EditorLevelComponentAPIComponent>(),
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
|
||||
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
|
||||
#include <AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h>
|
||||
#include <Prefab/ProceduralPrefabSystemComponent.h>
|
||||
|
||||
AZ_DEFINE_BUDGET(AzToolsFramework);
|
||||
|
||||
@@ -82,6 +83,7 @@ namespace AzToolsFramework
|
||||
SliceRequestComponent::CreateDescriptor(),
|
||||
Prefab::PrefabSystemComponent::CreateDescriptor(),
|
||||
Prefab::EditorPrefabComponent::CreateDescriptor(),
|
||||
Prefab::ProceduralPrefabSystemComponent::CreateDescriptor(),
|
||||
Components::EditorEntityActionComponent::CreateDescriptor(),
|
||||
Components::EditorEntityIconComponent::CreateDescriptor(),
|
||||
Components::EditorInspectorComponent::CreateDescriptor(),
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/ProceduralPrefabSystemComponentInterface.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
@@ -185,6 +186,20 @@ namespace AzToolsFramework
|
||||
TemplateReference newTemplateReference = m_prefabSystemComponentInterface->FindTemplate(newTemplateId);
|
||||
Template& newTemplate = newTemplateReference->get();
|
||||
|
||||
if(newTemplate.IsProcedural())
|
||||
{
|
||||
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
|
||||
|
||||
if (!proceduralPrefabSystemComponentInterface)
|
||||
{
|
||||
AZ_Error("Prefab", false, "Failed to find ProceduralPrefabSystemComponentInterface. Procedural Prefab will not be tracked for hotloading.");
|
||||
}
|
||||
else
|
||||
{
|
||||
proceduralPrefabSystemComponentInterface->RegisterProceduralPrefab(relativePath.Native(), newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the file as being in progress.
|
||||
progressedFilePathsSet.emplace(relativePath);
|
||||
|
||||
|
||||
@@ -26,6 +26,14 @@ namespace AzToolsFramework
|
||||
|
||||
virtual void OnPrefabTemplateDirtyFlagUpdated(
|
||||
[[maybe_unused]] TemplateId templateId, [[maybe_unused]] bool status) {}
|
||||
|
||||
// Sent after a single template has been removed
|
||||
// Does not get sent when all templates are being removed at once. Be sure to handle OnAllTemplatesRemoved as well.
|
||||
virtual void OnTemplateRemoved([[maybe_unused]] TemplateId templateId) {}
|
||||
|
||||
// Sent after all templates have been removed
|
||||
// Does not trigger individual OnTemplateRemoved events
|
||||
virtual void OnAllTemplatesRemoved() {}
|
||||
};
|
||||
|
||||
using PrefabPublicNotificationBus = AZ::EBus<PrefabPublicNotifications>;
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace AzToolsFramework
|
||||
newInstance->SetTemplateId(newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
|
||||
@@ -442,7 +442,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
m_templateFilePathToIdMap.emplace(AZStd::make_pair(filePath, newTemplateId));
|
||||
|
||||
|
||||
return newTemplateId;
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
m_templateFilePathToIdMap.erase(templateToChange.GetFilePath());
|
||||
if (!m_templateFilePathToIdMap.try_emplace(filePath, templateId).second)
|
||||
{
|
||||
@@ -500,7 +500,7 @@ namespace AzToolsFramework
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Remove all Links owned by the Template from TemplateToLinkIdsMap.
|
||||
Template& templateToDelete = findTemplateResult->get();
|
||||
const Template::Links& linkIdsToDelete = templateToDelete.GetLinks();
|
||||
@@ -546,7 +546,7 @@ namespace AzToolsFramework
|
||||
templateId, templateToDelete.GetFilePath().c_str());
|
||||
|
||||
m_templateInstanceMapper.UnregisterTemplate(templateId);
|
||||
|
||||
|
||||
result = m_templateIdMap.erase(templateId) != 0;
|
||||
AZ_Assert(result,
|
||||
"Prefab - PrefabSystemComponent::RemoveTemplate - "
|
||||
@@ -554,7 +554,10 @@ namespace AzToolsFramework
|
||||
"from Template Id Map.",
|
||||
templateId, templateToDelete.GetFilePath().c_str());
|
||||
|
||||
return;
|
||||
if (!m_removingAllTemplates)
|
||||
{
|
||||
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnTemplateRemoved, templateId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::RemoveAllTemplates()
|
||||
@@ -568,10 +571,15 @@ namespace AzToolsFramework
|
||||
templateIds.emplace_back(id);
|
||||
}
|
||||
|
||||
m_removingAllTemplates = true;
|
||||
|
||||
for (auto id : templateIds)
|
||||
{
|
||||
RemoveTemplate(id);
|
||||
}
|
||||
|
||||
m_removingAllTemplates = false;
|
||||
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnAllTemplatesRemoved);
|
||||
}
|
||||
|
||||
LinkId PrefabSystemComponent::AddLink(
|
||||
@@ -859,7 +867,7 @@ namespace AzToolsFramework
|
||||
|
||||
void PrefabSystemComponent::SaveAllDirtyTemplates(TemplateId rootTemplateId)
|
||||
{
|
||||
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = GetDirtyTemplatePaths(rootTemplateId);
|
||||
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = GetDirtyTemplatePaths(rootTemplateId);
|
||||
|
||||
for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths)
|
||||
{
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace AzToolsFramework
|
||||
public:
|
||||
|
||||
using TargetTemplateIdToLinkIdMap = AZStd::unordered_map<TemplateId, AZStd::pair<AZStd::unordered_set<LinkId>, bool>>;
|
||||
|
||||
|
||||
AZ_COMPONENT(PrefabSystemComponent, "{27203AE6-A398-4614-881B-4EEB5E9B34E9}");
|
||||
|
||||
PrefabSystemComponent() = default;
|
||||
@@ -220,7 +220,7 @@ namespace AzToolsFramework
|
||||
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
|
||||
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr,
|
||||
InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override;
|
||||
|
||||
|
||||
PrefabDom& FindTemplateDom(TemplateId templateId) override;
|
||||
|
||||
/**
|
||||
@@ -244,7 +244,7 @@ namespace AzToolsFramework
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
|
||||
|
||||
|
||||
/**
|
||||
* Builds a new Prefab Template out of entities and instances and returns the first instance comprised of
|
||||
* these entities and instances.
|
||||
@@ -263,14 +263,14 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* Updates all the linked Instances corresponding to the linkIds in the provided queue.
|
||||
* Queue gets populated with more linkId lists as linked instances are updated. Updating stops when the queue is empty.
|
||||
*
|
||||
*
|
||||
* @param linkIdsQueue A queue of vector of link-Ids to update.
|
||||
*/
|
||||
void UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue);
|
||||
|
||||
/**
|
||||
* Given a vector of link ids to update, splits them into smaller lists based on the target template id of the links.
|
||||
*
|
||||
*
|
||||
* @param linkIdsToUpdate The list of link ids to update.
|
||||
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
|
||||
* whether any of the instances of the target template were updated.
|
||||
@@ -279,9 +279,9 @@ namespace AzToolsFramework
|
||||
TargetTemplateIdToLinkIdMap& targetTemplateIdToLinkIdMap);
|
||||
|
||||
/**
|
||||
* Updates a single linked instance corresponding to the given link Id and adds more linkIds to the
|
||||
* Updates a single linked instance corresponding to the given link Id and adds more linkIds to the
|
||||
* template change propagation queue(linkIdsQueue) when necessary.
|
||||
*
|
||||
*
|
||||
* @param linkIdToUpdate The id of the linked instance to update
|
||||
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
|
||||
* whether any of the instances of the target template were updated.
|
||||
@@ -293,7 +293,7 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* If all linked instances of a target template are updated and if the content of any of the linked instances changed,
|
||||
* this method fetches all the linked instances sourced by it and adds their corresponding ids to the LinkIdsQueue.
|
||||
*
|
||||
*
|
||||
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
|
||||
* whether any of the instances of the target template were updated.
|
||||
* @param targetTemplateId The id of the template, whose linked instances we need to find if the template was updated.
|
||||
@@ -414,6 +414,9 @@ namespace AzToolsFramework
|
||||
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
|
||||
|
||||
PrefabSystemScriptingHandler m_prefabSystemScriptingHandler;
|
||||
|
||||
// If true, individual template-remove messages will be suppressed
|
||||
bool m_removingAllTemplates = false;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 <AssetCatalog/PlatformAddressedAssetCatalogBus.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzFramework/Asset/AssetCatalogBus.h>
|
||||
#include <AzToolsFramework/Prefab/ProceduralPrefabSystemComponent.h>
|
||||
#include <Prefab/PrefabDomUtils.h>
|
||||
#include <Prefab/PrefabLoaderInterface.h>
|
||||
#include <Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
void ProceduralPrefabSystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializationContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializationContext->Class<ProceduralPrefabSystemComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::Activate()
|
||||
{
|
||||
AZ::Interface<ProceduralPrefabSystemComponentInterface>::Register(this);
|
||||
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::Deactivate()
|
||||
{
|
||||
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
|
||||
AZ::Interface<ProceduralPrefabSystemComponentInterface>::Unregister(this);
|
||||
|
||||
AZStd::scoped_lock lock(m_lookupMutex);
|
||||
m_assetIdToTemplateLookup.clear();
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
|
||||
{
|
||||
TemplateId templateId = InvalidTemplateId;
|
||||
|
||||
{
|
||||
AZStd::scoped_lock lock(m_lookupMutex);
|
||||
auto itr = m_assetIdToTemplateLookup.find(assetId);
|
||||
|
||||
if (itr != m_assetIdToTemplateLookup.end())
|
||||
{
|
||||
templateId = itr->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (templateId != InvalidTemplateId)
|
||||
{
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
|
||||
if (!prefabSystemComponentInterface)
|
||||
{
|
||||
AZ_Error("Prefab", false, "Failed to get PrefabSystemComponentInterface");
|
||||
return;
|
||||
}
|
||||
|
||||
AZStd::string assetPath;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetPath, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetPathById, assetId);
|
||||
|
||||
auto readResult = AZ::Utils::ReadFile(assetPath, AZStd::numeric_limits<size_t>::max());
|
||||
if (!readResult.IsSuccess())
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false,
|
||||
"ProceduralPrefabSystemComponent::OnCatalogAssetChanged - Failed to read Prefab file from '%.*s'."
|
||||
"Error message: '%s'",
|
||||
AZ_STRING_ARG(assetPath), readResult.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::Outcome<PrefabDom, AZStd::string> readPrefabFileResult = AZ::JsonSerializationUtils::ReadJsonString(readResult.TakeValue());
|
||||
if (!readPrefabFileResult.IsSuccess())
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefab", false,
|
||||
"ProceduralPrefabSystemComponent::OnCatalogAssetChanged - Failed to read Prefab json from '%.*s'."
|
||||
"Error message: '%s'",
|
||||
AZ_STRING_ARG(assetPath), readPrefabFileResult.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::IO::Path relativePath = AZ::Interface<PrefabLoaderInterface>::Get()->GenerateRelativePath(assetPath.c_str());
|
||||
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
|
||||
sourcePath.Set(readPrefabFileResult.GetValue(), relativePath.Native().c_str());
|
||||
|
||||
prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, readPrefabFileResult.TakeValue());
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::OnTemplateRemoved(TemplateId removedTemplateId)
|
||||
{
|
||||
AZStd::scoped_lock lock(m_lookupMutex);
|
||||
|
||||
for (const auto& [assetId, templateId] : m_assetIdToTemplateLookup)
|
||||
{
|
||||
if (templateId == removedTemplateId)
|
||||
{
|
||||
m_assetIdToTemplateLookup.erase(assetId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::OnAllTemplatesRemoved()
|
||||
{
|
||||
AZStd::scoped_lock lock(m_lookupMutex);
|
||||
|
||||
m_assetIdToTemplateLookup.clear();
|
||||
}
|
||||
|
||||
void ProceduralPrefabSystemComponent::RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId)
|
||||
{
|
||||
AZ::Data::AssetId assetId;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, prefabFilePath.c_str(),
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(), false);
|
||||
|
||||
if (assetId.IsValid())
|
||||
{
|
||||
AZStd::scoped_lock lock(m_lookupMutex);
|
||||
m_assetIdToTemplateLookup[assetId] = templateId;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("Prefab", false, "Failed to find AssetId for prefab %s", prefabFilePath.c_str());
|
||||
}
|
||||
}
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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/Component/Component.h>
|
||||
#include <Prefab/ProceduralPrefabSystemComponentInterface.h>
|
||||
#include <AzFramework/Asset/AssetCatalogBus.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
class ProceduralPrefabSystemComponent
|
||||
: public AZ::Component
|
||||
, ProceduralPrefabSystemComponentInterface
|
||||
, AzFramework::AssetCatalogEventBus::Handler
|
||||
, PrefabPublicNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(ProceduralPrefabSystemComponent, "{81211818-088A-49E6-894B-7A11764106B1}");
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
protected:
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
void OnCatalogAssetChanged(const AZ::Data::AssetId&) override;
|
||||
|
||||
void OnTemplateRemoved(TemplateId templateId) override;
|
||||
void OnAllTemplatesRemoved() override;
|
||||
|
||||
void RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId) override;
|
||||
|
||||
AZStd::mutex m_lookupMutex;
|
||||
AZStd::unordered_map<AZ::Data::AssetId, TemplateId> m_assetIdToTemplateLookup;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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/RTTI/RTTI.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
class ProceduralPrefabSystemComponentInterface
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ProceduralPrefabSystemComponentInterface, "{C403B89C-63DD-418C-B821-FBCBE1EF42AE}");
|
||||
|
||||
virtual ~ProceduralPrefabSystemComponentInterface() = default;
|
||||
|
||||
// Registers a procedural prefab file + templateId so the system can track changes and handle updates
|
||||
virtual void RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId) = 0;
|
||||
};
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -670,6 +670,9 @@ set(FILES
|
||||
Prefab/PrefabSystemComponent.h
|
||||
Prefab/PrefabSystemComponent.cpp
|
||||
Prefab/PrefabSystemComponentInterface.h
|
||||
Prefab/ProceduralPrefabSystemComponent.h
|
||||
Prefab/ProceduralPrefabSystemComponent.cpp
|
||||
Prefab/ProceduralPrefabSystemComponentInterface.h
|
||||
Prefab/PrefabSystemScriptingBus.h
|
||||
Prefab/PrefabSystemScriptingHandler.h
|
||||
Prefab/PrefabSystemScriptingHandler.cpp
|
||||
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* 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 <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/Path/PathReflect.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
#include <AzFramework/IO/LocalFileIO.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
#include <Prefab/ProceduralPrefabSystemComponent.h>
|
||||
#include <Utils/Utils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
struct ProceduralPrefabSystemComponentTests
|
||||
: AllocatorsTestFixture
|
||||
, AZ::ComponentApplicationBus::Handler
|
||||
{
|
||||
void SetUp() override
|
||||
{
|
||||
TestRunner::Instance().m_suppressOutput = false;
|
||||
TestRunner::Instance().m_suppressPrintf = false;
|
||||
TestRunner::Instance().m_suppressWarnings = false;
|
||||
TestRunner::Instance().m_suppressErrors = false;
|
||||
TestRunner::Instance().m_suppressAsserts = false;
|
||||
|
||||
AllocatorsTestFixture::SetUp();
|
||||
|
||||
AZ::ComponentApplicationBus::Handler::BusConnect();
|
||||
|
||||
ASSERT_TRUE(m_temporaryDirectory.IsValid());
|
||||
|
||||
m_localFileIo = AZStd::make_unique<AZ::IO::LocalFileIO>();
|
||||
|
||||
m_prevIoBase = AZ::IO::FileIOBase::GetInstance();
|
||||
AZ::IO::FileIOBase::SetInstance(nullptr); // Need to clear the previous instance first
|
||||
AZ::IO::FileIOBase::SetInstance(m_localFileIo.get());
|
||||
|
||||
AZ::JsonSystemComponent::Reflect(&m_jsonContext);
|
||||
|
||||
m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
|
||||
AZ::SettingsRegistry::Register(m_settingsRegistry.get());
|
||||
|
||||
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, m_temporaryDirectory.GetDirectory());
|
||||
|
||||
m_prefabSystem = PrefabSystemComponent::CreateDescriptor();
|
||||
m_procSystem = ProceduralPrefabSystemComponent::CreateDescriptor();
|
||||
|
||||
m_prefabSystem->Reflect(&m_context);
|
||||
m_prefabSystem->Reflect(&m_jsonContext);
|
||||
m_procSystem->Reflect(&m_context);
|
||||
m_procSystem->Reflect(&m_jsonContext);
|
||||
|
||||
AZ::Entity::Reflect(&m_context);
|
||||
AZ::Entity::Reflect(&m_jsonContext);
|
||||
AZ::IO::PathReflect(&m_context);
|
||||
|
||||
m_systemEntity = AZStd::make_unique<AZ::Entity>();
|
||||
m_systemEntity->CreateComponent<PrefabSystemComponent>();
|
||||
m_systemEntity->CreateComponent<ProceduralPrefabSystemComponent>();
|
||||
|
||||
m_systemEntity->Init();
|
||||
m_systemEntity->Activate();
|
||||
|
||||
AZ::Data::AssetManager::Create({});
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
AZ::Data::AssetManager::Destroy();
|
||||
|
||||
m_systemEntity->Deactivate();
|
||||
m_systemEntity = nullptr;
|
||||
|
||||
m_jsonContext.EnableRemoveReflection();
|
||||
AZ::JsonSystemComponent::Reflect(&m_jsonContext);
|
||||
m_prefabSystem->Reflect(&m_jsonContext);
|
||||
m_procSystem->Reflect(&m_jsonContext);
|
||||
AZ::Entity::Reflect(&m_jsonContext);
|
||||
m_jsonContext.DisableRemoveReflection();
|
||||
|
||||
AZ::IO::FileIOBase::SetInstance(nullptr); // Clear the previous instance first
|
||||
AZ::IO::FileIOBase::SetInstance(m_prevIoBase);
|
||||
|
||||
m_prevIoBase = nullptr;
|
||||
|
||||
AZ::SettingsRegistry::Unregister(m_settingsRegistry.get());
|
||||
|
||||
AZ::ComponentApplicationBus::Handler::BusDisconnect();
|
||||
AllocatorsTestFixture::TearDown();
|
||||
|
||||
TestRunner::Instance().ResetSuppressionSettingsToDefault();
|
||||
}
|
||||
|
||||
// ComponentApplicationBus
|
||||
AZ::ComponentApplication* GetApplication() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RegisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
|
||||
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
|
||||
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
|
||||
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
|
||||
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
|
||||
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
|
||||
void SignalEntityActivated(AZ::Entity*) override { }
|
||||
void SignalEntityDeactivated(AZ::Entity*) override { }
|
||||
|
||||
bool AddEntity(AZ::Entity*) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveEntity(AZ::Entity*) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeleteEntity(const AZ::EntityId&) override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
AZ::Entity* FindEntity(const AZ::EntityId&) override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::SerializeContext* GetSerializeContext() override
|
||||
{
|
||||
return &m_context;
|
||||
}
|
||||
|
||||
AZ::BehaviorContext* GetBehaviorContext() override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::JsonRegistrationContext* GetJsonRegistrationContext() override
|
||||
{
|
||||
return &m_jsonContext;
|
||||
}
|
||||
|
||||
const char* GetEngineRoot() const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* GetExecutableFolder() const override
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EnumerateEntities(const EntityCallback& /*callback*/) override { }
|
||||
void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override { }
|
||||
////
|
||||
|
||||
AZ::ComponentDescriptor* m_prefabSystem{};
|
||||
AZ::ComponentDescriptor* m_procSystem{};
|
||||
AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_settingsRegistry;
|
||||
AZ::SerializeContext m_context;
|
||||
AZ::JsonRegistrationContext m_jsonContext;
|
||||
AZStd::unique_ptr<AZ::IO::LocalFileIO> m_localFileIo;
|
||||
ScopedTemporaryDirectory m_temporaryDirectory;
|
||||
AZStd::unique_ptr<AZ::Entity> m_systemEntity;
|
||||
|
||||
AZ::IO::FileIOBase* m_prevIoBase{};
|
||||
};
|
||||
|
||||
struct MockCatalog : AZ::Data::AssetCatalogRequestBus::Handler
|
||||
{
|
||||
static const inline AZ::Data::AssetId TestId{ AZ::Uuid::CreateRandom(), 1234 };
|
||||
|
||||
MockCatalog(AZStd::string testFile)
|
||||
: m_testFile(AZStd::move(testFile))
|
||||
{
|
||||
BusConnect();
|
||||
}
|
||||
|
||||
~MockCatalog() override
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
AZStd::string GetAssetPathById(const AZ::Data::AssetId& assetId) override
|
||||
{
|
||||
if (assetId == TestId)
|
||||
{
|
||||
return m_testFile;
|
||||
}
|
||||
|
||||
return "InvalidAssetId";
|
||||
}
|
||||
|
||||
AZ::Data::AssetId GetAssetIdByPath(const char* path, const AZ::Data::AssetType&, bool) override
|
||||
{
|
||||
AZ::IO::PathView pathView{ AZStd::string_view(path) };
|
||||
|
||||
if (AZ::IO::PathView(m_testFile) == pathView)
|
||||
{
|
||||
return TestId;
|
||||
}
|
||||
|
||||
AZ_Error("MockCatalog", false, "Requested path %s does not match expected asset path of %s", path, m_testFile.c_str());
|
||||
ADD_FAILURE();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
AZStd::string m_testFile;
|
||||
};
|
||||
|
||||
struct PrefabPublicNotificationsListener : PrefabPublicNotificationBus::Handler
|
||||
{
|
||||
PrefabPublicNotificationsListener()
|
||||
{
|
||||
BusConnect();
|
||||
}
|
||||
|
||||
~PrefabPublicNotificationsListener() override
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
void OnPrefabInstancePropagationBegin() override
|
||||
{
|
||||
m_updated = true;
|
||||
}
|
||||
|
||||
bool m_updated = false;
|
||||
};
|
||||
|
||||
TEST_F(ProceduralPrefabSystemComponentTests, RegisteredPrefabUpdates)
|
||||
{
|
||||
const AZStd::string prefabFile = (AZ::IO::Path(m_temporaryDirectory.GetDirectory()) / "test.prefab").Native();
|
||||
MockCatalog catalog(prefabFile.c_str());
|
||||
|
||||
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
|
||||
|
||||
ASSERT_NE(proceduralPrefabSystemComponentInterface, nullptr);
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
ASSERT_NE(prefabLoaderInterface, nullptr);
|
||||
|
||||
auto entity = aznew AZ::Entity();
|
||||
|
||||
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->CreatePrefab({ entity }, {}, prefabFile.c_str());
|
||||
|
||||
ASSERT_NE(instance, nullptr);
|
||||
|
||||
prefabLoaderInterface->SaveTemplateToFile(instance->GetTemplateId(), prefabFile.c_str());
|
||||
|
||||
proceduralPrefabSystemComponentInterface->RegisterProceduralPrefab(prefabFile, instance->GetTemplateId());
|
||||
|
||||
AzFramework::AssetCatalogEventBus::Broadcast(&AzFramework::AssetCatalogEventBus::Events::OnCatalogAssetChanged, MockCatalog::TestId);
|
||||
|
||||
PrefabPublicNotificationsListener listener;
|
||||
AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
|
||||
|
||||
EXPECT_TRUE(listener.m_updated);
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabSystemComponentTests, UnregisteredPrefabDoesNotUpdate)
|
||||
{
|
||||
PrefabPublicNotificationsListener listener;
|
||||
|
||||
const AZStd::string prefabFile = (AZ::IO::Path(m_temporaryDirectory.GetDirectory()) / "test.prefab").Native();
|
||||
MockCatalog catalog(prefabFile.c_str());
|
||||
|
||||
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
|
||||
|
||||
ASSERT_NE(proceduralPrefabSystemComponentInterface, nullptr);
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
ASSERT_NE(prefabLoaderInterface, nullptr);
|
||||
|
||||
auto entity = aznew AZ::Entity();
|
||||
|
||||
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->CreatePrefab({ entity }, {}, prefabFile.c_str());
|
||||
|
||||
ASSERT_NE(instance, nullptr);
|
||||
|
||||
prefabLoaderInterface->SaveTemplateToFile(instance->GetTemplateId(), prefabFile.c_str());
|
||||
|
||||
AzFramework::AssetCatalogEventBus::Broadcast(
|
||||
&AzFramework::AssetCatalogEventBus::Events::OnCatalogAssetChanged, MockCatalog::TestId);
|
||||
|
||||
AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
|
||||
|
||||
EXPECT_FALSE(listener.m_updated);
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ set(FILES
|
||||
Prefab/SpawnableSortEntitiesTests.cpp
|
||||
Prefab/PrefabScriptingTests.cpp
|
||||
Prefab/ProceduralPrefabAssetTests.cpp
|
||||
Prefab/ProceduralPrefabSystemComponentTests.cpp
|
||||
PropertyIntCtrlCommonTests.cpp
|
||||
PropertyIntCtrlCommonTests.h
|
||||
PropertyIntSliderCtrlTests.cpp
|
||||
|
||||
@@ -136,6 +136,12 @@ namespace UnitTest
|
||||
auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(Data::jsonPrefab);
|
||||
ASSERT_TRUE(jsonOutcome);
|
||||
|
||||
// Register the asset to generate an AssetId in the catalog
|
||||
AZ::Data::AssetId assetId;
|
||||
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
|
||||
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, "fake_prefab.procprefab",
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(), true);
|
||||
|
||||
auto prefabGroup = AZStd::make_shared<AZ::SceneAPI::SceneData::PrefabGroup>();
|
||||
prefabGroup.get()->SetId(AZ::Uuid::CreateRandom());
|
||||
prefabGroup.get()->SetName("fake_prefab");
|
||||
|
||||
Reference in New Issue
Block a user