Bugfixes to enable slice-to-prefab conversion to run with less warnings/errors/crashes (#768)

While trying to process all slices and levels in Automated Testing, a few bugs came up that needed to be addressed:
- [LYN-3832] TransformComponent had a field removed without updating the version number and converter, which caused a lot of excessive warnings
- SliceComponent would crash in debug builds on instantiation failures due to a null dereference that was guarded against in most but not all places
- SliceConverter now detects when nested slices exist and gracefully warns about it.
- InstanceUpdateExecutor / TemplateInstanceMapper will now immediately remove instances that are unregistered, so that any in the queue don't get processed on a subsequent tick.  This was causing crashes when the instance was destroyed before the processing occurred.  It also has a side benefit of preventing the same instance from executing multiple times.
- Minor logic bugfix to the pack close warning, the boolean check was flipped.

Also added an early-out on SetTemplateId, since this was causing some unnecessary instance queue entries.
This commit is contained in:
Mike Balfour
2021-05-17 14:58:18 -05:00
committed by GitHub
parent bade3229be
commit d084027b6e
8 changed files with 53 additions and 14 deletions
@@ -1740,7 +1740,10 @@ namespace AZ
if (!iter->IsInstantiated())
{
#if defined(AZ_ENABLE_TRACING)
Data::Asset<SliceAsset> thisAsset = Data::AssetManager::Instance().FindAsset(GetMyAsset()->GetId(), AZ::Data::AssetLoadBehavior::Default);
Data::Asset<SliceAsset> thisAsset = GetMyAsset()
? Data::Asset<SliceAsset>(Data::AssetManager::Instance().FindAsset(
GetMyAsset()->GetId(), AZ::Data::AssetLoadBehavior::Default))
: Data::Asset<SliceAsset>();
AZ_Warning("Slice", false, "Removing %d instances of slice asset %s from parent asset %s due to failed instantiation. "
"Saving parent asset will result in loss of slice data.",
iter->GetInstances().size(),
@@ -80,6 +80,12 @@ namespace AzToolsFramework
void Instance::SetTemplateId(const TemplateId& templateId)
{
// If we aren't changing the template Id, there's no need to unregister / re-register
if (templateId == m_templateId)
{
return;
}
// If this instance's templateId is valid, we should be able to unregister this instance from
// Template to Instance mapping successfully.
if (m_templateId != InvalidTemplateId &&
@@ -72,10 +72,18 @@ namespace AzToolsFramework
for (auto instance : findInstancesResult->get())
{
m_instancesUpdateQueue.emplace(instance);
m_instancesUpdateQueue.emplace_back(instance);
}
}
void InstanceUpdateExecutor::RemoveTemplateInstanceFromQueue(const Instance* instance)
{
AZStd::erase_if(m_instancesUpdateQueue, [instance](Instance* entry)
{
return entry == instance;
});
}
bool InstanceUpdateExecutor::UpdateTemplateInstancesInQueue()
{
bool isUpdateSuccessful = true;
@@ -97,9 +105,16 @@ namespace AzToolsFramework
ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, EntityIdList());
for (int i = 0; i < instanceCountToUpdateInBatch; ++i)
// Process all instances in the queue, capped to the batch size.
// Even though we potentially initialized the batch size to the queue, it's possible for the queue size to shrink
// during instance processing if the instance gets deleted and it was queued multiple times. To handle this, we
// make sure to end the loop once the queue is empty, regardless of what the initial size was.
for (int i = 0; (i < instanceCountToUpdateInBatch) && !m_instancesUpdateQueue.empty(); ++i)
{
Instance* instanceToUpdate = m_instancesUpdateQueue.front();
m_instancesUpdateQueue.pop_front();
AZ_Assert(instanceToUpdate != nullptr, "Invalid instance on update queue.");
TemplateId instanceTemplateId = instanceToUpdate->GetTemplateId();
if (currentTemplateId != instanceTemplateId)
{
@@ -115,7 +130,6 @@ namespace AzToolsFramework
// Remove the instance from update queue if its corresponding template couldn't be found
isUpdateSuccessful = false;
m_instancesUpdateQueue.pop();
continue;
}
}
@@ -127,7 +141,6 @@ namespace AzToolsFramework
// Since nested instances get reconstructed during propagation, remove any nested instance that no longer
// maps to a template.
isUpdateSuccessful = false;
m_instancesUpdateQueue.pop();
continue;
}
@@ -148,8 +161,6 @@ namespace AzToolsFramework
isUpdateSuccessful = false;
}
m_instancesUpdateQueue.pop();
}
for (auto entityIdIterator = selectedEntityIds.begin(); entityIdIterator != selectedEntityIds.end(); entityIdIterator++)
@@ -14,7 +14,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/containers/deque.h>
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
@@ -37,6 +37,7 @@ namespace AzToolsFramework
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId) override;
bool UpdateTemplateInstancesInQueue() override;
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;
void RegisterInstanceUpdateExecutorInterface();
void UnregisterInstanceUpdateExecutorInterface();
@@ -45,7 +46,7 @@ namespace AzToolsFramework
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
TemplateInstanceMapperInterface* m_templateInstanceMapperInterface = nullptr;
int m_instanceCountToUpdateInBatch = 0;
AZStd::queue<Instance*> m_instancesUpdateQueue;
AZStd::deque<Instance*> m_instancesUpdateQueue;
bool m_updatingTemplateInstancesInQueue { false };
};
}
@@ -31,6 +31,9 @@ namespace AzToolsFramework
// Update Instances in the waiting queue.
virtual bool UpdateTemplateInstancesInQueue() = 0;
// Remove an Instance from the waiting queue.
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) = 0;
};
}
}
@@ -14,6 +14,7 @@
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
namespace AzToolsFramework
{
@@ -71,6 +72,12 @@ namespace AzToolsFramework
bool TemplateInstanceMapper::UnregisterInstance(Instance& instance)
{
// The InstanceUpdateExecutor queries the TemplateInstanceMapper for a list of instances related to a template.
// Consequently, if an instance gets unregistered for a template, we need to notify the InstanceUpdateExecutor as well
// so that it clears any internal associations that it might have in its queue.
AZ_Assert(AZ::Interface<InstanceUpdateExecutorInterface>::Get() != nullptr, "InstanceUpdateExecutor doesn't exist");
AZ::Interface<InstanceUpdateExecutorInterface>::Get()->RemoveTemplateInstanceFromQueue(&instance);
auto found = m_templateIdToInstancesMap.find(instance.GetTemplateId());
return found != m_templateIdToInstancesMap.end() &&
found->second.erase(&instance) != 0;
@@ -162,6 +162,12 @@ namespace AzToolsFramework
classElement.RemoveElementByName(AZ_CRC("InterpolateScale", 0x9d00b831));
}
if (classElement.GetVersion() < 10)
{
// The "Sync Enabled" flag is no longer needed.
classElement.RemoveElementByName(AZ_CRC_CE("Sync Enabled"));
}
return true;
}
} // namespace Internal
@@ -1305,7 +1311,7 @@ namespace AzToolsFramework
Field("IsStatic", &TransformComponent::m_isStatic)->
Field("InterpolatePosition", &TransformComponent::m_interpolatePosition)->
Field("InterpolateRotation", &TransformComponent::m_interpolateRotation)->
Version(9, &Internal::TransformComponentDataConverter);
Version(10, &Internal::TransformComponentDataConverter);
if (AZ::EditContext* ptrEdit = serializeContext->GetEditContext())
{
@@ -143,7 +143,7 @@ namespace AZ
if (packOpened)
{
[[maybe_unused]] bool closeResult = archiveInterface->ClosePack(filePath);
AZ_Warning("Convert-Slice", !closeResult, "Failed to close '%s'.", filePath.c_str());
AZ_Warning("Convert-Slice", closeResult, "Failed to close '%s'.", filePath.c_str());
}
AZ_Printf("Convert-Slice", "Finished converting '%s' to '%s'\n", filePath.c_str(), outputPath.c_str());
@@ -166,14 +166,16 @@ namespace AZ
}
// Get all of the entities from the slice.
SliceComponent::EntityList sliceEntities;
bool getEntitiesResult = sliceComponent->GetEntities(sliceEntities);
if ((!getEntitiesResult) || (sliceEntities.empty()))
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
if (sliceEntities.empty())
{
AZ_Printf("Convert-Slice", " File not converted: Slice entities could not be retrieved.\n");
return false;
}
const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
AZ_Warning("Convert-Slice", sliceList.empty(), " Slice depends on other slices, this conversion will lose data.\n");
// Create the Prefab with the entities from the slice
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
prefabSystemComponent->CreatePrefab(sliceEntities, {}, outputPath));