[development] Fixed runaway memory in editor (#2220)
There was a change in bahaviour to how files/directories were collected in the AZ::IO::FindData::Scan* functions which allowed the addition of duplicate entries. This created a problem when attempting a recursively greedy search with zips included because of the level system pak files. Their mount points would be the root "levels" folder, so for N-levels there would be N identical entries to the "levels" folder being added perpetually each time ScanZips was called on the "levels" folder or above folders with "*" filtering. Letting the editor sit idle no longer sees the reported memory in the bottom status bar climb. Also, running the tests which prompted the initial change shows a negligible change in perf. Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
@@ -15,6 +15,11 @@
|
||||
|
||||
namespace AZ::IO
|
||||
{
|
||||
size_t ArchiveFileIteratorHash::operator()(const AZ::IO::ArchiveFileIterator& iter) const
|
||||
{
|
||||
return iter.GetHash();
|
||||
}
|
||||
|
||||
bool AZStdStringLessCaseInsensitive::operator()(AZStd::string_view left, AZStd::string_view right) const
|
||||
{
|
||||
// If one or both strings are 0-length, return true if the left side is smaller, false if they're equal or left is larger.
|
||||
@@ -68,11 +73,21 @@ namespace AZ::IO
|
||||
return operator++();
|
||||
}
|
||||
|
||||
bool ArchiveFileIterator::operator==(const AZ::IO::ArchiveFileIterator& rhs) const
|
||||
{
|
||||
return GetHash() == rhs.GetHash();
|
||||
}
|
||||
|
||||
ArchiveFileIterator::operator bool() const
|
||||
{
|
||||
return m_findData && m_lastFetchValid;
|
||||
}
|
||||
|
||||
size_t ArchiveFileIterator::GetHash() const
|
||||
{
|
||||
return AZStd::hash<AZ::IO::PathView>{}(m_filename.c_str());
|
||||
}
|
||||
|
||||
void FindData::Scan(IArchive* archive, AZStd::string_view szDir, bool bAllowUseFS, bool bScanZips)
|
||||
{
|
||||
// get the priority into local variable to avoid it changing in the course of
|
||||
@@ -113,14 +128,12 @@ namespace AZ::IO
|
||||
}
|
||||
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), [&](const char* filePath) -> bool
|
||||
{
|
||||
AZ::IO::ArchiveFileIterator fileIterator;
|
||||
fileIterator.m_filename = AZ::IO::PathView(filePath).Filename().Native();
|
||||
fileIterator.m_fileDesc.nAttrib = {};
|
||||
AZ::IO::ArchiveFileIterator fileIterator{ nullptr, AZ::IO::PathView(filePath).Filename().Native(), {} };
|
||||
|
||||
if (AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath))
|
||||
{
|
||||
fileIterator.m_fileDesc.nAttrib = fileIterator.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
|
||||
m_fileStack.emplace_back(AZStd::move(fileIterator));
|
||||
m_fileSet.emplace(AZStd::move(fileIterator));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -136,7 +149,7 @@ namespace AZ::IO
|
||||
// These times are not supported by our file interface
|
||||
fileIterator.m_fileDesc.tAccess = fileIterator.m_fileDesc.tWrite;
|
||||
fileIterator.m_fileDesc.tCreate = fileIterator.m_fileDesc.tWrite;
|
||||
m_fileStack.emplace_back(AZStd::move(fileIterator));
|
||||
m_fileSet.emplace(AZStd::move(fileIterator));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -167,7 +180,7 @@ namespace AZ::IO
|
||||
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive;
|
||||
fileDesc.nSize = fileEntry->desc.lSizeUncompressed;
|
||||
fileDesc.tWrite = fileEntry->GetModificationTime();
|
||||
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
|
||||
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
|
||||
}
|
||||
|
||||
ZipDir::FindDir findDirectoryEntry(zipCache);
|
||||
@@ -180,7 +193,7 @@ namespace AZ::IO
|
||||
}
|
||||
AZ::IO::FileDesc fileDesc;
|
||||
fileDesc.nAttrib = AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory;
|
||||
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
|
||||
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -249,7 +262,7 @@ namespace AZ::IO
|
||||
if (!bindRootIter->empty() && AZStd::wildcard_match(sourcePathRemainder.Native(), bindRootIter->Native()))
|
||||
{
|
||||
AZ::IO::FileDesc fileDesc{ AZ::IO::FileDesc::Attribute::ReadOnly | AZ::IO::FileDesc::Attribute::Archive | AZ::IO::FileDesc::Attribute::Subdirectory };
|
||||
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, bindRootIter->Native(), fileDesc });
|
||||
m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ this, bindRootIter->Native(), fileDesc });
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -265,7 +278,7 @@ namespace AZ::IO
|
||||
|
||||
AZ::IO::ArchiveFileIterator FindData::Fetch()
|
||||
{
|
||||
if (m_fileStack.empty())
|
||||
if (m_fileSet.empty())
|
||||
{
|
||||
AZ::IO::ArchiveFileIterator emptyFileIterator;
|
||||
emptyFileIterator.m_lastFetchValid = false;
|
||||
@@ -274,10 +287,10 @@ namespace AZ::IO
|
||||
}
|
||||
|
||||
// Remove Fetched item from the FindData map so that the iteration continues
|
||||
AZ::IO::ArchiveFileIterator fileIterator{ m_fileStack.back() };
|
||||
AZ::IO::ArchiveFileIterator fileIterator{ *m_fileSet.begin() };
|
||||
fileIterator.m_lastFetchValid = true;
|
||||
fileIterator.m_findData = this;
|
||||
m_fileStack.pop_back();
|
||||
m_fileSet.erase(m_fileSet.begin());
|
||||
return fileIterator;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/smart_ptr/intrusive_base.h>
|
||||
#include <AzCore/std/string/fixed_string.h>
|
||||
|
||||
@@ -44,8 +45,12 @@ namespace AZ::IO
|
||||
ArchiveFileIterator operator++();
|
||||
ArchiveFileIterator operator++(int);
|
||||
|
||||
bool operator==(const AZ::IO::ArchiveFileIterator& rhs) const;
|
||||
|
||||
explicit operator bool() const;
|
||||
|
||||
size_t GetHash() const;
|
||||
|
||||
inline static constexpr size_t FilenameMaxLength = 256;
|
||||
AZStd::fixed_string<FilenameMaxLength> m_filename;
|
||||
FileDesc m_fileDesc;
|
||||
@@ -56,6 +61,11 @@ namespace AZ::IO
|
||||
bool m_lastFetchValid{};
|
||||
};
|
||||
|
||||
struct ArchiveFileIteratorHash
|
||||
{
|
||||
size_t operator()(const AZ::IO::ArchiveFileIterator& iter) const;
|
||||
};
|
||||
|
||||
struct AZStdStringLessCaseInsensitive
|
||||
{
|
||||
bool operator()(AZStd::string_view left, AZStd::string_view right) const;
|
||||
@@ -75,7 +85,7 @@ namespace AZ::IO
|
||||
void ScanFS(IArchive* archive, AZStd::string_view path);
|
||||
void ScanZips(IArchive* archive, AZStd::string_view path);
|
||||
|
||||
using FileStack = AZStd::vector<ArchiveFileIterator>;
|
||||
FileStack m_fileStack;
|
||||
using FileSet = AZStd::unordered_set<ArchiveFileIterator, ArchiveFileIteratorHash>;
|
||||
FileSet m_fileSet;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace CustomMocks
|
||||
fileDesc.nSize = sizeof(AZ::IO::FileDesc);
|
||||
// Add a filename and file description reference to the TestFindData map to make sure the file iterator is valid
|
||||
m_findData = new TestFindData();
|
||||
m_findData->m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ static_cast<AZ::IO::FindData*>(m_findData.get()), m_levelName, fileDesc });
|
||||
m_findData->m_fileSet.emplace(AZ::IO::ArchiveFileIterator{ static_cast<AZ::IO::FindData*>(m_findData.get()), m_levelName, fileDesc });
|
||||
return m_findData->Fetch();
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace CustomMocks
|
||||
struct TestFindData
|
||||
: AZ::IO::FindData
|
||||
{
|
||||
using AZ::IO::FindData::m_fileStack;
|
||||
using AZ::IO::FindData::m_fileSet;
|
||||
};
|
||||
|
||||
AZStd::intrusive_ptr<TestFindData> m_findData;
|
||||
|
||||
Reference in New Issue
Block a user