Merge branch 'stabilization/2106' into Helios_AssImp_TransformImporterFix
This commit is contained in:
@@ -28,6 +28,10 @@ ly_add_target(
|
||||
AZ::AzToolsFramework
|
||||
)
|
||||
|
||||
if(LY_DEFAULT_PROJECT_PATH)
|
||||
set_property(TARGET AssetBuilder APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_DEFAULT_PROJECT_PATH}\"")
|
||||
endif()
|
||||
|
||||
# Aggregates all combined AssetBuilders into a single LY_ASSET_BUILDERS #define
|
||||
get_property(asset_builders GLOBAL PROPERTY LY_ASSET_BUILDERS)
|
||||
string (REPLACE ";" "," asset_builders "${asset_builders}")
|
||||
|
||||
@@ -259,8 +259,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
|
||||
ly_add_googletest(
|
||||
NAME AZ::AssetProcessor.Tests
|
||||
TEST_COMMAND $<TARGET_FILE:AZ::AssetProcessor.Tests> --unittest
|
||||
TEST_COMMAND $<TARGET_FILE:AZ::AssetProcessor.Tests> --unittest --gtest_filter=-*.SUITE_sandbox*
|
||||
)
|
||||
|
||||
|
||||
endif()
|
||||
|
||||
@@ -11,9 +11,127 @@
|
||||
*/
|
||||
#include <native/FileWatcher/FileWatcher.h>
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QHash>
|
||||
#include <QMutex>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/inotify.h>
|
||||
|
||||
|
||||
static constexpr int s_handleToFolderMapLockTimeout = 1000; // 1 sec timeout for obtaining the handle to folder map lock
|
||||
static constexpr size_t s_iNotifyMaxEntries = 1024 * 16; // Control the maximum number of entries (from inotify) that can be read at one time
|
||||
static constexpr size_t s_iNotifyEventSize = sizeof(struct inotify_event);
|
||||
static constexpr size_t s_iNotifyReadBufferSize = s_iNotifyMaxEntries * s_iNotifyEventSize;
|
||||
|
||||
struct FolderRootWatch::PlatformImplementation
|
||||
{
|
||||
PlatformImplementation() { }
|
||||
PlatformImplementation() = default;
|
||||
|
||||
int m_iNotifyHandle = -1;
|
||||
QMutex m_handleToFolderMapLock;
|
||||
QHash<int, QString> m_handleToFolderMap;
|
||||
|
||||
bool Initialize()
|
||||
{
|
||||
if (m_iNotifyHandle < 0)
|
||||
{
|
||||
m_iNotifyHandle = inotify_init();
|
||||
}
|
||||
return (m_iNotifyHandle >= 0);
|
||||
}
|
||||
|
||||
void Finalize()
|
||||
{
|
||||
if (m_iNotifyHandle >= 0)
|
||||
{
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
|
||||
return;
|
||||
}
|
||||
|
||||
QHashIterator<int, QString> iter(m_handleToFolderMap);
|
||||
while (iter.hasNext())
|
||||
{
|
||||
iter.next();
|
||||
int watchHandle = iter.key();
|
||||
inotify_rm_watch(m_iNotifyHandle, watchHandle);
|
||||
}
|
||||
m_handleToFolderMap.clear();
|
||||
m_handleToFolderMapLock.unlock();
|
||||
|
||||
::close(m_iNotifyHandle);
|
||||
m_iNotifyHandle = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void AddWatchFolder(QString folder)
|
||||
{
|
||||
if (m_iNotifyHandle >= 0)
|
||||
{
|
||||
// Clean up the path before accepting it as a watch folder
|
||||
QString cleanPath = QDir::cleanPath(folder);
|
||||
|
||||
// Add the folder to watch and track it
|
||||
int watchHandle = inotify_add_watch(m_iNotifyHandle,
|
||||
cleanPath.toUtf8().constData(),
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
|
||||
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
|
||||
return;
|
||||
}
|
||||
m_handleToFolderMap[watchHandle] = cleanPath;
|
||||
m_handleToFolderMapLock.unlock();
|
||||
|
||||
// Add all the subfolders to watch and track them
|
||||
QDirIterator dirIter(folder, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
QString dirName = dirIter.next();
|
||||
if (dirName.endsWith("/.") || dirName.endsWith("/.."))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int watchHandle = inotify_add_watch(m_iNotifyHandle,
|
||||
dirName.toUtf8().constData(),
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
|
||||
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
|
||||
return;
|
||||
}
|
||||
m_handleToFolderMap[watchHandle] = dirName;
|
||||
m_handleToFolderMapLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveWatchFolder(int watchHandle)
|
||||
{
|
||||
if (m_iNotifyHandle >= 0)
|
||||
{
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
AZ_Error("FileWatcher", false, "Unable to obtain inotify handle lock on thread");
|
||||
return;
|
||||
}
|
||||
|
||||
QHash<int, QString>::iterator handleToRemove = m_handleToFolderMap.find(watchHandle);
|
||||
if (handleToRemove != m_handleToFolderMap.end())
|
||||
{
|
||||
inotify_rm_watch(m_iNotifyHandle, watchHandle);
|
||||
m_handleToFolderMap.erase(handleToRemove);
|
||||
}
|
||||
|
||||
m_handleToFolderMapLock.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -36,17 +154,86 @@ FolderRootWatch::~FolderRootWatch()
|
||||
|
||||
bool FolderRootWatch::Start()
|
||||
{
|
||||
// TODO: Implement for Linux
|
||||
return false;
|
||||
// inotify will be used by linux to monitor file changes within directories under the root folder
|
||||
if (!m_platformImpl->Initialize())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
m_platformImpl->AddWatchFolder(m_root);
|
||||
|
||||
m_shutdownThreadSignal = false;
|
||||
m_thread = std::thread([this]() { WatchFolderLoop(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
void FolderRootWatch::Stop()
|
||||
{
|
||||
// TODO: Implement for Linux
|
||||
m_shutdownThreadSignal = true;
|
||||
|
||||
m_platformImpl->Finalize();
|
||||
|
||||
if (m_thread.joinable())
|
||||
{
|
||||
m_thread.join(); // wait for the thread to finish
|
||||
m_thread = std::thread(); //destroy
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FolderRootWatch::WatchFolderLoop()
|
||||
{
|
||||
// TODO: Implement for Linux
|
||||
char eventBuffer[s_iNotifyReadBufferSize];
|
||||
while (!m_shutdownThreadSignal)
|
||||
{
|
||||
ssize_t bytesRead = ::read(m_platformImpl->m_iNotifyHandle, eventBuffer, s_iNotifyReadBufferSize);
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
// Break out of the loop when the notify handle was closed (outside of this thread)
|
||||
break;
|
||||
}
|
||||
else if (bytesRead > 0)
|
||||
{
|
||||
for (size_t index=0; index<bytesRead;)
|
||||
{
|
||||
struct inotify_event *event = ( struct inotify_event * ) &eventBuffer[ index ];
|
||||
const char* eventName = event->name;
|
||||
|
||||
if (event->mask & (IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVE ))
|
||||
{
|
||||
QString pathStr = QString("%1%2%3").arg(m_platformImpl->m_handleToFolderMap[event->wd], QDir::separator(), event->name);
|
||||
|
||||
if (event->mask & (IN_CREATE | IN_MOVED_TO))
|
||||
{
|
||||
if ( event->mask & IN_ISDIR )
|
||||
{
|
||||
// New Directory, add it to the watch
|
||||
m_platformImpl->AddWatchFolder(pathStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessNewFileEvent(pathStr);
|
||||
}
|
||||
}
|
||||
else if (event->mask & (IN_DELETE | IN_MOVED_FROM))
|
||||
{
|
||||
if (event->mask & IN_ISDIR)
|
||||
{
|
||||
// Directory Deleted, remove it from the watch
|
||||
m_platformImpl->RemoveWatchFolder(event->wd);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessDeleteFileEvent(pathStr);
|
||||
}
|
||||
}
|
||||
else if ((event->mask & IN_MODIFY) && ((event->mask & IN_ISDIR) != IN_ISDIR))
|
||||
{
|
||||
ProcessModifyFileEvent(pathStr);
|
||||
}
|
||||
}
|
||||
index += s_iNotifyEventSize + event->len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <native/FileWatcher/FileWatcher.h>
|
||||
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
|
||||
struct FolderRootWatch::PlatformImplementation
|
||||
{
|
||||
PlatformImplementation() : m_directoryHandle(nullptr), m_ioHandle(nullptr) { }
|
||||
HANDLE m_directoryHandle;
|
||||
HANDLE m_ioHandle;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// FolderWatchRoot
|
||||
FolderRootWatch::FolderRootWatch(const QString rootFolder)
|
||||
: m_root(rootFolder)
|
||||
, m_shutdownThreadSignal(false)
|
||||
, m_fileWatcher(nullptr)
|
||||
, m_platformImpl(new PlatformImplementation())
|
||||
{
|
||||
}
|
||||
|
||||
FolderRootWatch::~FolderRootWatch()
|
||||
{
|
||||
// Destructor is required in here since this file contains the definition of struct PlatformImplementation
|
||||
Stop();
|
||||
|
||||
delete m_platformImpl;
|
||||
}
|
||||
|
||||
bool FolderRootWatch::Start()
|
||||
{
|
||||
m_platformImpl->m_directoryHandle = ::CreateFileW(m_root.toStdWString().data(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
|
||||
|
||||
if (m_platformImpl->m_directoryHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_platformImpl->m_ioHandle = ::CreateIoCompletionPort(m_platformImpl->m_directoryHandle, nullptr, 1, 0);
|
||||
if (m_platformImpl->m_ioHandle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_shutdownThreadSignal = false;
|
||||
m_thread = std::thread(std::bind(&FolderRootWatch::WatchFolderLoop, this));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FolderRootWatch::Stop()
|
||||
{
|
||||
m_shutdownThreadSignal = true;
|
||||
CloseHandle(m_platformImpl->m_ioHandle);
|
||||
m_platformImpl->m_ioHandle = nullptr;
|
||||
|
||||
if (m_thread.joinable())
|
||||
{
|
||||
m_thread.join(); // wait for the thread to finish
|
||||
m_thread = std::thread(); //destroy
|
||||
}
|
||||
CloseHandle(m_platformImpl->m_directoryHandle);
|
||||
m_platformImpl->m_directoryHandle = nullptr;
|
||||
}
|
||||
|
||||
void FolderRootWatch::WatchFolderLoop()
|
||||
{
|
||||
FILE_NOTIFY_INFORMATION aFileNotifyInformationList[50000];
|
||||
QString path;
|
||||
OVERLAPPED aOverlapped;
|
||||
LPOVERLAPPED pOverlapped;
|
||||
DWORD dwByteCount;
|
||||
ULONG_PTR ulKey;
|
||||
|
||||
while (!m_shutdownThreadSignal)
|
||||
{
|
||||
::memset(aFileNotifyInformationList, 0, sizeof(aFileNotifyInformationList));
|
||||
::memset(&aOverlapped, 0, sizeof(aOverlapped));
|
||||
|
||||
if (::ReadDirectoryChangesW(m_platformImpl->m_directoryHandle, aFileNotifyInformationList, sizeof(aFileNotifyInformationList), true, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_FILE_NAME, nullptr, &aOverlapped, nullptr))
|
||||
{
|
||||
//wait for up to a second for I/O to signal
|
||||
dwByteCount = 0;
|
||||
if (::GetQueuedCompletionStatus(m_platformImpl->m_ioHandle, &dwByteCount, &ulKey, &pOverlapped, INFINITE))
|
||||
{
|
||||
//if we are signaled to shutdown bypass
|
||||
if (!m_shutdownThreadSignal && ulKey)
|
||||
{
|
||||
if (dwByteCount)
|
||||
{
|
||||
int offset = 0;
|
||||
FILE_NOTIFY_INFORMATION* pFileNotifyInformation = aFileNotifyInformationList;
|
||||
do
|
||||
{
|
||||
pFileNotifyInformation = (FILE_NOTIFY_INFORMATION*)((char*)pFileNotifyInformation + offset);
|
||||
|
||||
path.clear();
|
||||
path.append(m_root);
|
||||
path.append(QString::fromWCharArray(pFileNotifyInformation->FileName, pFileNotifyInformation->FileNameLength / 2));
|
||||
|
||||
QString file = QDir::toNativeSeparators(QDir::cleanPath(path));
|
||||
|
||||
switch (pFileNotifyInformation->Action)
|
||||
{
|
||||
case FILE_ACTION_ADDED:
|
||||
case FILE_ACTION_RENAMED_NEW_NAME:
|
||||
ProcessNewFileEvent(file);
|
||||
break;
|
||||
case FILE_ACTION_REMOVED:
|
||||
case FILE_ACTION_RENAMED_OLD_NAME:
|
||||
ProcessDeleteFileEvent(file);
|
||||
break;
|
||||
case FILE_ACTION_MODIFIED:
|
||||
ProcessModifyFileEvent(file);
|
||||
break;
|
||||
}
|
||||
|
||||
offset = pFileNotifyInformation->NextEntryOffset;
|
||||
} while (offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,10 +445,10 @@ namespace AssetProcessor
|
||||
bool isExcludedDependency = dependencyPathSearch.starts_with(ExcludedDependenciesSymbol);
|
||||
dependencyPathSearch = isExcludedDependency ? dependencyPathSearch.substr(1) : dependencyPathSearch;
|
||||
bool isExactDependency = !AzFramework::StringFunc::Replace(dependencyPathSearch, '*', '%');
|
||||
SanitizeForDatabase(dependencyPathSearch);
|
||||
|
||||
if (cleanedupDependency.m_dependencyType == AssetBuilderSDK::ProductPathDependencyType::ProductFile)
|
||||
{
|
||||
SanitizeForDatabase(dependencyPathSearch);
|
||||
AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer productInfoContainer;
|
||||
QString productNameWithPlatform = QString("%1%2%3").arg(platform.c_str(), AZ_CORRECT_DATABASE_SEPARATOR_STRING, dependencyPathSearch.c_str());
|
||||
|
||||
@@ -508,6 +508,10 @@ namespace AssetProcessor
|
||||
}
|
||||
else
|
||||
{
|
||||
// For source assets, the casing of the input path must be maintained. Just fix up the path separators.
|
||||
AZStd::replace(dependencyPathSearch.begin(), dependencyPathSearch.end(), AZ_WRONG_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR);
|
||||
AzFramework::StringFunc::Replace(dependencyPathSearch, AZ_DOUBLE_CORRECT_DATABASE_SEPARATOR, AZ_CORRECT_DATABASE_SEPARATOR_STRING);
|
||||
|
||||
// See if path matches any source files
|
||||
AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer sourceInfoContainer;
|
||||
|
||||
|
||||
@@ -190,44 +190,55 @@ Please note that only those seed files will get updated that are active for your
|
||||
void SourceFileRelocator::HandleMetaDataFiles(QStringList pathMatches, QHash<QString, int>& sourceIndexMap, const ScanFolderInfo* scanFolderInfo, SourceFileRelocationContainer& metadataFiles, bool excludeMetaDataFiles) const
|
||||
{
|
||||
QSet<QString> metaDataFileEntries;
|
||||
for (QString file : pathMatches)
|
||||
|
||||
// Remove all the metadata files
|
||||
if (excludeMetaDataFiles)
|
||||
{
|
||||
pathMatches.erase(AZStd::remove_if(pathMatches.begin(), pathMatches.end(), [this](const QString& file)
|
||||
{
|
||||
for (int idx = 0; idx < m_platformConfig->MetaDataFileTypesCount(); idx++)
|
||||
{
|
||||
const auto& [metadataType, extension] = m_platformConfig->GetMetaDataFileTypeAt(idx);
|
||||
if (file.endsWith("." + metadataType, Qt::CaseInsensitive))
|
||||
{
|
||||
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Metadata file %s will be ignored because --excludeMetadataFiles was specified in the command line.\n",
|
||||
file.toUtf8().constData());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
pathMatches.end());
|
||||
}
|
||||
|
||||
for (const QString& file : pathMatches)
|
||||
{
|
||||
for (int idx = 0; idx < m_platformConfig->MetaDataFileTypesCount(); idx++)
|
||||
{
|
||||
QPair<QString, QString> metaInfo = m_platformConfig->GetMetaDataFileTypeAt(idx);
|
||||
if (file.endsWith("." + metaInfo.first, Qt::CaseInsensitive))
|
||||
const auto& [metadataType, extension] = m_platformConfig->GetMetaDataFileTypeAt(idx);
|
||||
if (file.endsWith("." + metadataType, Qt::CaseInsensitive))
|
||||
{
|
||||
//it is a metadata file
|
||||
if (excludeMetaDataFiles)
|
||||
const QString normalizedFilePath = AssetUtilities::NormalizeFilePath(file);
|
||||
if (!metaDataFileEntries.contains(normalizedFilePath))
|
||||
{
|
||||
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Metadata file %s will be ignored because --excludeMetadataFiles was specified in the command line.\n",
|
||||
file.toUtf8().constData());
|
||||
break; // don't check it against other metafile entries, we've already ascertained its a metafile.
|
||||
}
|
||||
else
|
||||
{
|
||||
QString normalizedFilePath = AssetUtilities::NormalizeFilePath(file);
|
||||
if (metaDataFileEntries.find(normalizedFilePath) == metaDataFileEntries.end())
|
||||
{
|
||||
SourceFileRelocationInfo metaDataFile(file.toUtf8().data(), scanFolderInfo);
|
||||
metaDataFile.m_isMetaDataFile = true;
|
||||
metadataFiles.emplace_back(metaDataFile);
|
||||
metaDataFileEntries.insert(normalizedFilePath);
|
||||
}
|
||||
SourceFileRelocationInfo metaDataFile(file.toUtf8().data(), scanFolderInfo);
|
||||
metaDataFile.m_isMetaDataFile = true;
|
||||
metadataFiles.emplace_back(metaDataFile);
|
||||
metaDataFileEntries.insert(normalizedFilePath);
|
||||
}
|
||||
}
|
||||
else if (!excludeMetaDataFiles && (file.endsWith("." + metaInfo.second, Qt::CaseInsensitive) || metaInfo.second.isEmpty()))
|
||||
else if (!excludeMetaDataFiles && (file.endsWith("." + extension, Qt::CaseInsensitive) || extension.isEmpty()))
|
||||
{
|
||||
// if we are here it implies that a metadata file might exists for this source file,
|
||||
// add metadata file only if it exists and is not added already
|
||||
AZStd::string metadataFilePath(file.toUtf8().data());
|
||||
if (metaInfo.second.isEmpty())
|
||||
if (extension.isEmpty())
|
||||
{
|
||||
metadataFilePath.append(AZStd::string::format(".%s", metaInfo.first.toUtf8().data()));
|
||||
metadataFilePath.append(AZStd::string::format(".%s", metadataType.toUtf8().data()));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::StringFunc::Path::ReplaceExtension(metadataFilePath, metaInfo.first.toUtf8().data());
|
||||
AZ::StringFunc::Path::ReplaceExtension(metadataFilePath, metadataType.toUtf8().data());
|
||||
};
|
||||
|
||||
// The metadata file can have a different case than the source file,
|
||||
|
||||
@@ -1697,7 +1697,10 @@ namespace AssetProcessor
|
||||
|
||||
if(productFileInfo.absoluteDir().entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot).empty())
|
||||
{
|
||||
productFileInfo.absoluteDir().rmdir(".");
|
||||
const QDir productDir = productFileInfo.absoluteDir();
|
||||
QDir parentDir = productDir;
|
||||
parentDir.cdUp();
|
||||
successfullyRemoved &= parentDir.rmdir(productDir.dirName());
|
||||
}
|
||||
|
||||
if (successfullyRemoved)
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace AssetProcessorMessagesTests
|
||||
using namespace AssetProcessor;
|
||||
using namespace AssetBuilderSDK;
|
||||
|
||||
static constexpr unsigned short AssetProcessorPort = static_cast<unsigned short>(888u);
|
||||
static constexpr unsigned short AssetProcessorPort{65535u};
|
||||
|
||||
class AssetProcessorMessages;
|
||||
|
||||
@@ -85,7 +85,6 @@ namespace AssetProcessorMessagesTests
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
#if !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
AssetUtilities::ResetGameName();
|
||||
|
||||
m_temporarySourceDir = QDir(m_temporaryDir.path());
|
||||
@@ -166,12 +165,10 @@ namespace AssetProcessorMessagesTests
|
||||
});
|
||||
|
||||
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
#if !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
QEventLoop eventLoop;
|
||||
|
||||
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
|
||||
@@ -182,7 +179,6 @@ namespace AssetProcessorMessagesTests
|
||||
|
||||
m_assetSystemComponent->Deactivate();
|
||||
m_batchApplicationManager->Destroy();
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
}
|
||||
|
||||
void RunNetworkRequest(AZStd::function<void()> func) const
|
||||
@@ -207,6 +203,7 @@ namespace AssetProcessorMessagesTests
|
||||
|
||||
thread.join();
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
MockAssetRequestHandler* m_assetRequestHandler{}; // Not owned, AP will delete this pointer
|
||||
@@ -226,11 +223,7 @@ namespace AssetProcessorMessagesTests
|
||||
AZStd::unique_ptr<AzFramework::AssetSystem::BaseAssetProcessorMessage> m_response;
|
||||
};
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(AssetProcessorMessages, DISABLED_All)
|
||||
#else
|
||||
TEST_F(AssetProcessorMessages, All)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
// Test that we can successfully send network messages and have them arrive for processing
|
||||
// For messages that have a response, it also verifies the response comes back
|
||||
@@ -311,11 +304,7 @@ namespace AssetProcessorMessagesTests
|
||||
});
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(AssetProcessorMessages, DISABLED_GetUnresolvedProductReferences_Succeeds)
|
||||
#else
|
||||
TEST_F(AssetProcessorMessages, GetUnresolvedProductReferences_Succeeds)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AzToolsFramework::AssetDatabase;
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ namespace UnitTests
|
||||
SourceDatabaseEntry sourceFile8 = { m_data->m_scanFolder1.m_scanFolderID, "test.txt", AZ::Uuid::CreateRandom(), "AnalysisFingerprint" };
|
||||
SourceDatabaseEntry sourceFile9 = { m_data->m_scanFolder1.m_scanFolderID, "duplicate/folder/file1.tif", AZ::Uuid::CreateRandom(), "AnalysisFingerprint" };
|
||||
SourceDatabaseEntry sourceFile10 = { m_data->m_scanFolder1.m_scanFolderID, "folder/file.foo", AZ::Uuid::CreateRandom(), "AnalysisFingerprint" };
|
||||
SourceDatabaseEntry sourceFile11 = { m_data->m_scanFolder1.m_scanFolderID, "testfolder/file.foo", AZ::Uuid::CreateRandom(), "AnalysisFingerprint" };
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile1));
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile2));
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile3));
|
||||
@@ -123,7 +122,6 @@ namespace UnitTests
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile8));
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile9));
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile10));
|
||||
ASSERT_TRUE(m_data->m_connection->SetSource(sourceFile11));
|
||||
|
||||
SourceFileDependencyEntry dependency1 = { AZ::Uuid::CreateRandom(), "subfolder1/somefile.tif", "subfolder1/otherfile.tif", SourceFileDependencyEntry::TypeOfDependency::DEP_SourceToSource, false };
|
||||
SourceFileDependencyEntry dependency2 = { AZ::Uuid::CreateRandom(), "subfolder1/otherfile.tif", "otherfile.tif", SourceFileDependencyEntry::TypeOfDependency::DEP_JobToJob, false };
|
||||
@@ -176,8 +174,6 @@ namespace UnitTests
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/dummy/foo.metadataextension")));
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/folder/file.foo")));
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/folder/file.bar")));
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/testfolder/file.foo")));
|
||||
ASSERT_TRUE(UnitTestUtils::CreateDummyFile(tempPath.absoluteFilePath("dev/testfolder/File.bar")));
|
||||
|
||||
if (AZ::IO::FileIOBase::GetInstance() == nullptr)
|
||||
{
|
||||
@@ -487,20 +483,12 @@ namespace UnitTests
|
||||
TestGetSourcesByPath("dev/", { }, false);
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(SourceFileRelocatorTest, DISABLED_GetSources_MultipleScanFolders_Fails)
|
||||
#else
|
||||
TEST_F(SourceFileRelocatorTest, GetSources_MultipleScanFolders_Fails)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
TestGetSourcesByPath("*", { }, false);
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(SourceFileRelocatorTest, DISABLED_GetSources_PartialPath_FailsWithNoResults)
|
||||
#else
|
||||
TEST_F(SourceFileRelocatorTest, GetSources_PartialPath_FailsWithNoResults)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
TestGetSourcesByPath("older/*", { }, false);
|
||||
}
|
||||
@@ -547,18 +535,6 @@ namespace UnitTests
|
||||
TestGetSourcesByPath(filePath.toUtf8().constData(), { "folder/file.foo" }, true, true);
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(SourceFileRelocatorTest, DISABLED_GetSources_HaveMetadataDifferentFileCase_AbsolutePath_Succeeds)
|
||||
#else
|
||||
TEST_F(SourceFileRelocatorTest, GetSources_HaveMetadataDifferentFileCase_AbsolutePath_Succeeds)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
auto filePath = QDir(tempPath.absoluteFilePath(m_data->m_scanFolder1.m_scanFolder.c_str())).absoluteFilePath("testfolder/file.foo");
|
||||
TestGetSourcesByPath(filePath.toUtf8().constData(), { "testfolder/file.foo", "testfolder/File.bar" }, true, false);
|
||||
}
|
||||
|
||||
TEST_F(SourceFileRelocatorTest, GetMetaDataFile_SingleFileWildcard_Succeeds)
|
||||
{
|
||||
QDir tempPath(m_tempDir.path());
|
||||
@@ -921,32 +897,46 @@ namespace UnitTests
|
||||
ASSERT_FALSE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData()));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(SourceFileRelocatorTest, DISABLED_Delete_Real_Readonly_Fails)
|
||||
#else
|
||||
TEST_F(SourceFileRelocatorTest, Delete_Real_Readonly_Fails)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
struct AutoResetDirectoryReadOnlyState
|
||||
{
|
||||
AutoResetDirectoryReadOnlyState(QString dirName)
|
||||
: m_dirName(AZStd::move(dirName))
|
||||
{
|
||||
AZ::IO::SystemFile::SetWritable(m_dirName.toUtf8().constData(), false);
|
||||
}
|
||||
~AutoResetDirectoryReadOnlyState()
|
||||
{
|
||||
AZ::IO::SystemFile::SetWritable(m_dirName.toUtf8().constData(), true);
|
||||
}
|
||||
AZ_DISABLE_COPY_MOVE(AutoResetDirectoryReadOnlyState)
|
||||
private:
|
||||
QString m_dirName;
|
||||
};
|
||||
|
||||
QDir tempPath(m_tempDir.path());
|
||||
|
||||
auto filePath = QDir(tempPath.absoluteFilePath(m_data->m_scanFolder1.m_scanFolder.c_str())).absoluteFilePath("duplicate/file1.tif");
|
||||
|
||||
ASSERT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData()));
|
||||
|
||||
AutoResetDirectoryReadOnlyState readOnlyResetter(QFileInfo(filePath).absoluteDir().absolutePath());
|
||||
|
||||
AZ::IO::SystemFile::SetWritable(filePath.toUtf8().constData(), false);
|
||||
|
||||
auto result = m_data->m_reporter->Delete(filePath.toUtf8().constData(), false);
|
||||
|
||||
ASSERT_TRUE(result.IsSuccess());
|
||||
EXPECT_TRUE(result.IsSuccess());
|
||||
|
||||
RelocationSuccess successResult = result.TakeValue();
|
||||
|
||||
ASSERT_EQ(successResult.m_moveSuccessCount, 0);
|
||||
ASSERT_EQ(successResult.m_moveFailureCount, 1);
|
||||
ASSERT_EQ(successResult.m_moveTotalCount, 1);
|
||||
ASSERT_EQ(successResult.m_updateTotalCount, 0);
|
||||
EXPECT_EQ(successResult.m_moveSuccessCount, 0);
|
||||
EXPECT_EQ(successResult.m_moveFailureCount, 1);
|
||||
EXPECT_EQ(successResult.m_moveTotalCount, 1);
|
||||
EXPECT_EQ(successResult.m_updateTotalCount, 0);
|
||||
|
||||
ASSERT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData()));
|
||||
EXPECT_TRUE(AZ::IO::FileIOBase::GetInstance()->Exists(filePath.toUtf8().constData()));
|
||||
}
|
||||
|
||||
TEST_F(SourceFileRelocatorTest, Delete_Real_WithDependencies_Fails)
|
||||
|
||||
@@ -30,11 +30,7 @@ public:
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies);
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_DeferredResolution);
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, DISABLED_AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
|
||||
#else
|
||||
friend class GTEST_TEST_CLASS_NAME_(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath);
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(AssetProcessorManagerTest, DeleteFolder_SignalsDeleteOfContainedFiles);
|
||||
|
||||
@@ -70,19 +66,11 @@ public:
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedProductPathDependency_AssetProcessedTwice_ValidatePathDependenciesMap);
|
||||
friend class GTEST_TEST_CLASS_NAME_(AbsolutePathProductDependencyTest, UnresolvedSourceFileTypeProductPathDependency_DependencyHasNoProductOutput_ValidatePathDependenciesMap);
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, DISABLED_ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping);
|
||||
#else
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping);
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_FileUnchanged);
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, DISABLED_ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform);
|
||||
#else
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform);
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile);
|
||||
friend class GTEST_TEST_CLASS_NAME_(ModtimeScanningTest, ModtimeSkipping_ModifyFile_AndThenRevert_ProcessesAgain);
|
||||
@@ -2362,11 +2350,7 @@ TEST_F(PathDependencyTest, ChangeDependencies_Existing_ResolveCorrectly)
|
||||
);
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(PathDependencyTest, DISABLED_MixedPathDependencies_Existing_ResolveCorrectly)
|
||||
#else
|
||||
TEST_F(PathDependencyTest, MixedPathDependencies_Existing_ResolveCorrectly)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AssetProcessor;
|
||||
using namespace AssetBuilderSDK;
|
||||
@@ -2661,11 +2645,7 @@ TEST_F(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDepende
|
||||
ASSERT_NE(SearchDependencies(dependencyContainer, asset1.m_products[0]), SearchDependencies(dependencyContainer, asset1.m_products[1]));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(MultiplatformPathDependencyTest, DISABLED_AssetProcessed_Impl_MultiplatformDependencies_SourcePath)
|
||||
#else
|
||||
TEST_F(MultiplatformPathDependencyTest, AssetProcessed_Impl_MultiplatformDependencies_SourcePath)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
// One product will be pc, one will be console (order is non-deterministic)
|
||||
TestAsset asset1("testAsset1");
|
||||
@@ -3894,7 +3874,7 @@ void ModtimeScanningTest::ProcessAssetJobs()
|
||||
|
||||
for (const auto& processResult : m_data->m_processResults)
|
||||
{
|
||||
auto file = QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName + ".arc1");
|
||||
auto file = QDir(processResult.m_destinationPath).absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName.toLower() + ".arc1");
|
||||
m_data->m_productPaths.emplace(
|
||||
QDir(processResult.m_jobEntry.m_watchFolderPath)
|
||||
.absoluteFilePath(processResult.m_jobEntry.m_databaseSourceName)
|
||||
@@ -3943,11 +3923,11 @@ void ModtimeScanningTest::ExpectWork(int createJobs, int processJobs)
|
||||
{
|
||||
ASSERT_TRUE(BlockUntilIdle(5000));
|
||||
|
||||
ASSERT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
|
||||
ASSERT_EQ(m_data->m_processResults.size(), processJobs);
|
||||
ASSERT_FALSE(m_data->m_processResults[0].m_autoFail);
|
||||
ASSERT_FALSE(m_data->m_processResults[1].m_autoFail);
|
||||
ASSERT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
EXPECT_EQ(m_data->m_mockBuilderInfoHandler.m_createJobsCount, createJobs);
|
||||
EXPECT_EQ(m_data->m_processResults.size(), processJobs);
|
||||
EXPECT_FALSE(m_data->m_processResults[0].m_autoFail);
|
||||
EXPECT_FALSE(m_data->m_processResults[1].m_autoFail);
|
||||
EXPECT_EQ(m_data->m_deletedSources.size(), 0);
|
||||
|
||||
m_isIdling = false;
|
||||
}
|
||||
@@ -3975,11 +3955,7 @@ void ModtimeScanningTest::SetFileContents(QString filePath, QString contents)
|
||||
file.close();
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(ModtimeScanningTest, DISABLED_ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
|
||||
#else
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged_WithoutModtimeSkipping)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
@@ -4008,11 +3984,7 @@ TEST_F(ModtimeScanningTest, ModtimeSkipping_FileUnchanged)
|
||||
ExpectNoWork();
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(ModtimeScanningTest, DISABLED_ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
|
||||
#else
|
||||
TEST_F(ModtimeScanningTest, ModtimeSkipping_EnablePlatform_ShouldProcessFilesForPlatform)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
|
||||
@@ -4633,11 +4605,7 @@ TEST_F(AssetProcessorManagerTest, UpdateSourceFileDependenciesDatabase_WildcardM
|
||||
dependList.clear();
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(AssetProcessorManagerTest, DISABLED_RemoveSource_RemoveCacheFolderIfEmpty_Ok)
|
||||
#else
|
||||
TEST_F(AssetProcessorManagerTest, RemoveSource_RemoveCacheFolderIfEmpty_Ok)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AssetProcessor;
|
||||
using namespace AssetBuilderSDK;
|
||||
|
||||
+40
-54
@@ -14,6 +14,7 @@
|
||||
#include "native/tests/platformconfiguration/platformconfigurationtests.h"
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
const char TestAppRoot[] = "@exefolder@/testdata";
|
||||
const char EmptyDummyProjectName[] = "EmptyDummyProject";
|
||||
@@ -238,33 +239,6 @@ TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetScanFolderForFile_Sub
|
||||
EXPECT_STREQ(info->GetDisplayName().toUtf8().constData(), "Editor ScanFolder");
|
||||
}
|
||||
|
||||
// note that in the case of GetOverridingFile, this SHOULD return the correct case if an override is found
|
||||
// because its possible to override a file with another file with different case in a different scan folder
|
||||
// such a situation is supposed to be very rare, so the cost of correcting the case is mitigated.
|
||||
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetOverridingFile_Exists_ReturnsCorrectCase)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
using namespace AssetProcessor;
|
||||
|
||||
// create two scan folders, since its order dependent, the ScanFolder1 is the "winner" in tie breakers (when they both contain same file relpath)
|
||||
QString scanfolder1Path = m_tempPath.filePath("scanfolder1");
|
||||
QString scanfolder2Path = m_tempPath.filePath("scanfolder2");
|
||||
QString caseSensitiveDummyFileName = m_tempPath.absoluteFilePath("scanfolder1/TestCase.tXt");
|
||||
QString differentCaseDummyFileName = m_tempPath.absoluteFilePath("scanfolder2/testcase.txt");
|
||||
UnitTestUtils::CreateDummyFile(caseSensitiveDummyFileName, QString("testcase1\n"));
|
||||
UnitTestUtils::CreateDummyFile(differentCaseDummyFileName, QString("testcase2\n"));
|
||||
m_config->AddScanFolder(ScanFolderInfo(scanfolder1Path, "ScanFolder1", "sf1", false, true, m_platforms), true);
|
||||
m_config->AddScanFolder(ScanFolderInfo(scanfolder2Path, "ScanFolder2", "sf2", false, true, m_platforms), true);
|
||||
|
||||
// Perform the test by asking it whether anyone overrides "testcase" (lowercase) in scanfolder 2.
|
||||
QString overrider = m_config->GetOverridingFile("testcase.txt", scanfolder2Path);
|
||||
|
||||
ASSERT_FALSE(overrider.isEmpty());
|
||||
// the result should be the real actual case of the file in scanfolder 1:
|
||||
EXPECT_STREQ(overrider.toUtf8().constData(), caseSensitiveDummyFileName.toUtf8().constData());
|
||||
}
|
||||
|
||||
|
||||
TEST_F(PlatformConfigurationUnitTests_OnePCHostFixture, GetOverridingFile_ExistsButNotOverridden_ReturnsEmpty)
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
@@ -360,7 +334,7 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularScanfolder)
|
||||
ASSERT_EQ(m_absorber.m_numErrorsAbsorbed, 0);
|
||||
|
||||
ASSERT_EQ(config.GetScanFolderCount(), 3); // the two, and then the one that has the same data as prior but different identifier.
|
||||
QString scanName = AssetUtilities::ComputeProjectPath() + " Scan Folder";
|
||||
QString scanName = AssetUtilities::ComputeProjectPath(true) + " Scan Folder";
|
||||
ASSERT_EQ(config.GetScanFolderAt(0).GetDisplayName(), scanName);
|
||||
ASSERT_EQ(config.GetScanFolderAt(0).RecurseSubFolders(), true);
|
||||
ASSERT_EQ(config.GetScanFolderAt(0).GetOrder(), 0);
|
||||
@@ -445,15 +419,11 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_RegularExcludes)
|
||||
ASSERT_FALSE(config.IsFileExcluded("blahblah/Levels/blahblahhold/whatever.test"));
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
TEST_F(PlatformConfigurationUnitTests, DISABLED_TestFailReadConfigFile_Recognizers)
|
||||
#else
|
||||
TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_PROCESSOR_TESTS
|
||||
{
|
||||
using namespace AzToolsFramework::AssetSystem;
|
||||
using namespace AssetProcessor;
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
#if defined(AZ_PLATFORM_WINDOWS) || defined(AZ_PLATFORM_LINUX)
|
||||
const char* platformWhichIsNotCurrentPlatform = "mac";
|
||||
#else
|
||||
const char* platformWhichIsNotCurrentPlatform = "pc";
|
||||
@@ -502,31 +472,47 @@ TEST_F(PlatformConfigurationUnitTests, TestFailReadConfigFile_Recognizers)
|
||||
// the "rend" test makes sure that even if you dont specify 'params' its still there by default for all enabled platforms.
|
||||
// (but platforms can override it)
|
||||
ASSERT_TRUE(recogs.contains("rend"));
|
||||
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
|
||||
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains("android"));
|
||||
ASSERT_TRUE(recogs["rend"].m_platformSpecs.contains("server"));
|
||||
ASSERT_FALSE(recogs["rend"].m_platformSpecs.contains(platformWhichIsNotCurrentPlatform)); // this is not an enabled platform and should not be there.
|
||||
ASSERT_EQ(recogs["rend"].m_platformSpecs.size(), 3);
|
||||
ASSERT_EQ(recogs["rend"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "rendererparams");
|
||||
ASSERT_EQ(recogs["rend"].m_platformSpecs["android"].m_extraRCParams, "rendererparams");
|
||||
ASSERT_EQ(recogs["rend"].m_platformSpecs["server"].m_extraRCParams, ""); // default if not specified is empty string
|
||||
EXPECT_THAT(
|
||||
recogs["rend"].m_platformSpecs.keys(),
|
||||
testing::AllOf(
|
||||
testing::UnorderedElementsAre(
|
||||
QString(AzToolsFramework::AssetSystem::GetHostAssetPlatform()),
|
||||
QString("android"),
|
||||
QString("server")
|
||||
),
|
||||
testing::Not(testing::Contains(platformWhichIsNotCurrentPlatform)) // this is not an enabled platform and should not be there.
|
||||
)
|
||||
);
|
||||
EXPECT_EQ(recogs["rend"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "rendererparams");
|
||||
EXPECT_EQ(recogs["rend"].m_platformSpecs["android"].m_extraRCParams, "rendererparams");
|
||||
EXPECT_EQ(recogs["rend"].m_platformSpecs["server"].m_extraRCParams, ""); // default if not specified is empty string
|
||||
|
||||
ASSERT_TRUE(recogs.contains("alldefault"));
|
||||
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
|
||||
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains("android"));
|
||||
ASSERT_TRUE(recogs["alldefault"].m_platformSpecs.contains("server"));
|
||||
ASSERT_FALSE(recogs["alldefault"].m_platformSpecs.contains(platformWhichIsNotCurrentPlatform)); // this is not an enabled platform and should not be there.
|
||||
ASSERT_EQ(recogs["alldefault"].m_platformSpecs.size(), 3);
|
||||
ASSERT_EQ(recogs["alldefault"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "");
|
||||
ASSERT_EQ(recogs["alldefault"].m_platformSpecs["android"].m_extraRCParams, "");
|
||||
ASSERT_EQ(recogs["alldefault"].m_platformSpecs["server"].m_extraRCParams, "");
|
||||
EXPECT_THAT(
|
||||
recogs["alldefault"].m_platformSpecs.keys(),
|
||||
testing::AllOf(
|
||||
testing::UnorderedElementsAre(
|
||||
QString(AzToolsFramework::AssetSystem::GetHostAssetPlatform()),
|
||||
QString("android"),
|
||||
QString("server")
|
||||
),
|
||||
testing::Not(testing::Contains(platformWhichIsNotCurrentPlatform)) // this is not an enabled platform and should not be there.
|
||||
)
|
||||
);
|
||||
EXPECT_EQ(recogs["alldefault"].m_platformSpecs[AzToolsFramework::AssetSystem::GetHostAssetPlatform()].m_extraRCParams, "");
|
||||
EXPECT_EQ(recogs["alldefault"].m_platformSpecs["android"].m_extraRCParams, "");
|
||||
EXPECT_EQ(recogs["alldefault"].m_platformSpecs["server"].m_extraRCParams, "");
|
||||
|
||||
ASSERT_TRUE(recogs.contains("skipallbutone"));
|
||||
ASSERT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
|
||||
ASSERT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains("android"));
|
||||
ASSERT_TRUE(recogs["skipallbutone"].m_platformSpecs.contains("server")); // server is only one enabled (set to copy)
|
||||
ASSERT_EQ(recogs["skipallbutone"].m_platformSpecs.size(), 1);
|
||||
ASSERT_EQ(recogs["skipallbutone"].m_platformSpecs["server"].m_extraRCParams, "copy");
|
||||
EXPECT_THAT(
|
||||
recogs["skipallbutone"].m_platformSpecs.keys(),
|
||||
testing::UnorderedElementsAre(
|
||||
QString("server") // server is only one enabled (set to copy)
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains(AzToolsFramework::AssetSystem::GetHostAssetPlatform()));
|
||||
EXPECT_FALSE(recogs["skipallbutone"].m_platformSpecs.contains("android"));
|
||||
EXPECT_EQ(recogs["skipallbutone"].m_platformSpecs["server"].m_extraRCParams, "copy");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -137,10 +137,13 @@ TEST_F(AssetUtilitiesTest, UpdateToCorrectCase_ExistingFile_ReturnsTrue_Corrects
|
||||
thingsToTry << "SomeFile.TxT";
|
||||
thingsToTry << "otherfile.txt";
|
||||
thingsToTry << "subfolder1/otherfile.txt";
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
thingsToTry << "subfolder2\\otherfile.txt";
|
||||
thingsToTry << "subFolder3\\somefile.txt";
|
||||
thingsToTry << "subFolder4\\subfolder6\\somefile.txt";
|
||||
thingsToTry << "subFolder5\\subfolder7/someFile.txt";
|
||||
#endif // AZ_PLATFORM_WINDOWS
|
||||
thingsToTry << "specialFileName[.txt";
|
||||
thingsToTry << "specialFileName].txt";
|
||||
thingsToTry << "specialFileName!.txt";
|
||||
|
||||
@@ -175,6 +175,16 @@ namespace AssetProcessor
|
||||
UNIT_TEST_EXPECT_FALSE(gameName.isEmpty());
|
||||
// should create cache folder in the root, and read everything from there.
|
||||
|
||||
// There is a sub-case of handling mixed cases, but is only supported on case-insensitive filesystems.
|
||||
#if defined(AZ_PLATFORM_LINUX)
|
||||
// Linux is case-sensitive, so 'basefile.txt' will stay the same case as the other subfolder versions
|
||||
constexpr const char* subfolder3BaseFilePath = "subfolder3/basefile.txt";
|
||||
constexpr int expectedLegacyAssetIdCount = 1;
|
||||
#else
|
||||
constexpr const char* subfolder3BaseFilePath = "subfolder3/BaseFile.txt";
|
||||
constexpr int expectedLegacyAssetIdCount = 2;
|
||||
#endif
|
||||
|
||||
QSet<QString> expectedFiles;
|
||||
// set up some interesting files:
|
||||
expectedFiles << tempPath.absoluteFilePath("rootfile2.txt");
|
||||
@@ -185,7 +195,9 @@ namespace AssetProcessor
|
||||
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/basefile.txt");
|
||||
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/ccc/basefile.txt");
|
||||
expectedFiles << tempPath.absoluteFilePath("subfolder2/aaa/bbb/ccc/ddd/basefile.txt");
|
||||
expectedFiles << tempPath.absoluteFilePath("subfolder3/BaseFile.txt"); // note the case upper here
|
||||
|
||||
expectedFiles << tempPath.absoluteFilePath(subfolder3BaseFilePath);
|
||||
|
||||
expectedFiles << tempPath.absoluteFilePath("subfolder8/a/b/c/test.txt");
|
||||
|
||||
// subfolder3 is not recursive so none of these should show up in any scan or override check
|
||||
@@ -1521,7 +1533,8 @@ namespace AssetProcessor
|
||||
|
||||
// -------------- override test -----------------
|
||||
// set up by letting it compile basefile.txt from 3:
|
||||
absolutePath = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("subfolder3/BaseFile.txt"));
|
||||
|
||||
absolutePath = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath(subfolder3BaseFilePath));
|
||||
QMetaObject::invokeMethod(&apm, "AssessModifiedFile", Qt::QueuedConnection, Q_ARG(QString, absolutePath));
|
||||
UNIT_TEST_EXPECT_TRUE(BlockUntil(idling, 5000));
|
||||
|
||||
@@ -1583,8 +1596,7 @@ namespace AssetProcessor
|
||||
UNIT_TEST_EXPECT_TRUE(assetMessages.size() == 4);
|
||||
for (auto element : assetMessages)
|
||||
{
|
||||
// because the source asset had UPPER CASE in it, we should have multiple legacy IDs
|
||||
UNIT_TEST_EXPECT_TRUE(element.m_legacyAssetIds.size() == 2);
|
||||
UNIT_TEST_EXPECT_TRUE(element.m_legacyAssetIds.size() == expectedLegacyAssetIdCount);
|
||||
}
|
||||
|
||||
// ------------- setup complete, now do the test...
|
||||
@@ -1607,7 +1619,7 @@ namespace AssetProcessor
|
||||
|
||||
// delete the highest priority override file and ensure that it generates tasks
|
||||
// for the next highest priority! Basically, deleting this file should "reveal" the file underneath it in the other subfolder
|
||||
QString deletedFile = tempPath.absoluteFilePath("subfolder3/BaseFile.txt");
|
||||
QString deletedFile = tempPath.absoluteFilePath(subfolder3BaseFilePath);
|
||||
QString expectedReplacementInputFile = AssetUtilities::NormalizeFilePath(tempPath.absoluteFilePath("subfolder2/basefile.txt"));
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(QFile::remove(deletedFile));
|
||||
@@ -1621,6 +1633,11 @@ namespace AssetProcessor
|
||||
|
||||
sortAssetToProcessResultList(processResults);
|
||||
|
||||
#if defined(AZ_PLATFORM_LINUX)
|
||||
// On Linux, because of we cannot change the case of the source file, the job fingerprint is not updated due the case-switch so
|
||||
// there will be actually nothing to process
|
||||
UNIT_TEST_EXPECT_TRUE(processResults.size() == 0);
|
||||
#else
|
||||
// --------- same result as above ----------
|
||||
UNIT_TEST_EXPECT_TRUE(processResults.size() == 4); // 2 each for pc and android,since we have two recognizer for .txt file
|
||||
UNIT_TEST_EXPECT_TRUE(processResults[0].m_jobEntry.m_platformInfo.m_identifier == processResults[1].m_jobEntry.m_platformInfo.m_identifier);
|
||||
@@ -1641,7 +1658,7 @@ namespace AssetProcessor
|
||||
UNIT_TEST_EXPECT_TRUE(processFile1.startsWith(platformFolder));
|
||||
UNIT_TEST_EXPECT_TRUE(processResults[checkIdx].m_jobEntry.m_computedFingerprint != 0);
|
||||
}
|
||||
|
||||
#endif // defined(AZ_PLATFORM_LINUX)
|
||||
relativePathFromWatchFolder = "somefile.xxx";
|
||||
watchFolderPath = tempPath.absoluteFilePath("subfolder3");
|
||||
absolutePath = watchFolderPath + "/" + relativePathFromWatchFolder;
|
||||
@@ -2721,7 +2738,12 @@ namespace AssetProcessor
|
||||
{
|
||||
AssetBuilderSDK::JobDescriptor secondDescriptor = descriptor;
|
||||
secondDescriptor.m_jobKey = "yyy";
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
sourceFileDependency.m_sourceFileDependencyPath = "some\\random/Folders/FILEa.TxT";
|
||||
#else
|
||||
sourceFileDependency.m_sourceFileDependencyPath = "some/random/folders/FileA.txt";
|
||||
#endif // defined(AZ_PLATFORM_WINDOWS)
|
||||
|
||||
// ... declare a job dependency on job A ('FileA.txt', 'xxx', platform)
|
||||
AssetBuilderSDK::JobDependency jobDependency("xxx", platformInfo.m_identifier.c_str(), AssetBuilderSDK::JobDependencyType::Fingerprint, sourceFileDependency);
|
||||
secondDescriptor.m_jobDependencyList.push_back(jobDependency);
|
||||
@@ -2805,11 +2827,11 @@ namespace AssetProcessor
|
||||
QDir cacheRoot;
|
||||
UNIT_TEST_EXPECT_TRUE(AssetUtilities::ComputeProjectCacheRoot(cacheRoot));
|
||||
|
||||
QString productFileAPath = cacheRoot.filePath(QString("pc/FileAProduct.txt"));
|
||||
QString productFileBPath = cacheRoot.filePath(QString("pc/FileBProduct1.txt"));
|
||||
QString product2FileBPath = cacheRoot.filePath(QString("pc/FileBProduct2.txt"));
|
||||
QString productFileCPath = cacheRoot.filePath(QString("pc/FileCProduct.txt"));
|
||||
QString product2FileCPath = cacheRoot.filePath(QString("pc/FileCProduct2.txt"));
|
||||
QString productFileAPath = cacheRoot.filePath(QString("pc/fileaproduct.txt"));
|
||||
QString productFileBPath = cacheRoot.filePath(QString("pc/filebproduct1.txt"));
|
||||
QString product2FileBPath = cacheRoot.filePath(QString("pc/filebproduct2.txt"));
|
||||
QString productFileCPath = cacheRoot.filePath(QString("pc/filecproduct.txt"));
|
||||
QString product2FileCPath = cacheRoot.filePath(QString("pc/filecproduct2.txt"));
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(sourceFileAPath, ""));
|
||||
UNIT_TEST_EXPECT_TRUE(CreateDummyFile(sourceFileBPath, ""));
|
||||
|
||||
@@ -106,7 +106,7 @@ void FileWatcherUnitTestRunner::StartTest()
|
||||
AZ_TracePrintf(AssetProcessor::DebugChannel, "Waiting for remaining notifications: %d \n", outstandingFiles.count());
|
||||
}
|
||||
|
||||
if (outstandingFiles.count() > 0)
|
||||
if (outstandingFiles.count() > 0)
|
||||
{
|
||||
#if defined(AZ_ENABLE_TRACING)
|
||||
AZ_TracePrintf(AssetProcessor::DebugChannel, "Timed out waiting for file changes: %d / %d missed\n", outstandingFiles.count(), maxFiles);
|
||||
@@ -226,7 +226,9 @@ void FileWatcherUnitTestRunner::StartTest()
|
||||
UNIT_TEST_EXPECT_TRUE(fileAddCalled);
|
||||
UNIT_TEST_EXPECT_TRUE(fileRemoveCalled);
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in (Only on Windows)
|
||||
#endif // AZ_PLATFORM_WINDOWS
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileRemoveName).toLower() == QDir::toNativeSeparators(originalName).toLower());
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileAddName).toLower() == QDir::toNativeSeparators(newName1).toLower());
|
||||
@@ -249,7 +251,10 @@ void FileWatcherUnitTestRunner::StartTest()
|
||||
// the new1 was "removed" and the new2 was "added"
|
||||
UNIT_TEST_EXPECT_TRUE(fileAddCalled);
|
||||
UNIT_TEST_EXPECT_TRUE(fileRemoveCalled);
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in (Only on Windows)
|
||||
#endif // AZ_PLATFORM_WINDOWS
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileRemoveName).toLower() == QDir::toNativeSeparators(newName1).toLower());
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileAddName).toLower() == QDir::toNativeSeparators(newName2).toLower());
|
||||
|
||||
@@ -270,11 +275,15 @@ void FileWatcherUnitTestRunner::StartTest()
|
||||
// the new1 was "removed" and the new2 was "added"
|
||||
UNIT_TEST_EXPECT_TRUE(fileAddCalled);
|
||||
UNIT_TEST_EXPECT_TRUE(fileRemoveCalled);
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
UNIT_TEST_EXPECT_TRUE(fileModifiedCalled); // modified should be called on the folder that the file lives in (Only on Windows)
|
||||
#endif // AZ_PLATFORM_WINDOWS
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileRemoveName).toLower() == QDir::toNativeSeparators(newName2).toLower());
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileAddName).toLower() == QDir::toNativeSeparators(newName3).toLower());
|
||||
|
||||
// final test... make sure that renaming a DIRECTORY works too
|
||||
#if !defined(AZ_PLATFORM_LINUX)
|
||||
// final test... make sure that renaming a DIRECTORY works too.
|
||||
// Note that linux does not get any callbacks if just the directory is renamed (from inotify)
|
||||
QDir renamer;
|
||||
fileAddCalled = false;
|
||||
fileRemoveCalled = false;
|
||||
@@ -297,7 +306,7 @@ void FileWatcherUnitTestRunner::StartTest()
|
||||
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileRemoveName).toLower() == QDir::toNativeSeparators(tempDirPath.absoluteFilePath("dir3")).toLower());
|
||||
UNIT_TEST_EXPECT_TRUE(QDir::toNativeSeparators(fileAddName).toLower() == QDir::toNativeSeparators(tempDirPath.absoluteFilePath("dir4")).toLower());
|
||||
|
||||
#endif // AZ_PLATFORM_LINUX
|
||||
|
||||
QObject::disconnect(connectionRemove);
|
||||
QObject::disconnect(connectionAdd);
|
||||
|
||||
@@ -545,10 +545,13 @@ void RCcontrollerUnitTests::RunRCControllerTests()
|
||||
rcJob.SetCheckExclusiveLock(true);
|
||||
rcJob.Start();
|
||||
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
// on windows, opening a file for reading locks it
|
||||
// but on other platforms, this is not the case.
|
||||
// we only expect work to begin when we can gain an exclusive lock on this file.
|
||||
UNIT_TEST_EXPECT_FALSE(UnitTestUtils::BlockUntil(beginWork, 5000));
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
// Once we release the file, it should process normally
|
||||
lockFileTest.close();
|
||||
#else
|
||||
|
||||
@@ -227,8 +227,9 @@ void UtilitiesUnitTests::StartTest()
|
||||
#else
|
||||
int handle = open(lockTestFileName.toUtf8().constData(), O_RDONLY | O_EXLOCK | O_NONBLOCK);
|
||||
#endif // AZ_PLATFORM_WINDOWS
|
||||
UNIT_TEST_EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
|
||||
|
||||
#if defined(AZ_PLATFORM_WINDOWS)
|
||||
UNIT_TEST_EXPECT_FALSE(AssetUtilities::CheckCanLock(lockTestFileName));
|
||||
lockTestFile.close();
|
||||
#else
|
||||
if (handle != -1)
|
||||
|
||||
@@ -507,8 +507,13 @@ namespace AssetUtilities
|
||||
return QString::fromUtf8(s_projectName.c_str(), aznumeric_cast<int>(s_projectName.size()));
|
||||
}
|
||||
|
||||
QString ComputeProjectPath()
|
||||
QString ComputeProjectPath(bool resetCachedProjectPath/*=false*/)
|
||||
{
|
||||
if (resetCachedProjectPath)
|
||||
{
|
||||
// Clear any cached value if reset was requested
|
||||
s_projectPath.clear();
|
||||
}
|
||||
if (s_projectPath.empty())
|
||||
{
|
||||
// Check command-line args first
|
||||
|
||||
@@ -104,7 +104,8 @@ namespace AssetUtilities
|
||||
QString ComputeProjectName(QString projectNameOverride = QString(), bool force = false);
|
||||
|
||||
//! Determine the absolute path of the current project
|
||||
QString ComputeProjectPath();
|
||||
//! The path computed path will be cached on subsequent calls unless resetCachedProjectPath=true
|
||||
QString ComputeProjectPath(bool resetCachedProjectPath = false);
|
||||
|
||||
//! Reads the allowed list directly from the bootstrap file
|
||||
QString ReadAllowedlistFromSettingsRegistry(QString initialFolder = QString());
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
|
||||
include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
|
||||
|
||||
if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED AND NOT LY_MONOLITHIC_GAME)
|
||||
|
||||
ly_add_target(
|
||||
NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE}
|
||||
NAMESPACE AZ
|
||||
@@ -32,19 +32,23 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
AZ::AzTest
|
||||
AZ::AzFramework
|
||||
)
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
|
||||
ly_add_target(
|
||||
NAME AzTestRunner.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
aztestrunner_test_files.cmake
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME AzTestRunner.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
aztestrunner_test_files.cmake
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME AZ::AzTestRunner.Tests
|
||||
)
|
||||
|
||||
ly_add_googletest(
|
||||
NAME AZ::AzTestRunner.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
|
||||
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE MODULE)
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
|
||||
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
|
||||
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
|
||||
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
|
||||
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "o3de_editor.ico"
|
||||
@@ -151,7 +151,6 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void ProjectButton::ReadySetup()
|
||||
{
|
||||
connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); });
|
||||
connect(m_projectImageLabel->GetBuildButton(), &QPushButton::clicked, [this](){ emit BuildProject(m_projectInfo); });
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace O3DE::ProjectManager
|
||||
AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str());
|
||||
if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
|
||||
{
|
||||
AZ_Warning("python", false, "Python home path must exist. path:%s", pyBasePath.c_str());
|
||||
AZ_Assert(false, "Python home path must exist. path:%s", pyBasePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -277,11 +277,12 @@ namespace O3DE::ProjectManager
|
||||
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
|
||||
pybind11::gil_scoped_acquire acquire;
|
||||
|
||||
// Setup sys.path
|
||||
int result = PyRun_SimpleString("import sys");
|
||||
AZ_Warning("ProjectManagerWindow", result != -1, "Import sys failed");
|
||||
result = PyRun_SimpleString(AZStd::string::format("sys.path.append('%s')", m_enginePath.c_str()).c_str());
|
||||
AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed");
|
||||
// sanity import check
|
||||
if (PyRun_SimpleString("import sys") != 0)
|
||||
{
|
||||
AZ_Assert(false, "Import sys failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// import required modules
|
||||
m_cmake = pybind11::module::import("o3de.cmake");
|
||||
@@ -295,11 +296,11 @@ namespace O3DE::ProjectManager
|
||||
// make sure the engine is registered
|
||||
RegisterThisEngine();
|
||||
|
||||
return result == 0 && !PyErr_Occurred();
|
||||
return !PyErr_Occurred();
|
||||
}
|
||||
catch ([[maybe_unused]] const std::exception& e)
|
||||
{
|
||||
AZ_Warning("ProjectManagerWindow", false, "Py_Initialize() failed with %s", e.what());
|
||||
AZ_Assert(false, "Py_Initialize() failed with %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -452,9 +453,9 @@ namespace O3DE::ProjectManager
|
||||
return result;
|
||||
}
|
||||
|
||||
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path)
|
||||
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path, const QString& projectPath)
|
||||
{
|
||||
GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString()));
|
||||
GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString()), pybind11::str(projectPath.toStdString()));
|
||||
if (gemInfo.IsValid())
|
||||
{
|
||||
return AZ::Success(AZStd::move(gemInfo));
|
||||
@@ -473,7 +474,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
for (auto path : m_manifest.attr("get_engine_gems")())
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
gems.push_back(GemInfoFromPath(path, pybind11::none()));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
@@ -494,7 +495,7 @@ namespace O3DE::ProjectManager
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path));
|
||||
gems.push_back(GemInfoFromPath(path, pyProjectPath));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
@@ -632,12 +633,12 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
GemInfo PythonBindings::GemInfoFromPath(pybind11::handle path)
|
||||
GemInfo PythonBindings::GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath)
|
||||
{
|
||||
GemInfo gemInfo;
|
||||
gemInfo.m_path = Py_To_String(path);
|
||||
|
||||
auto data = m_manifest.attr("get_gem_json_data")(pybind11::none(), path);
|
||||
auto data = m_manifest.attr("get_gem_json_data")(pybind11::none(), path, pyProjectPath);
|
||||
if (pybind11::isinstance<pybind11::dict>(data))
|
||||
{
|
||||
try
|
||||
@@ -782,12 +783,12 @@ namespace O3DE::ProjectManager
|
||||
});
|
||||
}
|
||||
|
||||
ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path)
|
||||
ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath)
|
||||
{
|
||||
ProjectTemplateInfo templateInfo;
|
||||
templateInfo.m_path = Py_To_String(pybind11::str(path));
|
||||
|
||||
auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path);
|
||||
auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path, pyProjectPath);
|
||||
if (pybind11::isinstance<pybind11::dict>(data))
|
||||
{
|
||||
try
|
||||
@@ -829,14 +830,15 @@ namespace O3DE::ProjectManager
|
||||
return templateInfo;
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplates()
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplates(const QString& projectPath)
|
||||
{
|
||||
QVector<ProjectTemplateInfo> templates;
|
||||
|
||||
bool result = ExecuteWithLock([&] {
|
||||
pybind11::str pyProjectPath = projectPath.toStdString();
|
||||
for (auto path : m_manifest.attr("get_templates_for_project_creation")())
|
||||
{
|
||||
templates.push_back(ProjectTemplateInfoFromPath(path));
|
||||
templates.push_back(ProjectTemplateInfoFromPath(path, pyProjectPath));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace O3DE::ProjectManager
|
||||
bool SetEngineInfo(const EngineInfo& engineInfo) override;
|
||||
|
||||
// Gem
|
||||
AZ::Outcome<GemInfo> GetGemInfo(const QString& path) override;
|
||||
AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
|
||||
@@ -55,16 +55,16 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
|
||||
|
||||
// ProjectTemplate
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
|
||||
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
|
||||
bool ExecuteWithLock(AZStd::function<void()> executionCallback);
|
||||
GemInfo GemInfoFromPath(pybind11::handle path);
|
||||
GemInfo GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
|
||||
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
|
||||
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path);
|
||||
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
|
||||
bool RegisterThisEngine();
|
||||
bool StartPython();
|
||||
bool StopPython();
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace O3DE::ProjectManager
|
||||
* @param path the absolute path to the Gem
|
||||
* @return an outcome with GemInfo on success
|
||||
*/
|
||||
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path) = 0;
|
||||
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) = 0;
|
||||
|
||||
/**
|
||||
* Get all available gem infos. This concatenates gems registered by the engine and the project.
|
||||
@@ -147,7 +147,7 @@ namespace O3DE::ProjectManager
|
||||
* Get info about all known project templates
|
||||
* @return an outcome with ProjectTemplateInfos on success
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() = 0;
|
||||
virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Resources/ProjectManager.rc
|
||||
Resources/ProjectManager.qrc
|
||||
Resources/ProjectManager.qss
|
||||
Source/main.cpp
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace AZ
|
||||
Application::Application(int argc, char** argv)
|
||||
: AzToolsFramework::ToolsApplication(&argc, &argv)
|
||||
{
|
||||
// We need a specialized variant of EditorEntityContextCompnent for the SliceConverter, so we register the descriptor here.
|
||||
// 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();
|
||||
|
||||
@@ -128,8 +128,7 @@ namespace AZ
|
||||
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.
|
||||
@@ -177,11 +176,22 @@ namespace AZ
|
||||
}
|
||||
|
||||
AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr);
|
||||
return ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
|
||||
bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
|
||||
// Clear out the references to any nested slices so that the nested assets get unloaded correctly at the end of
|
||||
// the conversion.
|
||||
ClearSliceAssetReferences(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;
|
||||
@@ -256,7 +266,7 @@ namespace AZ
|
||||
for (auto& alias : entityAliases)
|
||||
{
|
||||
auto id = sourceInstance->GetEntityId(alias);
|
||||
auto result = m_aliasIdMapper.emplace(TemplateEntityIdPair(templateId, id), 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");
|
||||
@@ -342,10 +352,9 @@ namespace AZ
|
||||
// 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.
|
||||
@@ -429,6 +438,28 @@ namespace AZ
|
||||
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
|
||||
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
|
||||
|
||||
// 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
|
||||
{
|
||||
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
}
|
||||
|
||||
// Before processing any further, save off all the known entity IDs from this instance 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.
|
||||
UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
|
||||
|
||||
// Create a new unmodified prefab Instance for the nested slice instance.
|
||||
auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>();
|
||||
AzToolsFramework::Prefab::Instance::EntityList newEntities;
|
||||
@@ -465,15 +496,62 @@ namespace AZ
|
||||
auto instantiated =
|
||||
dataPatch.Apply(&sourceObjects, dependentSlice->GetSerializeContext(), filterDesc, sourceDataFlags, targetDataFlags);
|
||||
|
||||
// Run through all the instantiated entities and fix up their 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 = nestedInstance->GetContainerEntity();
|
||||
auto containerEntityId = containerEntity->get().GetId();
|
||||
for (auto entity : instantiated->m_entities)
|
||||
// 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)
|
||||
{
|
||||
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)
|
||||
@@ -482,45 +560,61 @@ namespace AZ
|
||||
auto parentId = transformComponent->GetParentId();
|
||||
if (parentId.IsValid())
|
||||
{
|
||||
auto parentAlias = m_aliasIdMapper.find(TemplateEntityIdPair(topLevelInstance->GetTemplateId(), parentId));
|
||||
if (parentAlias != m_aliasIdMapper.end())
|
||||
// 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())
|
||||
{
|
||||
// Set the container's parent to this entity's parent, and set this entity's parent to the container
|
||||
// (i.e. go from A->B to A->container->B)
|
||||
auto newParentId = topLevelInstance->GetEntityId(parentAlias->second);
|
||||
SetParentEntity(containerEntity->get(), newParentId, false);
|
||||
onlySetIfInvalid = false;
|
||||
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);
|
||||
SetParentEntity(*entity, containerEntityId, onlySetIfInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
// Replace all the entities in the instance with the new patched ones.
|
||||
// (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;
|
||||
});
|
||||
for (auto& entity : instantiated->m_entities)
|
||||
{
|
||||
auto entityAlias = m_aliasIdMapper.find(TemplateEntityIdPair(nestedInstance->GetTemplateId(), entity->GetId()));
|
||||
if (entityAlias != m_aliasIdMapper.end())
|
||||
{
|
||||
nestedInstance->AddEntity(*entity, entityAlias->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Failed to find entity alias.");
|
||||
nestedInstance->AddEntity(*entity);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
@@ -531,21 +625,7 @@ namespace AZ
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
|
||||
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance);
|
||||
|
||||
// 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
|
||||
{
|
||||
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
}
|
||||
// Use the deterministic instance alias for this new instance
|
||||
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance), instanceAlias);
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter;
|
||||
@@ -670,5 +750,48 @@ namespace AZ
|
||||
AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully.");
|
||||
}
|
||||
|
||||
void SliceConverter::ClearSliceAssetReferences(AZ::Entity* rootEntity)
|
||||
{
|
||||
SliceComponent* sliceComponent = AZ::EntityUtils::FindFirstDerivedComponent<SliceComponent>(rootEntity);
|
||||
// Make a copy of the slice list and remove all of them from the loaded component.
|
||||
AZ::SliceComponent::SliceList slices = sliceComponent->GetSlices();
|
||||
for (auto& slice : slices)
|
||||
{
|
||||
sliceComponent->RemoveSlice(&slice);
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace SerializeContextTools
|
||||
} // namespace AZ
|
||||
|
||||
@@ -42,8 +42,6 @@ namespace AZ
|
||||
bool ConvertSliceFiles(Application& application);
|
||||
|
||||
private:
|
||||
using TemplateEntityIdPair = AZStd::pair<AzToolsFramework::Prefab::TemplateId, AZ::EntityId>;
|
||||
|
||||
bool ConnectToAssetProcessor();
|
||||
void DisconnectFromAssetProcessor();
|
||||
|
||||
@@ -60,10 +58,32 @@ namespace AZ
|
||||
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 ClearSliceAssetReferences(AZ::Entity* rootEntity);
|
||||
void UpdateSliceEntityInstanceMappings(
|
||||
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap,
|
||||
const AZStd::string& currentInstanceAlias);
|
||||
|
||||
// Track all of the entity IDs created and the prefab entity aliases that map to them. This mapping is used
|
||||
// with nested slice conversion to remap parent entity IDs to the correct prefab entity IDs.
|
||||
AZStd::unordered_map<TemplateEntityIdPair, AzToolsFramework::Prefab::EntityAlias> m_aliasIdMapper;
|
||||
// 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)
|
||||
: m_templateId(templateId)
|
||||
, m_entityAlias(entityAlias)
|
||||
{
|
||||
}
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId m_templateId;
|
||||
AzToolsFramework::Prefab::EntityAlias m_entityAlias;
|
||||
AZStd::vector<AzToolsFramework::Prefab::InstanceAlias> m_nestedInstanceAliases;
|
||||
};
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace AzToolsFramework
|
||||
SliceConverterEditorEntityContextComponent() : EditorEntityContextComponent() {}
|
||||
|
||||
// Simple API to selectively disable this logic *only* when performing slice to prefab conversion.
|
||||
static void DisableOnContextEntityLogic()
|
||||
static inline void DisableOnContextEntityLogic()
|
||||
{
|
||||
m_enableOnContextEntityLogic = false;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user