[AssetProcessor] Refactor the FileWatcher to use only one watch thread

This change reworks the AssetProcessor's FileWatcher so that it only uses
one thread. This is motivated by getting better support for inotify on
Linux. The previous architecture required calling `inotify_init` once for
each directory that was being watched, and using separate inotify instances
for each watched tree. In addition, having separate threads per watched
tree is not necessary, and just consumes system resources. Each platform
supports watching multiple directories with the same platform-specific
watcher API, so each platform has been updated accordingly.

The interface to the FileWatcher class is greatly simplified. Previously,
it supported client-supplied filtering of the paths that would generate
notifications. This was done by subclassing `FolderWatchBase` and
implementing `OnFileChange`. However, only one filter was ever used, so
that filter is now hard-coded in the FileWatcher class, and the classes
driving the old filtering mechanism are removed. Users of the interface
now have a much easier time, they just call `AddFolderWatch` with the path
to watch, and only have to connect to one set of signals, instead of
separate signals per watched directory.

Signed-off-by: Chris Burel <burelc@amazon.com>
This commit is contained in:
Chris Burel
2021-11-15 11:30:37 -08:00
parent 7ac5bc3d5c
commit ce0bb1ca2b
22 changed files with 658 additions and 932 deletions
@@ -433,63 +433,77 @@ void ApplicationManagerBase::DestroyPlatformConfiguration()
void ApplicationManagerBase::InitFileMonitor()
{
m_folderWatches.reserve(m_platformConfiguration->GetScanFolderCount());
m_watchHandles.reserve(m_platformConfiguration->GetScanFolderCount());
for (int folderIdx = 0; folderIdx < m_platformConfiguration->GetScanFolderCount(); ++folderIdx)
{
const AssetProcessor::ScanFolderInfo& info = m_platformConfiguration->GetScanFolderAt(folderIdx);
FolderWatchCallbackEx* newFolderWatch = new FolderWatchCallbackEx(info.ScanPath(), "", info.RecurseSubFolders());
// hook folder watcher to assess files on add/modify
// relevant files will be sent to resource compiler
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessAddedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessModifiedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessDeletedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [this](QString path) { m_fileStateCache->AddFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified, [this](QString path) { m_fileStateCache->UpdateFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved, [this](QString path) { m_fileStateCache->RemoveFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [](QString path) { AZ::Interface<AssetProcessor::ExcludedFolderCacheInterface>::Get()->FileAdded(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded,
m_fileProcessor.get(), &AssetProcessor::FileProcessor::AssessAddedFile);
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_fileProcessor.get(), &AssetProcessor::FileProcessor::AssessDeletedFile);
m_folderWatches.push_back(AZStd::unique_ptr<FolderWatchCallbackEx>(newFolderWatch));
m_watchHandles.push_back(m_fileWatcher.AddFolderWatch(newFolderWatch, info.RecurseSubFolders()));
m_fileWatcher.AddFolderWatch(info.ScanPath(), info.RecurseSubFolders());
}
// also hookup monitoring for the cache (output directory)
QDir cacheRoot;
if (AssetUtilities::ComputeProjectCacheRoot(cacheRoot))
{
FolderWatchCallbackEx* newFolderWatch = new FolderWatchCallbackEx(cacheRoot.absolutePath(), "", true);
m_fileWatcher.AddFolderWatch(cacheRoot.absolutePath(), true);
}
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileAdded, [this](QString path) { m_fileStateCache->AddFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileModified, [this](QString path) { m_fileStateCache->UpdateFile(path); });
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved, [this](QString path) { m_fileStateCache->RemoveFile(path); });
if (m_platformConfiguration->GetScanFolderCount() || !cacheRoot.path().isEmpty())
{
const auto cachePath = QDir::toNativeSeparators(cacheRoot.absolutePath());
// we only care about cache root deletions.
QObject::connect(newFolderWatch, &FolderWatchCallbackEx::fileRemoved,
m_assetProcessorManager, &AssetProcessor::AssetProcessorManager::AssessDeletedFile);
const auto OnFileAdded = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_fileStateCache->AddFile(path);
}
else
{
m_assetProcessorManager->AssessAddedFile(path);
m_fileStateCache->AddFile(path);
AZ::Interface<AssetProcessor::ExcludedFolderCacheInterface>::Get()->FileAdded(path);
m_fileProcessor->AssetProcessor::FileProcessor::AssessAddedFile(path);
}
};
m_folderWatches.push_back(AZStd::unique_ptr<FolderWatchCallbackEx>(newFolderWatch));
m_watchHandles.push_back(m_fileWatcher.AddFolderWatch(newFolderWatch));
const auto OnFileModified = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_assetProcessorManager->AssessModifiedFile(path);
}
else
{
m_assetProcessorManager->AssessModifiedFile(path);
m_fileStateCache->UpdateFile(path);
}
};
const auto OnFileRemoved = [this, cachePath](QString path)
{
const bool isCacheRoot = path.startsWith(cachePath);
if (isCacheRoot)
{
m_fileStateCache->RemoveFile(path);
m_assetProcessorManager->AssessDeletedFile(path);
}
else
{
m_assetProcessorManager->AssessDeletedFile(path);
m_fileStateCache->RemoveFile(path);
m_fileProcessor->AssessDeletedFile(path);
}
};
connect(&m_fileWatcher, &FileWatcher::fileAdded, OnFileAdded);
connect(&m_fileWatcher, &FileWatcher::fileModified, OnFileModified);
connect(&m_fileWatcher, &FileWatcher::fileRemoved, OnFileRemoved);
}
}
void ApplicationManagerBase::DestroyFileMonitor()
{
for (int watchHandle : m_watchHandles)
{
m_fileWatcher.RemoveFolderWatch(watchHandle);
}
m_folderWatches.resize(0);
m_fileWatcher.ClearFolderWatches();
}
void ApplicationManagerBase::DestroyApplicationServer()
@@ -798,8 +812,6 @@ ApplicationManager::BeforeRunStatus ApplicationManagerBase::BeforeRun()
qRegisterMetaType<AzFramework::AssetSystem::AssetStatus>("AzFramework::AssetSystem::AssetStatus");
qRegisterMetaType<AzFramework::AssetSystem::AssetStatus>("AssetStatus");
qRegisterMetaType<FileChangeInfo>("FileChangeInfo");
qRegisterMetaType<AssetProcessor::AssetScanningStatus>("AssetScanningStatus");
qRegisterMetaType<AssetProcessor::NetworkRequestID>("NetworkRequestID");