Prechádzať zdrojové kódy

Add support for AP-compliant relative paths (#998)

The method "PrefabLoader::GetRelativePathToProject" has been changed to "PrefabLoader::GenerateRelativePath", and reworked to get a correct relative path.  GetFullPath has also been modified to get correct relative paths too.  This requires an Asset Processor connection - if one isn't available (like during unit tests), the methods have fallback logic to produce project-relative paths.

With this change, SliceConverter can't use SaveTemplate() to save the file any more, because GetFullPath now expects to find an existing path, which doesn't work for not-yet-created files.  Instead, it now has to use the same technique as the Editor and call SaveTemplateToString then save the string out as a file.
Mike Balfour 4 rokov pred
rodič
commit
96905a26d7

+ 18 - 11
Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.cpp

@@ -57,7 +57,6 @@ namespace AzToolsFramework
             "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
 
         m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab"));
-
         m_sliceOwnershipService.BusConnect(m_entityContextId);
         m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
         m_editorSliceOwnershipService.BusConnect();
@@ -91,14 +90,17 @@ namespace AzToolsFramework
 
     void PrefabEditorEntityOwnershipService::Reset()
     {
-        Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
-        if (templateId != Prefab::InvalidTemplateId)
+        if (m_rootInstance)
         {
-            m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId);
-            m_prefabSystemComponent->RemoveTemplate(templateId);
+            Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
+            if (templateId != Prefab::InvalidTemplateId)
+            {
+                m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId);
+                m_prefabSystemComponent->RemoveTemplate(templateId);
+            }
+            m_rootInstance->Reset();
+            m_rootInstance->SetContainerEntityName("Level");
         }
-        m_rootInstance->Reset();
-        m_rootInstance->SetContainerEntityName("Level");
 
         AzFramework::EntityOwnershipServiceNotificationBus::Event(
             m_entityContextId, &AzFramework::EntityOwnershipServiceNotificationBus::Events::OnEntityOwnershipServiceReset);
@@ -202,7 +204,7 @@ namespace AzToolsFramework
         }
 
         m_rootInstance->SetTemplateId(templateId);
-        m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GetRelativePathToProject(filename));
+        m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename));
         m_rootInstance->SetContainerEntityName("Level");
         m_prefabSystemComponent->PropagateTemplateChanges(templateId);
 
@@ -220,7 +222,7 @@ namespace AzToolsFramework
 
     bool PrefabEditorEntityOwnershipService::SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename)
     {
-        AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename);
+        AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename);
         AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath);
 
         m_rootInstance->SetTemplateSourcePath(relativePath);
@@ -267,7 +269,7 @@ namespace AzToolsFramework
 
     void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename)
     {
-        AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename);
+        AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename);
         AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath);
 
         m_rootInstance->SetTemplateSourcePath(relativePath);
@@ -378,7 +380,12 @@ namespace AzToolsFramework
     Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::GetRootPrefabInstance()
     {
         AZ_Assert(m_rootInstance, "A valid root prefab instance couldn't be found in PrefabEditorEntityOwnershipService.");
-        return *m_rootInstance;
+        if (m_rootInstance)
+        {
+            return *m_rootInstance;
+        }
+
+        return AZStd::nullopt;
     }
 
     const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData()

+ 1 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceSerializer.cpp

@@ -124,7 +124,7 @@ namespace AzToolsFramework
                         "PrefabLoaderInterface could not be found. It is required to load Prefab Instances");
 
                     // Make sure we have a relative path
-                    instance->m_templateSourcePath = loaderInterface->GetRelativePathToProject(instance->m_templateSourcePath);
+                    instance->m_templateSourcePath = loaderInterface->GenerateRelativePath(instance->m_templateSourcePath);
 
                     TemplateId templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(instance->GetTemplateSourcePath());
 

+ 88 - 7
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp

@@ -18,7 +18,9 @@
 #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
 #include <AzCore/StringFunc/StringFunc.h>
 
+#include <AzFramework/Asset/AssetSystemBus.h>
 #include <AzFramework/FileFunc/FileFunc.h>
+#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
 #include <AzToolsFramework/API/ToolsApplicationAPI.h>
 #include <AzToolsFramework/Prefab/PrefabDomUtils.h>
 #include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
@@ -112,7 +114,7 @@ namespace AzToolsFramework
                 return InvalidTemplateId;
             }
 
-            AZ::IO::Path relativePath = GetRelativePathToProject(originPath);
+            AZ::IO::Path relativePath = GenerateRelativePath(originPath);
 
             // Cyclical dependency detected if the prefab file is already part of the progressed
             // file path set.
@@ -385,21 +387,100 @@ namespace AzToolsFramework
             AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred();
             if (pathWithOSSeparator.IsAbsolute())
             {
+                // If an absolute path was passed in, just return it as-is.
                 return path;
             }
 
-            return AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
+            // A relative path was passed in, so try to turn it back into an absolute path.
+
+            AZ::IO::Path fullPath;
+
+            bool pathFound = false;
+            AZ::Data::AssetInfo assetInfo;
+            AZStd::string rootFolder;
+            AZStd::string inputPath(path.Native());
+
+            // Given an input path that's expected to exist, try to look it up.
+            AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
+                pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
+                inputPath.c_str(), assetInfo, rootFolder);
+
+            if (pathFound)
+            {
+                // The asset system provided us with a valid root folder and relative path, so return it.
+                fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath;
+            }
+            else
+            {
+                // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
+
+                // Check to see if the AssetProcessor is ready.  If it *is* and we didn't get a path, print an error then follow
+                // the fallback logic.  If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
+                // a unit test, so just execute the fallback logic without an error.
+                [[maybe_unused]] bool assetProcessorReady = false;
+                AzFramework::AssetSystemRequestBus::BroadcastResult(
+                    assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
+
+                AZ_Error(
+                    "Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
+                    AZ_STRING_ARG(path.Native()));
+
+                // If a relative path was passed in, make it relative to the project root.
+                fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
+            }
+
+            return fullPath;
         }
 
