Merge main to mp_session_integ

This commit is contained in:
puvvadar
2021-06-03 15:36:45 -07:00
186 changed files with 8449 additions and 9305 deletions
@@ -97,16 +97,19 @@ class Cdk:
env=self._cdk_env,
shell=True)
def deploy(self, context_variable: str = '') -> List[str]:
def deploy(self, context_variable: str = '', additonal_params: List[str] = None) -> List[str]:
"""
Deploys all the CDK stacks.
:param context_variable: Context variable for enabling optional features.
:param additonal_params: Additonal parameters like --all can be passed in this way.
:return List of deployed stack arns.
"""
if not self._cdk_path:
return []
deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never']
if additonal_params:
deploy_cdk_application_cmd.extend(additonal_params)
if context_variable:
deploy_cdk_application_cmd.extend(['-c', f'{context_variable}'])
@@ -137,5 +137,6 @@ enum class AnimParamType
Invalid = static_cast<int>(0xFFFFFFFF)
};
static const int OLD_APARAM_USER = 100;
#endif // CRYINCLUDE_CRYCOMMON_MAESTRO_TYPES_ANIMPARAMTYPE_H
+1 -1
View File
@@ -561,7 +561,7 @@ void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex)
if (pex)
{
MINIDUMP_TYPE mdumpValue;
MINIDUMP_TYPE mdumpValue = MiniDumpNormal;
bool bDump = true;
switch (g_cvars.sys_dump_type)
{
+1 -1
View File
@@ -66,7 +66,7 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
}
if (pSystem && !pSystem->IsQuitting())
{
LRESULT result;
LRESULT result = 0;
bool bAny = false;
for (std::vector<IWindowMessageHandler*>::const_iterator it = pSystem->m_windowMessageHandlers.begin(); it != pSystem->m_windowMessageHandlers.end(); ++it)
{
+1 -1
View File
@@ -634,7 +634,7 @@ ICVar* CSystem::attachVariable (const char* szVarName, int* pContainer, const ch
IConsole* pConsole = GetIConsole();
ICVar* pOldVar = pConsole->GetCVar (szVarName);
int nDefault;
int nDefault = 0;
if (pOldVar)
{
nDefault = pOldVar->GetIVal();
@@ -127,13 +127,14 @@ namespace AZ
*/
class EditContext
{
public:
/// @cond EXCLUDE_DOCS
class ClassBuilder;
class EnumBuilder;
using ClassInfo = ClassBuilder; ///< @deprecated Use EditContext::ClassBuilder
using EnumInfo = EnumBuilder; ///< @deprecated Use EditContext::EnumBuilder
/// @endcond
public:
AZ_CLASS_ALLOCATOR(EditContext, SystemAllocator, 0);
/**
@@ -186,6 +187,7 @@ namespace AZ
* look at the unit tests and example to see use cases.
*
*/
public:
class ClassBuilder
{
friend EditContext;
@@ -399,6 +401,7 @@ namespace AZ
EnumBuilder* Value(const char* name, E value);
};
private:
typedef AZStd::list<Edit::ClassData> ClassDataListType;
typedef AZStd::unordered_map<AZ::Uuid, Edit::ElementData> EnumDataMapType;
@@ -101,6 +101,9 @@ namespace AZ
class SerializeContext
: public ReflectContext
{
static const unsigned int VersionClassDeprecated = (unsigned int)-1;
public:
/// @cond EXCLUDE_DOCS
friend class EditContext;
class ClassBuilder;
@@ -108,9 +111,6 @@ namespace AZ
/// @endcond
class EnumBuilder;
static const unsigned int VersionClassDeprecated = (unsigned int)-1;
public:
class ClassData;
struct EnumerateInstanceCallContext;
struct ClassElement;
@@ -1131,6 +1131,7 @@ namespace AZ
* ->Version(3,&MyVersionConverter)
* ->Field("data",&MyStruct::m_data);
*/
public:
class ClassBuilder
{
friend class SerializeContext;
@@ -1330,7 +1331,8 @@ namespace AZ
AZStd::vector<AttributeSharedPair, AZStdFunctorAllocator>* m_currentAttributes = nullptr;
};
EditContext* m_editContext; ///< Pointer to optional edit context.
private:
EditContext* m_editContext; ///< Pointer to optional edit context.
UuidToClassMap m_uuidMap; ///< Map for all class in this serialize context
AZStd::unordered_multimap<AZ::Crc32, AZ::Uuid> m_classNameToUuid; /// Map all class names to their uuid
AZStd::unordered_multimap<Uuid, GenericClassInfo*> m_uuidGenericMap; ///< Uuid to ClassData map of reflected classes with GenericTypeInfo
@@ -971,6 +971,10 @@ namespace AZ
*/
void RestoreCachedInstances();
/// Returns data flags for use when instantiating an instance of this slice.
/// These data flags include those harvested from the entire slice ancestry.
const DataFlagsPerEntity& GetDataFlagsForInstances() const;
protected:
//////////////////////////////////////////////////////////////////////////
@@ -1004,9 +1008,6 @@ namespace AZ
DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId);
const DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId) const;
/// Returns data flags for use when instantiating an instance of this slice.
/// These data flags include those harvested from the entire slice ancestry.
const DataFlagsPerEntity& GetDataFlagsForInstances() const;
void BuildDataFlagsForInstances();
/**
@@ -13,9 +13,10 @@
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
#include <libgen.h>
@@ -61,10 +62,11 @@ namespace AZ
// If it doesn't attempt to append the path to the executable path
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
auto candidatePath = Platform::GetModulePath() / fullFilePath;
AZ::IO::FixedMaxPath candidatePath = Platform::GetModulePath() / fullFilePath;
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
fullFilePath = candidatePath;
m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size());
return;
}
}
@@ -74,19 +76,26 @@ namespace AZ
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= fullFilePath;
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
fullFilePath = projectModulePath;
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
}
}
}
}
m_fileName = AZStd::string_view{fullFilePath.Native()};
else
{
// The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded.
AZStd::optional<AZ::IO::FixedMaxPathString> absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName);
if (absPathOptional.has_value())
{
m_fileName.assign(absPathOptional->c_str(), absPathOptional->size());
}
}
}
~DynamicModuleHandleUnixLike() override
@@ -24,9 +24,9 @@ namespace AZ
: public DynamicModuleHandle
{
public:
AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0)
AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0);
DynamicModuleHandleWindows(const char* fullFileName)
DynamicModuleHandleWindows(const char* fullFileName)
: DynamicModuleHandle(fullFileName)
, m_handle(nullptr)
{
@@ -52,6 +52,7 @@ namespace AZ
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size());
return;
}
}
}
@@ -65,7 +66,7 @@ namespace AZ
// Therefore an existence check is needed
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= AZStd::string_view(m_fileName);
@@ -76,6 +77,15 @@ namespace AZ
}
}
}
else
{
// The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded.
AZStd::optional<AZ::IO::FixedMaxPathString> absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName);
if (absPathOptional.has_value())
{
m_fileName.assign(absPathOptional->c_str(), absPathOptional->size());
}
}
}
~DynamicModuleHandleWindows() override
+8 -8
View File
@@ -1914,7 +1914,7 @@ namespace UnitTest
TEST_F(String, StringView_CompareIsConstexpr)
{
using TypeParam = char;
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
return "HelloWorld";
};
@@ -1922,7 +1922,7 @@ namespace UnitTest
{
return "HelloPearl";
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2();
constexpr basic_string_view<TypeParam> lhsView(compileTimeString1);
constexpr basic_string_view<TypeParam> rhsView(compileTimeString2);
@@ -1937,11 +1937,11 @@ namespace UnitTest
TEST_F(String, StringView_CompareOperatorsAreConstexpr)
{
using TypeParam = char;
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto TestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
return "HelloWorld";
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = TestMakeCompileTimeString1();
constexpr basic_string_view<TypeParam> compareView(compileTimeString1);
static_assert(compareView == "HelloWorld", "string_view operator== comparison has failed");
static_assert(compareView != "MadWorld", "string_view operator!= comparison has failed");
@@ -1955,7 +1955,7 @@ namespace UnitTest
{
auto swap_test_func = []() constexpr -> basic_string_view<TypeParam>
{
constexpr auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
constexpr auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
if constexpr (AZStd::is_same_v<TypeParam, char>)
{
@@ -1977,7 +1977,7 @@ namespace UnitTest
return L"InuWorld";
}
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2();
basic_string_view<TypeParam> lhsView(compileTimeString1);
basic_string_view<TypeParam> rhsView(compileTimeString2);
@@ -2001,7 +2001,7 @@ namespace UnitTest
TYPED_TEST(BasicStringViewConstexprFixture, HashString_FunctionIsConstexpr)
{
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
if constexpr (AZStd::is_same_v<TypeParam, char>)
{
@@ -2012,7 +2012,7 @@ namespace UnitTest
return L"HelloWorld";
}
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr basic_string_view<TypeParam> hashView(compileTimeString1);
constexpr size_t compileHash = AZStd::hash<basic_string_view<TypeParam>>{}(hashView);
static_assert(compileHash != 0, "Hash of \"HelloWorld\" should not be 0");
+2 -1
View File
@@ -395,7 +395,8 @@ namespace UnitTest
}
else
{
int result1, result2;
int result1 = 0;
int result2 = 0;
Job* job1 = aznew FibonacciJob2(m_n - 1, &result1, m_context);
Job* job2 = aznew FibonacciJob2(m_n - 2, &result2, m_context);
StartAsChild(job1);
@@ -59,7 +59,7 @@ namespace UnitTest
TEST(MATH_Matrix4x4, TestCreateFrom)
{
float testFloats[] =
float thisTestFloats[] =
{
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f,
@@ -67,20 +67,20 @@ namespace UnitTest
13.0f, 14.0f, 15.0f, 16.0f
};
float testFloatMtx[16];
Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(testFloats);
Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(thisTestFloats);
AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 2.0f, 3.0f, 4.0f));
AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(5.0f, 6.0f, 7.0f, 8.0f));
AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(9.0f, 10.0f, 11.0f, 12.0f));
AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(13.0f, 14.0f, 15.0f, 16.0f));
m1.StoreToRowMajorFloat16(testFloatMtx);
AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0);
m1 = Matrix4x4::CreateFromColumnMajorFloat16(testFloats);
AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0);
m1 = Matrix4x4::CreateFromColumnMajorFloat16(thisTestFloats);
AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 5.0f, 9.0f, 13.0f));
AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(2.0f, 6.0f, 10.0f, 14.0f));
AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(3.0f, 7.0f, 11.0f, 15.0f));
AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(4.0f, 8.0f, 12.0f, 16.0f));
m1.StoreToColumnMajorFloat16(testFloatMtx);
AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0);
AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0);
}
TEST(MATH_Matrix4x4, TestCreateFromMatrix3x4)
+12 -12
View File
@@ -119,10 +119,10 @@ namespace UnitTest
TEST(MATH_Obb, Contains)
{
const Vector3 position(1.0f, 2.0f, 3.0f);
const Quaternion rotation = Quaternion::CreateRotationZ(DegToRad(30.0f));
const Vector3 halfLengths(2.0f, 1.0f, 2.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(1.0f, 2.0f, 3.0f);
const Quaternion testRotation = Quaternion::CreateRotationZ(DegToRad(30.0f));
const Vector3 testHalfLengths(2.0f, 1.0f, 2.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
// test some pairs of points which should be just either side of the Obb boundary
EXPECT_TRUE(obb.Contains(Vector3(1.35f, 3.35f, 3.5f)));
EXPECT_FALSE(obb.Contains(Vector3(1.35f, 3.4f, 3.5f)));
@@ -134,10 +134,10 @@ namespace UnitTest
TEST(MATH_Obb, GetDistance)
{
const Vector3 position(5.0f, 3.0f, 2.0f);
const Quaternion rotation = Quaternion::CreateRotationX(DegToRad(60.0f));
const Vector3 halfLengths(0.5f, 2.0f, 1.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(5.0f, 3.0f, 2.0f);
const Quaternion testRotation = Quaternion::CreateRotationX(DegToRad(60.0f));
const Vector3 testHalfLengths(0.5f, 2.0f, 1.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
EXPECT_NEAR(obb.GetDistance(Vector3(5.3f, 3.2f, 1.8f)), 0.0f, 1e-3f);
EXPECT_NEAR(obb.GetDistance(Vector3(5.1f, 1.1f, 3.7f)), 0.9955f, 1e-3f);
EXPECT_NEAR(obb.GetDistance(Vector3(4.7f, 4.5f, 4.2f)), 0.6553f, 1e-3f);
@@ -146,10 +146,10 @@ namespace UnitTest
TEST(MATH_Obb, GetDistanceSq)
{
const Vector3 position(1.0f, 4.0f, 3.0f);
const Quaternion rotation = Quaternion::CreateRotationY(DegToRad(45.0f));
const Vector3 halfLengths(1.5f, 3.0f, 1.0f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(1.0f, 4.0f, 3.0f);
const Quaternion testRotation = Quaternion::CreateRotationY(DegToRad(45.0f));
const Vector3 testHalfLengths(1.5f, 3.0f, 1.0f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(1.1f, 4.3f, 2.7f)), 0.0f, 1e-3f);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(-0.7f, 3.5f, 2.0f)), 0.8266f, 1e-3f);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(2.4f, 0.5f, 1.5f)), 0.5532f, 1e-3f);
@@ -353,6 +353,7 @@ namespace AzFramework
// Get the dimensions of the display device on which the window is currently displayed.
MONITORINFO monitorInfo;
memset(&monitorInfo, 0, sizeof(MONITORINFO)); // C4701 potentially uninitialized local variable 'monitorInfo' used
monitorInfo.cbSize = sizeof(MONITORINFO);
const BOOL success = monitor ? GetMonitorInfo(monitor, &monitorInfo) : FALSE;
if (!success)
@@ -46,7 +46,7 @@ namespace AzNetworking
}
else if (m_updateRate < updateTimeMs)
{
AZLOG_INFO("TimedThread bled %d ms", aznumeric_cast<int32_t>(updateTimeMs - m_updateRate));
AZLOG(NET_TimedThread, "TimedThread bled %d ms", aznumeric_cast<int32_t>(updateTimeMs - m_updateRate));
}
}
OnStop();
@@ -18,6 +18,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
@@ -49,8 +50,7 @@ namespace AzToolsFramework
m_alias = GenerateInstanceAlias();
m_containerEntity = containerEntity ? AZStd::move(containerEntity)
: AZStd::make_unique<AZ::Entity>();
EntityAlias containerEntityAlias = GenerateEntityAlias();
RegisterEntity(m_containerEntity->GetId(), containerEntityAlias);
RegisterEntity(m_containerEntity->GetId(), PrefabDomUtils::ContainerEntityName);
}
Instance::~Instance()
@@ -311,8 +311,15 @@ namespace AzToolsFramework
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance)
{
InstanceAlias newInstanceAlias = GenerateInstanceAlias();
return AddInstance(AZStd::move(instance), newInstanceAlias);
}
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias newInstanceAlias)
{
AZ_Assert(instance.get(), "instance argument is nullptr");
AZ_Assert(m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), "InstanceAlias' unique id collision, this should never happen.");
AZ_Assert(
m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(),
"InstanceAlias' unique id collision, this should never happen.");
instance->m_parent = this;
instance->m_alias = newInstanceAlias;
return *(m_nestedInstances[newInstanceAlias] = std::move(instance));
@@ -613,6 +620,7 @@ namespace AzToolsFramework
AZStd::unique_ptr<AZ::Entity> Instance::DetachContainerEntity()
{
m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId());
return AZStd::move(m_containerEntity);
}
}
@@ -48,6 +48,7 @@ namespace AzToolsFramework
using EntityAliasOptionalReference = AZStd::optional<AZStd::reference_wrapper<EntityAlias>>;
using InstanceOptionalReference = AZStd::optional<AZStd::reference_wrapper<Instance>>;
using InstanceOptionalConstReference = AZStd::optional<AZStd::reference_wrapper<const Instance>>;
using InstanceSet = AZStd::unordered_set<Instance*>;
using InstanceSetConstReference = AZStd::optional<AZStd::reference_wrapper<const InstanceSet>>;
using EntityOptionalReference = AZStd::optional<AZStd::reference_wrapper<AZ::Entity>>;
@@ -85,12 +86,14 @@ namespace AzToolsFramework
bool AddEntity(AZ::Entity& entity);
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);
void RemoveNestedEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
void Reset();
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
Instance& AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias instanceAlias);
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
/**
@@ -182,7 +185,6 @@ namespace AzToolsFramework
void ClearEntities();
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
void RemoveEntities(const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter);
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
@@ -19,6 +19,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
@@ -191,24 +192,7 @@ namespace AzToolsFramework
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
{
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
previousPatch.Accept(writer);
QString previousPatchString(buffer.GetString());
for (AZ::Entity* entity : entities)
{
AZ::EntityId entityId = entity->GetId();
AZStd::string oldEntityAlias = oldEntityAliases[entityId];
EntityAliasOptionalReference newEntityAlias = instanceToCreate->get().GetEntityAlias(entityId);
AZ_Assert(
newEntityAlias.has_value(),
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
static_cast<AZ::u64>(entityId));
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
}
previousPatch.Parse(previousPatchString.toUtf8().constData());
UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->get());
}
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
@@ -243,11 +227,33 @@ namespace AzToolsFramework
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
AZStd::move(patch));
// Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom.
// The new values were saved in the link, so propagation will apply them correctly.
{
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
PrefabDom containerBeforeReset;
m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity);
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId());
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
PrefabDom containerAfterReset;
m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity);
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(containerEntityId)));
state->SetParent(undoBatch.GetUndoBatch());
state->Capture(containerBeforeReset, containerAfterReset, containerEntityId);
state->Redo();
}
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
// We are doing this so that the changes in those enities are not queued up twice for propagation.
// We are doing this so that the changes in those entities are not queued up twice for propagation.
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
// Select Container Entity
{
auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity");
@@ -291,7 +297,7 @@ namespace AzToolsFramework
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter), parentEntityId);
return AZStd::move(patch);
}
@@ -385,8 +391,8 @@ namespace AzToolsFramework
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
}
return AZ::Success();
@@ -589,54 +595,199 @@ namespace AzToolsFramework
{
// Create Undo node on entities if they belong to an instance
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (owningInstance.has_value())
if (!owningInstance.has_value())
{
PrefabDom afterState;
AZ::Entity* entity = GetEntityById(entityId);
if (entity)
return;
}
AZ::Entity* entity = GetEntityById(entityId);
if (!entity)
{
m_prefabUndoCache.PurgeCache(entityId);
return;
}
PrefabDom beforeState;
AZ::EntityId beforeParentId;
m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId);
PrefabDom afterState;
AZ::EntityId afterParentId;
AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId);
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
{
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
bool isNewParentOwnedByDifferentInstance = false;
if (beforeParentId != afterParentId)
{
PrefabDom beforeState;
m_prefabUndoCache.Retrieve(entityId, beforeState);
// If the entity parent changed, verify if the owning instance changed too
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() &&
(&beforeOwningInstance->get() != &afterOwningInstance->get()))
{
if (IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId))
{
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
// Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate =
aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
linkUpdate->SetParent(parentUndoBatch);
linkUpdate->Capture(patch, owningInstance->get().GetLinkId());
linkUpdate->Redo();
}
else
{
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(parentUndoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
}
isNewParentOwnedByDifferentInstance = true;
}
}
// Update the cache
m_prefabUndoCache.Store(entityId, AZStd::move(afterState));
if (isInstanceContainerEntity)
{
if (isNewParentOwnedByDifferentInstance)
{
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
PrefabDom afterStateafterReparenting;
m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity);
PrefabDom newPatch;
m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId);
InstanceOptionalReference owningInstanceAfterReparenting =
m_instanceEntityMapperInterface->FindOwningInstance(entityId);
Internal_HandleContainerOverride(
parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId());
}
else
{
Internal_HandleContainerOverride(
parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId());
}
}
else
{
m_prefabUndoCache.PurgeCache(entityId);
Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState);
if (isNewParentOwnedByDifferentInstance)
{
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
}
}
}
m_prefabUndoCache.UpdateCache(entityId);
}
void PrefabPublicHandler::Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId)
{
// Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
linkUpdate->SetParent(undoBatch);
linkUpdate->Capture(patch, linkId);
linkUpdate->Redo();
}
void PrefabPublicHandler::Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState)
{
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(undoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
}
void PrefabPublicHandler::Internal_HandleInstanceChange(
UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId)
{
// If the entity parent changed, verify if the owning instance changed too
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
EntityList entities;
AZStd::vector<Instance*> instances;
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
// Remove Entities and Instances from the prior instance
{
// Remove Instances
for (Instance* nestedInstance : instances)
{
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
PrefabDom oldLinkPatches;
if (linkRef.has_value())
{
auto patches = linkRef->get().GetLinkPatches();
if (patches.has_value())
{
oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator());
}
}
auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch);
instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches)));
instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr));
}
// Get the previous state of the prior instance for undo/redo purposes
PrefabDom beforeInstanceDomBeforeRemoval;
m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get());
// Remove Entities
for (AZ::Entity* nestedEntity : entities)
{
beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release();
}
// Create the Update node for the prior owning instance
// Instance removal will be taken care of from the RemoveLink function for undo/redo purposes
PrefabUndoHelpers::UpdatePrefabInstance(
beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch);
}
// Add Entities and Instances to new instance
{
// Add Instances
for (auto& instanceUniquePtr : instanceUniquePtrs)
{
afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr));
}
// Create Links
for (auto& instanceInfo : instancePatches)
{
// Add a new link with the old dom
CreateLink(
*instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch,
AZStd::move(instanceInfo.second));
}
// Get the previous state of the new instance for undo/redo purposes
PrefabDom afterInstanceDomBeforeAdd;
m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get());
// Add Entities
for (AZ::Entity* nestedEntity : entities)
{
afterOwningInstance->get().AddEntity(*nestedEntity);
}
// Create the Update node for the new owning instance
PrefabUndoHelpers::UpdatePrefabInstance(
afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch);
}
}
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
@@ -989,6 +1140,123 @@ namespace AzToolsFramework
return AZ::Success();
}
PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
{
if (!containerEntityId.IsValid())
{
return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity."));
}
if (IsLevelInstanceContainerEntity(containerEntityId))
{
return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance."));
}
InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId);
if (owningInstance->get().GetContainerEntityId() != containerEntityId)
{
return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity."));
}
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture");
ScopedUndoBatch undoBatch("Detach Prefab");
InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance();
AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity.");
auto& parentInstance = getParentInstanceResult->get();
const auto parentTemplateId = parentInstance.GetTemplateId();
{
auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias());
AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance.");
RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch());
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance);
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get());
auto containerEntityPtr = instancePtr->DetachContainerEntity();
auto& containerEntity = *containerEntityPtr.release();
auto editorPrefabComponent = containerEntity.FindComponent<EditorPrefabComponent>();
containerEntity.Deactivate();
const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent);
AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed.");
delete editorPrefabComponent;
containerEntity.Activate();
const bool containerEntityAdded = parentInstance.AddEntity(containerEntity);
AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed.");
EntityIdList entityIds;
entityIds.emplace_back(containerEntity.GetId());
instancePtr->GetEntities(
[&](AZStd::unique_ptr<AZ::Entity>& entityPtr)
{
oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get());
return true;
});
instancePtr->DetachEntities(
[&](AZStd::unique_ptr<AZ::Entity> entityPtr)
{
auto& entity = *entityPtr.release();
const bool entityAdded = parentInstance.AddEntity(entity);
AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed.");
entityIds.emplace_back(entity.GetId());
});
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
command->SetParent(undoBatch.GetUndoBatch());
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo");
command->RunRedo();
}
const auto instanceTemplateId = instancePtr->GetTemplateId();
auto parentContainerEntityId = parentInstance.GetContainerEntityId();
instancePtr->GetNestedInstances(
[&](AZStd::unique_ptr<Instance>& nestedInstancePtr)
{
//get previous link patch
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId());
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
AZ_Assert(
linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.",
nestedInstancePtr->GetLinkId());
PrefabDom linkPatchesCopy;
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch());
UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance);
CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(),
AZStd::move(linkPatchesCopy), true);
});
}
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities);
}
return AZ::Success();
}
void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities,
AZ::Vector3& translation, AZ::Quaternion& rotation)
{
@@ -1252,5 +1520,30 @@ namespace AzToolsFramework
stringToReplace.replace(oldAliasPathRef, newAliasPathRef);
}
void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases(
PrefabDom& linkPatch,
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
Instance& newParent)
{
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
linkPatch.Accept(writer);
QString previousPatchString(buffer.GetString());
for (const auto& [entityId, oldEntityAlias] : oldEntityAliases)
{
EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId);
AZ_Assert(
newEntityAlias.has_value(),
"Could not fetch entity alias for entity with id '%llu' during prefab creation.",
static_cast<AZ::u64>(entityId));
ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get());
}
linkPatch.Parse(previousPatchString.toUtf8().constData());
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -64,6 +64,8 @@ namespace AzToolsFramework
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
private:
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
@@ -88,8 +90,8 @@ namespace AzToolsFramework
/**
* Creates a link between the templates of an instance and its parent.
*
* \param sourceInstance The instance that corresponds to the source template of the link.
* \param targetInstance The id of the target template.
* \param sourceInstance The instance that corresponds to the source template of the link (child).
* \param targetInstance The id of the target template (parent).
* \param undoBatch The undo batch to set as parent for this create link action.
* \param patch The patch to store in the newly created link dom.
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
@@ -132,7 +134,18 @@ namespace AzToolsFramework
bool IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
static void Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId);
static void Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState);
void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId);
void UpdateLinkPatchesWithNewEntityAliases(
PrefabDom& linkPatch,
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
Instance& newParent);
static void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias);
static Instance* GetParentInstance(Instance* instance);
static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant);
@@ -150,6 +150,17 @@ namespace AzToolsFramework
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
/**
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
* owned by this instance under the parent instance.
* Bails if the entity is not a container entity or belongs to the level prefab instance.
* @param containerEntityId The container entity id of the instance to detach.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
};
} // namespace Prefab
@@ -652,7 +652,8 @@ namespace AzToolsFramework
if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd())
{
instancesValue->get().AddMember(
rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator());
rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(),
targetTemplateDom.GetAllocator());
}
Template& sourceTemplate = sourceTemplateRef->get();
@@ -705,14 +706,14 @@ namespace AzToolsFramework
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from TemplateToLinkIdsMap.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str());
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId());
result = RemoveLinkFromTargetTemplate(linkId, link);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from target Template with Id '%llu'.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str(), link.GetTargetTemplateId());
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId(), link.GetTargetTemplateId());
m_linkIdMap.erase(linkId);
@@ -73,14 +73,16 @@ namespace AzToolsFramework
}
PrefabDom oldData;
Retrieve(entityId, oldData);
AZ::EntityId oldParentId;
Retrieve(entityId, oldData, oldParentId);
UpdateCache(entityId);
PrefabDom newData;
Retrieve(entityId, newData);
AZ::EntityId newParentId;
Retrieve(entityId, newData, newParentId);
if (newData != oldData)
if (newData != oldData || oldParentId != newParentId)
{
// display a useful message
AZ::Entity* entity = nullptr;
@@ -106,7 +108,7 @@ namespace AzToolsFramework
// Clear out newly generated data and
// replace with original data to ensure debug mode has the same data as profile/release
// in the event of the consistency check failing.
m_entitySavedStates[entityId] = AZStd::move(oldData);
m_entitySavedStates[entityId] = {AZStd::move(oldData), oldParentId};
#endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
}
@@ -140,10 +142,13 @@ namespace AzToolsFramework
return;
}
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
// Capture it
PrefabDom entityDom;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity);
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(entityDom)));
m_entitySavedStates[entityId] = {AZStd::move(entityDom), parentId};
AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast<AZ::u64>(entityId), entity->GetName().c_str());
@@ -155,7 +160,7 @@ namespace AzToolsFramework
m_entitySavedStates.erase(entityId);
}
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom)
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId)
{
auto it = m_entitySavedStates.find(entityId);
@@ -164,14 +169,15 @@ namespace AzToolsFramework
return false;
}
outDom = AZStd::move(m_entitySavedStates[entityId]);
outDom = AZStd::move(m_entitySavedStates[entityId].dom);
parentId = m_entitySavedStates[entityId].parentId;
m_entitySavedStates.erase(entityId);
return true;
}
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom)
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId)
{
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(dom)));
m_entitySavedStates[entityId] = {AZStd::move(dom), parentId};
}
void PrefabUndoCache::Clear()
@@ -46,14 +46,19 @@ namespace AzToolsFramework
void Validate(const AZ::EntityId& entityId) override;
// Retrieve the last known state for an entity
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom);
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId);
// Store dom as the cached state of entityId
void Store(const AZ::EntityId& entityId, PrefabDom&& dom);
void Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId);
private:
typedef AZStd::unordered_map<AZ::EntityId, PrefabDom> EntityDomMap;
EntityDomMap m_entitySavedStates;
struct PrefabUndoCacheItem
{
PrefabDom dom;
AZ::EntityId parentId;
};
typedef AZStd::unordered_map<AZ::EntityId, PrefabUndoCacheItem> EntityCache;
EntityCache m_entitySavedStates;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
@@ -237,6 +237,24 @@ namespace AzToolsFramework
{
deleteAction->setDisabled(true);
}
// Detach Prefab
if (selectedEntities.size() == 1)
{
AZ::EntityId selectedEntity = selectedEntities[0];
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) &&
!s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity))
{
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
QObject::connect(
detachPrefabAction, &QAction::triggered, detachPrefabAction,
[this, selectedEntity]
{
ContextMenu_DetachPrefab(selectedEntity);
});
}
}
}
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
@@ -392,6 +410,17 @@ namespace AzToolsFramework
}
}
void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity)
{
PrefabOperationResult detachPrefabResult =
s_prefabPublicInterface->DetachPrefab(containerEntity);
if (!detachPrefabResult.IsSuccess())
{
WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError());
}
}
void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@@ -93,6 +93,7 @@ namespace AzToolsFramework
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
static void ContextMenu_DeleteSelected();
static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity);
// Prompt and resolve dialogs
static bool QueryUserForPrefabSaveLocation(
@@ -969,7 +969,8 @@ namespace AzToolsFramework
{
// Build up components to display
SharedComponentArray sharedComponentArray;
BuildSharedComponentArray(sharedComponentArray, selectionEntityTypeInfo != SelectionEntityTypeInfo::OnlyStandardEntities);
BuildSharedComponentArray(sharedComponentArray,
!(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities));
if (sharedComponentArray.size() == 0)
{
@@ -124,4 +124,15 @@ namespace AzToolsFramework
return cameraState;
}
float GetScreenDisplayScaling(const int viewportId)
{
float scaling = 1.0f;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
scaling, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::DeviceScalingFactor);
return scaling;
}
} // namespace AzToolsFramework
@@ -60,6 +60,9 @@ namespace AzToolsFramework
/// Wrapper for EBus call to return the CameraState for a given viewport.
AzFramework::CameraState GetCameraState(int viewportId);
/// Wrapper for EBus call to return the DPI scaling for a given viewport.
float GetScreenDisplayScaling(const int viewportId);
/// A utility to return the center of several points.
/// Take several positions and store the min and max of each in
/// turn - when all points have been added return the center/midpoint.
@@ -3573,9 +3573,10 @@ namespace AzToolsFramework
debugDisplay.SetLineWidth(1.0f);
const float labelOffset = cl_viewportGizmoAxisLabelOffset;
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize;
const float screenScale = GetScreenDisplayScaling(viewportId);
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
// draw the label of of each axis for the gizmo
const float labelSize = cl_viewportGizmoAxisLabelSize;
+16
View File
@@ -9,6 +9,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <Launcher.h>
#include <AzCore/Casting/numeric_cast.h>
@@ -22,6 +23,8 @@
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/IO/RemoteStorageDrive.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/Windowing/WindowBus.h>
#include <AzGameFramework/Application/GameApplication.h>
@@ -45,6 +48,19 @@ extern "C" void CreateStaticModules(AZStd::vector<AZ::Module*>& modulesOut);
namespace
{
void OnViewportResize(const AZ::Vector2& value);
AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate,
"The default size for the launcher viewport, 0 0 means full screen");
void OnViewportResize(const AZ::Vector2& value)
{
AzFramework::NativeWindowHandle windowHandle = nullptr;
AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle);
AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast<int32_t>(value.GetX()), aznumeric_cast<int32_t>(value.GetY()));
AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize);
}
void ExecuteConsoleCommandFile(AzFramework::Application& application)
{
const AZStd::string_view customConCmdKey = "console-command-file";
+9 -9
View File
@@ -1233,7 +1233,7 @@ void EditorViewportWidget::SetViewportId(int id)
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
controller->SetCameraListBuilderCallback(
[](AzFramework::Cameras& cameras)
[id](AzFramework::Cameras& cameras)
{
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraFreeLookButton);
auto firstPersonPanCamera =
@@ -1243,17 +1243,17 @@ void EditorViewportWidget::SetViewportId(int id)
auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
orbitCamera->SetLookAtFn(
[](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
[id](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
{
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
AZStd::optional<AZ::Vector3> lookAtAfterInterpolation;
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
lookAtAfterInterpolation, id,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation);
// initially attempt to use manipulator transform if one exists (there is a selection)
if (manipulatorTransform)
// initially attempt to use the last set look at point after an interpolation has finished
if (lookAtAfterInterpolation.has_value())
{
return manipulatorTransform->GetTranslation();
return *lookAtAfterInterpolation;
}
const float RayDistance = 1000.0f;
+1 -1
View File
@@ -553,7 +553,7 @@ void CLogFile::OnWriteToConsole(const char* sText, bool bNewLine)
// remember selection and the top row
int len = m_hWndEditBox->document()->toPlainText().length();
int top;
int top = 0;
int from = m_hWndEditBox->textCursor().selectionStart();
int to = from + m_hWndEditBox->textCursor().selectionEnd();
bool keepPos = false;
+3 -3
View File
@@ -121,7 +121,7 @@ protected:
};
#endif
Q_GLOBAL_STATIC(QtViewPaneManager, s_instance)
Q_GLOBAL_STATIC(QtViewPaneManager, s_viewPaneManagerInstance)
QWidget* QtViewPane::CreateWidget()
@@ -611,12 +611,12 @@ void QtViewPaneManager::UnregisterPane(const QString& name)
QtViewPaneManager* QtViewPaneManager::instance()
{
return s_instance();
return s_viewPaneManagerInstance();
}
bool QtViewPaneManager::exists()
{
return s_instance.exists();
return s_viewPaneManagerInstance.exists();
}
void QtViewPaneManager::SetMainWindow(AzQtComponents::DockMainWindow* mainWindow, QSettings* settings, const QByteArray& lastMainWindowState)
@@ -59,7 +59,7 @@ namespace
{
int fps;
const char* fpsDesc;
} fps[] = {
} fpsOptions[] = {
{24, "Film(24)"}, {25, "PAL(25)"}, {30, "NTSC(30)"},
{48, "Show(48)"}, {50, "PAL Field(50)"}, {60, "NTSC Field(60)"}
};
@@ -213,9 +213,9 @@ void CSequenceBatchRenderDialog::OnInitDialog()
m_ui->m_resolutionCombo->setCurrentIndex(0);
// Fill the FPS combo box.
for (int i = 0; i < AZStd::size(fps); ++i)
for (int i = 0; i < AZStd::size(fpsOptions); ++i)
{
m_ui->m_fpsCombo->addItem(fps[i].fpsDesc);
m_ui->m_fpsCombo->addItem(fpsOptions[i].fpsDesc);
}
m_ui->m_fpsCombo->setCurrentIndex(0);
@@ -306,9 +306,9 @@ void CSequenceBatchRenderDialog::OnRenderItemSelChange()
m_ui->m_destinationEdit->setText(item.folder);
// fps
bool bFound = false;
for (int i = 0; i < arraysize(fps); ++i)
for (int i = 0; i < arraysize(fpsOptions); ++i)
{
if (item.fps == fps[i].fps)
if (item.fps == fpsOptions[i].fps)
{
m_ui->m_fpsCombo->setCurrentIndex(i);
bFound = true;
@@ -621,7 +621,7 @@ void CSequenceBatchRenderDialog::OnFPSEditChange()
void CSequenceBatchRenderDialog::OnFPSChange(int itemIndex)
{
m_customFPS = fps[itemIndex].fps;
m_customFPS = fpsOptions[itemIndex].fps;
CheckForEnableUpdateButton();
}
@@ -1543,13 +1543,13 @@ bool CSequenceBatchRenderDialog::SetUpNewRenderItem(SRenderItem& item)
item.frameRange = Range(m_ui->m_startFrame->value() / m_fpsForTimeToFrameConversion,
m_ui->m_endFrame->value() / m_fpsForTimeToFrameConversion);
// fps
if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fps[m_ui->m_fpsCombo->currentIndex()].fpsDesc)
if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fpsOptions[m_ui->m_fpsCombo->currentIndex()].fpsDesc)
{
item.fps = m_customFPS;
}
else
{
item.fps = fps[m_ui->m_fpsCombo->currentIndex()].fps;
item.fps = fpsOptions[m_ui->m_fpsCombo->currentIndex()].fps;
}
// prefix
item.prefix = m_ui->BATCH_RENDER_FILE_PREFIX->text();
+2 -2
View File
@@ -157,7 +157,7 @@ static Quatern Qt_FromMatrix(HMatrix mat)
* |w| is greater than 1/2, which is as small as a largest component can be.
* Otherwise, the largest diagonal entry corresponds to the largest of |x|,
* |y|, or |z|, one of which must be larger than |w|, and at least 1/2. */
Quatern qu;
Quatern qu = { 0.0f, 0.0f, 0.0f, 1.0f };
double tr, s;
tr = mat[X][X] + mat[Y][Y] + mat[Z][Z];
@@ -531,7 +531,7 @@ Quatern snuggle(Quatern q, HVect* k)
#define swap(a, i, j) {a[3] = a[i]; a[i] = a[j]; a[j] = a[3]; }
#define cycle(a, p) if (p) {a[3] = a[0]; a[0] = a[1]; a[1] = a[2]; a[2] = a[3]; } \
else {a[3] = a[2]; a[2] = a[1]; a[1] = a[0]; a[0] = a[3]; }
Quatern p;
Quatern p = { 0.0f, 0.0f, 0.0f, 1.0f };
float ka[4];
int i, turn = -1;
ka[X] = k->x;
+2 -1
View File
@@ -2239,7 +2239,8 @@ uint32 CFileUtil::GetAttributes(const char* filename, bool bUseSourceControl /*=
bool CFileUtil::CompareFiles(const QString& strFilePath1, const QString& strFilePath2)
{
// Get the size of both files. If either fails we say they are different (most likely one doesn't exist)
uint64 size1, size2;
uint64 size1 = 0;
uint64 size2 = 0;
if (!GetDiskFileSize(strFilePath1.toUtf8().data(), size1) || !GetDiskFileSize(strFilePath2.toUtf8().data(), size2))
{
return false;
+1
View File
@@ -116,6 +116,7 @@ bool CImageBT::Load(const QString& fileName, CFloatImage& image)
// Get the BT header data
BtHeader header;
memset(&header, 0, sizeof(BtHeader)); // C4701 potentially uninitialized local variable 'header' used
bool validData = true;
validData = validData && (fread(&header, sizeof(BtHeader), 1, file) != 0);
+1 -1
View File
@@ -419,7 +419,7 @@ static inline bool MatchesWildcardsIgnoreCaseExt_Tpl(const TS& str, const TS& wi
const typename TS::value_type* savedStrBegin = 0;
const typename TS::value_type* savedStrEnd = 0;
const typename TS::value_type* savedWild = 0;
size_t savedWildCount;
size_t savedWildCount = 0;
const typename TS::value_type* pStr = str.c_str();
const typename TS::value_type* pWild = wildcards.c_str();
@@ -179,11 +179,11 @@ ComponentEntityEditorPlugin::ComponentEntityEditorPlugin([[maybe_unused]] IEdito
LyViewPane::EntityOutliner,
LyViewPane::CategoryTools,
outlinerOptions);
}
AzToolsFramework::ViewPaneOptions options;
options.preferedDockingArea = Qt::NoDockWidgetArea;
RegisterViewPane<SliceRelationshipWidget>(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options);
AzToolsFramework::ViewPaneOptions options;
options.preferedDockingArea = Qt::NoDockWidgetArea;
RegisterViewPane<SliceRelationshipWidget>(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options);
}
RegisterModuleResourceSelectors(GetIEditor()->GetResourceSelectorHost());
@@ -1732,13 +1732,14 @@ void SandboxIntegrationManager::GoToEntitiesInViewports(const AzToolsFramework::
// compute new camera transform
const float fov = AzFramework::RetrieveFov(viewportContext->GetCameraProjectionMatrix());
const float fovScale = (1.0f / AZStd::tan(fov * 0.5f));
const float distanceToTarget = selectionSize * fovScale * centerScale;
const float distanceToLookAt = selectionSize * fovScale * centerScale;
const AZ::Transform nextCameraTransform =
AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToTarget), aabb.GetCenter());
AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToLookAt), aabb.GetCenter());
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
viewportContext->GetId(),
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform);
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform,
distanceToLookAt);
}
}
}
@@ -15,7 +15,6 @@
#include <PythonBindingsInterface.h>
#include <NewProjectSettingsScreen.h>
#include <ScreenHeaderWidget.h>
#include <GemCatalog/GemCatalogScreen.h>
#include <QDialogButtonBox>
#include <QHBoxLayout>
@@ -42,9 +41,10 @@ namespace O3DE::ProjectManager
m_stack = new QStackedWidget(this);
m_stack->setObjectName("body");
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding));
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
m_stack->addWidget(new NewProjectSettingsScreen());
m_stack->addWidget(new GemCatalogScreen());
m_gemCatalog = new GemCatalogScreen();
m_stack->addWidget(m_gemCatalog);
vLayout->addWidget(m_stack);
QDialogButtonBox* backNextButtons = new QDialogButtonBox();
@@ -88,6 +88,7 @@ namespace O3DE::ProjectManager
emit GotoPreviousScreenRequest();
}
}
void CreateProjectCtrl::HandleNextButton()
{
ScreenWidget* currentScreen = reinterpret_cast<ScreenWidget*>(m_stack->currentWidget());
@@ -106,6 +107,9 @@ namespace O3DE::ProjectManager
m_projectInfo = newProjectScreen->GetProjectInfo();
m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath();
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true);
}
}
@@ -129,6 +133,9 @@ namespace O3DE::ProjectManager
{
QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project."));
}
// Enable/disable gems for the newly created project.
m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path);
}
}
@@ -14,6 +14,7 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <ProjectInfo.h>
#include <GemCatalog/GemCatalogScreen.h>
#endif
QT_FORWARD_DECLARE_CLASS(QStackedWidget)
@@ -48,6 +49,8 @@ namespace O3DE::ProjectManager
QString m_projectTemplatePath;
ProjectInfo m_projectInfo;
GemCatalogScreen* m_gemCatalog = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -15,13 +15,12 @@
#include <GemCatalog/GemCatalogHeaderWidget.h>
#include <GemCatalog/GemListHeaderWidget.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <GemCatalog/GemFilterWidget.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
//#define USE_TESTGEMDATA
#include <PythonBindingsInterface.h>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
@@ -29,47 +28,32 @@ namespace O3DE::ProjectManager
: ScreenWidget(parent)
{
m_gemModel = new GemModel(this);
GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
m_proxModel = new GemSortFilterProxyModel(m_gemModel, this);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setMargin(0);
vLayout->setSpacing(0);
setLayout(vLayout);
GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(proxyModel);
GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel);
vLayout->addWidget(headerWidget);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
vLayout->addLayout(hLayout);
m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this);
m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this);
m_gemInspector = new GemInspector(m_gemModel, this);
m_gemInspector->setFixedWidth(320);
m_gemInspector->setFixedWidth(240);
// Start: Temporary gem test data
#ifdef USE_TESTGEMDATA
QVector<GemInfo> testGemData = GenerateTestData();
for (const GemInfo& gemInfo : testGemData)
{
m_gemModel->AddGem(gemInfo);
}
#else
// End: Temporary gem test data
auto result = PythonBindingsInterface::Get()->GetGems();
if (result.IsSuccess())
{
for (auto gemInfo : result.GetValue())
{
m_gemModel->AddGem(gemInfo);
}
}
#endif
QWidget* filterWidget = new QWidget(this);
filterWidget->setFixedWidth(240);
m_filterWidgetLayout = new QVBoxLayout();
m_filterWidgetLayout->setMargin(0);
m_filterWidgetLayout->setSpacing(0);
filterWidget->setLayout(m_filterWidgetLayout);
GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel);
filterWidget->setFixedWidth(250);
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(proxyModel);
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel);
QVBoxLayout* middleVLayout = new QVBoxLayout();
middleVLayout->setMargin(0);
@@ -80,98 +64,111 @@ namespace O3DE::ProjectManager
hLayout->addWidget(filterWidget);
hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemInspector);
proxyModel->InvalidateFilter();
}
QVector<GemInfo> GemCatalogScreen::GenerateTestData()
void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject)
{
QVector<GemInfo> result;
m_gemModel->clear();
FillModel(projectPath, isNewProject);
GemInfo gem("EMotion FX",
"O3DE Foundation",
"EMFX is a real-time character animation system. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
(GemInfo::Android | GemInfo::iOS | GemInfo::macOS | GemInfo::Windows | GemInfo::Linux),
true);
gem.m_directoryLink = "C:/";
gem.m_documentationLink = "http://www.amazon.com";
gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"});
gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"});
gem.m_types = (GemInfo::Code | GemInfo::Asset);
gem.m_version = "v1.01";
gem.m_lastUpdatedDate = "24th April 2021";
gem.m_binarySizeInKB = 40;
gem.m_features = QStringList({"Animation", "Assets", "Physics"});
gem.m_gemOrigin = GemInfo::O3DEFoundation;
result.push_back(gem);
if (m_filterWidget)
{
m_filterWidget->hide();
m_filterWidget->deleteLater();
}
gem.m_name = "Atom";
gem.m_creator = "O3DE Seattle";
gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
gem.m_platforms = (GemInfo::Android | GemInfo::Windows | GemInfo::Linux | GemInfo::macOS);
gem.m_isAdded = true;
gem.m_directoryLink = "C:/";
gem.m_documentationLink = "https://aws.amazon.com/gametech/";
gem.m_dependingGemUuids = QStringList({"EMotionFX", "Core", "AudioSystem", "Camera", "Particles"});
gem.m_conflictingGemUuids = QStringList({"CloudCanvas", "NovaNet"});
gem.m_version = "v2.31";
gem.m_lastUpdatedDate = "24th November 2020";
gem.m_features = QStringList({"Assets", "Rendering", "UI", "VR", "Debug", "Environment"});
gem.m_binarySizeInKB = 2087;
result.push_back(gem);
m_filterWidget = new GemFilterWidget(m_proxModel);
m_filterWidgetLayout->addWidget(m_filterWidget);
gem.m_name = "Physics";
gem.m_creator = "O3DE London";
gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
gem.m_platforms = (GemInfo::Android | GemInfo::Linux | GemInfo::macOS);
gem.m_isAdded = true;
gem.m_directoryLink = "C:/";
gem.m_documentationLink = "https://aws.amazon.com/gametech/";
gem.m_dependingGemUuids = QStringList({"GraphCanvas", "ExpressionEvaluation", "UI Lib", "Multiplayer", "GameStateSamples"});
gem.m_conflictingGemUuids = QStringList({"Cloud Canvas", "EMotion FX", "Streaming", "MessagePopup", "Cloth", "Graph Canvas", "Twitch Integration"});
gem.m_version = "v1.5.102145";
gem.m_lastUpdatedDate = "1st January 2021";
gem.m_binarySizeInKB = 2000000;
gem.m_features = QStringList({"Physics", "Gameplay", "Debug", "Assets"});
result.push_back(gem);
m_proxModel->InvalidateFilter();
result.push_back(O3DE::ProjectManager::GemInfo("Certificate Manager",
"O3DE Irvine",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
GemInfo::Windows,
false));
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Framework",
"O3DE Seattle",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
GemInfo::iOS | GemInfo::Linux,
false));
void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject)
{
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult;
if (isNewProject)
{
allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos();
}
else
{
allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
}
result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Core",
"O3DE Foundation",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
true));
if (allGemInfosResult.IsSuccess())
{
// Add all available gems to the model.
const QVector<GemInfo> allGemInfos = allGemInfosResult.GetValue();
for (const GemInfo& gemInfo : allGemInfos)
{
m_gemModel->AddGem(gemInfo);
}
result.push_back(O3DE::ProjectManager::GemInfo("Gestures",
"O3DE Foundation",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
false));
// Gather enabled gems for the given project.
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
if (enabledGemNamesResult.IsSuccess())
{
const QVector<AZStd::string> enabledGemNames = enabledGemNamesResult.GetValue();
for (const AZStd::string& enabledGemName : enabledGemNames)
{
const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str());
if (modelIndex.isValid())
{
GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true);
GemModel::SetIsAdded(*m_gemModel, modelIndex, true);
}
else
{
AZ_Warning("ProjectManager::GemCatalog", false,
"Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.",
enabledGemName.c_str());
}
}
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
}
}
else
{
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str()));
}
}
result.push_back(O3DE::ProjectManager::GemInfo("Effects System",
"O3DE Foundation",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
true));
void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath)
{
IPythonBindings* pythonBindings = PythonBindingsInterface::Get();
QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded();
QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved();
result.push_back(O3DE::ProjectManager::GemInfo("Microphone",
"O3DE Foundation",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus euismod ligula vitae dui dictum, a sodales dolor luctus. Sed id elit dapibus, finibus neque sed, efficitur mi. Nam facilisis ligula at eleifend pellentesque. Praesent non ex consectetur, blandit tellus in, venenatis lacus. Duis nec neque in urna ullamcorper euismod id eu leo. Nam efficitur dolor sed odio vehicula venenatis. Suspendisse nec est non velit commodo cursus in sit amet dui. Ut bibendum nisl et libero hendrerit dapibus. Vestibulum ultrices ullamcorper urna, placerat porttitor est lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer a magna ac tellus sollicitudin porttitor. Phasellus lobortis viverra justo id bibendum. Etiam ac pharetra risus. Nulla vitae justo nibh. Nulla viverra leo et molestie interdum. Duis sit amet bibendum nulla, sit amet vehicula augue.",
GemInfo::Android | GemInfo::Windows | GemInfo::Linux,
false));
for (const QModelIndex& modelIndex : toBeAdded)
{
const QString gemPath = GemModel::GetPath(modelIndex);
const AZ::Outcome<void, AZStd::string> result = pythonBindings->AddGemToProject(gemPath, projectPath);
if (!result.IsSuccess())
{
QMessageBox::critical(nullptr, "Operation failed",
QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str()));
}
}
return result;
for (const QModelIndex& modelIndex : toBeRemoved)
{
const QString gemPath = GemModel::GetPath(modelIndex);
const AZ::Outcome<void, AZStd::string> result = pythonBindings->RemoveGemFromProject(gemPath, projectPath);
if (!result.IsSuccess())
{
QMessageBox::critical(nullptr, "Operation failed",
QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str()));
}
}
}
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
@@ -14,9 +14,11 @@
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#include <GemCatalog/GemFilterWidget.h>
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemInspector.h>
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#endif
namespace O3DE::ProjectManager
@@ -29,11 +31,17 @@ namespace O3DE::ProjectManager
~GemCatalogScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
void ReinitForProject(const QString& projectPath, bool isNewProject);
void EnableDisableGemsForProject(const QString& projectPath);
private:
QVector<GemInfo> GenerateTestData();
void FillModel(const QString& projectPath, bool isNewProject);
GemListView* m_gemListView = nullptr;
GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr;
GemSortFilterProxyModel* m_proxModel = nullptr;
QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -79,4 +79,9 @@ namespace O3DE::ProjectManager
{
return (m_platforms & platform);
}
bool GemInfo::operator<(const GemInfo& gemInfo) const
{
return (m_displayName < gemInfo.m_displayName);
}
} // namespace O3DE::ProjectManager
@@ -61,6 +61,8 @@ namespace O3DE::ProjectManager
bool IsValid() const;
bool operator<(const GemInfo& gemInfo) const;
QString m_path;
QString m_name = "Unknown Gem Name";
QString m_displayName = "Unknown Gem Name";
@@ -283,12 +283,16 @@ namespace O3DE::ProjectManager
AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed");
// import required modules
m_cmake = pybind11::module::import("o3de.cmake");
m_register = pybind11::module::import("o3de.register");
m_manifest = pybind11::module::import("o3de.manifest");
m_engineTemplate = pybind11::module::import("o3de.engine_template");
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
// make sure the engine is registered
RegisterThisEngine();
return result == 0 && !PyErr_Occurred();
} catch ([[maybe_unused]] const std::exception& e)
{
@@ -311,7 +315,37 @@ namespace O3DE::ProjectManager
return !PyErr_Occurred();
}
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
bool PythonBindings::RegisterThisEngine()
{
bool registrationResult = true; // already registered is considered successful
bool pythonResult = ExecuteWithLock(
[&]
{
// check current engine path against all other registered engines
// to see if we are already registered
auto allEngines = m_manifest.attr("get_engines")();
if (pybind11::isinstance<pybind11::list>(allEngines))
{
for (auto engine : allEngines)
{
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"]));
if (enginePath.Compare(m_enginePath) == 0)
{
return;
}
}
}
auto result = m_register.attr("register")(m_enginePath.c_str());
registrationResult = (result.cast<int>() == 0);
});
bool finalResult = (registrationResult && pythonResult);
AZ_Assert(finalResult, "Registration of this engine failed!");
return finalResult;
}
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
{
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_release release;
@@ -320,13 +354,19 @@ namespace O3DE::ProjectManager
try
{
executionCallback();
return true;
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Python exception %s", e.what());
return false;
return AZ::Failure<AZStd::string>(e.what());
}
return AZ::Success();
}
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
{
return ExecuteWithLockErrorHandling(executionCallback).IsSuccess();
}
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
@@ -419,7 +459,7 @@ namespace O3DE::ProjectManager
return result;
}
AZ::Outcome<GemInfo> PythonBindings::GetGem(const QString& path)
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path)
{
GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString()));
if (gemInfo.IsValid())
@@ -432,32 +472,79 @@ namespace O3DE::ProjectManager
}
}
AZ::Outcome<QVector<GemInfo>> PythonBindings::GetGems()
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetEngineGemInfos()
{
QVector<GemInfo> gems;
bool result = ExecuteWithLock([&] {
// external gems
for (auto path : m_manifest.attr("get_gems")())
auto result = ExecuteWithLockErrorHandling([&]
{
gems.push_back(GemInfoFromPath(path));
}
for (auto path : m_manifest.attr("get_engine_gems")())
{
gems.push_back(GemInfoFromPath(path));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
// gems from the engine
for (auto path : m_manifest.attr("get_engine_gems")())
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath)
{
QVector<GemInfo> gems;
auto result = ExecuteWithLockErrorHandling([&]
{
gems.push_back(GemInfoFromPath(path));
}
});
pybind11::str pyProjectPath = projectPath.toStdString();
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
{
gems.push_back(GemInfoFromPath(path));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
if (!result)
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<AZStd::string>, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath)
{
// Retrieve the path to the cmake file that lists the enabled gems.
pybind11::str enabledGemsFilename;
auto result = ExecuteWithLockErrorHandling([&]
{
const pybind11::str pyProjectPath = projectPath.toStdString();
enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
pybind11::none(), // project_name
pyProjectPath); // project_path
});
if (!result.IsSuccess())
{
return AZ::Failure();
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
else
// Retrieve the actual list of names from the cmake file.
QVector<AZStd::string> gemNames;
result = ExecuteWithLockErrorHandling([&]
{
const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
for (auto gemName : pyGemNames)
{
gemNames.push_back(Py_To_String(gemName));
}
});
if (!result.IsSuccess())
{
return AZ::Success(AZStd::move(gems));
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
return AZ::Success(AZStd::move(gemNames));
}
bool PythonBindings::AddProject(const QString& path)
@@ -637,38 +724,36 @@ namespace O3DE::ProjectManager
}
}
bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
{
bool result = ExecuteWithLock([&] {
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
return ExecuteWithLockErrorHandling([&]
{
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
m_enableGemProject.attr("enable_gem_in_project")(
pybind11::none(), // gem_name
pyGemPath,
pybind11::none(), // project_name
pyProjectPath
);
});
return result;
m_enableGemProject.attr("enable_gem_in_project")(
pybind11::none(), // gem name not needed as path is provided
pyGemPath,
pybind11::none(), // project name not needed as path is provided
pyProjectPath
);
});
}
bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
{
bool result = ExecuteWithLock([&] {
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
return ExecuteWithLockErrorHandling([&]
{
pybind11::str pyGemPath = gemPath.toStdString();
pybind11::str pyProjectPath = projectPath.toStdString();
m_disableGemProject.attr("disable_gem_in_project")(
pybind11::none(), // gem_name
pyGemPath,
pybind11::none(), // project_name
pyProjectPath
);
});
return result;
m_disableGemProject.attr("disable_gem_in_project")(
pybind11::none(), // gem name not needed as path is provided
pyGemPath,
pybind11::none(), // project name not needed as path is provided
pyProjectPath
);
});
}
bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)
@@ -39,8 +39,10 @@ namespace O3DE::ProjectManager
bool SetEngineInfo(const EngineInfo& engineInfo) override;
// Gem
AZ::Outcome<GemInfo> GetGem(const QString& path) override;
AZ::Outcome<QVector<GemInfo>> GetGems() override;
AZ::Outcome<GemInfo> GetGemInfo(const QString& path) override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() override;
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
// Project
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
@@ -49,8 +51,8 @@ namespace O3DE::ProjectManager
bool AddProject(const QString& path) override;
bool RemoveProject(const QString& path) override;
bool UpdateProject(const ProjectInfo& projectInfo) override;
bool AddGemToProject(const QString& gemPath, const QString& projectPath) override;
bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) override;
AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override;
// ProjectTemplate
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
@@ -58,16 +60,20 @@ namespace O3DE::ProjectManager
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
bool ExecuteWithLock(AZStd::function<void()> executionCallback);
GemInfo GemInfoFromPath(pybind11::handle path);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path);
bool RegisterThisEngine();
bool StartPython();
bool StopPython();
AZ::IO::FixedMaxPath m_enginePath;
pybind11::handle m_engineTemplate;
AZStd::recursive_mutex m_lock;
pybind11::handle m_cmake;
pybind11::handle m_register;
pybind11::handle m_manifest;
pybind11::handle m_enableGemProject;
@@ -57,13 +57,27 @@ namespace O3DE::ProjectManager
* @param path the absolute path to the Gem
* @return an outcome with GemInfo on success
*/
virtual AZ::Outcome<GemInfo> GetGem(const QString& path) = 0;
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path) = 0;
/**
* Get info about all known Gems
* @return an outcome with GemInfos on success
* Get all available gem infos. This concatenates gems registered by the engine and the project.
* @param path The absolute path to the project.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>> GetGems() = 0;
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
/**
* Get engine gem infos.
* @return A list of all registered gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetEngineGemInfos() = 0;
/**
* Get a list of all enabled gem names for a given project.
* @param[in] projectPath Absolute file path to the project.
* @return A list of gem names of all the enabled gems for a given project or a error message on failure.
*/
virtual AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0;
// Projects
@@ -114,17 +128,17 @@ namespace O3DE::ProjectManager
* Add a gem to a project
* @param gemPath the absolute path to the gem
* @param projectPath the absolute path to the project
* @return true on success, false on failure
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0;
virtual AZ::Outcome<void, AZStd::string> AddGemToProject(const QString& gemPath, const QString& projectPath) = 0;
/**
* Remove gem to a project
* @param gemPath the absolute path to the gem
* @param projectPath the absolute path to the project
* @return true on success, false on failure
* @return An outcome with the success flag as well as an error message in case of a failure.
*/
virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
virtual AZ::Outcome<void, AZStd::string> RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0;
// Project Templates
@@ -97,6 +97,9 @@ namespace O3DE::ProjectManager
void UpdateProjectCtrl::HandleGemsButton()
{
// The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog.
m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false);
m_stack->setCurrentWidget(m_gemCatalogScreen);
Update();
}
@@ -113,6 +116,7 @@ namespace O3DE::ProjectManager
emit GotoPreviousScreenRequest();
}
}
void UpdateProjectCtrl::HandleNextButton()
{
if (m_stack->currentIndex() == ScreenOrder::Settings)
@@ -152,6 +156,12 @@ namespace O3DE::ProjectManager
}
}
if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
{
// Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
}
emit ChangeScreenRequest(ProjectManagerScreen::Projects);
}
@@ -17,6 +17,7 @@
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
#include <SliceConverterEditorEntityContextComponent.h>
namespace AZ
{
@@ -34,6 +35,9 @@ 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.
RegisterComponentDescriptor(AzToolsFramework::SliceConverterEditorEntityContextComponent::CreateDescriptor());
AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath();
if (projectPath.empty())
{
@@ -110,10 +114,21 @@ namespace AZ
AZ::ComponentTypeList Application::GetRequiredSystemComponents() const
{
// Use all of the default system components, but also add in the ThumbnailerNullComponent so that components requiring
// a ThumbnailService can still be started up.
// By default, we use all of the standard system components.
AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents();
// Also add in the ThumbnailerNullComponent so that components requiring a ThumbnailService can still be started up.
components.emplace_back(azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerNullComponent>());
// The Slice Converter requires a specialized variant of the EditorEntityContextComponent that exposes the ability
// to disable the behavior of activating entities on creation. During conversion, the creation flow will be triggered,
// but entity activation requires a significant amount of subsystem initialization that's unneeded for conversion.
// So, to get around this, we swap out EditorEntityContextComponent with SliceConverterEditorEntityContextComponent.
components.erase(
AZStd::remove(
components.begin(), components.end(), azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>()),
components.end());
components.emplace_back(azrtti_typeid<AzToolsFramework::SliceConverterEditorEntityContextComponent>());
return components;
}
} // namespace SerializeContextTools
@@ -30,13 +30,16 @@
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <Application.h>
#include <SliceConverter.h>
#include <SliceConverterEditorEntityContextComponent.h>
#include <Utilities.h>
// SliceConverter reads in a slice file (saved in an ObjectStream format), instantiates it, creates a prefab out of the data,
// and saves the prefab in a JSON format. This can be used for one-time migrations of slices or slice-based levels to prefabs.
//
@@ -99,12 +102,26 @@ namespace AZ
bool result = true;
rapidjson::StringBuffer scratchBuffer;
// For slice conversion, disable the EditorEntityContextComponent logic that activates entities on creation.
// This prevents a lot of error messages and crashes during conversion due to lack of full environment and subsystem setup.
AzToolsFramework::SliceConverterEditorEntityContextComponent::DisableOnContextEntityLogic();
// Loop through the list of requested files and convert them.
AZStd::vector<AZStd::string> fileList = Utilities::ReadFileListFromCommandLine(application, "files");
for (AZStd::string& filePath : fileList)
{
bool convertResult = ConvertSliceFile(convertSettings.m_serializeContext, filePath, isDryRun);
result = result && convertResult;
// Clear out all registered prefab templates between each top-level file that gets processed.
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
for (auto templateId : m_createdTemplateIds)
{
// We don't just want to call RemoveAllTemplates() because the root template should remain between file conversions.
prefabSystemComponentInterface->RemoveTemplate(templateId);
}
m_aliasIdMapper.clear();
m_createdTemplateIds.clear();
}
DisconnectFromAssetProcessor();
@@ -114,6 +131,13 @@ namespace AZ
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.
* If the input file is a level file (.ly), we actually need to load the level slice file ("levelentities.editor_xml") from
* within the level file, which effectively is a zip file of the level slice file and a bunch of legacy level files that won't
* be converted, since the systems that would use them no longer exist.
*/
bool result = true;
bool packOpened = false;
@@ -144,7 +168,7 @@ namespace AZ
AZ_STRING_ARG(fileExtension.Native()));
}
auto callback = [&outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
auto callback = [this, &outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context)
{
if (classId != azrtti_typeid<AZ::Entity>())
{
@@ -178,6 +202,13 @@ namespace AZ
bool SliceConverter::ConvertSliceToPrefab(
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity)
{
/* Given a root slice entity, we convert it to a prefab by doing the following:
* - Locate the SliceComponent
* - Take all the entities directly located on the slice, and put them into a prefab
* - Fix up any top-level entities to have the prefab container entity as their parent
* - If there are any nested slice instances, convert the nested slices to prefabs, then convert the instances.
*/
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
// Find the slice from the root entity.
@@ -192,9 +223,14 @@ namespace AZ
SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities();
AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size());
// Create the Prefab with the entities from the slice
// Create the Prefab with the entities from the slice.
// The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> sourceInstance(
prefabSystemComponentInterface->CreatePrefab(sliceEntities, {}, outputPath));
prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath));
for (auto& entity : sliceEntities)
{
sourceInstance->AddEntity(*entity, AZStd::string::format("Entity_%s", entity->GetId().ToString().c_str()));
}
// Dispatch events here, because prefab creation might trigger asset loads in rare circumstances.
AZ::Data::AssetManager::Instance().DispatchEvents();
@@ -204,12 +240,28 @@ namespace AZ
AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity();
FixPrefabEntities(container->get(), sliceEntities);
// Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure
// the data doesn't stick around between file conversions.
auto templateId = sourceInstance->GetTemplateId();
if (templateId == AzToolsFramework::Prefab::InvalidTemplateId)
{
AZ_Printf("Convert-Slice", " Path error. Path could be invalid, or the prefab may not be loaded in this level.\n");
return false;
}
m_createdTemplateIds.emplace(templateId);
// Save off a mapping of the original slice entity IDs to the new prefab template entity aliases.
// When converting nested slices, this mapping will be needed to fix up the parent entity hierarchy correctly.
auto entityAliases = sourceInstance->GetEntityAliases();
for (auto& alias : entityAliases)
{
auto id = sourceInstance->GetEntityId(alias);
auto result = m_aliasIdMapper.emplace(TemplateEntityIdPair(templateId, id), alias);
if (!result.second)
{
AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n");
}
}
// Update the prefab template with the fixed-up data in our prefab instance.
AzToolsFramework::Prefab::PrefabDom prefabDom;
@@ -254,21 +306,26 @@ namespace AZ
// via an EditorRequests EBus in CreatePrefab, but the subsystem that listens for it isn't present in this tool.)
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, containerEntity);
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
if (containerEntity.FindComponent<AzToolsFramework::Prefab::EditorPrefabComponent>() == nullptr)
{
containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent());
}
// Make all the components on the container entity have deterministic component IDs, so that multiple runs of the tool
// on the same slice will produce the same prefab output. We're going to cheat a bit and just use the component type hash
// as the component ID. This would break if we had multiple components of the same type, but that currently doesn't
// happen for the container entity.
auto containerComponents = containerEntity.GetComponents();
for (auto& component : containerComponents)
{
component->SetId(component->GetUnderlyingComponentType().GetHash());
}
// Reparent any root-level slice entities to the container entity.
for (auto entity : sliceEntities)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
if (!transformComponent->GetParentId().IsValid())
{
transformComponent->SetParent(containerEntity.GetId());
transformComponent->UpdateCachedWorldTransform();
}
}
constexpr bool onlySetIfInvalid = true;
SetParentEntity(*entity, containerEntity.GetId(), onlySetIfInvalid);
}
}
@@ -276,9 +333,13 @@ namespace AZ
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
AZ::SerializeContext* serializeContext, bool isDryRun)
{
/* Given a root slice, find all the nested slices and convert them. */
// Get the list of nested slices that this slice uses.
const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices();
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
// For each nested slice, convert it.
for (auto& slice : sliceList)
{
// Get the nested slice asset
@@ -312,7 +373,7 @@ namespace AZ
return false;
}
// Load the prefab template for the newly-created nested prefab.
// Find the prefab template we created for the newly-created nested prefab.
// To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path.
AZ::IO::Path nestedPrefabPath = assetPath;
nestedPrefabPath.ReplaceExtension("prefab");
@@ -346,11 +407,25 @@ namespace AZ
}
bool SliceConverter::ConvertSliceInstance(
[[maybe_unused]] AZ::SliceComponent::SliceInstance& instance,
[[maybe_unused]] AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
AZ::SliceComponent::SliceInstance& instance,
AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
AzToolsFramework::Prefab::TemplateReference nestedTemplate,
AzToolsFramework::Prefab::Instance* topLevelInstance)
{
/* To convert a slice instance, it's important to understand the similarities and differences between slices and prefabs.
* Both slices and prefabs have the concept of instances of a nested slice/prefab, where each instance can have its own
* set of changed data (transforms, component values, etc).
* For slices, the changed data comes from applying a DataPatch to an instantiated set of entities from the nested slice.
* From prefabs, the changed data comes from Json patches that are applied to the instantiated set of entities from the
* nested prefab. The prefab instance entities also have different IDs than the slice instance entities, so we'll need
* to remap some of them along the way.
* To get from one to the other, we'll need to do the following:
* - Instantiate the nested slice and nested prefab
* - Patch the nested slice instance and fix up the entity ID references
* - Replace the nested prefab instance entities with the fixed-up slice ones
* - Add the nested instance (and the link patch) to the top-level prefab
*/
auto instanceToTemplateInterface = AZ::Interface<AzToolsFramework::Prefab::InstanceToTemplateInterface>::Get();
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
@@ -371,22 +446,83 @@ namespace AZ
AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom;
instanceToTemplateInterface->GenerateDomForInstance(unmodifiedNestedInstanceDom, *(nestedInstance.get()));
// Currently, DataPatch conversions for nested slices aren't implemented, so all nested slice overrides will
// be lost.
AZ_Warning(
"Convert-Slice", false, " Nested slice instances will lose all of their override data during conversion.",
nestedTemplate->get().GetFilePath().c_str());
// Instantiate a new instance of the nested slice
SliceComponent* dependentSlice = sliceAsset.Get()->GetComponent();
[[maybe_unused]] AZ::SliceComponent::InstantiateResult instantiationResult = dependentSlice->Instantiate();
AZ_Assert(instantiationResult == AZ::SliceComponent::InstantiateResult::Success, "Failed to instantiate instance");
// Set the container entity of the nested prefab to have the top-level prefab as the parent.
// Once DataPatch conversions are supported, this will need to change to nest the prefab under the appropriate entity
// within the level.
// Apply the data patch for this instance of the nested slice. This will provide us with a version of the slice's entities
// with all data overrides applied to them.
DataPatch::FlagsMap sourceDataFlags = dependentSlice->GetDataFlagsForInstances().GetDataFlagsForPatching();
DataPatch::FlagsMap targetDataFlags = instance.GetDataFlags().GetDataFlagsForPatching(&instance.GetEntityIdToBaseMap());
AZ::ObjectStream::FilterDescriptor filterDesc(AZ::Data::AssetFilterNoAssetLoading);
AZ::SliceComponent::InstantiatedContainer sourceObjects(false);
dependentSlice->GetEntities(sourceObjects.m_entities);
dependentSlice->GetAllMetadataEntities(sourceObjects.m_metadataEntities);
const DataPatch& dataPatch = instance.GetDataPatch();
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();
AzToolsFramework::Components::TransformComponent* transformComponent =
containerEntity->get().FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
auto containerEntityId = containerEntity->get().GetId();
for (auto entity : instantiated->m_entities)
{
transformComponent->SetParent(topLevelInstance->GetContainerEntityId());
transformComponent->UpdateCachedWorldTransform();
AzToolsFramework::Components::TransformComponent* transformComponent =
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
bool onlySetIfInvalid = true;
auto parentId = transformComponent->GetParentId();
if (parentId.IsValid())
{
auto parentAlias = m_aliasIdMapper.find(TemplateEntityIdPair(topLevelInstance->GetTemplateId(), parentId));
if (parentAlias != 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;
}
}
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.
{
constexpr bool onlySetIfInvalid = true;
SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid);
}
// Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance,
@@ -395,7 +531,22 @@ namespace AZ
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore;
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance);
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance));
// 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());
}
AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance), instanceAlias);
AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter;
instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomAfter, *topLevelInstance);
@@ -418,9 +569,26 @@ namespace AZ
AzToolsFramework::Prefab::InvalidLinkId);
prefabSystemComponentInterface->PropagateTemplateChanges(topLevelInstance->GetTemplateId());
AZ::Interface<AzToolsFramework::Prefab::InstanceUpdateExecutorInterface>::Get()->UpdateTemplateInstancesInQueue();
return true;
}
void SliceConverter::SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
entity.FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
// Only set the parent if we didn't set the onlySetIfInvalid flag, or if we did and the parent is currently invalid
if (!onlySetIfInvalid || !transformComponent->GetParentId().IsValid())
{
transformComponent->SetParent(parentId);
transformComponent->UpdateCachedWorldTransform();
}
}
}
void SliceConverter::PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId)
{
auto prefabSystemComponentInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabSystemComponentInterface>::Get();
@@ -39,24 +39,35 @@ namespace AZ
class SliceConverter : public Converter
{
public:
static bool ConvertSliceFiles(Application& application);
bool ConvertSliceFiles(Application& application);
private:
static bool ConnectToAssetProcessor();
static void DisconnectFromAssetProcessor();
using TemplateEntityIdPair = AZStd::pair<AzToolsFramework::Prefab::TemplateId, AZ::EntityId>;
static bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
static bool ConvertSliceToPrefab(
bool ConnectToAssetProcessor();
void DisconnectFromAssetProcessor();
bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun);
bool ConvertSliceToPrefab(
AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity);
static void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
static bool ConvertNestedSlices(
void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities);
bool ConvertNestedSlices(
SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance,
AZ::SerializeContext* serializeContext, bool isDryRun);
static bool ConvertSliceInstance(
bool ConvertSliceInstance(
AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance);
static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
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);
// 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;
// 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.
AZStd::unordered_set<AzToolsFramework::Prefab::TemplateId> m_createdTemplateIds;
};
} // namespace SerializeContextTools
} // namespace AZ
@@ -0,0 +1,65 @@
/*
* 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 <AzToolsFramework/Entity/EditorEntityContextComponent.h>
namespace AzToolsFramework
{
// This class is an inelegant workaround for use by the Slice Converter to selectively disable entity add/remove logic
// during slice conversion in the EditorEntityContextComponent. Specifically, the standard versions of these methods will
// attempt to activate the entities as they're added. This is both unnecessary and undesirable during slice conversion, since
// entity activation requires a lot of subsystems to be active and valid.
// Instead, by selectively disabling this logic, the entities can remain in an initialized state, which is sufficient for conversion,
// without requiring those extra subsystems.
// This problem also could have been solved by adding APIs to the EditorEntityContextComponent or the EntityContext, but there aren't
// any other known valid use cases for disabling this logic, so the extra APIs would simply encourage "bad behavior" by using them
// when they likely aren't necessary or desired.
class SliceConverterEditorEntityContextComponent
: public EditorEntityContextComponent
{
public:
AZ_COMPONENT(SliceConverterEditorEntityContextComponent, "{1CB0C38F-8E85-4422-91C6-E1F3B9B4B853}");
SliceConverterEditorEntityContextComponent() : EditorEntityContextComponent() {}
// Simple API to selectively disable this logic *only* when performing slice to prefab conversion.
static void DisableOnContextEntityLogic()
{
m_enableOnContextEntityLogic = false;
}
protected:
void OnContextEntitiesAdded([[maybe_unused]] const EntityList& entities) override
{
if (m_enableOnContextEntityLogic)
{
EditorEntityContextComponent::OnContextEntitiesAdded(entities);
}
}
void OnContextEntityRemoved([[maybe_unused]] const AZ::EntityId& id) override
{
if (m_enableOnContextEntityLogic)
{
EditorEntityContextComponent::OnContextEntityRemoved(id);
}
}
// By default, act just like the EditorEntityContextComponent
static inline bool m_enableOnContextEntityLogic = true;
};
} // namespace AzToolsFramework
+2 -1
View File
@@ -125,7 +125,8 @@ int main(int argc, char** argv)
}
else if (AZ::StringFunc::Equal("convert-slice", action.c_str()))
{
result = SliceConverter::ConvertSliceFiles(application);
SliceConverter sliceConverter;
result = sliceConverter.ConvertSliceFiles(application);
}
else
{
@@ -17,6 +17,7 @@ set(FILES
Dumper.h
Dumper.cpp
main.cpp
SliceConverterEditorEntityContextComponent.h
SliceConverter.h
SliceConverter.cpp
Utilities.h
+5 -1
View File
@@ -56,9 +56,13 @@ ly_add_target(
Gem::HttpRequestor
)
# servers and clients use the above module.
# Load the "Gem::AWSClientAuth" module in all types of applications.
ly_create_alias(NAME AWSClientAuth.Servers NAMESPACE Gem TARGETS Gem::AWSClientAuth)
ly_create_alias(NAME AWSClientAuth.Clients NAMESPACE Gem TARGETS Gem::AWSClientAuth)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
ly_create_alias(NAME AWSClientAuth.Tools NAMESPACE Gem TARGETS Gem::AWSClientAuth)
ly_create_alias(NAME AWSClientAuth.Builders NAMESPACE Gem TARGETS Gem::AWSClientAuth)
endif()
################################################################################
# Tests
+2 -4
View File
@@ -79,14 +79,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
INCLUDE_DIRECTORIES
PRIVATE
Include/Private
COMPILE_DEFINITIONS
PRIVATE
AWSCORE_EDITOR
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
Gem::AWSCore.Static
Gem::AWSCore.Editor.Static
RUNTIME_DEPENDENCIES
Gem::AWSCore
)
ly_add_target(
@@ -11,15 +11,15 @@
#pragma once
#include <AWSCoreModule.h>
#include <AzCore/Module/Module.h>
namespace AWSCore
{
class AWSCoreEditorModule
: public AWSCoreModule
:public AZ::Module
{
public:
AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AWSCoreModule);
AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module);
AZ_CLASS_ALLOCATOR(AWSCoreEditorModule, AZ::SystemAllocator, 0);
AWSCoreEditorModule();
@@ -15,7 +15,6 @@
namespace AWSCore
{
AWSCoreEditorModule::AWSCoreEditorModule()
: AWSCoreModule()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
@@ -28,10 +27,9 @@ namespace AWSCore
*/
AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList requiredComponents = AWSCoreModule::GetRequiredSystemComponents();
requiredComponents.push_back(azrtti_typeid<AWSCoreEditorSystemComponent>());
return requiredComponents;
return AZ::ComponentTypeList{
azrtti_typeid<AWSCoreEditorSystemComponent>()
};
}
}
@@ -40,9 +40,7 @@ namespace AWSCore
}
#if !defined(AWSCORE_EDITOR)
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AWSCore, AWSCore::AWSCoreModule)
#endif
@@ -48,7 +48,8 @@ class ConfigurationManager(object):
def configuration(self, new_configuration: ConfigurationManager) -> None:
self._configuration = new_configuration
def setup(self, config_path: str) -> None:
def setup(self, config_path: str) -> bool:
result: bool = True
logger.info("Setting up default configuration ...")
try:
normalized_config_path: str = file_utils.normalize_file_path(config_path);
@@ -63,5 +64,7 @@ class ConfigurationManager(object):
self._configuration.account_id = aws_utils.get_default_account_id()
self._configuration.region = aws_utils.get_default_region()
except (RuntimeError, FileNotFoundError) as e:
logger.exception(e)
logger.error(e)
result = False
logger.debug(self._configuration)
return result
@@ -74,11 +74,18 @@ if __name__ == "__main__":
logger.warning("Failed to load style sheet for resource mapping tool")
logger.info("Initializing boto3 default session ...")
aws_utils.setup_default_session(arguments.profile)
try:
aws_utils.setup_default_session(arguments.profile)
except RuntimeError as error:
logger.error(error)
environment_utils.cleanup_qt_environment()
exit(-1)
logger.info("Initializing configuration manager ...")
configuration_manager: ConfigurationManager = ConfigurationManager()
configuration_manager.setup(arguments.config_path)
if not configuration_manager.setup(arguments.config_path):
environment_utils.cleanup_qt_environment()
exit(-1)
logger.info("Initializing thread manager ...")
thread_manager: ThreadManager = ThreadManager()
File diff suppressed because it is too large Load Diff
@@ -12,10 +12,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
import boto3
from botocore.paginate import (PageIterator, Paginator)
from botocore.client import BaseClient
from botocore.exceptions import ClientError
from botocore.exceptions import (ClientError, ConfigNotFound, NoCredentialsError, ProfileNotFound)
from typing import Dict, List
from model import (constants, error_messages)
from model import error_messages
from model.basic_resource_attributes import (BasicResourceAttributes, BasicResourceAttributesBuilder)
"""
@@ -65,8 +65,11 @@ def _initialize_boto3_aws_client(service: str, region: str = "") -> BaseClient:
def setup_default_session(profile: str) -> None:
global default_session
default_session = boto3.session.Session(profile_name=profile)
try:
global default_session
default_session = boto3.session.Session(profile_name=profile)
except (ConfigNotFound, ProfileNotFound) as error:
raise RuntimeError(error)
def get_default_account_id() -> str:
@@ -76,6 +79,8 @@ def get_default_account_id() -> str:
except ClientError as error:
raise RuntimeError(error_messages.AWS_SERVICE_REQUEST_CLIENT_ERROR_MESSAGE.format(
"get_caller_identity", error.response['Error']['Code'], error.response['Error']['Message']))
except NoCredentialsError as error:
raise RuntimeError(error)
def get_default_region() -> str:
@@ -11,7 +11,5 @@
set(FILES
Include/Private/AWSCoreEditorModule.h
Include/Private/AWSCoreModule.h
Source/AWSCoreEditorModule.cpp
Source/AWSCoreModule.cpp
)
+5 -1
View File
@@ -46,9 +46,13 @@ ly_add_target(
Gem::AWSCore
)
# Servers and Clients use the above metrics module
# Load the "Gem::AWSMetrics" module in all types of applications.
ly_create_alias(NAME AWSMetrics.Servers NAMESPACE Gem TARGETS Gem::AWSMetrics)
ly_create_alias(NAME AWSMetrics.Clients NAMESPACE Gem TARGETS Gem::AWSMetrics)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
ly_create_alias(NAME AWSMetrics.Tools NAMESPACE Gem TARGETS Gem::AWSMetrics)
ly_create_alias(NAME AWSMetrics.Builders NAMESPACE Gem TARGETS Gem::AWSMetrics)
endif()
################################################################################
# Tests
@@ -88,7 +88,7 @@ namespace ImageProcessingAtom
int dstPosition;
signed short int n;
bool trimZeros = true, stillzero;
int lastnonzero, hWeight, highest;
int lastnonzero = 0, hWeight, highest = 0;
signed int sumiWeights, iWeight;
signed short int* weightsPtr;
signed short int* weightsMem;
@@ -1106,7 +1106,7 @@ namespace ImageProcessingAtom
//fractional amount to apply change in tap intensity along edge to taps
// in a perpendicular direction to edge
CP_ITYPE fixupFrac = (CP_ITYPE)(fixupDist - iFixup) / (CP_ITYPE)(fixupDist);
CP_ITYPE fixupWeight;
CP_ITYPE fixupWeight = 0.0f;
switch(a_FixupType )
{
@@ -37,7 +37,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial
}
#include <Atom/Features/PBR/DefaultObjectSrg.azsli>
#include <Atom/Features/PBR/TransparentPassSrg.azsli>
#include <Atom/Features/PBR/ForwardPassSrg.azsli>
#include <Atom/Features/Shadow/DirectionalLightShadow.azsli>
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
@@ -124,7 +124,7 @@
"DrawListSortType": "KeyThenReverseDepth",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg"
"FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg"
}
}
}
@@ -35,4 +35,5 @@ ShaderResourceGroup PassSrg : SRG_PerPass
Texture2D<uint4> m_tileLightData;
StructuredBuffer<uint> m_lightListRemapped;
Texture2D<float> m_linearDepthTexture;
}
@@ -1,39 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <Atom/Features/SrgSemantics.azsli>
ShaderResourceGroup PassSrg : SRG_PerPass
{
// [GFX TODO][ATOM-2012] adapt to multiple shadowmaps
Texture2DArray<float> m_directionalLightShadowmap;
Texture2DArray<float> m_directionalLightExponentialShadowmap;
Texture2DArray<float> m_projectedShadowmaps;
Texture2DArray<float> m_projectedExponentialShadowmap;
Texture2D m_brdfMap;
Sampler LinearSampler
{
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
AddressW = Clamp;
};
Texture2D<uint4> m_tileLightData;
StructuredBuffer<uint> m_lightListRemapped;
Texture2D<float> m_linearDepthTexture;
}
@@ -246,7 +246,6 @@ set(FILES
ShaderLib/Atom/Features/PBR/Hammersley.azsli
ShaderLib/Atom/Features/PBR/LightingOptions.azsli
ShaderLib/Atom/Features/PBR/LightingUtils.azsli
ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli
ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli
ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli
ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli
@@ -46,8 +46,6 @@ namespace AZ
uint16_t m_padding; // Explicit padding.
};
static constexpr size_t size = sizeof(DiskLightData);
//! DiskLightFeatureProcessorInterface provides an interface to acquire, release, and update a disk light. This is necessary for code outside of
//! the Atom features gem to communicate with the DiskLightFeatureProcessor.
class DiskLightFeatureProcessorInterface
@@ -484,6 +484,7 @@ namespace AZ
}
D3D12_RESOURCE_TRANSITION_BARRIER transition;
memset(&transition, 0, sizeof(D3D12_RESOURCE_TRANSITION_BARRIER)); // C4701 potentially unitialized local variable 'transition' used
transition.pResource = image.GetMemoryView().GetMemory();
Scope& firstScope = static_cast<Scope&>(scopeAttachment->GetScope());
@@ -45,7 +45,7 @@ namespace AZ
Fence* fenceToSignal)
{
AZStd::vector<VkCommandBuffer> vkCommandBuffers;
AZStd::vector<VkSemaphore> vkWaitSemaphores;
AZStd::vector<VkSemaphore> vkWaitSemaphoreVector; // vulkan.h has a #define called vkWaitSemaphores, so we name this differently
AZStd::vector<VkPipelineStageFlags> vkWaitPipelineStages;
AZStd::vector<VkSemaphore> vkSignalSemaphores;
VkSubmitInfo submitInfo;
@@ -65,11 +65,11 @@ namespace AZ
return item->GetNativeSemaphore();
});
vkWaitPipelineStages.reserve(waitSemaphoresInfo.size());
vkWaitSemaphores.reserve(waitSemaphoresInfo.size());
vkWaitSemaphoreVector.reserve(waitSemaphoresInfo.size());
AZStd::for_each(waitSemaphoresInfo.begin(), waitSemaphoresInfo.end(), [&](auto& item)
{
vkWaitPipelineStages.push_back(item.first);
vkWaitSemaphores.push_back(item.second->GetNativeSemaphore());
vkWaitSemaphoreVector.push_back(item.second->GetNativeSemaphore());
// Wait until the wait semaphores has been submitted for signaling.
item.second->WaitEvent();
});
@@ -77,8 +77,8 @@ namespace AZ
submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.pNext = nullptr;
submitInfo.waitSemaphoreCount = static_cast<uint32_t>(vkWaitSemaphores.size());
submitInfo.pWaitSemaphores = vkWaitSemaphores.empty() ? nullptr : vkWaitSemaphores.data();
submitInfo.waitSemaphoreCount = static_cast<uint32_t>(vkWaitSemaphoreVector.size());
submitInfo.pWaitSemaphores = vkWaitSemaphoreVector.empty() ? nullptr : vkWaitSemaphoreVector.data();
submitInfo.pWaitDstStageMask = vkWaitPipelineStages.empty() ? nullptr : vkWaitPipelineStages.data();
submitInfo.commandBufferCount = static_cast<uint32_t>(vkCommandBuffers.size());
submitInfo.pCommandBuffers = vkCommandBuffers.empty() ? nullptr : vkCommandBuffers.data();
+2
View File
@@ -10,3 +10,5 @@
#
add_subdirectory(Code)
add_subdirectory(Tools)
@@ -43,7 +43,7 @@ namespace AZ
{
PrimitiveType = AZ_BIT(0),
DepthState = AZ_BIT(1),
EnableStencil = AZ_BIT(2),
StencilState = AZ_BIT(2),
FaceCullMode = AZ_BIT(3),
BlendMode = AZ_BIT(4)
};
@@ -110,8 +110,8 @@ namespace AZ
//! Set DepthState if DrawStateOptions::DepthState option is enabled
void SetDepthState(RHI::DepthState depthState);
//! Enable/disable stencil if DrawStateOptions::EnableStencil option is enabled
void SetEnableStencil(bool enable);
//! Set StencilState if DrawStateOptions::StencilState option is enabled
void SetStencilState(RHI::StencilState stencilState);
//! Set CullMode if DrawStateOptions::FaceCullMode option is enabled
void SetCullMode(RHI::CullMode cullMode);
//! Set TargetBlendState for target 0 if DrawStateOptions::BlendMode option is enabled
@@ -188,7 +188,7 @@ namespace AZ
// states available for change
RHI::CullMode m_cullMode;
RHI::DepthState m_depthState;
bool m_enableStencil;
RHI::StencilState m_stencilState;
RHI::PrimitiveTopology m_topology;
RHI::TargetBlendState m_blendState0;
@@ -239,16 +239,26 @@ namespace AZ
void CullingScene::RegisterOrUpdateCullable(Cullable& cullable)
{
m_cullDataConcurrencyCheck.soft_lock();
// Multiple threads can call RegisterOrUpdateCullable at the same time
// since the underlying visScene is thread safe, but if you're inserting or
// updating between BeginCulling and EndCulling, you'll get non-deterministic
// results depending on a race condition if you happen to update before or after
// the culling system starts Enumerating, so use soft_lock_shared here
m_cullDataConcurrencyCheck.soft_lock_shared();
m_visScene->InsertOrUpdateEntry(cullable.m_cullData.m_visibilityEntry);
m_cullDataConcurrencyCheck.soft_unlock();
m_cullDataConcurrencyCheck.soft_unlock_shared();
}
void CullingScene::UnregisterCullable(Cullable& cullable)
{
m_cullDataConcurrencyCheck.soft_lock();
// Multiple threads can call RegisterOrUpdateCullable at the same time
// since the underlying visScene is thread safe, but if you're inserting or
// updating between BeginCulling and EndCulling, you'll get non-deterministic
// results depending on a race condition if you happen to update before or after
// the culling system starts Enumerating, so use soft_lock_shared here
m_cullDataConcurrencyCheck.soft_lock_shared();
m_visScene->RemoveEntry(cullable.m_cullData.m_visibilityEntry);
m_cullDataConcurrencyCheck.soft_unlock();
m_cullDataConcurrencyCheck.soft_unlock_shared();
}
uint32_t CullingScene::GetNumCullables() const
@@ -30,25 +30,7 @@ namespace AZ
constexpr const char* PerContextSrgName = "PerContextSrg";
constexpr const char* PerDrawSrgName = "PerDrawSrg";
};
bool CompareTargetBlendState(const RHI::TargetBlendState& firstState, const RHI::TargetBlendState& secondState)
{
return !(firstState.m_enable != secondState.m_enable
|| firstState.m_blendOp != secondState.m_blendOp
|| firstState.m_blendDest != secondState.m_blendDest
|| firstState.m_blendSource != secondState.m_blendSource
|| firstState.m_blendAlphaDest != secondState.m_blendAlphaDest
|| firstState.m_blendAlphaOp != secondState.m_blendAlphaOp
|| firstState.m_blendAlphaSource != secondState.m_blendAlphaSource);
}
bool CompareDepthState(const RHI::DepthState& firstState, const RHI::DepthState& secondState)
{
return !(firstState.m_enable != secondState.m_enable
|| firstState.m_func != secondState.m_func
|| firstState.m_writeMask != secondState.m_writeMask);
}
void DynamicDrawContext::MultiStates::UpdateHash(const DrawStateOptions& drawStateOptions)
{
if (!m_isDirty)
@@ -70,9 +52,19 @@ namespace AZ
seed = TypeHash64(m_depthState.m_writeMask, seed);
}
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::EnableStencil))
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::StencilState))
{
seed = TypeHash64(m_enableStencil, seed);
seed = TypeHash64(m_stencilState.m_enable, seed);
seed = TypeHash64(m_stencilState.m_readMask, seed);
seed = TypeHash64(m_stencilState.m_writeMask, seed);
seed = TypeHash64(m_stencilState.m_frontFace.m_failOp, seed);
seed = TypeHash64(m_stencilState.m_frontFace.m_depthFailOp, seed);
seed = TypeHash64(m_stencilState.m_frontFace.m_passOp, seed);
seed = TypeHash64(m_stencilState.m_frontFace.m_func, seed);
seed = TypeHash64(m_stencilState.m_backFace.m_failOp, seed);
seed = TypeHash64(m_stencilState.m_backFace.m_depthFailOp, seed);
seed = TypeHash64(m_stencilState.m_backFace.m_passOp, seed);
seed = TypeHash64(m_stencilState.m_backFace.m_func, seed);
}
if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::FaceCullMode))
@@ -203,7 +195,7 @@ namespace AZ
m_currentStates.m_cullMode = m_pipelineState->ConstDescriptor().m_renderStates.m_rasterState.m_cullMode;
m_currentStates.m_topology = m_pipelineState->ConstDescriptor().m_inputStreamLayout.GetTopology();
m_currentStates.m_depthState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_depth;
m_currentStates.m_enableStencil = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil.m_enable;
m_currentStates.m_stencilState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil;
m_currentStates.m_blendState0 = m_pipelineState->ConstDescriptor().m_renderStates.m_blendState.m_targets[0];
m_currentStates.UpdateHash(m_drawStateOptions);
@@ -291,7 +283,7 @@ namespace AZ
{
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::DepthState))
{
if (!CompareDepthState(m_currentStates.m_depthState, depthState))
if (!(m_currentStates.m_depthState == depthState))
{
m_currentStates.m_depthState = depthState;
m_currentStates.m_isDirty = true;
@@ -303,19 +295,19 @@ namespace AZ
}
}
void DynamicDrawContext::SetEnableStencil(bool enable)
void DynamicDrawContext::SetStencilState(RHI::StencilState stencilState)
{
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil))
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState))
{
if (m_currentStates.m_enableStencil != enable)
if (!(m_currentStates.m_stencilState == stencilState))
{
m_currentStates.m_enableStencil = enable;
m_currentStates.m_stencilState = stencilState;
m_currentStates.m_isDirty = true;
}
}
else
{
AZ_Warning("RHI", false, "Can't set SetEnableStencil if DrawVariation::EnableStencil wasn't enabled");
AZ_Warning("RHI", false, "Can't set SetStencilState if DrawVariation::StencilState wasn't enabled");
}
}
@@ -340,7 +332,7 @@ namespace AZ
{
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::BlendMode))
{
if (!CompareTargetBlendState(m_currentStates.m_blendState0, blendState))
if (!(m_currentStates.m_blendState0 == blendState))
{
m_currentStates.m_blendState0 = blendState;
m_currentStates.m_isDirty = true;
@@ -695,9 +687,9 @@ namespace AZ
{
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_depth = m_currentStates.m_depthState;
}
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil))
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState))
{
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil.m_enable = m_currentStates.m_enableStencil;
m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil = m_currentStates.m_stencilState;
}
if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::FaceCullMode))
{
@@ -10,6 +10,7 @@
*
*/
#include <Atom/RPI.Reflect/Shader/ShaderAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -34,10 +35,6 @@ namespace AZ
uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType)
{
static constexpr uint32_t RhiIndexBitPosition = 30;
static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition;
static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1;
static constexpr uint32_t SubProductTypeBitPosition = 0;
static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
@@ -10,6 +10,7 @@
*
*/
#include <Atom/RPI.Reflect/Shader/ShaderVariantAsset.h>
#include <Atom/RPI.Reflect/Shader/ShaderCommonTypes.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -24,10 +25,6 @@ namespace AZ
uint32_t ShaderVariantAsset::MakeAssetProductSubId(
uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType)
{
static constexpr uint32_t RhiIndexBitPosition = 30;
static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition;
static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1;
static constexpr uint32_t SubProductTypeBitPosition = 17;
static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition;
static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1;
@@ -9,14 +9,15 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
ly_download_associated_package(pyside2)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools)
ly_add_pytest(
NAME test_pyside
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py
)
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_pytest(
NAME RPI::atom_rpi_tools_tests
PATH ${CMAKE_CURRENT_LIST_DIR}/atom_rpi_tools/tests/
TIMEOUT 30
)
endif()
endif()
ly_add_pytest(
NAME test_projects
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py
)
+39
View File
@@ -0,0 +1,39 @@
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.
INTRODUCTION
------------
atom_rpi_tools is a Python project that contains a collection of tools
developed by the Atom team. The project contains the following tools:
* Render pipeline merge tool:
A library to manipulate .pass asset files and help gems create scripts to update render pipeline
REQUIREMENTS
------------
* Python 3.7.5 (64-bit)
It is recommended that you completely remove any other versions of Python
installed on your system.
INSTALL
-----------
It is recommended to set up these these tools with Lumberyard's CMake build commands.
UNINSTALLATION
--------------
The preferred way to uninstall the project is:
(engine install root)/python/python -m pip uninstall atom_rpi_tools
+10
View File
@@ -0,0 +1,10 @@
"""
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.
"""
@@ -0,0 +1,210 @@
"""
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.
"""
import sys, os
import json
import shutil
class PassTemplate:
# This class provide necessary functions for insert pass requests and update connections
# which are common functions required for adding features.
# It doesn't include the remove/delete furnctions since that's not common case for merging render pipeline
def __init__(self, filePath: str):
self.initialized = False
self.file_path: str = filePath
#load the json file
json_data = open(filePath, "r")
self.file_data = json.load(json_data)
if 'ClassName' not in self.file_data or 'ClassData' not in self.file_data or self.file_data['ClassName']!='PassAsset' or 'PassTemplate' not in self.file_data['ClassData']:
raise KeyError('the json file is not a PassAsset file')
return
if 'PassRequests' in self.file_data['ClassData']['PassTemplate']:
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
if 'Slots' in self.file_data['ClassData']['PassTemplate']:
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
self.initialized = True
print('PassTemplate is loaded from ', filePath)
def find_pass(self, passName):
# return pass's index in PassRequests if a PassRequest with input passName exists
if not hasattr(self, 'passRequests'):
return -1
index = 0
for passRequest in self.passRequests:
if passRequest['Name'] == passName:
return index
index += 1
return -1
def get_pass_count(self):
if not hasattr(self, 'passRequests'):
return 0
return len(self.passRequests)
def __validate_pass_request_data(self, passRequest):
if ('Name' not in passRequest or 'TemplateName' not in passRequest):
raise KeyError('invalid pass request data')
def __ensure_pass_requests_key(self):
if not hasattr(self, 'passRequests'):
self.file_data['ClassData']['PassTemplate']['PassRequests'] = []
self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests']
def __ensure_pass_slots_key(self):
if not hasattr(self, 'slots'):
self.file_data['ClassData']['PassTemplate']['Slots'] = []
self.slots = self.file_data['ClassData']['PassTemplate']['Slots']
def insert_pass_request(self, location, passRequest):
self.__validate_pass_request_data(passRequest)
if (self.find_pass(passRequest['Name']) >= 0):
raise ValueError('pass request ', passRequest['Name'], ' is already exist')
# insert a passRequest before the specified location
self.__ensure_pass_requests_key()
self.passRequests.insert(location, passRequest)
def replace_references_after(self, startPassRequest, oldPass, oldSlot, newPass, newSlot):
if not hasattr(self, 'passRequests'):
return 0
# from all pass requests after startPassRequest
# replace all attachment references which uses oldPass and oldSlot
# with newPass and newSlot
started = False
replaced_count = 0
for request in self.passRequests:
if started:
if ('Connections' in request):
for connection in request['Connections']:
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
connection['AttachmentRef']['Pass'] = newPass
connection['AttachmentRef']['Attachment'] = newSlot
replaced_count += 1
if request['Name'] == startPassRequest and not started:
started = True
return replaced_count
def replace_references_for(self, passRequest, oldPass, oldSlot, newPass, newSlot):
if not hasattr(self, 'passRequests'):
return 0
#replace pass reference for the specified passRequest
replaced_count = 0
for request in self.passRequests:
if request['Name'] == passRequest:
if ('Connections' in request):
for connection in request['Connections']:
if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot:
connection['AttachmentRef']['Pass'] = newPass
connection['AttachmentRef']['Attachment'] = newSlot
replaced_count += 1
return replaced_count #return when the specified pass request is updated.
return replaced_count
def __validate_slot_data(self, slotData):
if ('Name' not in slotData or 'SlotType' not in slotData):
raise KeyError('invalid slot data')
def get_slot_count(self):
if not hasattr(self, 'slots'):
return 0
return len(self.slots)
def find_slot(self, slotName):
# return slot's index in Slots if a PassRequest with input passName exists
if not hasattr(self, 'slots'):
return -1
index = 0
for slot in self.slots:
if slot['Name'] == slotName:
return index
index += 1
return -1
def insert_slot(self, location, newSlotData):
# insert a new slot at specified location
self.__validate_slot_data(newSlotData)
# check if the slot already exist
if (self.find_slot(newSlotData['Name']) >= 0):
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
self.__ensure_pass_slots_key()
self.slots.insert(location, newSlotData)
def add_slot(self, newSlotData):
# append a new slot to slots
self.__validate_slot_data(newSlotData)
# check if the slot already exist
if (self.find_slot(newSlotData['Name']) >= 0):
raise ValueError('Slot ', newSlotData['Name'], ' is already exist')
self.__ensure_pass_slots_key()
self.slots.append(newSlotData)
def get_pass_request(self, passName):
if not hasattr(self, 'passRequests'):
return
# Get the pass request from PassRequests with matching pass name
for passRequest in self.passRequests:
if passRequest['Name'] == passName:
return passRequest
def save(self):
# backup the original file
backupFilePath = self.file_path +'.backup'
shutil.copyfile(self.file_path, backupFilePath)
# save and overwrite file
with open(self.file_path, 'w') as json_file:
json.dump(self.file_data, json_file, indent = 4)
print('File [', self.file_path, '] is updated. Old version is saved in [', backupFilePath, ']')
class PassRequest:
def __init__(self, passRequest: object):
self.pass_request = passRequest
if 'Connections' in passRequest:
self.connections = passRequest['Connections']
def __validate_connection(self, connection):
if ('LocalSlot' not in connection or 'AttachmentRef' not in connection):
raise KeyError('invalid connection data')
def __ensure_connections_key(self):
if not hasattr(self, 'connections'):
self.pass_request['Connections'] = []
self.connections = self.pass_request['Connections']
def get_connection_count(self):
if not hasattr(self, 'connections'):
return 0
return len(self.connections)
def find_connection(self, localSlotName):
if not hasattr(self, 'connections'):
return -1
index = 0
for connection in self.connections:
if connection['LocalSlot'] == localSlotName:
return index
index += 1
return -1
def add_connection(self, newConnection):
self.__validate_connection(newConnection)
if self.find_connection(newConnection['LocalSlot']) >= 0:
raise ValueError('connection ', newConnection['LocalSlot'], ' already exists')
self.__ensure_connections_key()
self.connections.append(newConnection)
@@ -0,0 +1,10 @@
"""
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.
"""
@@ -0,0 +1,303 @@
"""
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.
Unit tests for pass_data.py
"""
import os
import pytest
import shutil
import json
from atom_rpi_tools.pass_data import PassTemplate
from atom_rpi_tools.pass_data import PassRequest
good_pass_requests_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_requests.json')
good_pass_slots_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_slots.json')
bad_test_data_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_test_bad.json')
@pytest.fixture
def pass_requests_template(tmpdir):
filename = 'pass_requests.json'
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
shutil.copyfile(source_path, destFilePath)
return PassTemplate(destFilePath)
@pytest.fixture
def pass_slots_template(tmpdir):
filename = 'pass_slots.json'
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
destFilePath = os.path.join(tmpdir, 'pass_requests.json')
shutil.copyfile(source_path, destFilePath)
return PassTemplate(destFilePath)
@pytest.fixture
def new_pass_request():
pass_request = json.loads('{\"Name\": \"InsertPass\",\"TemplateName\": \"InsertPassTemplate\"}')
return pass_request
@pytest.fixture
def new_slot():
slot = json.loads('{\"Name\": \"NewSlot\",\"SlotType\": \"Input\"}')
return slot
@pytest.fixture
def new_connection():
connection = json.loads('{\"LocalSlot\": \"color\", \"AttachmentRef\": { \"Pass\": \"Parent\", \"Attachment\": \"DepthStencil\"}}')
return connection
def test_PassTemplate_Initialize_BadPassTemplateData_ExceptionThrown():
with pytest.raises(KeyError):
PassTemplate(bad_test_data_file)
def test_PassTemplate_FindPass_Success(pass_requests_template):
assert pass_requests_template.find_pass('OpaquePass') == 0
assert pass_requests_template.find_pass('ImGuiPass') == 4
assert pass_requests_template.find_pass('NotExistPass') == -1
def test_PassTemplate_InsertPassRequest_AtBegining_Success(pass_requests_template, new_pass_request):
template = pass_requests_template
pass_count = template.get_pass_count()
template.insert_pass_request(0, new_pass_request)
assert template.find_pass(new_pass_request['Name']) == 0
assert template.get_pass_count() == pass_count+1
# verify the change is saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.find_pass(new_pass_request['Name'])== 0
assert saved_tamplate.get_pass_count() == pass_count+1
def test_PassTemplate_InsertPassRequest_AtEnd_Success(pass_requests_template, new_pass_request):
template = pass_requests_template
pass_count = template.get_pass_count()
template.insert_pass_request(pass_count, new_pass_request)
assert template.find_pass(new_pass_request['Name']) == pass_count
assert template.get_pass_count() == pass_count+1
# verify the change is saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.find_pass(new_pass_request['Name']) == pass_count
assert saved_tamplate.get_pass_count() == pass_count+1
def test_PassTemplate_InsertPassRequest_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_pass_request):
template = pass_requests_template
# insert new pass request
template.insert_pass_request(0, new_pass_request)
pass_count = template.get_pass_count()
# exception when insert the same pass again
with pytest.raises(ValueError):
template.insert_pass_request(2, new_pass_request)
# pass count doesn't change
assert template.get_pass_count() == pass_count
def test_PassTemplate_InsertPassRequest_WithBadData_ExceptionThrown(pass_requests_template):
template = pass_requests_template
pass_count = template.get_pass_count()
bad_pass_request = json.loads('{\"name\":\"value\"}')
with pytest.raises(KeyError):
template.insert_pass_request(2, bad_pass_request)
assert template.get_pass_count() == pass_count
def test_PassTemplate_InsertPassRequest_AtOutOfRange_AppendSuccess(pass_requests_template, new_pass_request):
template = pass_requests_template
pass_count = template.get_pass_count()
template.insert_pass_request(pass_count+2, new_pass_request)
assert template.find_pass(new_pass_request['Name']) == pass_count
assert template.get_pass_count() == pass_count+1
def test_PassTemplate_ReplaceReferencesAfter_Success(pass_requests_template):
# replace OpaquePass.DepthStencil with Parent.DepthStencil'
refPass = 'OpaquePass'
# there are 2 passes after OpaquePass which use OpaquePass.DepthStencil as attachment reference
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 2
# after the previous replacement, there it no OpaquePass.DepthStencil reference
refPass = 'TransparentPass'
assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
# verify changes are saved
pass_requests_template.save()
saved_tamplate = PassTemplate(pass_requests_template.file_path)
assert saved_tamplate.replace_references_after('OpaquePass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
def test_PassTemplate_ReplaceReferencesFor_Success(pass_requests_template):
refPass = 'TransparentPass'
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 1
refPass = '2DPass'
assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
# verify changes are saved
pass_requests_template.save()
saved_tamplate = PassTemplate(pass_requests_template.file_path)
# no reference of OpaquePass.DepthStencil in TransparentPass
assert saved_tamplate.replace_references_for('TransparentPass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0
def test_PassTemplate_FindSlot_Success(pass_slots_template):
assert pass_slots_template.find_slot('Color') == -1
assert pass_slots_template.find_slot('DepthStencil') == 0
assert pass_slots_template.find_slot('ColorInputOutput') == 1
def test_PassTemplate_InsertSlot_AtBegining_Success(pass_slots_template, new_slot):
template = pass_slots_template
slot_count = template.get_slot_count()
depth_stencil_slot = template.find_slot('DepthStencil')
template.insert_slot(0, new_slot)
assert template.find_slot(new_slot['Name']) == 0
assert template.find_slot('DepthStencil') == depth_stencil_slot+1 # DepthStencil moved back by 1
assert template.get_slot_count() == slot_count+1
# verify the change is saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.find_slot(new_slot['Name']) == 0
assert saved_tamplate.get_slot_count() == slot_count+1
def test_PassTemplate_InsertSlot_AtEnd_Success(pass_slots_template, new_slot):
template = pass_slots_template
slot_count = template.get_slot_count()
depth_stencil_slot = template.find_slot('DepthStencil')
template.insert_slot(slot_count, new_slot)
assert template.find_slot(new_slot['Name']) == slot_count
assert template.find_slot('DepthStencil') == depth_stencil_slot
assert template.get_slot_count() == slot_count+1
# verify the change is saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
assert saved_tamplate.get_slot_count() == slot_count+1
def test_PassTemplate_AddSlot_GoodSlotData_Success(pass_slots_template, new_slot):
template = pass_slots_template
slot_count = template.get_slot_count()
depth_stencil_slot = template.find_slot('DepthStencil')
template.add_slot(new_slot)
assert template.find_slot(new_slot['Name']) == slot_count
assert template.find_slot('DepthStencil') == depth_stencil_slot
assert template.get_slot_count() == slot_count+1
# verify the change is saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.find_slot(new_slot['Name']) == slot_count
assert saved_tamplate.get_slot_count() == slot_count+1
def test_PassTemplate_InsertSlot_OutOfRange_AppendSuccess(pass_slots_template, new_slot):
template = pass_slots_template
slot_count = template.get_slot_count()
template.insert_slot(slot_count+3, new_slot)
assert template.find_slot(new_slot['Name']) == slot_count
assert template.get_slot_count() == slot_count+1
def test_PassTemplate_AddDuplicateSlot_ExceptionThrown(pass_slots_template, new_slot):
template = pass_slots_template
slot_count = template.get_slot_count()
template.add_slot(new_slot)
with pytest.raises(ValueError):
template.insert_slot(0, new_slot)
with pytest.raises(ValueError):
template.add_slot(new_slot)
def test_PassTemplate_InsertOrAddSlot_WithBadSlotData_ExceptionThrown(pass_slots_template):
template = pass_slots_template
slot_count = template.get_slot_count()
bad_slot = json.loads('{\"slot\": \"xxx\"}')
with pytest.raises(KeyError):
template.insert_slot(0, bad_slot)
with pytest.raises(KeyError):
template.add_slot(bad_slot)
def test_PassReqeuest_Initialize_WithExistPassReqeuestFromPassTemplate_Success(pass_requests_template):
template = pass_requests_template
request = PassRequest(template.get_pass_request('OpaquePass'))
connection_count = request.get_connection_count()
assert connection_count == 2
def test_PassTemplate_GetPassRequest_NotExist_ReturnNull(pass_requests_template):
assert not pass_requests_template.get_pass_request('NotExistPass')
def test_PassReqeuest_AddConnection_WithExistingConnections_Success(pass_requests_template, new_connection):
template = pass_requests_template
request = PassRequest(template.get_pass_request('OpaquePass'))
connection_count = request.get_connection_count()
request.add_connection(new_connection)
connection_count += 1
assert request.get_connection_count() == connection_count
# verify changes are saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
saved_request = PassRequest(saved_tamplate.get_pass_request('OpaquePass'))
assert saved_request.get_connection_count() == connection_count
def test_PassReqeuest_AddConnection_WithNoExistingConnections_Success(pass_requests_template, new_connection):
template = pass_requests_template
request = PassRequest(template.get_pass_request('ImGuiPass'))
assert request.get_connection_count() == 0
request.add_connection(new_connection)
assert request.get_connection_count() == 1
# verify changes are saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
saved_request = PassRequest(saved_tamplate.get_pass_request('ImGuiPass'))
assert saved_request.get_connection_count() == 1
def test_PassReqeuest_AddConnection_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_connection):
template = pass_requests_template
request = PassRequest(template.get_pass_request('OpaquePass'))
request.add_connection(new_connection)
with pytest.raises(ValueError):
request.add_connection(new_connection)
def test_PassReqeuest_AddConnect_BadConnectionData_ExceptionThrown(pass_requests_template, new_connection):
template = pass_requests_template
request = PassRequest(template.get_pass_request('OpaquePass'))
bad_connection = json.loads('{\"xxx\": \"xxx\"}')
with pytest.raises(KeyError):
request.add_connection(bad_connection)
def test_PassTemplate_InsertSlot_ToEmptyList_Success(pass_requests_template, new_slot):
template = pass_requests_template
# test insert slot function to pass template which doesn't have any slots
slot_count = template.get_slot_count()
assert slot_count == 0
assert template.find_slot(new_slot['Name'])==-1
pass_requests_template.insert_slot(0, new_slot)
assert template.find_slot(new_slot['Name']) == 0
assert template.get_slot_count() == 1
# verify changes are saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.get_slot_count() == 1
def test_PassTempalte_InsertPassRequest_ToEmptyList_Success(pass_slots_template, new_pass_request):
template = pass_slots_template
# test insert pass function to pass template which doesn't have any pass requests
pass_count = template.get_pass_count()
assert pass_count == 0
template.insert_pass_request(0, new_pass_request)
assert template.find_pass(new_pass_request['Name']) == 0
assert template.get_pass_count() == 1
# verify changes are saved
template.save()
saved_tamplate = PassTemplate(template.file_path)
assert saved_tamplate.get_pass_count() == 1
def test_PassTemplate_Save_Success(pass_requests_template):
pass_requests_template.save()
saved_tamplate = PassTemplate(pass_requests_template.file_path)
assert os.path.exists(pass_requests_template.file_path)
assert os.path.exists(pass_requests_template.file_path +'.backup')
@@ -0,0 +1,53 @@
"""
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.
Unit tests for utils.py
"""
import pytest
import os
import atom_rpi_tools.utils as utils
def test_FindOrCopyFile_DestFileNotExist_CopySuccess(tmpdir):
# created dir and copied
filename = 'pass_requests.json'
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
assert not os.path.exists(dest_path)
utils.find_or_copy_file(dest_path, source_path)
assert os.path.exists(dest_path)
source_size = os.path.getsize(source_path)
dest_size = os.path.getsize(dest_path)
assert source_size == dest_size
def test_FindOrCopyFile_DestFileAlreadyExists_Skip(tmpdir):
# copy %cur_dir%/testdata/pass_requests.json to tempdir/testdata/pass_requests.json
filename = 'pass_requests.json'
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename)
dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json')
utils.find_or_copy_file(dest_path, source_path)
# skip if dest_path already exists
assert os.path.exists(dest_path)
before_size = os.path.getsize(dest_path)
source_path = os.path.join(os.path.dirname(__file__), 'testdata/', 'pass_slots.json')
before_source_size = os.path.getsize(source_path)
assert before_size != source_path
utils.find_or_copy_file(dest_path, source_path)
after_size = os.path.getsize(dest_path)
assert before_size == after_size
def test_FindOrCopyFile_SourceFileNotExists_ExceptionThrown(tmpdir):
# report error if source doesn't exist
bad_source_path = 'notexist.dat'
dest_path = os.path.join(tmpdir, 'notexist.dat')
with pytest.raises(ValueError):
utils.find_or_copy_file(dest_path, bad_source_path)
@@ -0,0 +1,116 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "PipelineTemplate",
"PassClass": "ParentPass",
"PassRequests": [
{
"Name": "OpaquePass",
"TemplateName": "OpaquePassTemplate",
"Connections": [
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "DepthStencil"
}
},
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "ColorInputOutput"
}
}
]
},
{
"Name": "TransparentPass",
"TemplateName": "TransparentPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "OpaquePass",
"Attachment": "DepthStencil"
}
},
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "OpaquePass",
"Attachment": "Color"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "transparent",
"DrawListSortType": "KeyThenReverseDepth",
"PipelineViewTag": "MainCamera",
"PassSrgAsset": {
"FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg"
}
}
},
{
"Name": "AuxGeomPass",
"TemplateName": "AuxGeomPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "DepthStencil",
"AttachmentRef": {
"Pass": "OpaquePass",
"Attachment": "DepthStencil"
}
},
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "TransparentPass",
"Attachment": "ColorInputOutput"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "auxgeom",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "2DPass",
"TemplateName": "UIPassTemplate",
"Enabled": true,
"Connections": [
{
"LocalSlot": "ColorInputOutput",
"AttachmentRef": {
"Pass": "TransparentPass",
"Attachment": "ColorInputOutput"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "2dpass",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "ImGuiPass",
"TemplateName": "ImGuiPassTemplate",
"PassData": {
"$type": "ImGuiPassData",
"IsDefaultImGui": true
}
}
]
}
}
}
@@ -0,0 +1,21 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "PipelineTemplate",
"PassClass": "ParentPass",
"Slots": [
{
"Name": "DepthStencil",
"SlotType": "InputOutput"
},
{
"Name": "ColorInputOutput",
"SlotType": "InputOutput"
}
]
}
}
}
@@ -0,0 +1,6 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassData": {
}
}
@@ -0,0 +1,31 @@
"""
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.
"""
import os.path
from os import path
import shutil
import json
def find_or_copy_file(destFilePath, sourceFilePath):
if path.exists(destFilePath):
return
if not path.exists(sourceFilePath):
raise ValueError('find_or_copy_file: source file [', sourceFilePath, '] doesn\'t exist')
return
dstDir = path.dirname(destFilePath)
if not path.isdir(dstDir):
os.makedirs(dstDir)
shutil.copyfile(sourceFilePath, destFilePath)
def load_json_file(filePath):
file_stream = open(filePath, "r")
return json.load(file_stream)
+33
View File
@@ -0,0 +1,33 @@
"""
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.
"""
import os
import platform
from setuptools import setup, find_packages
PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
PYTHON_64 = platform.architecture()[0] == '64bit'
if __name__ == '__main__':
if not PYTHON_64:
raise RuntimeError("32-bit Python is not a supported platform.")
with open(os.path.join(PROJECT_ROOT, 'README.txt')) as f:
long_description = f.read()
setup(
name="atom_rpi_tools",
version="1.0.0",
description='Python interface to Atom RPI tools',
long_description=long_description,
packages=find_packages(exclude=['tests'])
)
@@ -51,7 +51,8 @@ namespace AtomToolsFramework
void UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) override;
// ModularViewportCameraControllerRequestBus overrides ...
void InterpolateToTransform(const AZ::Transform& worldFromLocal) override;
void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override;
AZStd::optional<AZ::Vector3> LookAtAfterInterpolation() const override;
private:
// AzFramework::ViewportDebugDisplayEventBus overrides ...
@@ -71,6 +72,8 @@ namespace AtomToolsFramework
AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity();
float m_animationT = 0.0f;
CameraMode m_cameraMode = CameraMode::Control;
AZStd::optional<AZ::Vector3> m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished.
//!< Will be cleared when the view changes (camera looks away).
bool m_updatingTransform = false;
AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler;
@@ -32,7 +32,12 @@ namespace AtomToolsFramework
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Begin a smooth transition of the camera to the requested transform.
virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal) = 0;
//! @param worldFromLocal The transform of where the camera should end up.
//! @param lookAtDistance The distance between the camera transform and the imagined look at point.
virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) = 0;
//! Look at point after an interpolation has finished and no translation has occurred.
virtual AZStd::optional<AZ::Vector3> LookAtAfterInterpolation() const = 0;
protected:
~ModularViewportCameraControllerRequests() = default;
@@ -140,6 +140,18 @@ namespace AtomToolsFramework
m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count());
m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, event.m_deltaTime.count());
// if there has been an interpolation, only clear the look at point if it is no longer
// centered in the view (the camera has looked away from it)
if (m_lookAtAfterInterpolation.has_value())
{
if (const float lookDirection =
(*m_lookAtAfterInterpolation - m_camera.Translation()).GetNormalized().Dot(m_camera.Transform().GetBasisY());
!AZ::IsCloseMag(lookDirection, 1.0f, 0.001f))
{
m_lookAtAfterInterpolation = {};
}
}
viewportContext->SetCameraTransform(m_camera.Transform());
}
else if (m_cameraMode == CameraMode::Animation)
@@ -148,8 +160,8 @@ namespace AtomToolsFramework
{
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
};
const float transitionT = smootherStepFn(m_animationT);
const float transitionT = smootherStepFn(m_animationT);
const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation(
m_transformStart.GetRotation().Slerp(m_transformEnd.GetRotation(), transitionT),
m_transformStart.GetTranslation().Lerp(m_transformEnd.GetTranslation(), transitionT));
@@ -185,11 +197,17 @@ namespace AtomToolsFramework
}
}
void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal)
void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance)
{
m_animationT = 0.0f;
m_cameraMode = CameraMode::Animation;
m_transformStart = m_camera.Transform();
m_transformEnd = worldFromLocal;
m_lookAtAfterInterpolation = m_transformEnd.GetTranslation() + m_transformEnd.GetBasisY() * lookAtDistance;
}
AZStd::optional<AZ::Vector3> ModernViewportCameraControllerInstance::LookAtAfterInterpolation() const
{
return m_lookAtAfterInterpolation;
}
} // namespace AtomToolsFramework
@@ -29,7 +29,8 @@ ly_add_target(
Legacy::CryCommon
Gem::Atom_RHI.Reflect
Gem::Atom_RPI.Public
Gem::Atom_Bootstrap.Headers
PUBLIC
Gem::Atom_AtomBridge.Static
)
################################################################################

Some files were not shown because too many files have changed in this diff Show More