Merge branch 'stabilization/2106' into BenchMarksReset

This commit is contained in:
srikappa-amzn
2021-06-15 10:59:36 -07:00
347 changed files with 13817 additions and 12324 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ struct CryPakMock
MOCK_METHOD1(PoolMalloc, void*(size_t size));
MOCK_METHOD1(PoolFree, void(void* p));
MOCK_METHOD3(PoolAllocMemoryBlock, AZStd::intrusive_ptr<AZ::IO::MemoryBlock> (size_t nSize, const char* sUsage, size_t nAlign));
MOCK_METHOD3(FindFirst, AZ::IO::ArchiveFileIterator(AZStd::string_view pDir, uint32_t nFlags, bool bAllOwUseFileSystem));
MOCK_METHOD2(FindFirst, AZ::IO::ArchiveFileIterator(AZStd::string_view pDir, AZ::IO::IArchive::EFileSearchType));
MOCK_METHOD1(FindNext, AZ::IO::ArchiveFileIterator(AZ::IO::ArchiveFileIterator handle));
MOCK_METHOD1(FindClose, bool(AZ::IO::ArchiveFileIterator));
MOCK_METHOD1(GetModificationTime, AZ::IO::IArchive::FileTime(AZ::IO::HandleType f));
@@ -306,8 +306,7 @@ void CLevelSystem::ScanFolder(const char* subfolder, bool modFolder)
AZStd::unordered_set<AZStd::string> pakList;
bool allowFileSystem = true;
AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(search.c_str(), 0, allowFileSystem);
AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(search.c_str(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskOnly);
if (handle)
{
@@ -320,7 +319,7 @@ void CLevelSystem::ScanFolder(const char* subfolder, bool modFolder)
{
if (AZ::StringFunc::Equal(handle.m_filename.data(), LevelPakName))
{
// level folder contain pak files like 'level.pak'
// level folder contain pak files like 'level.pak'
// which we only want to load during level loading.
continue;
}
@@ -351,7 +350,7 @@ void CLevelSystem::ScanFolder(const char* subfolder, bool modFolder)
PopulateLevels(search, folder, pPak, modFolder, false);
// Load levels outside of the bundles to maintain backward compatibility.
PopulateLevels(search, folder, pPak, modFolder, true);
}
void CLevelSystem::PopulateLevels(
@@ -360,7 +359,7 @@ void CLevelSystem::PopulateLevels(
{
// allow this find first to actually touch the file system
// (causes small overhead but with minimal amount of levels this should only be around 150ms on actual DVD Emu)
AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(searchPattern.c_str(), 0, fromFileSystemOnly);
AZ::IO::ArchiveFileIterator handle = pPak->FindFirst(searchPattern.c_str(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskOnly);
if (handle)
{
@@ -973,7 +972,7 @@ void CLevelSystem::UnloadLevel()
m_lastLevelName.clear();
SAFE_RELEASE(m_pCurrentLevel);
// Force Lua garbage collection (may no longer be needed now the legacy renderer has been removed).
// Normally the GC step is triggered at the end of this method (by the ESYSTEM_EVENT_LEVEL_POST_UNLOAD event).
EBUS_EVENT(AZ::ScriptSystemRequestBus, GarbageCollect);
-2
View File
@@ -9,8 +9,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
cmake_minimum_required(VERSION 3.0)
ly_add_target(
NAME AzAutoGen HEADERONLY
NAMESPACE AZ
@@ -292,8 +292,13 @@ namespace AZ
const typename VecType::FloatType cmp2 = VecType::AndNot(cmp0, cmp1);
// -1/x
// this step is calculated for all values of x, but only used if x > Sqrt(2) + 1
// in order to avoid a division by zero, detect if xabs is zero here and replace it with an arbitrary value
// if xabs does equal zero, the value here doesn't matter because the result will be thrown away
typename VecType::FloatType xabsSafe =
VecType::Add(xabs, VecType::And(VecType::CmpEq(xabs, VecType::ZeroFloat()), FastLoadConstant<VecType>(Simd::g_vec1111)));
const typename VecType::FloatType y0 = VecType::And(cmp0, FastLoadConstant<VecType>(Simd::g_HalfPi));
typename VecType::FloatType x0 = VecType::Div(FastLoadConstant<VecType>(Simd::g_vec1111), xabs);
typename VecType::FloatType x0 = VecType::Div(FastLoadConstant<VecType>(Simd::g_vec1111), xabsSafe);
x0 = VecType::Xor(x0, VecType::CastToFloat(FastLoadConstant<VecType>(Simd::g_negateMask)));
const typename VecType::FloatType y1 = VecType::And(cmp2, FastLoadConstant<VecType>(Simd::g_QuarterPi));
@@ -368,8 +373,12 @@ namespace AZ
typename VecType::FloatType offset = VecType::And(x_lt_0, offset1);
// the result of this part of the computation is thrown away if x equals 0,
// but if x does equal 0, it will cause a division by zero
// so replace zero by an arbitrary value here in that case
typename VecType::FloatType xSafe = VecType::Add(x, VecType::And(x_eq_0, FastLoadConstant<VecType>(Simd::g_vec1111)));
const typename VecType::FloatType atan_mask = VecType::Not(VecType::Or(x_eq_0, y_eq_0));
const typename VecType::FloatType atan_arg = VecType::Div(y, x);
const typename VecType::FloatType atan_arg = VecType::Div(y, xSafe);
typename VecType::FloatType atan_result = VecType::Atan(atan_arg);
atan_result = VecType::Add(atan_result, offset);
atan_result = VecType::AndNot(pio2_mask, atan_result);
@@ -471,6 +471,7 @@ namespace AZ
AZ_MATH_INLINE Vec2::FloatType Vec2::Reciprocal(FloatArgType value)
{
value = Sse::ReplaceFourth(Sse::ReplaceThird(value, 1.0f), 1.0f);
return Sse::Reciprocal(value);
}
@@ -513,6 +514,7 @@ namespace AZ
AZ_MATH_INLINE Vec2::FloatType Vec2::SqrtInv(FloatArgType value)
{
value = Sse::ReplaceFourth(Sse::ReplaceThird(value, 1.0f), 1.0f);
return Sse::SqrtInv(value);
}
@@ -507,6 +507,7 @@ namespace AZ
AZ_MATH_INLINE Vec3::FloatType Vec3::Reciprocal(FloatArgType value)
{
value = Sse::ReplaceFourth(value, 1.0f);
return Sse::Reciprocal(value);
}
@@ -549,6 +550,7 @@ namespace AZ
AZ_MATH_INLINE Vec3::FloatType Vec3::SqrtInv(FloatArgType value)
{
value = Sse::ReplaceFourth(value, 1.0f);
return Sse::SqrtInv(value);
}
@@ -175,4 +175,11 @@ namespace AZ::Utils
path /= ".o3de";
return path.Native();
}
AZ::IO::FixedMaxPathString GetO3deLogsDirectory()
{
AZ::IO::FixedMaxPath path = GetO3deManifestDirectory();
path /= "Logs";
return path.Native();
}
}
@@ -97,6 +97,9 @@ namespace AZ
//! Retrieves the full path where the manifest file lives, i.e. "<userhome>/.o3de/o3de_manifest.json"
AZ::IO::FixedMaxPathString GetEngineManifestPath();
//! Retrieves the full directory to the O3DE logs directory, i.e. "<userhome>/.o3de/Logs"
AZ::IO::FixedMaxPathString GetO3deLogsDirectory();
//! Retrieves the App root path to use on the current platform
//! If the optional is not engaged the AppRootPath should be calculated based
//! on the location of the bootstrap.cfg file
@@ -1290,7 +1290,7 @@ namespace AZ::IO
//////////////////////////////////////////////////////////////////////////
AZ::IO::ArchiveFileIterator Archive::FindFirst(AZStd::string_view pDir, [[maybe_unused]] uint32_t nPathFlags, bool bAllowUseFileSystem)
AZ::IO::ArchiveFileIterator Archive::FindFirst(AZStd::string_view pDir, EFileSearchType searchType)
{
auto szFullPath = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pDir);
if (!szFullPath)
@@ -1299,8 +1299,26 @@ namespace AZ::IO
return {};
}
bool bScanZips{};
bool bAllowUseFileSystem{};
switch (searchType)
{
case IArchive::eFileSearchType_AllowInZipsOnly:
bAllowUseFileSystem = false;
bScanZips = true;
break;
case IArchive::eFileSearchType_AllowOnDiskAndInZips:
bAllowUseFileSystem = true;
bScanZips = true;
break;
case IArchive::eFileSearchType_AllowOnDiskOnly:
bAllowUseFileSystem = true;
bScanZips = false;
break;
}
AZStd::intrusive_ptr<AZ::IO::FindData> pFindData = new AZ::IO::FindData();
pFindData->Scan(this, szFullPath->Native(), bAllowUseFileSystem);
pFindData->Scan(this, szFullPath->Native(), bAllowUseFileSystem, bScanZips);
return pFindData->Fetch();
}
@@ -1676,7 +1694,7 @@ namespace AZ::IO
return true;
}
if (AZ::IO::ArchiveFileIterator fileIterator = FindFirst(pWildcardIn, 0, true); fileIterator)
if (AZ::IO::ArchiveFileIterator fileIterator = FindFirst(pWildcardIn, IArchive::eFileSearchType_AllowOnDiskOnly); fileIterator)
{
AZStd::vector<AZStd::string> files;
do
@@ -234,7 +234,7 @@ namespace AZ::IO
uint64_t FTell(AZ::IO::HandleType handle) override;
int FFlush(AZ::IO::HandleType handle) override;
int FClose(AZ::IO::HandleType handle) override;
AZ::IO::ArchiveFileIterator FindFirst(AZStd::string_view pDir, uint32_t nPathFlags = 0, bool bAllOwUseFileSystem = false) override;
AZ::IO::ArchiveFileIterator FindFirst(AZStd::string_view pDir, EFileSearchType searchType = eFileSearchType_AllowInZipsOnly) override;
AZ::IO::ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator fileIterator) override;
bool FindClose(AZ::IO::ArchiveFileIterator fileIterator) override;
int FEof(AZ::IO::HandleType handle) override;
@@ -77,7 +77,7 @@ namespace AZ::IO
return m_findData && m_lastFetchValid;
}
void FindData::Scan(IArchive* archive, AZStd::string_view szDir, bool bAllowUseFS)
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
// this function execution
@@ -87,12 +87,18 @@ namespace AZ::IO
{
// first, find the file system files
ScanFS(archive, szDir);
ScanZips(archive, szDir);
if (bScanZips)
{
ScanZips(archive, szDir);
}
}
else
{
// first, find the zip files
ScanZips(archive, szDir);
if (bScanZips)
{
ScanZips(archive, szDir);
}
if (bAllowUseFS || nVarPakPriority != ArchiveLocationPriority::ePakPriorityPakOnly)
{
ScanFS(archive, szDir);
@@ -111,30 +117,31 @@ namespace AZ::IO
}
AZ::IO::FileIOBase::GetDirectInstance()->FindFiles(searchDirectory.c_str(), pattern.c_str(), [&](const char* filePath) -> bool
{
AZ::IO::FileDesc fileDesc;
AZStd::string filePathEntry{filePath};
AZ::IO::ArchiveFileIterator fileIterator;
fileIterator.m_filename = AZ::IO::PathView(filePath).Filename().Native();
fileIterator.m_fileDesc.nAttrib = {};
if (AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath))
{
fileDesc.nAttrib = fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
fileIterator.m_fileDesc.nAttrib = fileIterator.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::Subdirectory;
m_fileStack.emplace_back(AZStd::move(fileIterator));
}
else
{
if (AZ::IO::FileIOBase::GetDirectInstance()->IsReadOnly(filePath))
{
fileDesc.nAttrib = fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::ReadOnly;
fileIterator.m_fileDesc.nAttrib = fileIterator.m_fileDesc.nAttrib | AZ::IO::FileDesc::Attribute::ReadOnly;
}
AZ::u64 fileSize = 0;
AZ::IO::FileIOBase::GetDirectInstance()->Size(filePath, fileSize);
fileDesc.nSize = fileSize;
fileDesc.tWrite = AZ::IO::FileIOBase::GetDirectInstance()->ModificationTime(filePath);
fileIterator.m_fileDesc.nSize = fileSize;
fileIterator.m_fileDesc.tWrite = AZ::IO::FileIOBase::GetDirectInstance()->ModificationTime(filePath);
// These times are not supported by our file interface
fileDesc.tAccess = fileDesc.tWrite;
fileDesc.tCreate = fileDesc.tWrite;
fileIterator.m_fileDesc.tAccess = fileIterator.m_fileDesc.tWrite;
fileIterator.m_fileDesc.tCreate = fileIterator.m_fileDesc.tWrite;
m_fileStack.emplace_back(AZStd::move(fileIterator));
}
[[maybe_unused]] auto result = m_mapFiles.emplace(AZStd::move(filePathEntry), fileDesc);
AZ_Assert(result.second, "Failed to insert FindData entry for filePath %s", filePath);
return true;
});
}
@@ -164,7 +171,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_mapFiles.emplace(fname, fileDesc);
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
}
ZipDir::FindDir findDirectoryEntry(zipCache);
@@ -177,7 +184,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_mapFiles.emplace(fname, fileDesc);
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, fname, fileDesc });
}
};
@@ -246,7 +253,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_mapFiles.emplace(bindRootIter->Native(), fileDesc);
m_fileStack.emplace_back(AZ::IO::ArchiveFileIterator{ this, bindRootIter->Native(), fileDesc });
}
}
else
@@ -262,22 +269,19 @@ namespace AZ::IO
AZ::IO::ArchiveFileIterator FindData::Fetch()
{
AZ::IO::ArchiveFileIterator fileIterator;
fileIterator.m_findData = this;
if (m_mapFiles.empty())
if (m_fileStack.empty())
{
return fileIterator;
AZ::IO::ArchiveFileIterator emptyFileIterator;
emptyFileIterator.m_lastFetchValid = false;
emptyFileIterator.m_findData = this;
return emptyFileIterator;
}
auto pakFileIter = m_mapFiles.begin();
AZStd::string fullFilePath;
AZ::StringFunc::Path::GetFullFileName(pakFileIter->first.c_str(), fullFilePath);
fileIterator.m_filename = AZStd::move(fullFilePath);
fileIterator.m_fileDesc = pakFileIter->second;
fileIterator.m_lastFetchValid = true;
// Remove Fetched item from the FindData map so that the iteration continues
m_mapFiles.erase(pakFileIter);
AZ::IO::ArchiveFileIterator fileIterator{ m_fileStack.back() };
fileIterator.m_lastFetchValid = true;
fileIterator.m_findData = this;
m_fileStack.pop_back();
return fileIterator;
}
}
@@ -15,7 +15,6 @@
#include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/string/fixed_string.h>
namespace AZ::IO
{
struct IArchive;
@@ -74,13 +73,13 @@ namespace AZ::IO
AZ_CLASS_ALLOCATOR(FindData, AZ::SystemAllocator, 0);
FindData() = default;
AZ::IO::ArchiveFileIterator Fetch();
void Scan(IArchive* archive, AZStd::string_view path, bool bAllowUseFS = false);
void Scan(IArchive* archive, AZStd::string_view path, bool bAllowUseFS = false, bool bScanZips = true);
protected:
void ScanFS(IArchive* archive, AZStd::string_view path);
void ScanZips(IArchive* archive, AZStd::string_view path);
using FileMap = AZStd::map<AZStd::string, AZ::IO::FileDesc, AZStdStringLessCaseInsensitive>;
FileMap m_mapFiles;
using FileStack = AZStd::vector<ArchiveFileIterator>;
FileStack m_fileStack;
};
}
@@ -197,6 +197,13 @@ namespace AZ::IO
eInMemoryPakLocale_PAK,
};
enum EFileSearchType
{
eFileSearchType_AllowInZipsOnly = 0,
eFileSearchType_AllowOnDiskAndInZips,
eFileSearchType_AllowOnDiskOnly
};
using SignedFileSize = int64_t;
virtual ~IArchive() = default;
@@ -315,7 +322,7 @@ namespace AZ::IO
// Arguments:
// nFlags is a combination of EPathResolutionRules flags.
virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, uint32_t nFlags = 0, bool bAllowUseFileSystem = false) = 0;
virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, EFileSearchType searchType = eFileSearchType_AllowInZipsOnly) = 0;
virtual ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator handle) = 0;
virtual bool FindClose(AZ::IO::ArchiveFileIterator handle) = 0;
//returns file modification time
@@ -693,11 +693,11 @@ namespace AzFramework
// set the __index so we can read values in case we change the script
// after we export the component
lua_pushliteral(lua, "__index");
lua_pushcclosure(lua, &Internal::Properties__Index, 1);
lua_pushcclosure(lua, &Internal::Properties__Index, 0);
lua_rawset(lua, -3);
lua_pushliteral(lua, "__newindex");
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 0);
lua_rawset(lua, -3);
}
lua_pop(lua, 1); // pop the properties table (or the nil value)
@@ -900,11 +900,11 @@ namespace AzFramework
// Ensure that this instance of Properties table has the proper __index and __newIndex metamethods.
lua_newtable(lua); // This new table will become the Properties instance metatable. Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {}
lua_pushliteral(lua, "__index"); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index
lua_pushcclosure(lua, &Internal::Properties__Index, 1); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
lua_pushcclosure(lua, &Internal::Properties__Index, 0); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {} __index function
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index}
lua_pushliteral(lua, "__newindex");
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 1);
lua_pushcclosure(lua, &Internal::Properties__NewIndex, 0);
lua_rawset(lua, -3); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {} {__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex}
lua_setmetatable(lua, -2); // Stack: ScriptRootTable PropertiesTable EntityTable "Properties" {Meta{__index=Internal::Properties__Index __newindex=Internal::Properties__NewIndex} }
+22 -20
View File
@@ -8,25 +8,27 @@
# 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.
#
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME})
ly_add_target(
NAME AzTest STATIC
NAMESPACE AZ
FILES_CMAKE
AzTest/aztest_files.cmake
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
${pal_dir}
BUILD_DEPENDENCIES
PUBLIC
3rdParty::googletest::GMock
3rdParty::googletest::GTest
3rdParty::GoogleBenchmark
AZ::AzCore
PLATFORM_INCLUDE_FILES
if(NOT LY_MONOLITHIC_GAME)
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME})
ly_add_target(
NAME AzTest STATIC
NAMESPACE AZ
FILES_CMAKE
AzTest/aztest_files.cmake
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
.
${pal_dir}
BUILD_DEPENDENCIES
PUBLIC
3rdParty::googletest::GMock
3rdParty::googletest::GTest
3rdParty::GoogleBenchmark
AZ::AzCore
PLATFORM_INCLUDE_FILES
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
)
)
endif()
@@ -159,12 +159,23 @@ namespace AzToolsFramework
void PrefabEditorEntityOwnershipService::GetNonPrefabEntities(EntityList& entities)
{
m_rootInstance->GetEntities(entities, false);
m_rootInstance->GetEntities(
[&entities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
entities.emplace_back(entity.get());
return true;
});
}
bool PrefabEditorEntityOwnershipService::GetAllEntities(EntityList& entities)
{
m_rootInstance->GetEntities(entities, true);
m_rootInstance->GetAllEntitiesInHierarchy(
[&entities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
entities.emplace_back(entity.get());
return true;
});
return true;
}
@@ -252,13 +263,20 @@ namespace AzToolsFramework
}
AZStd::string out;
if (m_loaderInterface->SaveTemplateToString(m_rootInstance->GetTemplateId(), out))
if (!m_loaderInterface->SaveTemplateToString(m_rootInstance->GetTemplateId(), out))
{
const size_t bytesToWrite = out.size();
const size_t bytesWritten = stream.Write(bytesToWrite, out.data());
return bytesWritten == bytesToWrite;
return false;
}
return false;
const size_t bytesToWrite = out.size();
const size_t bytesWritten = stream.Write(bytesToWrite, out.data());
if(bytesWritten != bytesToWrite)
{
return false;
}
m_prefabSystemComponent->SetTemplateDirtyFlag(templateId, false);
return true;
}
void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename)
@@ -544,7 +562,7 @@ namespace AzToolsFramework
return;
}
m_rootInstance->GetNestedEntities([this](AZStd::unique_ptr<AZ::Entity>& entity)
m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
@@ -187,13 +187,14 @@ namespace AzToolsFramework
return removedEntity;
}
void Instance::DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback)
void Instance::DetachAllEntitiesInHierarchy(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback)
{
callback(AZStd::move(DetachContainerEntity()));
DetachEntities(callback);
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->DetachNestedEntities(callback);
instance->DetachAllEntitiesInHierarchy(callback);
}
}
@@ -372,17 +373,25 @@ namespace AzToolsFramework
}
}
void Instance::GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback)
bool Instance::GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetConstEntities(callback);
for (const auto& [instanceAlias, instance] : m_nestedInstances)
for (auto& [entityAlias, entity] : m_entities)
{
instance->GetConstNestedEntities(callback);
if (!entity)
{
continue;
}
if (!callback(entity))
{
return false;
}
}
return true;
}
void Instance::GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback)
bool Instance::GetConstEntities_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
for (const auto& [entityAlias, entity] : m_entities)
{
@@ -393,19 +402,83 @@ namespace AzToolsFramework
if (!callback(*entity))
{
break;
return false;
}
}
return true;
}
void Instance::GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
bool Instance::GetAllEntitiesInHierarchy_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetEntities(callback);
if (HasContainerEntity())
{
if (!callback(m_containerEntity))
{
return false;
}
}
if (!GetEntities_Impl(callback))
{
return false;
}
for (auto& [instanceAlias, instance] : m_nestedInstances)
{
instance->GetNestedEntities(callback);
if (!instance->GetAllEntitiesInHierarchy_Impl(callback))
{
return false;
}
}
return true;
}
bool Instance::GetAllEntitiesInHierarchyConst_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
if (HasContainerEntity())
{
if (!callback(*m_containerEntity))
{
return false;
}
}
if (!GetConstEntities_Impl(callback))
{
return false;
}
for (const auto& [instanceAlias, instance] : m_nestedInstances)
{
if (!instance->GetAllEntitiesInHierarchyConst_Impl(callback))
{
return false;
}
}
return true;
}
void Instance::GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetEntities_Impl(callback);
}
void Instance::GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
GetConstEntities_Impl(callback);
}
void Instance::GetAllEntitiesInHierarchy(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
GetAllEntitiesInHierarchy_Impl(callback);
}
void Instance::GetAllEntitiesInHierarchyConst(const AZStd::function<bool(const AZ::Entity&)>& callback) const
{
GetAllEntitiesInHierarchyConst_Impl(callback);
}
void Instance::GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback)
@@ -416,44 +489,6 @@ namespace AzToolsFramework
}
}
void Instance::GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
{
for (auto& [entityAlias, entity] : m_entities)
{
if (!callback(entity))
{
break;
}
}
}
void Instance::GetEntities(EntityList& entities, bool includeNestedEntities)
{
// Non-recursive traversal of instances
AZStd::vector<Instance*> instancesToTraverse = { this };
while (!instancesToTraverse.empty())
{
Instance* currentInstance = instancesToTraverse.back();
instancesToTraverse.pop_back();
if (includeNestedEntities)
{
instancesToTraverse.reserve(instancesToTraverse.size() + currentInstance->m_nestedInstances.size());
for (const auto& instanceByAlias : currentInstance->m_nestedInstances)
{
instancesToTraverse.push_back(instanceByAlias.second.get());
}
}
// Size increases by 1 for each instance because we have to count the container entity also.
entities.reserve(entities.size() + currentInstance->m_entities.size() + 1);
entities.push_back(m_containerEntity.get());
for (const auto& entityByAlias : currentInstance->m_entities)
{
entities.push_back(entityByAlias.second.get());
}
}
}
EntityAliasOptionalReference Instance::GetEntityAlias(const AZ::EntityId& id)
{
if (m_instanceToTemplateEntityIdMap.count(id))
@@ -87,7 +87,15 @@ namespace AzToolsFramework
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void DetachNestedEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
/**
* Detaches all entities in the instance hierarchy.
* Includes all direct entities, all nested entities, and all container entities.
* Note that without container entities the hierarchy that remains cannot be used further without restoring new ones.
* @param callback A user provided callback that can be used to capture ownership and manipulate the detached entities.
*/
void DetachAllEntitiesInHierarchy(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
void Reset();
@@ -113,10 +121,10 @@ namespace AzToolsFramework
/**
* Gets the entities in the Instance DOM. Can recursively trace all nested instances.
*/
void GetConstNestedEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback);
void GetNestedEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetEntities(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetConstEntities(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
void GetAllEntitiesInHierarchy(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
void GetAllEntitiesInHierarchyConst(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
void GetNestedInstances(const AZStd::function<void(AZStd::unique_ptr<Instance>&)>& callback);
/**
@@ -176,12 +184,6 @@ namespace AzToolsFramework
static InstanceAlias GenerateInstanceAlias();
protected:
/**
* Gets the entities owned by this instance
*/
void GetEntities(EntityList& entities, bool includeNestedEntities = false);
private:
static constexpr const char s_aliasPathSeparator = '/';
@@ -189,6 +191,11 @@ namespace AzToolsFramework
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
bool GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
bool GetConstEntities_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
bool GetAllEntitiesInHierarchy_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback);
bool GetAllEntitiesInHierarchyConst_Impl(const AZStd::function<bool(const AZ::Entity&)>& callback) const;
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);
@@ -154,7 +154,7 @@ namespace AzToolsFramework
InstanceOptionalReference owningInstanceReference = m_storingInstance->m_instanceEntityMapper->FindOwningInstance(entityId);
// Start with an empty alias to build out our reference path
// If we can't resolve this id we'll return a random new alias instead of a reference path
// If we can't resolve this id we'll return a new alias based on the entity ID instead of a reference path
AliasPath relativeEntityAliasPath;
if (!owningInstanceReference)
{
@@ -162,7 +162,7 @@ namespace AzToolsFramework
"Prefab - EntityIdMapper: Entity with Id %s has no registered owning instance",
entityId.ToString().c_str());
return Instance::GenerateEntityAlias();
return AZStd::string::format("Entity_%s", entityId.ToString().c_str());
}
Instance* owningInstance = &(owningInstanceReference->get());
@@ -62,25 +62,16 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
}
}
AZStd::vector<AZ::Entity*> EditorInfoRemover::GetEntitiesFromInstance(AZStd::unique_ptr<Instance>& instance)
void EditorInfoRemover::GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities)
{
AZStd::vector<AZ::Entity*> result;
instance->GetNestedEntities(
[&result](const AZStd::unique_ptr<AZ::Entity>& entity)
instance->GetAllEntitiesInHierarchy(
[&hierarchyEntities](const AZStd::unique_ptr<AZ::Entity>& entity)
{
result.emplace_back(entity.get());
hierarchyEntities.emplace_back(entity.get());
return true;
}
);
if (instance->HasContainerEntity())
{
auto containerEntityReference = instance->GetContainerEntity();
result.emplace_back(&containerEntityReference->get());
}
return result;
}
void EditorInfoRemover::SetEditorOnlyEntityHandlerFromCandidates(const EntityList& entities)
@@ -543,7 +534,9 @@ exportComponent, prefabProcessorContext);
}
// grab all nested entities from the Instance as source entities.
EntityList sourceEntities = GetEntitiesFromInstance(instance);
EntityList sourceEntities;
GetEntitiesFromInstance(instance, sourceEntities);
EntityList exportEntities;
// prepare for validation of component requirements.
@@ -616,7 +609,7 @@ exportComponent, prefabProcessorContext);
);
// replace entities of instance with exported ones.
instance->GetNestedEntities(
instance->GetAllEntitiesInHierarchy(
[&exportEntitiesMap](AZStd::unique_ptr<AZ::Entity>& entity)
{
auto entityId = entity->GetId();
@@ -625,14 +618,6 @@ exportComponent, prefabProcessorContext);
}
);
if (instance->HasContainerEntity())
{
if (auto found = exportEntitiesMap.find(instance->GetContainerEntityId()); found != exportEntitiesMap.end())
{
instance->SetContainerEntity(*found->second);
}
}
// save the final result in the target Prefab DOM.
PrefabDom filteredPrefab;
if (!PrefabDomUtils::StoreInstanceInPrefabDom(*instance, filteredPrefab))
@@ -55,8 +55,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
protected:
using EntityList = AZStd::vector<AZ::Entity*>;
static EntityList GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance);
static void GetEntitiesFromInstance(
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance>& instance, EntityList& hierarchyEntities);
static bool ReadComponentAttribute(
AZ::Component* component,
@@ -38,11 +38,7 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
// going to be used to create clones of the entities.
{
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
if (instance.HasContainerEntity())
{
entities.emplace_back(AZStd::move(instance.DetachContainerEntity()));
}
instance.DetachNestedEntities(
instance.DetachAllEntitiesInHierarchy(
[&entities](AZStd::unique_ptr<AZ::Entity> entity)
{
entities.emplace_back(AZStd::move(entity));
@@ -1206,7 +1206,7 @@ namespace AzToolsFramework
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)->
Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)->
DataElement(AZ::Edit::UIHandlers::Default, &EditorTransform::m_rotate, "Rotate", "Local Rotation (Relative to parent) in degrees.")->
Attribute(AZ::Edit::Attributes::Step, 0.1f)->
Attribute(AZ::Edit::Attributes::Step, 1.0f)->
Attribute(AZ::Edit::Attributes::Suffix, " deg")->
Attribute(AZ::Edit::Attributes::ReadOnly, &EditorTransform::m_locked)->
Attribute(AZ::Edit::Attributes::SliceFlags, AZ::Edit::SliceFlags::NotPushableOnSliceRoot)->
@@ -58,8 +58,17 @@ namespace AzToolsFramework
if (!path.empty())
{
infoString =
QObject::tr("<span style=\"font-style: italic; font-weight: 400;\">(%1)</span>").arg(path.Filename().Native().data());
QString saveFlag = "";
auto dirtyOutcome = m_prefabPublicInterface->HasUnsavedChanges(path);
if (dirtyOutcome.IsSuccess() && dirtyOutcome.GetValue() == true)
{
saveFlag = "*";
}
infoString = QObject::tr("<span style=\"font-style: italic; font-weight: 400;\">(%1%2)</span>")
.arg(path.Filename().Native().data())
.arg(saveFlag);
}
return infoString;
@@ -28,6 +28,7 @@
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
#include <QApplication>
#include <QFileDialog>
@@ -588,15 +589,6 @@ namespace AzToolsFramework
bool PrefabIntegrationManager::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath)
{
QWidget* mainWindow = nullptr;
EditorRequests::Bus::BroadcastResult(mainWindow, &EditorRequests::Bus::Events::GetMainWindow);
if (mainWindow == nullptr)
{
AZ_Assert(false, "Prefab - Could not detect Editor main window to generate the asset picker.");
return false;
}
AssetSelectionModel selection;
// Note, stringfilter will match every source file CONTAINING ".prefab".
@@ -624,7 +616,7 @@ namespace AzToolsFramework
selection.SetDisplayFilter(compositeFilterPtr);
selection.SetSelectionFilter(compositeFilterPtr);
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, mainWindow);
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, AzToolsFramework::GetActiveWindow());
if (!selection.IsValid())
{
@@ -983,12 +975,7 @@ namespace AzToolsFramework
includedEntities.c_str(),
referencedEntities.c_str());
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequests::Bus::BroadcastResult(
mainWindow,
&AzToolsFramework::EditorRequests::Bus::Events::GetMainWindow);
QMessageBox msgBox(mainWindow);
QMessageBox msgBox(AzToolsFramework::GetActiveWindow());
msgBox.setWindowTitle("External Entity References");
msgBox.setText("The prefab contains references to external entities that are not selected.");
msgBox.setInformativeText("You can move the referenced entities into this prefab or retain the external references.");
@@ -93,7 +93,7 @@ namespace UnitTest
// Retrieve the entity pointer from the component application bus.
AZ::Entity* wheelEntityUnderAxle = nullptr;
axleInstance->GetNestedEntities([&wheelEntityUnderAxle, wheelEntityIdUnderAxle](AZStd::unique_ptr<AZ::Entity>& entity)
axleInstance->GetAllEntitiesInHierarchy([&wheelEntityUnderAxle, wheelEntityIdUnderAxle](AZStd::unique_ptr<AZ::Entity>& entity)
{
if (entity->GetId() == wheelEntityIdUnderAxle)
{
@@ -71,24 +71,29 @@ namespace UnitTest
m_prefabSystemComponent->CreatePrefab({ entitiesCreated[0] }, {}, "test/path1"));
ASSERT_TRUE(firstInstance);
ASSERT_TRUE(firstInstance->HasContainerEntity());
expectedEntityNameSet.insert(firstInstance->GetContainerEntity()->get().GetName());
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> secondInstance(
m_prefabSystemComponent->CreatePrefab({ entitiesCreated[1] }, MakeInstanceList(AZStd::move(firstInstance)), "test/path2"));
ASSERT_TRUE(secondInstance);
ASSERT_TRUE(secondInstance->HasContainerEntity());
expectedEntityNameSet.insert(secondInstance->GetContainerEntity()->get().GetName());
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> thirdInstance(
m_prefabSystemComponent->CreatePrefab({ entitiesCreated[2] }, MakeInstanceList(AZStd::move(secondInstance)), "test/path3"));
ASSERT_TRUE(thirdInstance);
ASSERT_TRUE(thirdInstance->HasContainerEntity());
auto& containerEntity = thirdInstance->GetContainerEntity()->get();
expectedEntityNameSet.insert(containerEntity.GetName());
expectedEntityNameSet.insert(thirdInstance->GetContainerEntity()->get().GetName());
//Create Spawnable
auto& prefabDom = m_prefabSystemComponent->FindTemplateDom(thirdInstance->GetTemplateId());
AzFramework::Spawnable spawnable;
AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(spawnable, prefabDom);
EXPECT_EQ(spawnable.GetEntities().size() - 1, normalEntityCount); // 1 for container entity
EXPECT_EQ(spawnable.GetEntities().size(), normalEntityCount + 3); // +1 for each container entity
const auto& spawnableEntities = spawnable.GetEntities();
AZStd::unordered_set<AZStd::string> actualEntityNameSet;
@@ -97,6 +102,6 @@ namespace UnitTest
actualEntityNameSet.insert(spawnableEntity->GetName());
}
EXPECT_EQ(expectedEntityNameSet, actualEntityNameSet);
EXPECT_EQ(actualEntityNameSet, expectedEntityNameSet);
}
}
@@ -213,7 +213,7 @@ namespace UnitTest
AZStd::unique_ptr<Instance> convertedInstance(aznew Instance());
ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*convertedInstance, m_prefabDom));
convertedInstance->DetachNestedEntities(
convertedInstance->DetachAllEntitiesInHierarchy(
[this](AZStd::unique_ptr<AZ::Entity> entity)
{
m_runtimeEntities.emplace_back(entity.release());
@@ -196,6 +196,16 @@ function(ly_delayed_generate_static_modules_inl)
ly_get_gem_load_dependencies(all_game_gem_dependencies ${project_name}.GameLauncher)
foreach(game_gem_dependency ${all_game_gem_dependencies})
# Sometimes, a gem's Client variant may be an interface library
# which dependes on multiple gem targets. The interface libraries
# should be skipped; the real dependencies of the interface will be processed
if(TARGET ${game_gem_dependency})
get_target_property(target_type ${game_gem_dependency} TYPE)
if(${target_type} STREQUAL "INTERFACE_LIBRARY")
continue()
endif()
endif()
# To match the convention on how gems targets vs gem modules are named,
# we remove the ".Static" from the suffix
# Replace "." with "_"
@@ -224,6 +234,14 @@ function(ly_delayed_generate_static_modules_inl)
list(APPEND all_server_gem_dependencies ${server_gem_load_dependencies} ${server_gem_dependency})
endforeach()
foreach(server_gem_dependency ${all_server_gem_dependencies})
# Skip interface libraries
if(TARGET ${server_gem_dependency})
get_target_property(target_type ${server_gem_dependency} TYPE)
if(${target_type} STREQUAL "INTERFACE_LIBRARY")
continue()
endif()
endif()
# Replace "." with "_"
string(REPLACE "." "_" server_gem_dependency ${server_gem_dependency})
@@ -715,11 +715,11 @@ QMenu* LevelEditorMenuHandler::CreateViewMenu()
{
return view.IsViewportPane();
});
#endif
viewportViewsMenuWrapper.AddAction(ID_WIREFRAME);
viewportViewsMenuWrapper.AddSeparator();
#endif
if (CViewManager::IsMultiViewportEnabled())
{
viewportViewsMenuWrapper.AddAction(ID_VIEW_CONFIGURELAYOUT);
+1 -1
View File
@@ -2891,7 +2891,7 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen)
{
// provide the current project path for in case we want to update the project
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project_path %s", screen.c_str(), projectPath.c_str());
const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project-path %s", screen.c_str(), projectPath.c_str());
bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions);
if (!launchSuccess)
{
+1 -1
View File
@@ -1139,7 +1139,7 @@ bool CCryEditDoc::SaveLevel(const QString& filename)
const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*");
const QString oldLevelName = Path::GetFile(GetLevelPathName());
const QString oldLevelXml = Path::ReplaceExtension(oldLevelName, "xml");
AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), 0, true);
AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskAndInZips);
if (findHandle)
{
do
+27 -5
View File
@@ -514,18 +514,28 @@ void EditorViewportWidget::Update()
// Disable rendering to avoid recursion into Update()
PushDisableRendering();
//get debug display interface for the viewport
AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus;
AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, GetViewportId());
AZ_Assert(debugDisplayBus, "Invalid DebugDisplayRequestBus.");
AzFramework::DebugDisplayRequests* debugDisplay =
AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus);
// draw debug visualizations
if (m_debugDisplay)
if (debugDisplay)
{
const AZ::u32 prevState = m_debugDisplay->GetState();
m_debugDisplay->SetState(
const AZ::u32 prevState = debugDisplay->GetState();
debugDisplay->SetState(
e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn);
AzFramework::EntityDebugDisplayEventBus::Broadcast(
&AzFramework::EntityDebugDisplayEvents::DisplayEntityViewport,
AzFramework::ViewportInfo{ GetViewportId() }, *m_debugDisplay);
AzFramework::ViewportInfo{ GetViewportId() }, *debugDisplay);
m_debugDisplay->SetState(prevState);
debugDisplay->SetState(prevState);
}
QtViewport::Update();
@@ -2664,6 +2674,18 @@ bool EditorViewportWidget::GetActiveCameraPosition(AZ::Vector3& cameraPos)
return false;
}
bool EditorViewportWidget::GetActiveCameraState(AzFramework::CameraState& cameraState)
{
if (m_pPrimaryViewport == this)
{
cameraState = GetCameraState();
return true;
}
return false;
}
void EditorViewportWidget::OnStartPlayInEditor()
{
if (m_viewEntityId.IsValid())
@@ -184,6 +184,7 @@ public:
void SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) override;
AZ::EntityId GetCurrentViewEntityId() override { return m_viewEntityId; }
bool GetActiveCameraPosition(AZ::Vector3& cameraPos) override;
bool GetActiveCameraState(AzFramework::CameraState& cameraState) override;
// AzToolsFramework::EditorEntityContextNotificationBus (handler moved to cpp to resolve link issues in unity builds)
virtual void OnStartPlayInEditor();
-7
View File
@@ -833,13 +833,6 @@ void MainWindow::InitActions()
.Connect(&QAction::triggered, []() { SandboxEditor::SetAngleSnapping(!SandboxEditor::AngleSnappingEnabled()); });
// Display actions
am->AddAction(ID_WIREFRAME, tr("&Wireframe"))
.SetShortcut(tr("F3"))
.SetToolTip(tr("Wireframe (F3)"))
.SetCheckable(true)
.SetStatusTip(tr("Render in Wireframe Mode."))
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateWireframe);
am->AddAction(ID_SWITCHCAMERA_DEFAULTCAMERA, tr("Default Camera")).SetCheckable(true)
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSwitchToDefaultCamera);
am->AddAction(ID_SWITCHCAMERA_SEQUENCECAMERA, tr("Sequence Camera")).SetCheckable(true)
+3 -3
View File
@@ -115,10 +115,10 @@ protected:
float m_prevMoveSpeed;
// Speed combobox/lineEdit settings
double m_minSpeed = 0.1;
double m_minSpeed = 0.01;
double m_maxSpeed = 100.0;
double m_speedStep = 0.1;
int m_numDecimals = 1;
double m_speedStep = 0.01;
int m_numDecimals = 3;
// Speed presets
float m_speedPresetValues[3] = { 0.1f, 1.0f, 10.0f };
@@ -154,11 +154,23 @@ SandboxIntegrationManager::SandboxIntegrationManager()
{
// Required to receive events from the Cry Engine undo system
GetIEditor()->GetUndoManager()->AddListener(this);
// Only create the PrefabIntegrationManager if prefabs are enabled
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_prefabIntegrationManager = aznew AzToolsFramework::Prefab::PrefabIntegrationManager();
}
}
SandboxIntegrationManager::~SandboxIntegrationManager()
{
GetIEditor()->GetUndoManager()->RemoveListener(this);
delete m_prefabIntegrationManager;
m_prefabIntegrationManager = nullptr;
}
void SandboxIntegrationManager::Setup()
@@ -187,11 +199,16 @@ void SandboxIntegrationManager::Setup()
AZ_Assert((m_editorEntityUiInterface != nullptr),
"SandboxIntegrationManager requires a EditorEntityUiInterface instance to be present on Setup().");
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
AZ_Assert(
(m_prefabIntegrationInterface != nullptr),
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup().");
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
AZ_Assert(
(m_prefabIntegrationInterface != nullptr),
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup().");
}
m_editorEntityAPI = AZ::Interface<AzToolsFramework::EditorEntityAPI>::Get();
AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup().");
@@ -305,7 +305,7 @@ private:
bool m_debugDisplayBusImplementationActive = false;
AzToolsFramework::Prefab::PrefabIntegrationManager m_prefabIntegrationManager;
AzToolsFramework::Prefab::PrefabIntegrationManager* m_prefabIntegrationManager = nullptr;
AzToolsFramework::EditorEntityUiInterface* m_editorEntityUiInterface = nullptr;
AzToolsFramework::Prefab::PrefabIntegrationInterface* m_prefabIntegrationInterface = nullptr;
@@ -19,6 +19,7 @@ set(FILES
native/AssetManager/AssetRequestHandler.cpp
native/AssetManager/AssetRequestHandler.h
native/AssetManager/assetScanFolderInfo.h
native/AssetManager/assetScanFolderInfo.cpp
native/AssetManager/assetScanner.cpp
native/AssetManager/assetScanner.h
native/AssetManager/assetScannerWorker.cpp
@@ -0,0 +1,42 @@
/*
* 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/AssetManager/assetScanFolderInfo.h>
#include <native/utilities/assetUtils.h>
namespace AssetProcessor
{
ScanFolderInfo::ScanFolderInfo(
QString path,
QString displayName,
QString portableKey,
bool isRoot,
bool recurseSubFolders,
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms,
int order,
AZ::s64 scanFolderID,
bool canSaveNewAssets)
: m_scanPath(path)
, m_displayName(displayName)
, m_portableKey (portableKey)
, m_isRoot(isRoot)
, m_recurseSubFolders(recurseSubFolders)
, m_order(order)
, m_scanFolderID(scanFolderID)
, m_platforms(platforms)
, m_canSaveNewAssets(canSaveNewAssets)
{
m_scanPath = AssetUtilities::NormalizeFilePath(m_scanPath);
// note that m_scanFolderID is 0 unless its filled in from the DB.
}
} // end namespace AssetProcessor
@@ -33,19 +33,7 @@ namespace AssetProcessor
AZStd::vector<AssetBuilderSDK::PlatformInfo> platforms = AZStd::vector<AssetBuilderSDK::PlatformInfo>{},
int order = 0,
AZ::s64 scanFolderID = 0,
bool canSaveNewAssets = false)
: m_scanPath(path)
, m_displayName(displayName)
, m_portableKey (portableKey)
, m_isRoot(isRoot)
, m_recurseSubFolders(recurseSubFolders)
, m_order(order)
, m_scanFolderID(scanFolderID)
, m_platforms(platforms)
, m_canSaveNewAssets(canSaveNewAssets)
{
// note that m_scanFolderID is 0 unless its filled in from the DB.
}
bool canSaveNewAssets = false);
ScanFolderInfo() = default;
ScanFolderInfo(const ScanFolderInfo& other) = default;
+1 -1
View File
@@ -13,7 +13,7 @@ ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${P
include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED)
if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED AND NOT LY_MONOLITHIC_GAME)
ly_add_target(
NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE}
+58 -5
View File
@@ -20,12 +20,11 @@ if (NOT python_package_name)
message(WARNING "Python was not found in the package assocation list. Did someone call ly_associate_package(xxxxxxx Python) ?")
endif()
ly_add_target(
NAME ProjectManager APPLICATION
OUTPUT_NAME o3de
NAME ProjectManager.Static STATIC
NAMESPACE AZ
AUTOMOC
AUTORCC
FILES_CMAKE
project_manager_files.cmake
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
@@ -47,6 +46,60 @@ ly_add_target(
3rdParty::pybind11
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
AZ::AzQtComponents
)
)
ly_add_target(
NAME ProjectManager APPLICATION
OUTPUT_NAME o3de
NAMESPACE AZ
AUTORCC
FILES_CMAKE
project_manager_app_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Concurrent
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzCore
AZ::AzFramework
AZ::AzQtComponents
AZ::ProjectManager.Static
)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME ProjectManager.Tests EXECUTABLE
NAMESPACE AZ
AUTORCC
FILES_CMAKE
project_manager_tests_files.cmake
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
Platform/${PAL_PLATFORM_NAME}
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
3rdParty::Qt::Concurrent
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzTest
AZ::AzFramework
AZ::AzFrameworkTestShared
AZ::ProjectManager.Static
)
ly_add_googletest(
NAME AZ::ProjectManager.Tests
TEST_COMMAND $<TARGET_FILE:AZ::ProjectManager.Tests> --unittest
)
endif()
@@ -0,0 +1,15 @@
#
# 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.
#
set(FILES
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Linux.h
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS true
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <ProjectManager_Test_Traits_Linux.h>
@@ -0,0 +1,15 @@
#
# 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.
#
set(FILES
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Mac.h
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <ProjectManager_Test_Traits_Mac.h>
@@ -0,0 +1,15 @@
#
# 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.
#
set(FILES
ProjectManager_Test_Traits_Platform.h
ProjectManager_Test_Traits_Windows.h
)
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <ProjectManager_Test_Traits_Windows.h>
@@ -0,0 +1,15 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false
@@ -7,6 +7,11 @@ QMainWindow {
margin:0;
}
#ScreensCtrl {
min-width:1200px;
min-height:800px;
}
QPushButton:focus {
outline: none;
border:1px solid #1e70eb;
@@ -0,0 +1,186 @@
/*
* 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 <Application.h>
#include <ProjectUtils.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Logging/LoggingComponent.h>
#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
#include <QApplication>
#include <QDir>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
Application::~Application()
{
TearDown();
}
bool Application::Init(bool interactive)
{
constexpr const char* applicationName { "O3DE" };
QApplication::setOrganizationName(applicationName);
QApplication::setOrganizationDomain("o3de.org");
QCoreApplication::setApplicationName(applicationName);
QCoreApplication::setApplicationVersion("1.0");
// Use the LogComponent for non-dev logging log
RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
// set the log alias to .o3de/Logs instead of the default user/logs
AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
// DevWriteStorage is where the event log is written during development
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
// Save event logs to .o3de/Logs/eventlogger/EventLogO3DE.azsl
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::BuildTargetNameKey, applicationName);
Start(AzFramework::Application::Descriptor());
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
// Create the actual Qt Application - this needs to happen before using QMessageBox
m_app.reset(new QApplication(*GetArgC(), *GetArgV()));
if(!InitLog(applicationName))
{
AZ_Warning("ProjectManager", false, "Failed to init logging");
}
m_pythonBindings = AZStd::make_unique<PythonBindings>(GetEngineRoot());
if (!m_pythonBindings || !m_pythonBindings->PythonStarted())
{
if (interactive)
{
QMessageBox::critical(nullptr, QObject::tr("Failed to start Python"),
QObject::tr("This tool requires an O3DE engine with a Python runtime, "
"but either Python is missing or mis-configured. Please rename "
"your python/runtime folder to python/runtime_bak, then run "
"python/get_python.bat to restore the Python runtime folder."));
}
return false;
}
const AZ::CommandLine* commandLine = GetCommandLine();
AZ_Assert(commandLine, "Failed to get command line");
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects;
if (size_t screenSwitchCount = commandLine->GetNumSwitchValues("screen"); screenSwitchCount > 0)
{
QString screenOption = commandLine->GetSwitchValue("screen", screenSwitchCount - 1).c_str();
ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption);
if (screen != ProjectManagerScreen::Invalid)
{
startScreen = screen;
}
}
AZ::IO::FixedMaxPath projectPath;
if (size_t projectSwitchCount = commandLine->GetNumSwitchValues("project-path"); projectSwitchCount > 0)
{
projectPath = commandLine->GetSwitchValue("project-path", projectSwitchCount - 1).c_str();
}
m_mainWindow.reset(new ProjectManagerWindow(nullptr, projectPath, startScreen));
return true;
}
bool Application::InitLog(const char* logName)
{
if (!m_entity)
{
// override the log alias to the O3de Logs directory instead of the default project user/Logs folder
AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
AZ_Assert(fileIO, "Failed to get FileIOBase instance");
fileIO->SetAlias("@log@", path.LexicallyNormal().Native().c_str());
// this entity exists because we need a home for LogComponent
// and cannot use the system entity because we need to be able to call SetLogFileBaseName
// so the log will be named O3DE.log
m_entity = aznew AZ::Entity("Application Entity");
if (m_entity)
{
AzFramework::LogComponent* logger = aznew AzFramework::LogComponent();
AZ_Assert(logger, "Failed to create LogComponent");
logger->SetLogFileBaseName(logName);
m_entity->AddComponent(logger);
m_entity->Init();
m_entity->Activate();
}
}
return m_entity != nullptr;
}
void Application::TearDown()
{
if (m_entity)
{
m_entity->Deactivate();
delete m_entity;
m_entity = nullptr;
}
m_pythonBindings.reset();
m_mainWindow.reset();
m_app.reset();
}
bool Application::Run()
{
// Set up the Style Manager
AzQtComponents::StyleManager styleManager(qApp);
styleManager.initialize(qApp, GetEngineRoot());
// setup stylesheets and hot reloading
AZ::IO::FixedMaxPath engineRoot(GetEngineRoot());
QDir rootDir(engineRoot.c_str());
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
const auto qrcPath = QStringLiteral(":/ProjectManager/style");
AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRoot);
// set stylesheet after creating the main window or their styles won't get updated
AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:ProjectManager.qss"));
// the decoration wrapper is intended to remember window positioning and sizing
auto wrapper = new AzQtComponents::WindowDecorationWrapper();
wrapper->setGuest(m_mainWindow.data());
wrapper->show();
m_mainWindow->show();
qApp->setQuitOnLastWindowClosed(true);
// Run the application
return qApp->exec();
}
}
@@ -0,0 +1,48 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzFramework/Application/Application.h>
#include <QCoreApplication>
#include <PythonBindings.h>
#include <ProjectManagerWindow.h>
#endif
namespace AZ
{
class Entity;
}
namespace O3DE::ProjectManager
{
class Application
: public AzFramework::Application
{
public:
using AzFramework::Application::Application;
virtual ~Application();
bool Init(bool interactive = true);
bool Run();
void TearDown();
private:
bool InitLog(const char* logName);
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
QSharedPointer<QCoreApplication> m_app;
QSharedPointer<ProjectManagerWindow> m_mainWindow;
AZ::Entity* m_entity = nullptr;
};
}
@@ -25,7 +25,7 @@ namespace O3DE::ProjectManager
EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
: ScreenWidget(parent)
{
auto* layout = new QVBoxLayout(this);
auto* layout = new QVBoxLayout();
layout->setAlignment(Qt::AlignTop);
setObjectName("engineSettingsScreen");
@@ -82,6 +82,8 @@ namespace O3DE::ProjectManager
m_headerWidget->ReinitForProject();
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
@@ -26,6 +26,7 @@ namespace O3DE::ProjectManager
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
bool showAllLessButton,
bool collapsed,
int defaultShowCount,
QWidget* parent)
: QWidget(parent)
@@ -40,6 +41,7 @@ namespace O3DE::ProjectManager
QHBoxLayout* collapseLayout = new QHBoxLayout();
m_collapseButton = new QPushButton();
m_collapseButton->setCheckable(true);
m_collapseButton->setChecked(collapsed);
m_collapseButton->setFlat(true);
m_collapseButton->setFocusPolicy(Qt::NoFocus);
m_collapseButton->setFixedWidth(s_collapseButtonSize);
@@ -178,6 +180,11 @@ namespace O3DE::ProjectManager
return m_buttonGroup;
}
bool FilterCategoryWidget::IsCollapsed()
{
return m_collapseButton->isChecked();
}
GemFilterWidget::GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
: QScrollArea(parent)
, m_filterProxyModel(filterProxyModel)
@@ -193,20 +200,106 @@ namespace O3DE::ProjectManager
QWidget* mainWidget = new QWidget();
setWidget(mainWidget);
m_mainLayout = new QVBoxLayout();
m_mainLayout->setAlignment(Qt::AlignTop);
mainWidget->setLayout(m_mainLayout);
QVBoxLayout* mainLayout = new QVBoxLayout();
mainLayout->setAlignment(Qt::AlignTop);
mainWidget->setLayout(mainLayout);
QLabel* filterByLabel = new QLabel("Filter by");
filterByLabel->setStyleSheet("font-size: 16px;");
m_mainLayout->addWidget(filterByLabel);
mainLayout->addWidget(filterByLabel);
QWidget* filterSection = new QWidget(this);
mainLayout->addWidget(filterSection);
m_filterLayout = new QVBoxLayout();
m_filterLayout->setAlignment(Qt::AlignTop);
m_filterLayout->setContentsMargins(0, 0, 0, 0);
filterSection->setLayout(m_filterLayout);
ResetGemStatusFilter();
AddGemOriginFilter();
AddTypeFilter();
AddPlatformFilter();
AddFeatureFilter();
}
void GemFilterWidget::ResetGemStatusFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int totalGems = m_gemModel->rowCount();
const int selectedGemTotal = m_gemModel->TotalAddedGems();
elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Unselected));
elementCounts.push_back(totalGems - selectedGemTotal);
elementNames.push_back(GemSortFilterProxyModel::GetGemStatusString(GemSortFilterProxyModel::GemStatus::Selected));
elementCounts.push_back(selectedGemTotal);
bool wasCollapsed = false;
if (m_statusFilter)
{
wasCollapsed = m_statusFilter->IsCollapsed();
}
FilterCategoryWidget* filterWidget =
new FilterCategoryWidget("Status", elementNames, elementCounts, /*showAllLessButton=*/false, /*collapsed*/wasCollapsed);
if (m_statusFilter)
{
m_filterLayout->replaceWidget(m_statusFilter, filterWidget);
}
else
{
m_filterLayout->addWidget(filterWidget);
}
m_statusFilter->deleteLater();
m_statusFilter = filterWidget;
const GemSortFilterProxyModel::GemStatus currentFilterState = m_filterProxyModel->GetGemStatus();
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
for (int statusFilterIndex = 0; statusFilterIndex < buttons.size(); ++statusFilterIndex)
{
const GemSortFilterProxyModel::GemStatus gemStatus = static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex);
QAbstractButton* button = buttons[statusFilterIndex];
if (static_cast<GemSortFilterProxyModel::GemStatus>(statusFilterIndex) == currentFilterState)
{
button->setChecked(true);
}
connect(
button, &QAbstractButton::toggled, this,
[=](bool checked)
{
GemSortFilterProxyModel::GemStatus filterStatus = m_filterProxyModel->GetGemStatus();
if (checked)
{
if (filterStatus == GemSortFilterProxyModel::GemStatus::NoFilter)
{
filterStatus = gemStatus;
}
else
{
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter;
}
}
else
{
if (filterStatus != gemStatus)
{
filterStatus = static_cast<GemSortFilterProxyModel::GemStatus>(!gemStatus);
}
else
{
filterStatus = GemSortFilterProxyModel::GemStatus::NoFilter;
}
}
m_filterProxyModel->SetGemStatus(filterStatus);
});
}
}
void GemFilterWidget::AddGemOriginFilter()
{
QVector<QString> elementNames;
@@ -233,7 +326,7 @@ namespace O3DE::ProjectManager
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
@@ -283,7 +376,7 @@ namespace O3DE::ProjectManager
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
@@ -333,7 +426,7 @@ namespace O3DE::ProjectManager
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false);
m_mainLayout->addWidget(filterWidget);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
@@ -388,8 +481,8 @@ namespace O3DE::ProjectManager
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts,
/*showAllLessButton=*/true, /*defaultShowCount=*/5);
m_mainLayout->addWidget(filterWidget);
/*showAllLessButton=*/true, false, /*defaultShowCount=*/5);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
@@ -37,11 +37,14 @@ namespace O3DE::ProjectManager
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
bool showAllLessButton = true,
bool collapsed = false,
int defaultShowCount = 4,
QWidget* parent = nullptr);
QButtonGroup* GetButtonGroup();
bool IsCollapsed();
private:
void UpdateCollapseState();
void UpdateSeeMoreLess();
@@ -66,14 +69,18 @@ namespace O3DE::ProjectManager
explicit GemFilterWidget(GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
~GemFilterWidget() = default;
public slots:
void ResetGemStatusFilter();
private:
void AddGemOriginFilter();
void AddTypeFilter();
void AddPlatformFilter();
void AddFeatureFilter();
QVBoxLayout* m_mainLayout = nullptr;
QVBoxLayout* m_filterLayout = nullptr;
GemModel* m_gemModel = nullptr;
GemSortFilterProxyModel* m_filterProxyModel = nullptr;
FilterCategoryWidget* m_statusFilter = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -204,7 +204,6 @@ namespace O3DE::ProjectManager
painter->save();
const QRect buttonRect = CalcButtonRect(contentRect);
QPoint circleCenter;
QString buttonText;
const bool isAdded = GemModel::IsAdded(modelIndex);
if (isAdded)
@@ -213,34 +212,15 @@ namespace O3DE::ProjectManager
painter->setPen(m_buttonEnabledColor);
circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
buttonText = "Added";
}
else
{
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius, 1);
buttonText = "Get";
}
// Rounded rect
painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
// Text
QFont font;
QRect textRect = GetTextRect(font, buttonText, s_buttonFontSize);
if (isAdded)
{
textRect = QRect(buttonRect.left(), buttonRect.top(), buttonRect.width() - s_buttonCircleRadius * 2.0, buttonRect.height());
}
else
{
textRect = QRect(buttonRect.left() + s_buttonCircleRadius * 2.0, buttonRect.top(), buttonRect.width() - s_buttonCircleRadius * 2.0, buttonRect.height());
}
font.setPixelSize(s_buttonFontSize);
painter->setFont(font);
painter->setPen(m_textColor);
painter->drawText(textRect, Qt::AlignCenter, buttonText);
// Circle
painter->setBrush(m_textColor);
painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);
@@ -15,6 +15,7 @@
#include <QStandardItemModel>
#include <QLabel>
#include <QVBoxLayout>
#include <QSpacerItem>
namespace O3DE::ProjectManager
{
@@ -74,6 +75,15 @@ namespace O3DE::ProjectManager
gemSummaryLabel->setStyleSheet("font-size: 12px;");
columnHeaderLayout->addWidget(gemSummaryLabel);
QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
columnHeaderLayout->addSpacerItem(horizontalSpacer);
QLabel* gemSelectedLabel = new QLabel(tr("Selected"));
gemSelectedLabel->setStyleSheet("font-size: 12px;");
columnHeaderLayout->addWidget(gemSelectedLabel);
columnHeaderLayout->addSpacing(60);
vLayout->addLayout(columnHeaderLayout);
}
} // namespace O3DE::ProjectManager
@@ -235,4 +235,19 @@ namespace O3DE::ProjectManager
}
return result;
}
int GemModel::TotalAddedGems() const
{
int result = 0;
for (int row = 0; row < rowCount(); ++row)
{
const QModelIndex modelIndex = index(row, 0);
if (IsAdded(modelIndex))
{
++result;
}
}
return result;
}
} // namespace O3DE::ProjectManager
@@ -63,6 +63,8 @@ namespace O3DE::ProjectManager
QVector<QModelIndex> GatherGemsToBeAdded() const;
QVector<QModelIndex> GatherGemsToBeRemoved() const;
int TotalAddedGems() const;
private:
enum UserRole
{
@@ -37,6 +37,16 @@ namespace O3DE::ProjectManager
return false;
}
// Gem status
if (m_gemStatusFilter != GemStatus::NoFilter)
{
const GemStatus sourceGemStatus = static_cast<GemStatus>(GemModel::IsAdded(sourceIndex));
if (m_gemStatusFilter != sourceGemStatus)
{
return false;
}
}
// Gem origins
if (m_gemOriginFilter)
{
@@ -125,6 +135,19 @@ namespace O3DE::ProjectManager
return true;
}
QString GemSortFilterProxyModel::GetGemStatusString(GemStatus status)
{
switch (status)
{
case Unselected:
return "Unselected";
case Selected:
return "Selected";
default:
return "<Unknown Gem Status>";
}
}
void GemSortFilterProxyModel::InvalidateFilter()
{
invalidate();
@@ -29,8 +29,17 @@ namespace O3DE::ProjectManager
Q_OBJECT // AUTOMOC
public:
enum GemStatus
{
NoFilter = -1,
Unselected,
Selected
};
GemSortFilterProxyModel(GemModel* sourceModel, QObject* parent = nullptr);
static QString GetGemStatusString(GemStatus status);
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
GemModel* GetSourceModel() const { return m_sourceModel; }
@@ -38,6 +47,9 @@ namespace O3DE::ProjectManager
void SetSearchString(const QString& searchString) { m_searchString = searchString; InvalidateFilter(); }
GemStatus GetGemStatus() const { return m_gemStatusFilter; }
void SetGemStatus(GemStatus gemStatus) { m_gemStatusFilter = gemStatus; InvalidateFilter(); }
GemInfo::GemOrigins GetGemOrigins() const { return m_gemOriginFilter; }
void SetGemOrigins(const GemInfo::GemOrigins& gemOrigins) { m_gemOriginFilter = gemOrigins; InvalidateFilter(); }
@@ -61,6 +73,7 @@ namespace O3DE::ProjectManager
AzQtComponents::SelectionProxyModel* m_selectionProxyModel = nullptr;
QString m_searchString;
GemStatus m_gemStatusFilter = GemStatus::NoFilter;
GemInfo::GemOrigins m_gemOriginFilter = {};
GemInfo::Platforms m_platformFilter = {};
GemInfo::Types m_typeFilter = {};
@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
{
setObjectName("labelButton");
QVBoxLayout* vLayout = new QVBoxLayout(this);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setContentsMargins(0, 0, 0, 0);
vLayout->setSpacing(5);
@@ -13,21 +13,11 @@
#include <ProjectManagerWindow.h>
#include <ScreensCtrl.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <AzFramework/Application/Application.h>
#include <QDir>
namespace O3DE::ProjectManager
{
ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen)
ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen)
: QMainWindow(parent)
{
m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
setWindowTitle(tr("O3DE Project Manager"));
ScreensCtrl* screensCtrl = new ScreensCtrl();
@@ -44,15 +34,6 @@ namespace O3DE::ProjectManager
setCentralWidget(screensCtrl);
// setup stylesheets and hot reloading
QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
const auto qrcPath = QStringLiteral(":/ProjectManager/style");
AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath);
// set stylesheet after creating the screens or their styles won't get updated
AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss"));
// always push the projects screen first so we have something to come back to
if (startScreen != ProjectManagerScreen::Projects)
{
@@ -66,10 +47,4 @@ namespace O3DE::ProjectManager
emit screensCtrl->NotifyCurrentProject(path);
}
}
ProjectManagerWindow::~ProjectManagerWindow()
{
m_pythonBindings.reset();
}
} // namespace O3DE::ProjectManager
@@ -13,7 +13,7 @@
#if !defined(Q_MOC_RUN)
#include <QMainWindow>
#include <PythonBindings.h>
#include <AzCore/IO/Path/Path.h>
#include <ScreenDefs.h>
#endif
@@ -25,12 +25,8 @@ namespace O3DE::ProjectManager
Q_OBJECT
public:
explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath,
explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath,
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects);
~ProjectManagerWindow();
private:
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
};
} // namespace O3DE::ProjectManager
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
// if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
QFrame* projectSettingsFrame = new QFrame(this);
projectSettingsFrame->setObjectName("projectSettings");
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout = new QVBoxLayout();
// you cannot remove content margins in qss
m_verticalLayout->setContentsMargins(0, 0, 0, 0);
@@ -85,7 +85,7 @@ namespace O3DE::ProjectManager
QFrame* frame = new QFrame(this);
frame->setObjectName("firstTimeContent");
{
QVBoxLayout* layout = new QVBoxLayout(this);
QVBoxLayout* layout = new QVBoxLayout();
layout->setContentsMargins(0, 0, 0, 0);
layout->setAlignment(Qt::AlignTop);
frame->setLayout(layout);
@@ -100,7 +100,7 @@ namespace O3DE::ProjectManager
"available by downloading our sample project."));
layout->addWidget(introLabel);
QHBoxLayout* buttonLayout = new QHBoxLayout(this);
QHBoxLayout* buttonLayout = new QHBoxLayout();
buttonLayout->setAlignment(Qt::AlignLeft);
buttonLayout->setSpacing(s_spacerSize);
@@ -226,7 +226,7 @@ namespace O3DE::ProjectManager
PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
: m_enginePath(enginePath)
{
StartPython();
m_pythonStarted = StartPython();
}
PythonBindings::~PythonBindings()
@@ -234,6 +234,11 @@ namespace O3DE::ProjectManager
StopPython();
}
bool PythonBindings::PythonStarted()
{
return m_pythonStarted && Py_IsInitialized();
}
bool PythonBindings::StartPython()
{
if (Py_IsInitialized())
@@ -246,7 +251,7 @@ namespace O3DE::ProjectManager
AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str());
if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
{
AZ_Assert(false, "Python home path must exist. path:%s", pyBasePath.c_str());
AZ_Error("python", false, "Python home path does not exist: %s", pyBasePath.c_str());
return false;
}
@@ -351,6 +356,11 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
{
if (!Py_IsInitialized())
{
return AZ::Failure<AZStd::string>("Python is not initialized");
}
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_release release;
pybind11::gil_scoped_acquire acquire;
@@ -34,6 +34,8 @@ namespace O3DE::ProjectManager
~PythonBindings() override;
// PythonBindings overrides
bool PythonStarted() override;
// Engine
AZ::Outcome<EngineInfo> GetEngineInfo() override;
bool SetEngineInfo(const EngineInfo& engineInfo) override;
@@ -70,6 +72,8 @@ namespace O3DE::ProjectManager
bool StopPython();
bool m_pythonStarted = false;
AZ::IO::FixedMaxPath m_enginePath;
pybind11::handle m_engineTemplate;
AZStd::recursive_mutex m_lock;
@@ -34,6 +34,12 @@ namespace O3DE::ProjectManager
IPythonBindings() = default;
virtual ~IPythonBindings() = default;
/**
* Get whether Python was started or not. All Python functionality will fail if Python
* failed to start.
* @return true if Python was started successfully, false on failure
*/
virtual bool PythonStarted() = 0;
// Engine
@@ -189,11 +189,13 @@ namespace O3DE::ProjectManager
{
if (m_stack->currentIndex() == ScreenOrder::Gems)
{
m_header->setSubTitle(QString(tr("Configure Gems for \"%1\"")).arg(m_projectInfo.m_projectName));
m_nextButton->setText(tr("Confirm"));
m_header->setTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName));
m_header->setSubTitle(QString(tr("Configure Gems")));
m_nextButton->setText(tr("Finalize"));
}
else
{
m_header->setTitle("");
m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName));
m_nextButton->setText(tr("Save"));
}
+14 -73
View File
@@ -10,85 +10,26 @@
*
*/
#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/CommandLine/CommandLine.h>
#include <ProjectManagerWindow.h>
#include <ProjectUtils.h>
#include <QApplication>
#include <QCoreApplication>
#include <QGuiApplication>
using namespace O3DE::ProjectManager;
#include <AzQtComponents/Utilities/QtPluginPaths.h>
#include <Application.h>
int main(int argc, char* argv[])
{
QApplication::setOrganizationName("O3DE");
QApplication::setOrganizationDomain("o3de.org");
QCoreApplication::setApplicationName("ProjectManager");
QCoreApplication::setApplicationVersion("1.0");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
int runSuccess = 0;
// Call before using any Qt, or the app may not be able to locate Qt libs
AzQtComponents::PrepareQtPaths();
O3DE::ProjectManager::Application application(&argc, &argv);
if (!application.Init())
{
QApplication app(argc, argv);
// Need to use settings registry to get EngineRootFolder
AZ::IO::FixedMaxPath engineRootPath;
{
AZ::ComponentApplication componentApplication;
auto settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
}
AzQtComponents::StyleManager styleManager(&app);
styleManager.initialize(&app, engineRootPath);
// Get the initial start screen if one is provided via command line
constexpr char optionPrefix[] = "--";
AZ::CommandLine commandLine(optionPrefix);
commandLine.Parse(argc, argv);
ProjectManagerScreen startScreen = ProjectManagerScreen::Projects;
if(commandLine.HasSwitch("screen"))
{
QString screenOption = commandLine.GetSwitchValue("screen", 0).c_str();
ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption);
if (screen != ProjectManagerScreen::Invalid)
{
startScreen = screen;
}
}
AZ::IO::FixedMaxPath projectPath;
if (commandLine.HasSwitch("project-path"))
{
projectPath = commandLine.GetSwitchValue("project-path", 0).c_str();
}
ProjectManagerWindow window(nullptr, engineRootPath, projectPath, startScreen);
window.show();
// somethings is preventing us from moving the window to the center of the
// primary screen - likely an Az style or component helper
constexpr int width = 1200;
constexpr int height = 800;
window.resize(width, height);
runSuccess = app.exec();
AZ_Error("ProjectManager", false, "Failed to initialize");
runSuccess = 1;
}
else
{
runSuccess = application.Run() ? 0 : 1;
}
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
return runSuccess;
}
@@ -0,0 +1,17 @@
#
# 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.
#
set(FILES
Resources/ProjectManager.rc
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
Source/main.cpp
)
@@ -1,4 +1,5 @@
#
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
@@ -10,10 +11,8 @@
#
set(FILES
Resources/ProjectManager.rc
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
Source/main.cpp
Source/Application.h
Source/Application.cpp
Source/ScreenDefs.h
Source/ScreenFactory.h
Source/ScreenFactory.cpp
@@ -0,0 +1,17 @@
#
# 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.
#
set(FILES
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
tests/ApplicationTests.cpp
tests/main.cpp
)
@@ -0,0 +1,47 @@
/*
* 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 <AzCore/UnitTest/TestTypes.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <Application.h>
#include <ProjectManager_Test_Traits_Platform.h>
namespace O3DE::ProjectManager
{
class ProjectManagerApplicationTests
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
ProjectManagerApplicationTests()
{
m_application = AZStd::make_unique<ProjectManager::Application>();
}
~ProjectManagerApplicationTests()
{
m_application.reset();
}
AZStd::unique_ptr<ProjectManager::Application> m_application;
};
#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
TEST_F(ProjectManagerApplicationTests, DISABLED_Application_Init_Succeeds)
#else
TEST_F(ProjectManagerApplicationTests, Application_Init_Succeeds)
#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
{
// we don't want to interact with actual GUI or display it
EXPECT_TRUE(m_application->Init(/*interactive=*/false));
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* 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 <AzTest/AzTest.h>
DECLARE_AZ_UNIT_TEST_MAIN();
int runDefaultRunner(int argc, char* argv[])
{
INVOKE_AZ_UNIT_TEST_MAIN(nullptr)
return 0;
}
int main(int argc, char* argv[])
{
if (argc == 1)
{
// if no parameters are provided, add the --unittests parameter
constexpr int defaultArgc = 2;
char unittest_arg[] = "--unittests"; // Conversion from string literal to char* is not allowed per ISO C++11
char* defaultArgv[defaultArgc] = { argv[0], unittest_arg };
return runDefaultRunner(defaultArgc, defaultArgv);
}
INVOKE_AZ_UNIT_TEST_MAIN(nullptr);
return 0;
}
@@ -46,22 +46,69 @@ namespace AZ
serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
void GetAllBones(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
boneLookup[bone->mName.C_Str()] = bone;
}
}
}
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{
AZ_TraceContext("Importer", "transform");
const aiNode* currentNode = context.m_sourceNode.GetAssImpNode();
const aiScene* scene = context.m_sourceScene.GetAssImpScene();
if (currentNode == scene->mRootNode || IsPivotNode(currentNode->mName))
{
return Events::ProcessingResult::Ignored;
}
aiMatrix4x4 combinedTransform = GetConcatenatedLocalTransform(currentNode);
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
GetAllBones(scene, boneLookup);
auto boneIterator = boneLookup.find(currentNode->mName.C_Str());
const bool isBone = boneIterator != boneLookup.end();
aiMatrix4x4 combinedTransform;
if (isBone)
{
auto parentNode = currentNode->mParent;
aiMatrix4x4 offsetMatrix = boneIterator->second->mOffsetMatrix;
aiMatrix4x4 parentOffset {};
auto parentBoneIterator = boneLookup.find(parentNode->mName.C_Str());
if (parentNode && parentBoneIterator != boneLookup.end())
{
const auto& parentBone = parentBoneIterator->second;
parentOffset = parentBone->mOffsetMatrix;
}
auto inverseOffset = offsetMatrix;
inverseOffset.Inverse();
combinedTransform = parentOffset * inverseOffset;
}
else
{
combinedTransform = GetConcatenatedLocalTransform(currentNode);
}
DataTypes::MatrixType localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(combinedTransform);
context.m_sourceSceneSystem.SwapTransformForUpAxis(localTransform);
context.m_sourceSceneSystem.ConvertUnit(localTransform);
@@ -105,9 +152,7 @@ namespace AZ
}
else
{
bool addedData = context.m_scene.GetGraph().SetContent(
context.m_currentGraphPosition,
transformData);
bool addedData = context.m_scene.GetGraph().SetContent(context.m_currentGraphPosition, transformData);
AZ_Error(SceneAPI::Utilities::ErrorWindow, addedData, "Failed to add node data");
return addedData ? Events::ProcessingResult::Success : Events::ProcessingResult::Failure;
@@ -69,13 +69,14 @@ namespace AZ
// aiProcess_JoinIdenticalVertices is not enabled because O3DE has a mesh optimizer that also does this,
// this flag is disabled to keep AssImp output similar to FBX SDK to reduce downstream bugs for the initial AssImp release.
// There's currently a minimum of properties and flags set to maximize compatibility with the existing node graph.
// aiProcess_LimitBoneWeights is not enabled because it will remove bones which are not associated with a mesh.
// This results in the loss of the offset matrix data for nodes without a mesh which is required for the Transform Importer.
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false);
m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, false);
m_sceneFileName = fileName;
m_assImpScene = m_importer.ReadFile(fileName,
aiProcess_Triangulate //Triangulates all faces of all meshes
| aiProcess_LimitBoneWeights //Limits the number of bones that can affect a vertex to a maximum value
//dropping the least important and re-normalizing
| aiProcess_GenNormals); //Generate normals for meshes
#if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL
@@ -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,23 @@ namespace AZ
}
AZ::Entity* rootEntity = reinterpret_cast<AZ::Entity*>(classPtr);
return ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
bool convertResult = ConvertSliceToPrefab(context, outputPath, isDryRun, rootEntity);
// Delete the root entity pointer. Otherwise, it will leak itself along with all of the slice asset references held
// within it.
delete rootEntity;
return convertResult;
};
// Read in the slice file and call the callback on completion to convert the read-in slice to a prefab.
if (!Utilities::InspectSerializedFile(inputPath.c_str(), serializeContext, callback))
// This will also load dependent slice assets, but no other dependent asset types.
// Since we're not actually initializing any of the entities, we don't need any of the non-slice assets to be loaded.
if (!Utilities::InspectSerializedFile(
inputPath.c_str(), serializeContext, callback,
[](const AZ::Data::AssetFilterInfo& filterInfo)
{
return (filterInfo.m_assetType == azrtti_typeid<AZ::SliceAsset>());
}))
{
AZ_Warning("Convert-Slice", false, "Failed to load '%s'. File may not contain an object stream.", inputPath.c_str());
result = false;
@@ -219,8 +230,12 @@ namespace AZ
return false;
}
// Get all of the entities from the slice.
// Get all of the entities from the slice. We're taking ownership of them, so we also remove them from the slice component
// without deleting them.
constexpr bool deleteEntities = false;
constexpr bool removeEmptyInstances = true;
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
sliceComponent->RemoveAllEntities(deleteEntities, removeEmptyInstances);
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
// Create the Prefab with the entities from the slice.
@@ -256,13 +271,19 @@ 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");
}
}
// Save off a mapping of the slice's metadata entity ID as well, even though we never converted the entity itself.
// This will help us better detect entity ID mapping errors for nested slice instances.
AZ::Entity* metadataEntity = sliceComponent->GetMetadataEntity();
constexpr bool isMetadataEntity = true;
m_aliasIdMapper.emplace(metadataEntity->GetId(), SliceEntityMappingInfo(templateId, "MetadataEntity", isMetadataEntity));
// Update the prefab template with the fixed-up data in our prefab instance.
AzToolsFramework::Prefab::PrefabDom prefabDom;
bool storeResult = AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(*sourceInstance, prefabDom);
@@ -342,10 +363,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.
@@ -393,6 +413,21 @@ namespace AZ
"Convert-Slice", " Attaching %zu instances of nested slice '%s'.\n", instances.size(),
nestedPrefabPath.Native().c_str());
// Before processing any further, save off all the known entity IDs from all the instances and how they map back to
// the base nested prefab that they've come from (i.e. this one). As we proceed up the chain of nesting, this will
// build out a hierarchical list of owning instances for each entity that we can trace upwards to know where to add
// the entity into our nested prefab instance.
// This step needs to occur *before* converting the instances themselves, because while converting instances, they
// might have entity ID references that point to other instances. By having the full instance entity ID map in place
// before conversion, we'll be able to fix them up appropriately.
for (auto& instance : instances)
{
AZStd::string instanceAlias = GetInstanceAlias(instance);
UpdateSliceEntityInstanceMappings(instance.GetEntityIdToBaseMap(), instanceAlias);
}
// Now that we have all the entity ID mappings, convert all the instances.
for (auto& instance : instances)
{
bool instanceConvertResult = ConvertSliceInstance(instance, sliceAsset, nestedTemplate, sourceInstance);
@@ -406,6 +441,28 @@ namespace AZ
return true;
}
AZStd::string SliceConverter::GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance)
{
// When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one
// will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce
// the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains
// a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the
// unique instance ID.
AZStd::string instanceAlias;
auto entityIdMap = instance.GetEntityIdMap();
if (!entityIdMap.empty())
{
instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str());
}
else
{
AZ_Error("Convert-Slice", false, " Couldn't create deterministic instance alias.");
instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str());
}
return instanceAlias;
}
bool SliceConverter::ConvertSliceInstance(
AZ::SliceComponent::SliceInstance& instance,
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
@@ -429,6 +486,8 @@ namespace AZ
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
AZStd::string instanceAlias = GetInstanceAlias(instance);
// Create a new unmodified prefab Instance for the nested slice instance.
auto nestedInstance = AZStd::make_unique<AzToolsFramework::Prefab::Instance>();
AzToolsFramework::Prefab::Instance::EntityList newEntities;
@@ -465,15 +524,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,70 +588,76 @@ 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);
}
// After doing all of the above, run through entity references in any of the patched entities, and fix up the entity IDs to
// match the new ones in our prefabs.
RemapIdReferences(m_aliasIdMapper, topLevelInstance, nestedInstance.get(), instantiated, dependentSlice->GetSerializeContext());
// Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
// create a patch out of it, and patch the top-level prefab template.
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance);
// 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 +782,136 @@ namespace AZ
AZ_Error("Convert-Slice", disconnected, "Asset Processor failed to disconnect successfully.");
}
void SliceConverter::UpdateSliceEntityInstanceMappings(
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap, const AZStd::string& currentInstanceAlias)
{
// For each instanced entity, map its ID all the way back to the original prefab template and entity ID that it came from.
// This counts on being run recursively from the leaf nodes upwards, so we first get B->A,
// then C->B which becomes a C->A entry, then D->C which becomes D->A, etc.
for (auto& [newId, oldId] : sliceEntityIdMap)
{
// Try to find the conversion chain from the old ID. if it's there, copy it and use it for the new ID, plus add this
// instance's name to the end of the chain. If it's not there, skip it, since it's probably the slice metadata entity,
// which we didn't convert.
auto parentEntry = m_aliasIdMapper.find(oldId);
if (parentEntry != m_aliasIdMapper.end())
{
// Only add this instance's name if we don't already have an entry for the new ID.
if (m_aliasIdMapper.find(newId) == m_aliasIdMapper.end())
{
auto newMappingEntry = m_aliasIdMapper.emplace(newId, parentEntry->second).first;
newMappingEntry->second.m_nestedInstanceAliases.emplace_back(currentInstanceAlias);
}
else
{
// If we already had an entry for the new ID, it might be because the old and new ID are the same. This happens
// when nesting multiple prefabs directly underneath each other without a nesting entity in-between.
// If the IDs are different, it's an unexpected error condition.
AZ_Assert(oldId == newId, "The same entity instance ID has unexpectedly appeared twice in the same nested prefab.");
}
}
else
{
AZ_Warning("Convert-Slice", false, " Couldn't find an entity ID conversion for %s.", oldId.ToString().c_str());
}
}
}
void SliceConverter::RemapIdReferences(
const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
AzToolsFramework::Prefab::Instance* topLevelInstance,
AzToolsFramework::Prefab::Instance* nestedInstance,
SliceComponent::InstantiatedContainer* instantiatedEntities,
SerializeContext* context)
{
// Given a set of instantiated entities, run through all of them, look for entity references, and replace the entity IDs with
// new ones that match up with our prefabs.
IdUtils::Remapper<EntityId>::ReplaceIdsAndIdRefs(
instantiatedEntities,
[idMapper, &topLevelInstance, &nestedInstance](
const EntityId& sourceId, bool isEntityId, [[maybe_unused]] const AZStd::function<EntityId()>& idGenerator) -> EntityId
{
EntityId newId = sourceId;
// Only convert valid entity references. Actual entity IDs have already been taken care of elsewhere, so ignore them.
if (!isEntityId && sourceId.IsValid())
{
auto entityEntry = idMapper.find(sourceId);
// Since we've already remapped transform hierarchies to include container entities, it's possible that our entity
// reference is pointing to a container, which means it won't be in our slice mapping table. In that case, just
// return it as-is.
if (entityEntry == idMapper.end())
{
return sourceId;
}
// We've got a slice->prefab mapping entry, so now we need to use it.
auto& mappingStruct = entityEntry->second;
if (mappingStruct.m_nestedInstanceAliases.empty())
{
// If we don't have a chain of nested instance aliases, then this entity reference is either within the
// current nested instance or it's pointing to an entity in the top-level instance. We'll try them both
// to look for a match.
EntityId prefabId = nestedInstance->GetEntityId(mappingStruct.m_entityAlias);
if (!prefabId.IsValid())
{
prefabId = topLevelInstance->GetEntityId(mappingStruct.m_entityAlias);
}
if (prefabId.IsValid())
{
newId = prefabId;
}
else
{
AZ_Error("Convert-Slice", false, " Couldn't find source ID %s", sourceId.ToString().c_str());
}
}
else
{
// We *do* have a chain of nested instance aliases. This chain could either be relative to the nested instance
// or the top-level instance. We can tell which one it is by which one can find the first nested instance
// alias.
AzToolsFramework::Prefab::Instance* entityInstance = nestedInstance;
auto it = mappingStruct.m_nestedInstanceAliases.rbegin();
if (!entityInstance->FindNestedInstance(*it).has_value())
{
entityInstance = topLevelInstance;
}
// Now that we've got a starting point, iterate through the chain of nested instance aliases to find the
// correct instance to get the entity ID for. We have to go from slice IDs -> entity aliases -> entity IDs
// because prefab instance creation can change some of our entity IDs along the way.
for (; it != mappingStruct.m_nestedInstanceAliases.rend(); it++)
{
auto foundInstance = entityInstance->FindNestedInstance(*it);
if (foundInstance.has_value())
{
entityInstance = &(foundInstance->get());
}
else
{
AZ_Assert(false, "Couldn't find nested instance %s", it->c_str());
}
}
EntityId prefabId = entityInstance->GetEntityId(mappingStruct.m_entityAlias);
if (prefabId.IsValid())
{
newId = prefabId;
}
}
}
return newId;
},
context);
}
} // namespace SerializeContextTools
} // namespace AZ
@@ -42,7 +42,27 @@ namespace AZ
bool ConvertSliceFiles(Application& application);
private:
using TemplateEntityIdPair = AZStd::pair<AzToolsFramework::Prefab::TemplateId, AZ::EntityId>;
// When converting slice entities, especially for nested slices, we need to keep track of the original
// entity ID, the entity alias it uses in the prefab, and which template and nested instance path it maps to.
// As we encounter each instanced entity ID, we can look it up in this structure and use this to determine how to properly
// add it to the correct place in the hierarchy.
struct SliceEntityMappingInfo
{
SliceEntityMappingInfo(
AzToolsFramework::Prefab::TemplateId templateId,
AzToolsFramework::Prefab::EntityAlias entityAlias,
bool isMetadataEntity = false)
: m_templateId(templateId)
, m_entityAlias(entityAlias)
, m_isMetadataEntity(isMetadataEntity)
{
}
AzToolsFramework::Prefab::TemplateId m_templateId;
AzToolsFramework::Prefab::EntityAlias m_entityAlias;
AZStd::vector<AzToolsFramework::Prefab::InstanceAlias> m_nestedInstanceAliases;
bool m_isMetadataEntity{ false };
};
bool ConnectToAssetProcessor();
void DisconnectFromAssetProcessor();
@@ -60,10 +80,22 @@ 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 UpdateSliceEntityInstanceMappings(
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceEntityIdMap,
const AZStd::string& currentInstanceAlias);
AZStd::string GetInstanceAlias(const AZ::SliceComponent::SliceInstance& instance);
// 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;
void RemapIdReferences(
const AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo>& idMapper,
AzToolsFramework::Prefab::Instance* topLevelInstance,
AzToolsFramework::Prefab::Instance* nestedInstance,
SliceComponent::InstantiatedContainer* instantiatedEntities,
SerializeContext* context);
// Track all of the entity IDs created and associate them with enough conversion information to know how to place the
// entities in the correct place in the prefab hierarchy and fix up parent entity ID mappings to work with the nested
// prefab schema.
AZStd::unordered_map<AZ::EntityId, SliceEntityMappingInfo> m_aliasIdMapper;
// Track all of the created prefab template IDs on a slice conversion so that they can get removed at the end of the
// conversion for that file.
@@ -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);
+5 -1
View File
@@ -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;