Merge branch 'aws-lumberyard:development' into SerializeContextToolsConvertHandleAnyValidation
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
|
||||
#include <SliceConverterEditorEntityContextComponent.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -34,6 +35,9 @@ namespace AZ
|
||||
Application::Application(int argc, char** argv)
|
||||
: AzToolsFramework::ToolsApplication(&argc, &argv)
|
||||
{
|
||||
// We need a specialized variant of EditorEntityContextComponent for the SliceConverter, so we register the descriptor here.
|
||||
RegisterComponentDescriptor(AzToolsFramework::SliceConverterEditorEntityContextComponent::CreateDescriptor());
|
||||
|
||||
AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath();
|
||||
if (projectPath.empty())
|
||||
{
|
||||
@@ -110,10 +114,21 @@ namespace AZ
|
||||
|
||||
AZ::ComponentTypeList Application::GetRequiredSystemComponents() const
|
||||
{
|
||||
// Use all of the default system components, but also add in the ThumbnailerNullComponent so that components requiring
|
||||
// a ThumbnailService can still be started up.
|
||||
// By default, we use all of the standard system components.
|
||||
AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents();
|
||||
|
||||
// Also add in the ThumbnailerNullComponent so that components requiring a ThumbnailService can still be started up.
|
||||
components.emplace_back(azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerNullComponent>());
|
||||
|
||||
// The Slice Converter requires a specialized variant of the EditorEntityContextComponent that exposes the ability
|
||||
// to disable the behavior of activating entities on creation. During conversion, the creation flow will be triggered,
|
||||
// but entity activation requires a significant amount of subsystem initialization that's unneeded for conversion.
|
||||
// So, to get around this, we swap out EditorEntityContextComponent with SliceConverterEditorEntityContextComponent.
|
||||
components.erase(
|
||||
AZStd::remove(
|
||||
components.begin(), components.end(), azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>()),
|
||||
components.end());
|
||||
components.emplace_back(azrtti_typeid<AzToolsFramework::SliceConverterEditorEntityContextComponent>());
|
||||
return components;
|
||||
}
|
||||
} // namespace SerializeContextTools
|
||||
|
||||
@@ -30,13 +30,16 @@
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Application.h>
|
||||
#include <SliceConverter.h>
|
||||
#include <SliceConverterEditorEntityContextComponent.h>
|
||||
#include <Utilities.h>
|
||||
|
||||
|
||||
// SliceConverter reads in a slice file (saved in an ObjectStream format), instantiates it, creates a prefab out of the data,
|
||||
// and saves the prefab in a JSON format. This can be used for one-time migrations of slices or slice-based levels to prefabs.
|
||||
//
|
||||
@@ -99,21 +102,41 @@ namespace AZ
|
||||
bool result = true;
|
||||
rapidjson::StringBuffer scratchBuffer;
|
||||
|
||||
// For slice conversion, disable the EditorEntityContextComponent logic that activates entities on creation.
|
||||
// This prevents a lot of error messages and crashes during conversion due to lack of full environment and subsystem setup.
|
||||
AzToolsFramework::SliceConverterEditorEntityContextComponent::DisableOnContextEntityLogic();
|
||||
|
||||
// Loop through the list of requested files and convert them.
|
||||
AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
|
||||
for (AZStd::string& filePath : fileList)
|
||||
{
|
||||
bool convertResult = ConvertSliceFile(convertSettings.m_serializeContext, filePath, isDryRun);
|
||||
result = result && convertResult;
|
||||
|
||||
// Clear out all registered prefab templates between each top-level file that gets processed.
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
for (auto templateId : m_createdTemplateIds)
|
||||
{
|
||||
// We don't just want to call RemoveAllTemplates() because the root template should remain between file conversions.
|
||||
prefabSystemComponentInterface->RemoveTemplate(templateId);
|
||||
}
|
||||
m_aliasIdMapper.clear();
|
||||
m_createdTemplateIds.clear();
|
||||
}
|
||||
|
||||
DisconnectFromAssetProcessor();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SliceConverter::ConvertSliceFile(
|
||||
AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun)
|
||||
bool SliceConverter::ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun)
|
||||
{
|
||||
/* To convert a slice file, we read the input file in via ObjectStream, then use the "class ready" callback to convert
|
||||
* the data in memory to a Prefab.
|
||||
* If the input file is a level file (.ly), we actually need to load the level slice file ("levelentities.editor_xml") from
|
||||
* within the level file, which effectively is a zip file of the level slice file and a bunch of legacy level files that won't
|
||||
* be converted, since the systems that would use them no longer exist.
|
||||
*/
|
||||
|
||||
bool result = true;
|
||||
bool packOpened = false;
|
||||
|
||||
@@ -144,7 +167,7 @@ namespace AZ
|
||||
AZ_STRING_ARG(fileExtension.Native()));
|
||||
}
|
||||
|
||||
auto callback = [&outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
|
||||
auto callback = [this, &outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
|
||||
{
|
||||
if (classId != azrtti_typeid<AZ::Entity>())
|
||||
{
|
||||
@@ -153,11 +176,23 @@ namespace AZ
|
||||
}
|
||||
|
||||
AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr);
|
||||
return ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
|
||||
bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
|
||||
|
||||
// Delete the root entity pointer. Otherwise, it will leak itself along with all of the slice asset references held
|
||||
// within it.
|
||||
delete rootEntity;
|
||||
return convertResult;
|
||||
};
|
||||
|
||||
// Read in the slice file and call the callback on completion to convert the read-in slice to a prefab.
|
||||
if (!Utilities::InspectSerializedFile(inputPath.c_str(), serializeContext, callback))
|
||||
// This will also load dependent slice assets, but no other dependent asset types.
|
||||
// Since we're not actually initializing any of the entities, we don't need any of the non-slice assets to be loaded.
|
||||
if (!Utilities::InspectSerializedFile(
|
||||
inputPath.c_str(), serializeContext, callback,
|
||||
[](const AZ::Data::AssetFilterInfo& filterInfo)
|
||||
{
|
||||
return (filterInfo.m_assetType == azrtti_typeid<AZ::SliceAsset>());
|
||||
}))
|
||||
{
|
||||
AZ_Warning("Convert-Slice", false, "Failed to load '%s'. File may not contain an object stream.", inputPath.c_str());
|
||||
result = false;
|
||||
@@ -178,6 +213,13 @@ namespace AZ
|
||||
bool SliceConverter::ConvertSliceToPrefab(
|
||||
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity)
|
||||
{
|
||||
/* Given a root slice entity, we convert it to a prefab by doing the following:
|
||||
* - Locate the SliceComponent
|
||||
* - Take all the entities directly located on the slice, and put them into a prefab
|
||||
* - Fix up any top-level entities to have the prefab container entity as their parent
|
||||
* - If there are any nested slice instances, convert the nested slices to prefabs, then convert the instances.
|
||||
*/
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
// Find the slice from the root entity.
|
||||
@@ -188,13 +230,22 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all of the entities from the slice.
|
||||
// Get all of the entities from the slice. We're taking ownership of them, so we also remove them from the slice component
|
||||
// without deleting them.
|
||||
constexpr bool deleteEntities = false;
|
||||
constexpr bool removeEmptyInstances = true;
|
||||
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
|
||||
sliceComponent->RemoveAllEntities(deleteEntities, removeEmptyInstances);
|
||||
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
|
||||
|
||||
// Create the Prefab with the entities from the slice
|
||||
// Create the Prefab with the entities from the slice.
|
||||
// The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids
|
||||
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
|
||||
prefabSystemComponentInterface->CreatePrefab(sliceEntities, {}, outputPath));
|
||||
prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath));
|
||||
for (auto& entity : sliceEntities)
|
||||
{
|
||||
sourceInstance->AddEntity(*entity, AZStd::string::format("Entity_%s", entity->GetId().ToString().c_str()));
|
||||
}
|
||||
|
||||
// Dispatch events here, because prefab creation might trigger asset loads in rare circumstances.
|
||||
AZ::Data::AssetManager::Instance().DispatchEvents();
|
||||
@@ -204,12 +255,34 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
|
||||
FixPrefabEntities(container->get(), sliceEntities);
|
||||
|
||||
// Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure
|
||||
// the data doesn't stick around between file conversions.
|
||||
auto templateId = sourceInstance->GetTemplateId();
|
||||
if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
|
||||
{
|
||||
AZ_Printf("Convert-Slice", " Path error. Path could be invalid, or the prefab may not be loaded in this level.\n");
|
||||
return false;
|
||||
}
|
||||
m_createdTemplateIds.emplace(templateId);
|
||||
|
||||
// Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
|
||||
// When converting nested slices, this mapping will be needed to fix up the parent entity hierarchy correctly.
|
||||
auto entityAliases = sourceInstance->GetEntityAliases();
|
||||
for (auto& alias : entityAliases)
|
||||
{
|
||||
auto id = sourceInstance->GetEntityId(alias);
|
||||
auto result = m_aliasIdMapper.emplace(id, SliceEntityMappingInfo(templateId, alias));
|
||||
if (!result.second)
|
||||
{
|
||||
AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
|
||||
// This will help us better detect entity ID mapping errors for nested slice instances.
|
||||
AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
|
||||
constexpr bool isMetadataEntity = true;
|
||||
m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
|
||||
|
||||
// Update the prefab template with the fixed-up data in our prefab instance.
|
||||
AzToolsFramework::Prefab::PrefabDom prefabDom;
|
||||
@@ -244,7 +317,7 @@ namespace AZ
|
||||
}
|
||||
else
|
||||
{
|
||||
return SavePrefab(templateId);
|
||||
return SavePrefab(outputPath, templateId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,21 +327,26 @@ namespace AZ
|
||||
// via an EditorRequests EBus in CreatePrefab, but the subsystem that listens for it isn't present in this tool.)
|
||||
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
||||
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, containerEntity);
|
||||
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
|
||||
if (containerEntity.FindComponent<AzToolsFramework::Prefab::EditorPrefabComponent>() == nullptr)
|
||||
{
|
||||
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
|
||||
}
|
||||
|
||||
// Make all the components on the container entity have deterministic component IDs, so that multiple runs of the tool
|
||||
// on the same slice will produce the same prefab output. We're going to cheat a bit and just use the component type hash
|
||||
// as the component ID. This would break if we had multiple components of the same type, but that currently doesn't
|
||||
// happen for the container entity.
|
||||
auto containerComponents = containerEntity.GetComponents();
|
||||
for (auto& component : containerComponents)
|
||||
{
|
||||
component->SetId(component->GetUnderlyingComponentType().GetHash());
|
||||
}
|
||||
|
||||
// Reparent any root-level slice entities to the container entity.
|
||||
for (auto entity : sliceEntities)
|
||||
{
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
if (!transformComponent->GetParentId().IsValid())
|
||||
{
|
||||
transformComponent->SetParent(containerEntity.GetId());
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
}
|
||||
}
|
||||
constexpr bool onlySetIfInvalid = true;
|
||||
SetParentEntity(*entity, containerEntity.GetId(), onlySetIfInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,15 +354,18 @@ namespace AZ
|
||||
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
|
||||
AZ::SerializeContext* serializeContext, bool isDryRun)
|
||||
{
|
||||
/* Given a root slice, find all the nested slices and convert them. */
|
||||
|
||||
// Get the list of nested slices that this slice uses.
|
||||
const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
// For each nested slice, convert it.
|
||||
for (auto& slice : sliceList)
|
||||
{
|
||||
// Get the nested slice asset
|
||||
// Get the nested slice asset. These should already be preloaded due to loading the root asset.
|
||||
auto sliceAsset = slice.GetSliceAsset();
|
||||
sliceAsset.QueueLoad();
|
||||
sliceAsset.BlockUntilLoadComplete();
|
||||
AZ_Assert(sliceAsset.IsReady(), "slice asset hasn't been loaded yet!");
|
||||
|
||||
// The slice list gives us asset IDs, and we need to get to the source path. So first we get the asset path from the ID,
|
||||
// then we get the source path from the asset path.
|
||||
@@ -312,13 +393,13 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the prefab template for the newly-created nested prefab.
|
||||
// Find the prefab template we created for the newly-created nested prefab.
|
||||
// To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path.
|
||||
AZ::IO::Path nestedPrefabPath = assetPath;
|
||||
nestedPrefabPath.ReplaceExtension("prefab");
|
||||
|
||||
auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
|
||||
nestedPrefabPath = prefabLoaderInterface->GetRelativePathToProject(nestedPrefabPath);
|
||||
nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath);
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId nestedTemplateId =
|
||||
prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
|
||||
@@ -332,6 +413,21 @@ namespace AZ
|
||||
"Convert-Slice", " Attaching %zu instances of nested slice '%s'.\n", instances.size(),
|
||||
nestedPrefabPath.Native().c_str());
|
||||
|
||||
// Before processing any further, save off all the known entity IDs from all the instances and how they map back to
|
||||
// the base nested prefab that they've come from (i.e. this one). As we proceed up the chain of nesting, this will
|
||||
// build out a hierarchical list of owning instances for each entity that we can trace upwards to know where to add
|
||||
// the entity into our nested prefab instance.
|
||||
// This step needs to occur *before* converting the instances themselves, because while converting instances, they
|
||||
// might have entity ID references that point to other instances. By having the full instance entity ID map in place
|
||||
// before conversion, we'll be able to fix them up appropriately.
|
||||
|
||||
for (auto& instance : instances)
|
||||
{
|
||||
AZStd::string instanceAlias = GetInstanceAlias(instance);
|
||||
UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
|
||||
}
|
||||
|
||||
// Now that we have all the entity ID mappings, convert all the instances.
|
||||
for (auto& instance : instances)
|
||||
{
|
||||
bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance);
|
||||
@@ -345,15 +441,53 @@ namespace AZ
|
||||
return true;
|
||||
}
|
||||
|
||||
AZStd::string SliceConverter::GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance)
|
||||
{
|
||||
// When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one
|
||||
// will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
|
||||
// the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
|
||||
// a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
|
||||
// unique instance ID.
|
||||
AZStd::string instanceAlias;
|
||||
auto entityIdMap = instance.GetEntityIdMap();
|
||||
if (!entityIdMap.empty())
|
||||
{
|
||||
instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("Convert-Slice", false, " Couldn't create deterministic instance alias.");
|
||||
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
}
|
||||
return instanceAlias;
|
||||
}
|
||||
|
||||
|
||||
bool SliceConverter::ConvertSliceInstance(
|
||||
[[maybe_unused]] AZ::SliceComponent::SliceInstance& instance,
|
||||
[[maybe_unused]] AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AZ::SliceComponent::SliceInstance& instance,
|
||||
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AzToolsFramework::Prefab::TemplateReference nestedTemplate,
|
||||
AzToolsFramework::Prefab::Instance* topLevelInstance)
|
||||
{
|
||||
/* To convert a slice instance, it's important to understand the similarities and differences between slices and prefabs.
|
||||
* Both slices and prefabs have the concept of instances of a nested slice/prefab, where each instance can have its own
|
||||
* set of changed data (transforms, component values, etc).
|
||||
* For slices, the changed data comes from applying a DataPatch to an instantiated set of entities from the nested slice.
|
||||
* From prefabs, the changed data comes from Json patches that are applied to the instantiated set of entities from the
|
||||
* nested prefab. The prefab instance entities also have different IDs than the slice instance entities, so we'll need
|
||||
* to remap some of them along the way.
|
||||
* To get from one to the other, we'll need to do the following:
|
||||
* - Instantiate the nested slice and nested prefab
|
||||
* - Patch the nested slice instance and fix up the entity ID references
|
||||
* - Replace the nested prefab instance entities with the fixed-up slice ones
|
||||
* - Add the nested instance (and the link patch) to the top-level prefab
|
||||
*/
|
||||
|
||||
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
AZStd::string instanceAlias = GetInstanceAlias(instance);
|
||||
|
||||
// Create a new unmodified prefab Instance for the nested slice instance.
|
||||
auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>();
|
||||
AzToolsFramework::Prefab::Instance::EntityList newEntities;
|
||||
@@ -371,31 +505,160 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(unmodifiedNestedInstanceDom, *(nestedInstance.get()));
|
||||
|
||||
// Currently, DataPatch conversions for nested slices aren't implemented, so all nested slice overrides will
|
||||
// be lost.
|
||||
AZ_Warning(
|
||||
"Convert-Slice", false, " Nested slice instances will lose all of their override data during conversion.",
|
||||
nestedTemplate->get().GetFilePath().c_str());
|
||||
// Instantiate a new instance of the nested slice
|
||||
SliceComponent* dependentSlice = sliceAsset.Get()->GetComponent();
|
||||
[[maybe_unused]] AZ::SliceComponent::InstantiateResult instantiationResult = dependentSlice->Instantiate();
|
||||
AZ_Assert(instantiationResult == AZ::SliceComponent::InstantiateResult::Success, "Failed to instantiate instance");
|
||||
|
||||
// Set the container entity of the nested prefab to have the top-level prefab as the parent.
|
||||
// Once DataPatch conversions are supported, this will need to change to nest the prefab under the appropriate entity
|
||||
// within the level.
|
||||
auto containerEntity = nestedInstance->GetContainerEntity();
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
containerEntity->get().FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
// Apply the data patch for this instance of the nested slice. This will provide us with a version of the slice's entities
|
||||
// with all data overrides applied to them.
|
||||
DataPatch::FlagsMap sourceDataFlags = dependentSlice->GetDataFlagsForInstances().GetDataFlagsForPatching();
|
||||
DataPatch::FlagsMap targetDataFlags = instance.GetDataFlags().GetDataFlagsForPatching(&instance.GetEntityIdToBaseMap());
|
||||
AZ::ObjectStream::FilterDescriptor filterDesc(AZ::Data::AssetFilterNoAssetLoading);
|
||||
|
||||
AZ::SliceComponent::InstantiatedContainer sourceObjects(false);
|
||||
dependentSlice->GetEntities(sourceObjects.m_entities);
|
||||
dependentSlice->GetAllMetadataEntities(sourceObjects.m_metadataEntities);
|
||||
|
||||
const DataPatch& dataPatch = instance.GetDataPatch();
|
||||
auto instantiated =
|
||||
dataPatch.Apply(&sourceObjects, dependentSlice->GetSerializeContext(), filterDesc, sourceDataFlags, targetDataFlags);
|
||||
|
||||
// Replace all the entities in the instance with the new patched ones. To do this, we'll remove all existing entities
|
||||
// throughout the entire nested hierarchy, then add the new patched entities back in at the appropriate place in the hierarchy.
|
||||
// (This is easier than trying to figure out what the patched data changes are - we can let the JSON patch handle it for us)
|
||||
|
||||
nestedInstance->RemoveNestedEntities(
|
||||
[](const AZStd::unique_ptr<AZ::Entity>&)
|
||||
{
|
||||
return true;
|
||||
});
|
||||
|
||||
AZStd::vector<AZStd::pair<AZ::Entity*, AzToolsFramework::Prefab::Instance*>> addedEntityList;
|
||||
|
||||
for (auto& entity : instantiated->m_entities)
|
||||
{
|
||||
transformComponent->SetParent(topLevelInstance->GetContainerEntityId());
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
auto entityEntry = m_aliasIdMapper.find(entity->GetId());
|
||||
if (entityEntry != m_aliasIdMapper.end())
|
||||
{
|
||||
auto& mappingStruct = entityEntry->second;
|
||||
|
||||
// Starting with the current nested instance, walk downwards through the nesting hierarchy until we're at the
|
||||
// correct level for this instanced entity ID, then add it. Because we're adding it with the non-instanced alias,
|
||||
// it doesn't matter what the slice's instanced entity ID is, and the JSON patch will correctly pick up the changes
|
||||
// we've made for this instance.
|
||||
AzToolsFramework::Prefab::Instance* addingInstance = nestedInstance.get();
|
||||
for (auto it = mappingStruct.m_nestedInstanceAliases.rbegin(); it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
|
||||
{
|
||||
auto foundInstance = addingInstance->FindNestedInstance(*it);
|
||||
if (foundInstance.has_value())
|
||||
{
|
||||
addingInstance = &(foundInstance->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
|
||||
}
|
||||
}
|
||||
addingInstance->AddEntity(*entity, mappingStruct.m_entityAlias);
|
||||
addedEntityList.emplace_back(entity, addingInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Failed to find entity alias.");
|
||||
nestedInstance->AddEntity(*entity);
|
||||
addedEntityList.emplace_back(entity, nestedInstance.get());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& [entity, addingInstance] : addedEntityList)
|
||||
{
|
||||
// Fix up the parent hierarchy:
|
||||
// - Invalid parents need to get set to the container.
|
||||
// - Valid parents into the top-level instance mean that the nested slice instance is also child-nested under an entity.
|
||||
// Prefabs handle this type of nesting differently - we need to set the parent to the container, and the container's
|
||||
// parent to that other instance.
|
||||
auto containerEntity = addingInstance->GetContainerEntity();
|
||||
auto containerEntityId = containerEntity->get().GetId();
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
bool onlySetIfInvalid = true;
|
||||
auto parentId = transformComponent->GetParentId();
|
||||
if (parentId.IsValid())
|
||||
{
|
||||
// Look to see if the parent ID exists in the same instance (i.e. an entity in the nested slice is a
|
||||
// child of an entity in the containing slice). If this case exists, we need to adjust the parents so that
|
||||
// the child entity connects to the prefab container, and the *container* is the child of the entity in the
|
||||
// containing slice. (i.e. go from A->B to A->container->B)
|
||||
auto parentEntry = m_aliasIdMapper.find(parentId);
|
||||
if (parentEntry != m_aliasIdMapper.end())
|
||||
{
|
||||
auto& parentMappingInfo = parentEntry->second;
|
||||
if (parentMappingInfo.m_templateId != addingInstance->GetTemplateId())
|
||||
{
|
||||
if (topLevelInstance->GetTemplateId() == parentMappingInfo.m_templateId)
|
||||
{
|
||||
parentId = topLevelInstance->GetEntityId(parentMappingInfo.m_entityAlias);
|
||||
}
|
||||
else
|
||||
{
|
||||
AzToolsFramework::Prefab::Instance* parentInstance = addingInstance;
|
||||
|
||||
while ((parentInstance->GetParentInstance().has_value()) &&
|
||||
(parentInstance->GetTemplateId() != parentMappingInfo.m_templateId))
|
||||
{
|
||||
parentInstance = &(parentInstance->GetParentInstance()->get());
|
||||
}
|
||||
|
||||
if (parentInstance->GetTemplateId() == parentMappingInfo.m_templateId)
|
||||
{
|
||||
parentId = parentInstance->GetEntityId(parentMappingInfo.m_entityAlias);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Could not find parent instance");
|
||||
}
|
||||
}
|
||||
|
||||
// Set the container's parent to this entity's parent, and set this entity's parent to the container
|
||||
// auto newParentId = topLevelInstance->GetEntityId(parentMappingInfo.m_entityAlias);
|
||||
SetParentEntity(containerEntity->get(), parentId, false);
|
||||
onlySetIfInvalid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the parent ID is valid, but NOT in the top-level instance, then it's just a nested hierarchy inside
|
||||
// the slice and we don't need to adjust anything. "onlySetIfInvalid" will still be true, which means we
|
||||
// won't change the parent ID below.
|
||||
}
|
||||
|
||||
SetParentEntity(*entity, containerEntityId, onlySetIfInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the container entity of the nested prefab to have the top-level prefab as the parent if it hasn't already gotten
|
||||
// another entity as its parent.
|
||||
{
|
||||
auto containerEntity = nestedInstance->GetContainerEntity();
|
||||
constexpr bool onlySetIfInvalid = true;
|
||||
SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid);
|
||||
}
|
||||
|
||||
// After doing all of the above, run through entity references in any of the patched entities, and fix up the entity IDs to
|
||||
// match the new ones in our prefabs.
|
||||
RemapIdReferences(m_aliasIdMapper, topLevelInstance, nestedInstance.get(), instantiated, dependentSlice->GetSerializeContext());
|
||||
|
||||
// Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
|
||||
// create a patch out of it, and patch the top-level prefab template.
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance);
|
||||
|
||||
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance));
|
||||
// Use the deterministic instance alias for this new instance
|
||||
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance), instanceAlias);
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomAfter, *topLevelInstance);
|
||||
@@ -418,9 +681,26 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::InvalidLinkId);
|
||||
prefabSystemComponentInterface->PropagateTemplateChanges(topLevelInstance->GetTemplateId());
|
||||
|
||||
AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SliceConverter::SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid)
|
||||
{
|
||||
AzToolsFramework::Components::TransformComponent* transformComponent =
|
||||
entity.FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
if (transformComponent)
|
||||
{
|
||||
// Only set the parent if we didn't set the onlySetIfInvalid flag, or if we did and the parent is currently invalid
|
||||
if (!onlySetIfInvalid || !transformComponent->GetParentId().IsValid())
|
||||
{
|
||||
transformComponent->SetParent(parentId);
|
||||
transformComponent->UpdateCachedWorldTransform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SliceConverter::PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId)
|
||||
{
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
@@ -439,17 +719,31 @@ namespace AZ
|
||||
AZ::Debug::Trace::Instance().Output("", "\n");
|
||||
}
|
||||
|
||||
bool SliceConverter::SavePrefab(AzToolsFramework::Prefab::TemplateId templateId)
|
||||
bool SliceConverter::SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId)
|
||||
{
|
||||
auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
|
||||
|
||||
if (!prefabLoaderInterface->SaveTemplate(templateId))
|
||||
AZStd::string out;
|
||||
if (prefabLoaderInterface->SaveTemplateToString(templateId, out))
|
||||
{
|
||||
AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n");
|
||||
return false;
|
||||
IO::SystemFile outputFile;
|
||||
if (!outputFile.Open(
|
||||
AZStd::string(outputPath.Native()).c_str(),
|
||||
IO::SystemFile::OpenMode::SF_OPEN_CREATE |
|
||||
IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
|
||||
IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
|
||||
{
|
||||
AZ_Error("Convert-Slice", false, " Unable to create output file '%.*s'.", AZ_STRING_ARG(outputPath.Native()));
|
||||
return false;
|
||||
}
|
||||
|
||||
outputFile.Write(out.data(), out.size());
|
||||
outputFile.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SliceConverter::ConnectToAssetProcessor()
|
||||
@@ -488,5 +782,136 @@ namespace AZ
|
||||
AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully.");
|
||||
}
|
||||
|
||||
void SliceConverter::UpdateSliceEntityInstanceMappings(
|
||||
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZStd::string& currentInstanceAlias)
|
||||
{
|
||||
// For each instanced entity, map its ID all the way back to the original prefab template and entity ID that it came from.
|
||||
// This counts on being run recursively from the leaf nodes upwards, so we first get B->A,
|
||||
// then C->B which becomes a C->A entry, then D->C which becomes D->A, etc.
|
||||
for (auto& [newId, oldId] : sliceEntityIdMap)
|
||||
{
|
||||
// Try to find the conversion chain from the old ID. if it's there, copy it and use it for the new ID, plus add this
|
||||
// instance's name to the end of the chain. If it's not there, skip it, since it's probably the slice metadata entity,
|
||||
// which we didn't convert.
|
||||
auto parentEntry = m_aliasIdMapper.find(oldId);
|
||||
if (parentEntry != m_aliasIdMapper.end())
|
||||
{
|
||||
// Only add this instance's name if we don't already have an entry for the new ID.
|
||||
if (m_aliasIdMapper.find(newId) == m_aliasIdMapper.end())
|
||||
{
|
||||
auto newMappingEntry = m_aliasIdMapper.emplace(newId, parentEntry->second).first;
|
||||
newMappingEntry->second.m_nestedInstanceAliases.emplace_back(currentInstanceAlias);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we already had an entry for the new ID, it might be because the old and new ID are the same. This happens
|
||||
// when nesting multiple prefabs directly underneath each other without a nesting entity in-between.
|
||||
// If the IDs are different, it's an unexpected error condition.
|
||||
AZ_Assert(oldId == newId, "The same entity instance ID has unexpectedly appeared twice in the same nested prefab.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("Convert-Slice", false, " Couldn't find an entity ID conversion for %s.", oldId.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SliceConverter::RemapIdReferences(
|
||||
const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
|
||||
AzToolsFramework::Prefab::Instance* topLevelInstance,
|
||||
AzToolsFramework::Prefab::Instance* nestedInstance,
|
||||
SliceComponent::InstantiatedContainer* instantiatedEntities,
|
||||
SerializeContext* context)
|
||||
{
|
||||
// Given a set of instantiated entities, run through all of them, look for entity references, and replace the entity IDs with
|
||||
// new ones that match up with our prefabs.
|
||||
|
||||
IdUtils::Remapper<EntityId>::ReplaceIdsAndIdRefs(
|
||||
instantiatedEntities,
|
||||
[idMapper, &topLevelInstance, &nestedInstance](
|
||||
const EntityId& sourceId, bool isEntityId, [[maybe_unused]] const AZStd::function<EntityId()>& idGenerator) -> EntityId
|
||||
{
|
||||
EntityId newId = sourceId;
|
||||
|
||||
// Only convert valid entity references. Actual entity IDs have already been taken care of elsewhere, so ignore them.
|
||||
if (!isEntityId && sourceId.IsValid())
|
||||
{
|
||||
auto entityEntry = idMapper.find(sourceId);
|
||||
|
||||
// Since we've already remapped transform hierarchies to include container entities, it's possible that our entity
|
||||
// reference is pointing to a container, which means it won't be in our slice mapping table. In that case, just
|
||||
// return it as-is.
|
||||
if (entityEntry == idMapper.end())
|
||||
{
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
// We've got a slice->prefab mapping entry, so now we need to use it.
|
||||
auto& mappingStruct = entityEntry->second;
|
||||
|
||||
if (mappingStruct.m_nestedInstanceAliases.empty())
|
||||
{
|
||||
// If we don't have a chain of nested instance aliases, then this entity reference is either within the
|
||||
// current nested instance or it's pointing to an entity in the top-level instance. We'll try them both
|
||||
// to look for a match.
|
||||
|
||||
EntityId prefabId = nestedInstance->GetEntityId(mappingStruct.m_entityAlias);
|
||||
if (!prefabId.IsValid())
|
||||
{
|
||||
prefabId = topLevelInstance->GetEntityId(mappingStruct.m_entityAlias);
|
||||
}
|
||||
|
||||
if (prefabId.IsValid())
|
||||
{
|
||||
newId = prefabId;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error("Convert-Slice", false, " Couldn't find source ID %s", sourceId.ToString().c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We *do* have a chain of nested instance aliases. This chain could either be relative to the nested instance
|
||||
// or the top-level instance. We can tell which one it is by which one can find the first nested instance
|
||||
// alias.
|
||||
|
||||
AzToolsFramework::Prefab::Instance* entityInstance = nestedInstance;
|
||||
auto it = mappingStruct.m_nestedInstanceAliases.rbegin();
|
||||
if (!entityInstance->FindNestedInstance(*it).has_value())
|
||||
{
|
||||
entityInstance = topLevelInstance;
|
||||
}
|
||||
|
||||
// Now that we've got a starting point, iterate through the chain of nested instance aliases to find the
|
||||
// correct instance to get the entity ID for. We have to go from slice IDs -> entity aliases -> entity IDs
|
||||
// because prefab instance creation can change some of our entity IDs along the way.
|
||||
for (; it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
|
||||
{
|
||||
auto foundInstance = entityInstance->FindNestedInstance(*it);
|
||||
if (foundInstance.has_value())
|
||||
{
|
||||
entityInstance = &(foundInstance->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
EntityId prefabId = entityInstance->GetEntityId(mappingStruct.m_entityAlias);
|
||||
if (prefabId.IsValid())
|
||||
{
|
||||
newId = prefabId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newId;
|
||||
},
|
||||
context);
|
||||
}
|
||||
|
||||
} // namespace SerializeContextTools
|
||||
} // namespace AZ
|
||||
|
||||
@@ -39,24 +39,67 @@ namespace AZ
|
||||
class SliceConverter : public Converter
|
||||
{
|
||||
public:
|
||||
static bool ConvertSliceFiles(Application& application);
|
||||
bool ConvertSliceFiles(Application& application);
|
||||
|
||||
private:
|
||||
static bool ConnectToAssetProcessor();
|
||||
static void DisconnectFromAssetProcessor();
|
||||
// When converting slice entities, especially for nested slices, we need to keep track of the original
|
||||
// entity ID, the entity alias it uses in the prefab, and which template and nested instance path it maps to.
|
||||
// As we encounter each instanced entity ID, we can look it up in this structure and use this to determine how to properly
|
||||
// add it to the correct place in the hierarchy.
|
||||
struct SliceEntityMappingInfo
|
||||
{
|
||||
SliceEntityMappingInfo(
|
||||
AzToolsFramework::Prefab::TemplateId templateId,
|
||||
AzToolsFramework::Prefab::EntityAlias entityAlias,
|
||||
bool isMetadataEntity = false)
|
||||
: m_templateId(templateId)
|
||||
, m_entityAlias(entityAlias)
|
||||
, m_isMetadataEntity(isMetadataEntity)
|
||||
{
|
||||
}
|
||||
|
||||
static bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
|
||||
static bool ConvertSliceToPrefab(
|
||||
AzToolsFramework::Prefab::TemplateId m_templateId;
|
||||
AzToolsFramework::Prefab::EntityAlias m_entityAlias;
|
||||
AZStd::vector<AzToolsFramework::Prefab::InstanceAlias> m_nestedInstanceAliases;
|
||||
bool m_isMetadataEntity{ false };
|
||||
};
|
||||
|
||||
bool ConnectToAssetProcessor();
|
||||
void DisconnectFromAssetProcessor();
|
||||
|
||||
bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
|
||||
bool ConvertSliceToPrefab(
|
||||
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity);
|
||||
static void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
|
||||
static bool ConvertNestedSlices(
|
||||
void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
|
||||
bool ConvertNestedSlices(
|
||||
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
|
||||
AZ::SerializeContext* serializeContext, bool isDryRun);
|
||||
static bool SliceConverter::ConvertSliceInstance(
|
||||
bool ConvertSliceInstance(
|
||||
AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
|
||||
AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance);
|
||||
static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
static bool SavePrefab(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
void SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid);
|
||||
void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
|
||||
void UpdateSliceEntityInstanceMappings(
|
||||
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap,
|
||||
const AZStd::string& currentInstanceAlias);
|
||||
AZStd::string GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance);
|
||||
|
||||
void RemapIdReferences(
|
||||
const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
|
||||
AzToolsFramework::Prefab::Instance* topLevelInstance,
|
||||
AzToolsFramework::Prefab::Instance* nestedInstance,
|
||||
SliceComponent::InstantiatedContainer* instantiatedEntities,
|
||||
SerializeContext* context);
|
||||
|
||||
// Track all of the entity IDs created and associate them with enough conversion information to know how to place the
|
||||
// entities in the correct place in the prefab hierarchy and fix up parent entity ID mappings to work with the nested
|
||||
// prefab schema.
|
||||
AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo> m_aliasIdMapper;
|
||||
|
||||
// Track all of the created prefab template IDs on a slice conversion so that they can get removed at the end of the
|
||||
// conversion for that file.
|
||||
AZStd::unordered_set<AzToolsFramework::Prefab::TemplateId> m_createdTemplateIds;
|
||||
};
|
||||
} // namespace SerializeContextTools
|
||||
} // namespace AZ
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
// This class is an inelegant workaround for use by the Slice Converter to selectively disable entity add/remove logic
|
||||
// during slice conversion in the EditorEntityContextComponent. Specifically, the standard versions of these methods will
|
||||
// attempt to activate the entities as they're added. This is both unnecessary and undesirable during slice conversion, since
|
||||
// entity activation requires a lot of subsystems to be active and valid.
|
||||
// Instead, by selectively disabling this logic, the entities can remain in an initialized state, which is sufficient for conversion,
|
||||
// without requiring those extra subsystems.
|
||||
|
||||
// This problem also could have been solved by adding APIs to the EditorEntityContextComponent or the EntityContext, but there aren't
|
||||
// any other known valid use cases for disabling this logic, so the extra APIs would simply encourage "bad behavior" by using them
|
||||
// when they likely aren't necessary or desired.
|
||||
|
||||
class SliceConverterEditorEntityContextComponent
|
||||
: public EditorEntityContextComponent
|
||||
{
|
||||
public:
|
||||
|
||||
AZ_COMPONENT(SliceConverterEditorEntityContextComponent, "{1CB0C38F-8E85-4422-91C6-E1F3B9B4B853}");
|
||||
|
||||
SliceConverterEditorEntityContextComponent() : EditorEntityContextComponent() {}
|
||||
|
||||
// Simple API to selectively disable this logic *only* when performing slice to prefab conversion.
|
||||
static inline void DisableOnContextEntityLogic()
|
||||
{
|
||||
m_enableOnContextEntityLogic = false;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
void OnContextEntitiesAdded([[maybe_unused]] const EntityList& entities) override
|
||||
{
|
||||
if (m_enableOnContextEntityLogic)
|
||||
{
|
||||
EditorEntityContextComponent::OnContextEntitiesAdded(entities);
|
||||
}
|
||||
}
|
||||
|
||||
void OnContextEntityRemoved([[maybe_unused]] const AZ::EntityId& id) override
|
||||
{
|
||||
if (m_enableOnContextEntityLogic)
|
||||
{
|
||||
EditorEntityContextComponent::OnContextEntityRemoved(id);
|
||||
}
|
||||
}
|
||||
|
||||
// By default, act just like the EditorEntityContextComponent
|
||||
static inline bool m_enableOnContextEntityLogic = true;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
@@ -209,7 +209,11 @@ namespace AZ::SerializeContextTools
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Utilities::InspectSerializedFile(const char* filePath, SerializeContext* sc, const ObjectStream::ClassReadyCB& classCallback)
|
||||
bool Utilities::InspectSerializedFile(
|
||||
const char* filePath,
|
||||
SerializeContext* sc,
|
||||
const ObjectStream::ClassReadyCB& classCallback,
|
||||
Data::AssetFilterCB assetFilterCallback)
|
||||
{
|
||||
if (!AZ::IO::FileIOBase::GetInstance()->Exists(filePath))
|
||||
{
|
||||
@@ -248,9 +252,9 @@ namespace AZ::SerializeContextTools
|
||||
AZ::IO::MemoryStream stream(data.data(), fileLength);
|
||||
|
||||
ObjectStream::FilterDescriptor filter;
|
||||
// Never load dependencies. That's another file that would need to be processed
|
||||
// By default, never load dependencies. That's another file that would need to be processed
|
||||
// separately from this one.
|
||||
filter.m_assetCB = AZ::Data::AssetFilterNoAssetLoading;
|
||||
filter.m_assetCB = assetFilterCallback;
|
||||
if (!ObjectStream::LoadBlocking(&stream, *sc, classCallback, filter))
|
||||
{
|
||||
AZ_Printf("Verify", "Failed to deserialize '%s'\n", filePath);
|
||||
|
||||
@@ -39,7 +39,11 @@ namespace AZ
|
||||
|
||||
static AZStd::vector<AZ::Uuid> GetSystemComponents(const Application& application);
|
||||
|
||||
static bool InspectSerializedFile(const char* filePath, SerializeContext* sc, const ObjectStream::ClassReadyCB& classCallback);
|
||||
static bool InspectSerializedFile(
|
||||
const char* filePath,
|
||||
SerializeContext* sc,
|
||||
const ObjectStream::ClassReadyCB& classCallback,
|
||||
Data::AssetFilterCB assetFilterCallback = AZ::Data::AssetFilterNoAssetLoading);
|
||||
|
||||
private:
|
||||
Utilities() = delete;
|
||||
|
||||
@@ -125,7 +125,8 @@ int main(int argc, char** argv)
|
||||
}
|
||||
else if (AZ::StringFunc::Equal("convert-slice", action.c_str()))
|
||||
{
|
||||
result = SliceConverter::ConvertSliceFiles(application);
|
||||
SliceConverter sliceConverter;
|
||||
result = sliceConverter.ConvertSliceFiles(application);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ set(FILES
|
||||
Dumper.h
|
||||
Dumper.cpp
|
||||
main.cpp
|
||||
SliceConverterEditorEntityContextComponent.h
|
||||
SliceConverter.h
|
||||
SliceConverter.cpp
|
||||
Utilities.h
|
||||
|
||||
Reference in New Issue
Block a user