[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:
amzn-mike
2021-12-13 14:44:41 -06:00
committed by GitHub
parent 45abb9f88e
commit 71bb200e0a
14 changed files with 595 additions and 15 deletions
@@ -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
@@ -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
@@ -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
@@ -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