-        AZ::IO::Path PrefabLoader::GetRelativePathToProject(AZ::IO::PathView path)
+        AZ::IO::Path PrefabLoader::GenerateRelativePath(AZ::IO::PathView path)
         {
-            AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred();
-            if (!pathWithOSSeparator.IsAbsolute())
+            bool pathFound = false;
+
+            AZStd::string relativePath;
+            AZStd::string rootFolder;
+            AZ::IO::Path finalPath;
+
+            // The asset system allows for paths to be relative to multiple root folders, using a priority system.
+            // This request will make the input path relative to the most appropriate, highest-priority root folder.
+            AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
+                pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GenerateRelativeSourcePath, path.Native(),
+                relativePath, rootFolder);
+
+            if (pathFound && !relativePath.empty())
             {
-                return path;
+                // A relative path was generated successfully, so return it.
+                finalPath = relativePath;
+            }
+            else
+            {
+                // If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
+
+                // Check to see if the AssetProcessor is ready.  If it *is* and we didn't get a path, print an error then follow
+                // the fallback logic.  If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
+                // a unit test, so just execute the fallback logic without an error.
+                [[maybe_unused]] bool assetProcessorReady = false;
+                AzFramework::AssetSystemRequestBus::BroadcastResult(
+                    assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
+
+                AZ_Error("Prefab", !assetProcessorReady,
+                    "Relative source path for '%.*s' could not be determined. Using project path as relative root.",
+                    AZ_STRING_ARG(path.Native()));
+
+                AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred();
+
+                if (pathWithOSSeparator.IsAbsolute())
+                {
+                    // If an absolute path was passed in, make it relative to the project path.
+                    finalPath = AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator);
+                }
+                else
+                {
+                    // If a relative path was passed in, just return it.
+                    finalPath = path;
+                }
             }
 
-            return AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator);
+            return finalPath;
         }
 
         AZ::IO::Path PrefabLoaderInterface::GeneratePath()

+ 5 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.h

@@ -91,9 +91,11 @@ namespace AzToolsFramework
             //! The path will always have the correct separator for the current OS
             AZ::IO::Path GetFullPath(AZ::IO::PathView path) override;
 
-            //! Converts path into a relative path to the project, this will be the paths in .prefab file.
-            //! The path will always have '/' separator.
-            AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) override;
+            //! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered
+            //! with the engine.
+            //! This path will be the path that appears in the .prefab file.
+            //! The path will always use the '/' separator.
+            AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) override;
 
             //! Returns if the path is a valid path for a prefab
             static bool IsValidPrefabPath(AZ::IO::PathView path);

+ 5 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoaderInterface.h

@@ -74,9 +74,11 @@ namespace AzToolsFramework
             //! The path will always have the correct separator for the current OS
             virtual AZ::IO::Path GetFullPath(AZ::IO::PathView path) = 0;
 
-            //! Converts path into a relative path to the current project, this will be the paths in .prefab file.
-            //! The path will always have '/' separator.
-            virtual AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) = 0;
+            //! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered
+            //! with the engine.
+            //! This path will be the path that appears in the .prefab file.
+            //! The path will always use the '/' separator.
+            virtual AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) = 0;
 
         protected:
 

+ 1 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp

@@ -318,7 +318,7 @@ namespace AzToolsFramework
             }
 
             //Detect whether this instantiation would produce a cyclical dependency
-            auto relativePath = m_prefabLoaderInterface->GetRelativePathToProject(filePath);
+            auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath);
             Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
 
             if (templateId == InvalidTemplateId)

+ 1 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp

@@ -95,7 +95,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, bool shouldCreateLinks)
         {
-            AZ::IO::Path relativeFilePath = m_prefabLoader.GetRelativePathToProject(filePath);
+            AZ::IO::Path relativeFilePath = m_prefabLoader.GenerateRelativePath(filePath);
             if (GetTemplateIdFromFilePath(relativeFilePath) != InvalidTemplateId)
             {
                 AZ_Error("Prefab", false,

+ 2 - 1
Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp

@@ -333,7 +333,8 @@ namespace AzToolsFramework
                 }
             }
 
-            auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, s_prefabLoaderInterface->GetRelativePathToProject(prefabFilePath.data()));
+            auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(
+                selectedEntities, s_prefabLoaderInterface->GenerateRelativePath(prefabFilePath.data()));
 
             if (!createPrefabOutcome.IsSuccess())
             {

+ 21 - 7
Code/Tools/SerializeContextTools/SliceConverter.cpp

@@ -244,7 +244,7 @@ namespace AZ
             }
             else
             {
-                return SavePrefab(templateId);
+                return SavePrefab(outputPath, templateId);
             }
         }
 
@@ -318,7 +318,7 @@ namespace AZ
                 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);
@@ -439,17 +439,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()

+ 1 - 1
Code/Tools/SerializeContextTools/SliceConverter.h

@@ -56,7 +56,7 @@ namespace AZ
                 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);
+            static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
         };
     } // namespace SerializeContextTools
 } // namespace AZ