Merge branch 'dev' into audio/amzn-phist/RemoveLegacyXml

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>
This commit is contained in:
amzn-phist
2021-07-26 12:28:49 -05:00
50 changed files with 1008 additions and 696 deletions
@@ -10,28 +10,74 @@
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/Component/TickBus.h>
namespace AZ
{
namespace Debug
{
//! Trace Message Event Handler for Automation.
//! Since TraceMessageBus will be called from multiple threads and
//! python interpreter is single threaded, all the bus calls are
//! queued into a list and called at the end of the frame in the main thread.
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER
//! macro as the signature needs to be changed to connect to Tick bus.
class TraceMessageBusHandler
: public AZ::Debug::TraceMessageBus::Handler
, public AZ::BehaviorEBusHandler
, public AZ::TickBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0);
AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler);
AZ_EBUS_BEHAVIOR_BINDER(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::SystemAllocator
, OnPreAssert
, OnPreError
, OnPreWarning
, OnAssert
, OnError
, OnWarning
, OnException
, OnPrintf
, OnOutput
);
TraceMessageBusHandler();
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
decltype(&TraceMessageBusHandler::OnPreAssert),
decltype(&TraceMessageBusHandler::OnPreError),
decltype(&TraceMessageBusHandler::OnPreWarning),
decltype(&TraceMessageBusHandler::OnAssert),
decltype(&TraceMessageBusHandler::OnError),
decltype(&TraceMessageBusHandler::OnWarning),
decltype(&TraceMessageBusHandler::OnException),
decltype(&TraceMessageBusHandler::OnPrintf),
decltype(&TraceMessageBusHandler::OnOutput)
>;
enum
{
FN_OnPreAssert = 0,
FN_OnPreError,
FN_OnPreWarning,
FN_OnAssert,
FN_OnError,
FN_OnWarning,
FN_OnException,
FN_OnPrintf,
FN_OnOutput,
FN_MAX
};
static inline constexpr const char* m_functionNames[FN_MAX] =
{
"OnPreAssert",
"OnPreError",
"OnPreWarning",
"OnAssert",
"OnError",
"OnWarning",
"OnException",
"OnPrintf",
"OnOutput"
};
// AZ::BehaviorEBusHandler overrides...
int GetFunctionIndex(const char* functionName) const override;
void Disconnect() override;
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
bool IsConnected() override;
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
// TraceMessageBus
/*
@@ -48,63 +94,190 @@ namespace AZ
bool OnPrintf(const char* window, const char* message) override;
bool OnOutput(const char* window, const char* message) override;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
private:
template<class R, class... Args>
R CallResultReturn(const R& defaultReturnValue, int index, Args&&... args) const
{
R returnVal = defaultReturnValue;
CallResult(returnVal, index, AZStd::forward<Args>(args)...);
return returnVal;
}
void QueueMessageCall(AZStd::function<void()> messageCall);
void FlushMessageCalls();
AZStd::list<AZStd::function<void()>> m_messageCalls;
AZStd::mutex m_messageCallsLock;
};
TraceMessageBusHandler::TraceMessageBusHandler()
{
m_events.resize(FN_MAX);
SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]);
SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]);
SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]);
SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]);
SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]);
SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]);
SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]);
SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]);
SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]);
}
int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const
{
for (int i = 0; i < FN_MAX; ++i)
{
if (azstricmp(functionName, m_functionNames[i]) == 0)
{
return i;
}
}
return -1;
}
void TraceMessageBusHandler::Disconnect()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}
bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id)
{
AZ::TickBus::Handler::BusConnect();
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::Connect(this, id);
}
bool TraceMessageBusHandler::IsConnected()
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnected(this);
}
bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
{
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnectedId(this, id);
}
//////////////////////////////////////////////////////////////////////////
// TraceMessageBusHandler Implementation
inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message)
{
return CallResultReturn(false, FN_OnPreAssert, fileName, line, func, message);
QueueMessageCall(
[this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
{
return CallResultReturn(false, FN_OnPreError, window, fileName, line, func, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
{
return CallResultReturn(false, FN_OnPreWarning, window, fileName, line, func, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
{
return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnAssert(const char* message)
{
return CallResultReturn(false, FN_OnAssert, message);
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
return Call(FN_OnAssert, messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnError(const char* window, const char* message)
{
return CallResultReturn(false, FN_OnError, window, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnError, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message)
{
return CallResultReturn(false, FN_OnWarning, window, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnWarning, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnException(const char* message)
{
return CallResultReturn(false, FN_OnException, message);
QueueMessageCall(
[this, messageString = AZStd::string(message)]()
{
return Call(FN_OnException, messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message)
{
return CallResultReturn(false, FN_OnPrintf, window, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str());
});
return false;
}
inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message)
{
return CallResultReturn(false, FN_OnOutput, window, message);
QueueMessageCall(
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
{
return Call(FN_OnOutput, windowString.c_str(), messageString.c_str());
});
return false;
}
void TraceMessageBusHandler::OnTick(
[[maybe_unused]] float deltaTime,
[[maybe_unused]] AZ::ScriptTimePoint time)
{
FlushMessageCalls();
}
int TraceMessageBusHandler::GetTickOrder()
{
return AZ::TICK_LAST;
}
void TraceMessageBusHandler::QueueMessageCall(AZStd::function<void()> messageCall)
{
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
m_messageCalls.push_back(messageCall);
}
void TraceMessageBusHandler::FlushMessageCalls()
{
AZStd::list<AZStd::function<void()>> messageCalls;
{
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible
}
for (auto& messageCall : messageCalls)
{
messageCall();
}
}
void TraceReflect(ReflectContext* context)
{
@@ -14,6 +14,7 @@
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/Jobs/JobManager.h>
#include <AzCore/Jobs/JobContext.h>
#include <AzCore/Outcome/Outcome.h>
@@ -698,7 +699,7 @@ namespace UnitTest
auto& assetManager = AssetManager::Instance();
AssetBusCallbacks callbacks{};
callbacks.SetOnAssetReadyCallback([&](const Asset<AssetData>&, AssetBusCallbacks&)
callbacks.SetOnAssetReadyCallback([&, AssetNoRefB](const Asset<AssetData>&, AssetBusCallbacks&)
{
// This callback should run inside the "main thread" dispatch events loop
auto loadAsset = assetManager.GetAsset<AssetWithSerializedData>(AZ::Uuid(AssetNoRefB), AssetLoadBehavior::Default);
@@ -147,7 +147,9 @@ namespace AzFramework
m_scrollDelta = scroll->m_delta;
}
return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
m_handlingEvents = m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
return m_handlingEvents;
}
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
@@ -262,12 +262,14 @@ namespace AzFramework
public:
bool HandleEvents(const InputEvent& event);
Camera StepCamera(const Camera& targetCamera, float deltaTime);
bool HandlingEvents() const { return m_handlingEvents; }
Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller.
private:
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated).
};
//! A camera input to handle motion deltas that can rotate or orbit the camera.
@@ -240,15 +240,51 @@ namespace AzQtComponents
// Center within the parent
QRect geo = geometry();
// If the base size of the guest widget is larger than the screen,
// then we need to resize it so that it will fit by either using
// the minimum size (if one is set), or fallback to the screen size.
if (m_guestWidget)
{
if (auto screen = m_guestWidget->screen())
{
const QRect screenGeometry = screen->availableGeometry();
if (geo.width() > screenGeometry.width())
{
auto guestMinimumWidth = m_guestWidget->minimumWidth();
if (guestMinimumWidth && guestMinimumWidth <= screenGeometry.width())
{
geo.setWidth(guestMinimumWidth);
}
else
{
geo.setWidth(screenGeometry.width());
}
}
if (geo.height() > screenGeometry.height())
{
auto guestMinimumHeight = m_guestWidget->minimumHeight();
if (guestMinimumHeight && guestMinimumHeight <= screenGeometry.height())
{
geo.setHeight(guestMinimumHeight);
}
else
{
geo.setHeight(screenGeometry.height());
}
}
}
}
geo.moveCenter(parentWindowCenter);
QWindow *w = topLevelWidget->windowHandle();
QWindow* w = topLevelWidget->windowHandle();
if (!w)
{
return;
}
QScreen *screen = w->screen();
QScreen* screen = w->screen();
if (!screen)
{
// defensive, shouldn't happen
@@ -661,6 +697,10 @@ namespace AzQtComponents
if (!restoreGeometryFromSettings())
{
show();
// If we failed to restore from settings (the first time this window is loaded),
// then center it on the screen by default
centerOnScreen(this);
}
}
@@ -43,19 +43,20 @@ namespace AzQtComponents
{
const QChar decimalPoint = locale.decimalPoint();
const QChar zeroDigit = locale.zeroDigit();
const int numToStringDecimals = AZStd::max(numDecimals, 20);
// We want to truncate, not round. toString will round, so we add an extra decimal place to the formatting
// so we can remove the last value
QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numDecimals + 1 : 0);
// We want to truncate, not round. toString will round, so we add extra decimal places to the formatting
// so we can remove the last values
QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numToStringDecimals : 0);
// Handle special cases when we have decimals in our value
if (numDecimals > 0)
{
// Truncate the extra digit now, if it's still there
// Truncate the extra digits now, if they're still there
int decimalPointIndex = retValue.lastIndexOf(decimalPoint);
if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == (numDecimals + 1))
if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == numToStringDecimals)
{
retValue.resize(retValue.size() - 1);
retValue.resize(retValue.size() - (numToStringDecimals - numDecimals));
}
// Remove trailing zeros, since the locale conversion won't do
@@ -1951,12 +1951,13 @@ namespace AzToolsFramework
return;
}
// If prefabs are enabled, there will be no root slice so bail out here since we don't need
// to show any slice options in the menu
AZ::SliceComponent* rootSlice = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSlice, contextId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
if (!rootSlice)
{
AZ_Error("PropertyEditor", false, "Entity context has no root slice");
return;
}
@@ -2105,10 +2106,6 @@ namespace AzToolsFramework
{
QMenu* revertMenu = nullptr;
revertMenu = menu.addMenu(tr("Revert overrides"));
revertMenu->setToolTipsVisible(true);
revertMenu->setEnabled(false);
//check for changes on selected property
if (componentClassData)
{
@@ -2128,6 +2125,11 @@ namespace AzToolsFramework
return;
}
// Only add the "Revert overrides" menu option if it belongs to a slice
revertMenu = menu.addMenu(tr("Revert overrides"));
revertMenu->setToolTipsVisible(true);
revertMenu->setEnabled(false);
if (fieldNode)
{
bool hasChanges = fieldNode->HasChangesVersusComparison(false);
@@ -77,6 +77,19 @@ namespace UnitTest
m_intSpinBox.reset();
}
QString setupTruncationTest(QString textValue)
{
QString retval;
m_doubleSpinBoxWithLineEdit->setDecimals(7);
m_doubleSpinBoxWithLineEdit->setDisplayDecimals(3);
m_doubleSpinBoxWithLineEdit->setFocus();
m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(textValue);
m_doubleSpinBoxWithLineEdit->clearFocus();
return m_doubleSpinBoxWithLineEdit->textFromValue(m_doubleSpinBoxWithLineEdit->value());
}
AZStd::unique_ptr<QWidget> m_dummyWidget;
AZStd::unique_ptr<AzQtComponents::SpinBox> m_intSpinBox;
AZStd::unique_ptr<AzQtComponents::DoubleSpinBox> m_doubleSpinBox;
@@ -277,4 +290,34 @@ namespace UnitTest
// test would result in a crash
EXPECT_TRUE(m_intSpinBox.get() == nullptr);
}
TEST_F(SpinBoxFixture, SpinBoxCheckHighValueTruncatesCorrectly)
{
QString value = setupTruncationTest("0.9999999");
EXPECT_TRUE(value == "0.999");
}
TEST_F(SpinBoxFixture, SpinBoxCheckLowValueTruncatesCorrectly)
{
QString value = setupTruncationTest("0.0000001");
EXPECT_TRUE(value == "0.0");
}
TEST_F(SpinBoxFixture, SpinBoxCheckBugValuesTruncatesCorrectly)
{
QString value = setupTruncationTest("0.12395");
EXPECT_TRUE(value == "0.123");
value = setupTruncationTest("0.94496");
EXPECT_TRUE(value == "0.944");
value = setupTruncationTest("0.0009999");
EXPECT_TRUE(value == "0.0");
}
} // namespace UnitTest
@@ -41,9 +41,9 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
add_custom_target(${project_name}.Assets
COMMENT "Processing ${project_name} assets..."
COMMAND "${CMAKE_COMMAND}"
-DLY_LOCK_FILE=$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>/project_assets.lock
-DLY_LOCK_FILE=$<GENEX_EVAL:$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>>/project_assets.lock
-P ${LY_ROOT_FOLDER}/cmake/CommandExecution.cmake
EXEC_COMMAND $<TARGET_FILE:AZ::AssetProcessorBatch>
EXEC_COMMAND $<GENEX_EVAL:$<TARGET_FILE:AZ::AssetProcessorBatch>>
--zeroAnalysisMode
--project-path=${project_real_path}
--platforms=${LY_ASSET_DEPLOY_ASSET_TYPE}
@@ -170,7 +170,8 @@ namespace O3DE::ProjectManager
// the decoration wrapper is intended to remember window positioning and sizing
auto wrapper = new AzQtComponents::WindowDecorationWrapper();
wrapper->setGuest(m_mainWindow.data());
wrapper->show();
wrapper->enableSaveRestoreGeometry("O3DE", "ProjectManager", "mainWindowGeometry");
wrapper->showFromSettings();
m_mainWindow->show();
qApp->setQuitOnLastWindowClosed(true);
@@ -41,45 +41,6 @@ namespace AZ
}
}
void MakeBoneMap(const aiScene* scene, AZStd::unordered_map<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
aiMatrix4x4 CalculateWorldTransform(const aiNode* currentNode)
{
aiMatrix4x4 transform = {};
@@ -106,37 +67,39 @@ namespace AZ
return Events::ProcessingResult::Ignored;
}
bool isBone = false;
AZStd::unordered_multimap<AZStd::string, const aiBone*> boneByNameMap;
FindAllBones(scene, boneByNameMap);
bool isBone = FindFirstBoneByNodeName(currentNode, boneByNameMap);
if (!isBone)
{
AZStd::unordered_map<AZStd::string, const aiBone*> boneLookup;
MakeBoneMap(scene, boneLookup);
isBone = boneLookup.contains(currentNode->mName.C_Str());
// If we have an animation, the bones will be listed in there
if (!isBone)
for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex)
{
for(unsigned animIndex = 0; animIndex < scene->mNumAnimations; ++animIndex)
aiAnimation* animation = scene->mAnimations[animIndex];
for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex)
{
aiAnimation* animation = scene->mAnimations[animIndex];
aiNodeAnim* nodeAnim = animation->mChannels[channelIndex];
for (unsigned channelIndex = 0; channelIndex < animation->mNumChannels; ++channelIndex)
{
aiNodeAnim* nodeAnim = animation->mChannels[channelIndex];
if (nodeAnim->mNodeName == currentNode->mName)
{
isBone = true;
break;
}
}
if (isBone)
if (nodeAnim->mNodeName == currentNode->mName)
{
isBone = true;
break;
}
}
if (isBone)
{
break;
}
}
// In case any of the children, or children of children is a bone, make sure to not skip this node.
// Don't do this for the scene root itself, else wise all mesh nodes will be exported as bones and pollute the skeleton.
if (currentNode != scene->mRootNode &&
RecursiveHasChildBone(currentNode, boneByNameMap))
{
isBone = true;
}
}
@@ -6,12 +6,13 @@
*
*/
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <assimp/scene.h>
#include <AzCore/Debug/Trace.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <SceneAPI/SDKWrapper/AssImpTypeConverter.h>
#include <SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.h>
namespace AZ
{
@@ -85,6 +86,107 @@ namespace AZ
return combinedTransform;
}
void FindAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& outBoneByNameMap)
{
outBoneByNameMap.clear();
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
outBoneByNameMap.emplace(bone->mName.C_Str(), bone);
}
}
}
}
DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node)
{
AZStd::unordered_multimap<AZStd::string, const aiBone*> boneByNameMap;
FindAllBones(scene, boneByNameMap);
const aiBone* bone = FindFirstBoneByNodeName(node, boneByNameMap);
if (bone)
{
const DataTypes::MatrixType inverseOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(bone->mOffsetMatrix).GetInverseFull();
const aiBone* parentBone = FindFirstBoneByNodeName(node->mParent, boneByNameMap);
if (parentBone)
{
const DataTypes::MatrixType parentBoneOffsetMatrix = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(parentBone->mOffsetMatrix);
return parentBoneOffsetMatrix * inverseOffsetMatrix;
}
else
{
return inverseOffsetMatrix;
}
}
return AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(node));
}
const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneByNameMap)
{
if (!node)
{
return nullptr;
}
auto boneIterator = boneByNameMap.find(node->mName.C_Str());
if (boneIterator != boneByNameMap.end())
{
return boneIterator->second;
}
return nullptr;
}
bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneByNameMap)
{
const bool isBone = boneByNameMap.contains(node->mName.C_Str());
if (isBone)
{
return true;
}
for (int childIndex = 0; childIndex < node->mNumChildren; ++childIndex)
{
const aiNode* childNode = node->mChildren[childIndex];
if (RecursiveHasChildBone(childNode, boneByNameMap))
{
return true;
}
}
return false;
}
} // namespace SceneBuilder
} // namespace SceneAPI
} // namespace AZ
@@ -9,13 +9,15 @@
#pragma once
#include <assimp/matrix4x4.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <SceneAPI/SceneCore/DataTypes/MatrixType.h>
struct aiBone;
struct aiNode;
struct aiScene;
struct aiString;
namespace AZ::SceneAPI::SceneBuilder
{
inline constexpr char PivotNodeMarker[] = "_$AssimpFbx$_";
@@ -30,5 +32,16 @@ namespace AZ::SceneAPI::SceneBuilder
// Gets the entire, combined local transform for a node taking pivot nodes into account. When pivot nodes are not used, this just returns the node's transform
aiMatrix4x4 GetConcatenatedLocalTransform(const aiNode* currentNode);
DataTypes::MatrixType GetLocalSpaceBindPoseTransform(const aiScene* scene, const aiNode* node);
// Gather all bones from the scene. (Bone in AssImp corresponds to nodes that influence any of the vertices).
void FindAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& outBoneByNameMap);
// Find the first bone with the name of the given node.
const aiBone* FindFirstBoneByNodeName(const aiNode* node, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneByNameMap);
// Check if the given node or any of its children, or children of children, is a bone by checking if the node name is part of the given map.
bool RecursiveHasChildBone(const aiNode* node, const AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneByNameMap);
} // namespace AZ
@@ -42,45 +42,6 @@ namespace AZ
serializeContext->Class<AssImpTransformImporter, SceneCore::LoadingComponent>()->Version(1);
}
}
void GetAllBones(const aiScene* scene, AZStd::unordered_multimap<AZStd::string, const aiBone*>& boneLookup)
{
AZStd::queue<const aiNode*> queue;
AZStd::unordered_set<AZStd::string> nodesWithNoMesh;
queue.push(scene->mRootNode);
while (!queue.empty())
{
const aiNode* currentNode = queue.front();
queue.pop();
if (currentNode->mNumMeshes == 0)
{
nodesWithNoMesh.emplace(currentNode->mName.C_Str());
}
for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex)
{
queue.push(currentNode->mChildren[childIndex]);
}
}
for (unsigned meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex)
{
const aiMesh* mesh = scene->mMeshes[meshIndex];
for (unsigned boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex)
{
const aiBone* bone = mesh->mBones[boneIndex];
if (nodesWithNoMesh.contains(bone->mName.C_Str()))
{
boneLookup.emplace(bone->mName.C_Str(), bone);
}
}
}
}
Events::ProcessingResult AssImpTransformImporter::ImportTransform(AssImpSceneNodeAppendedContext& context)
{
@@ -93,54 +54,7 @@ namespace AZ
return Events::ProcessingResult::Ignored;
}
AZStd::unordered_multimap<AZStd::string, const aiBone*> boneLookup;
GetAllBones(scene, boneLookup);
auto boneIterator = boneLookup.find(currentNode->mName.C_Str());
const bool isBone = boneIterator != boneLookup.end();
DataTypes::MatrixType localTransform;
if (isBone)
{
AZStd::vector<DataTypes::MatrixType> offsets, inverseOffsets;
auto iteratingNode = currentNode;
while (iteratingNode && boneLookup.count(iteratingNode->mName.C_Str()))
{
AZStd::string name = iteratingNode->mName.C_Str();
auto range = boneLookup.equal_range(name);
if (range.first != range.second)
{
// There can be multiple offsetMatrices for a given bone, we're only interested in grabbing the first one
auto boneFirstOffsetMatrix = range.first->second->mOffsetMatrix;
auto azMat = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(boneFirstOffsetMatrix);
offsets.push_back(azMat);
inverseOffsets.push_back(azMat.GetInverseFull());
}
iteratingNode = iteratingNode->mParent;
}
if (inverseOffsets.size() == 1)
{
// If this is the root bone, just use the inverseOffset, otherwise the equation below just results in the identity matrix
localTransform = inverseOffsets[0];
}
else
{
localTransform = offsets.at(1) // parent bone offset
* inverseOffsets.at(inverseOffsets.size() - 1) // Inverse of root bone offset
* offsets.at(offsets.size() - 1) // Root bone offset
* inverseOffsets.at(0); // Inverse of current node offset
}
}
else
{
localTransform = AssImpSDKWrapper::AssImpTypeConverter::ToTransform(GetConcatenatedLocalTransform(currentNode));
}
DataTypes::MatrixType localTransform = GetLocalSpaceBindPoseTransform(scene, currentNode);
// Don't bother adding a node with the identity matrix
if (localTransform == DataTypes::MatrixType::Identity())
@@ -10,6 +10,7 @@
#include <Editor/Attribution/AWSCoreAttributionConstant.h>
#include <Framework/JsonWriter.h>
#include <sstream>
#include <time.h>
#pragma warning(disable : 4996)
+6 -7
View File
@@ -37,7 +37,7 @@ env = core.Environment(account=ACCOUNT, region=REGION)
app = core.App()
core = AWSCore(
core_construct = AWSCore(
app,
id_=f'{PROJECT_FEATURE_NAME}-Construct',
project_name=PROJECT_NAME,
@@ -46,20 +46,19 @@ core = AWSCore(
)
# Below is the Core example stack which is provided for working with AWSCore ScriptCanvas examples.
# It also provided as an example how to reference properties across stacks in the same CDK applications
# Note: This will make the consuming stack a dependent stack on core
# CDK will deploy the dependent stack first and then the core stack
# It also provided as an example how to reference resources across stacks via stack outputs.
# See https://docs.aws.amazon.com/cdk/latest/guide/resources.html#resource_stack
core_properties = core.properties
example = ExampleResources(
example_stack = ExampleResources(
app,
id_=f'{PROJECT_FEATURE_NAME}-Example-{env.region}',
props_=core_properties,
project_name=f'{PROJECT_NAME}',
feature_name=FEATURE_NAME,
tags={Constants.O3DE_PROJECT_TAG_NAME: PROJECT_NAME, Constants.O3DE_FEATURE_TAG_NAME: FEATURE_NAME},
env=env
)
#
# Add the common stack as a dependency of the feature stack
example_stack.add_dependency(core_construct.common_stack)
app.synth()
+4
View File
@@ -42,3 +42,7 @@ class AWSCore(core.Construct):
@property
def properties(self):
return self._feature_stack.properties
@property
def common_stack(self):
return self._feature_stack
+19 -7
View File
@@ -8,11 +8,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
from aws_cdk import (
core,
aws_iam as iam,
aws_s3 as s3,
aws_resourcegroups as resource_groups,
)
from constants import Constants
from core_stack_properties import CoreStackProperties
class CoreStack(core.Stack):
@@ -60,6 +60,17 @@ class CoreStack(core.Stack):
type='TAG_FILTERS_1_0')
)
# Create an S3 bucket for Amazon S3 server access logging
# See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html
self._server_access_logs_bucket = s3.Bucket(
self,
f'{self._project_name}-{self._feature_name}-Access-Log-Bucket',
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED,
access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE
)
self._server_access_logs_bucket.grant_read(self._admin_group)
# Define exports
# Export resource group
self._resource_group_output = core.CfnOutput(
@@ -83,9 +94,10 @@ class CoreStack(core.Stack):
export_name=f"{self._project_name}:AdminGroup",
value=self._admin_group.group_arn)
@property
def properties(self) -> CoreStackProperties:
_props = CoreStackProperties()
_props.user_group = self._user_group
_props.admin_group = self._admin_group
return _props
# Export access log bucket name
self._server_access_logs_bucket_output = core.CfnOutput(
self,
id=f'ServerAccessLogsBucketOutput',
description='Name of the S3 bucket for storing server access logs generated by the sample CDK application(s)',
export_name=f"{self._project_name}:ServerAccessLogsBucket",
value=self._server_access_logs_bucket.bucket_name)
-25
View File
@@ -1,25 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
from aws_cdk import (
core,
aws_iam as iam
)
class CoreStackProperties(core.StackProps):
"""
Support for cross stack references in the application.
Define any properties from the CoreStack other stacks in this application
may need to consume.
"""
# Common IAM group for users
user_group: iam.Group
# Common IAM group for Admin users
admin_group: iam.Group
@@ -8,13 +8,13 @@ import os
from aws_cdk import (
aws_lambda as lambda_,
aws_iam as iam,
aws_s3 as s3,
aws_s3_deployment as s3_deployment,
aws_dynamodb as dynamo,
core
)
from core_stack_properties import CoreStackProperties
from .auth import AuthPolicy
@@ -25,8 +25,7 @@ class ExampleResources(core.Stack):
* A python 'echo' lambda
* A small dynamodb table with the a primary 'id': str key
"""
def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str,
props_: CoreStackProperties, **kwargs) -> None:
def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str, **kwargs) -> None:
super().__init__(scope, id_, **kwargs,
description=f'Contains resources for the AWSCore examples as part of the '
f'{project_name} project')
@@ -42,17 +41,74 @@ class ExampleResources(core.Stack):
self.__create_outputs()
# Finally grant cross stack references
self.__grant_access(props=props_)
self.__grant_access()
def __grant_access(self, props: CoreStackProperties):
self._s3_bucket.grant_read(props.user_group)
self._s3_bucket.grant_read(props.admin_group)
def __grant_access(self):
user_group = iam.Group.from_group_arn(
self,
f'{self._project_name}-{self._feature_name}-ImportedUserGroup',
core.Fn.import_value(f'{self._project_name}:UserGroup')
)
admin_group = iam.Group.from_group_arn(
self,
f'{self._project_name}-{self._feature_name}-ImportedAdminGroup',
core.Fn.import_value(f'{self._project_name}:AdminGroup')
)
self._lambda.grant_invoke(props.user_group)
self._lambda.grant_invoke(props.admin_group)
# Provide the admin and user groups permissions to read the example S3 bucket.
# Cannot use the grant_read method defined by the Bucket structure since the method tries to add to
# the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are
# not valid principals in S3 bucket policies.
# Check https://aws.amazon.com/premiumsupport/knowledge-center/s3-invalid-principal-in-policy-error/
user_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*"
],
effect=iam.Effect.ALLOW,
resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*']
)
)
admin_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*"
],
effect=iam.Effect.ALLOW,
resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*']
)
)
self._table.grant_read_data(props.user_group)
self._table.grant_read_data(props.admin_group)
# Provide the admin and user groups permissions to invoke the example Lambda function.
# Cannot use the grant_invoke method defined by the Function structure since the method tries to add to
# the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are
# not valid principals in Lambda function policies.
user_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"lambda:InvokeFunction"
],
effect=iam.Effect.ALLOW,
resources=[self._lambda.function_arn]
)
)
admin_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"lambda:InvokeFunction"
],
effect=iam.Effect.ALLOW,
resources=[self._lambda.function_arn]
)
)
# Provide the admin and user groups permissions to read from the DynamoDB table.
self._table.grant_read_data(user_group)
self._table.grant_read_data(admin_group)
def __create_s3_bucket(self) -> s3.Bucket:
# Create a sample S3 bucket following S3 best practices
@@ -60,11 +116,21 @@ class ExampleResources(core.Stack):
# 1. Block all public access to the bucket
# 2. Use SSE-S3 encryption. Explore encryption at rest options via
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html
# 3. Enable Amazon S3 server access logging
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html
server_access_logs_bucket = s3.Bucket.from_bucket_name(
self,
f'{self._project_name}-{self._feature_name}-ImportedAccessLogsBucket',
core.Fn.import_value(f"{self._project_name}:ServerAccessLogsBucket")
)
example_bucket = s3.Bucket(
self,
f'{self._project_name}-{self._feature_name}-Example-S3bucket',
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED
encryption=s3.BucketEncryption.S3_MANAGED,
server_access_logs_bucket=server_access_logs_bucket,
server_access_logs_prefix=f'{self._project_name}-{self._feature_name}-{self.region}-AccessLogs'
)
s3_deployment.BucketDeployment(
+4 -2
View File
@@ -13,6 +13,7 @@ from aws_cdk import (
from .aws_metrics_stack import AWSMetricsStack
from aws_metrics.policy_statements_builder.user_policy_statements_builder import UserPolicyStatementsBuilder
from aws_metrics.policy_statements_builder.admin_policy_statements_builder import AdminPolicyStatementsBuilder
from .aws_utils import resource_name_sanitizer
class AuthPolicy:
@@ -58,12 +59,13 @@ class AuthPolicy:
policy = iam.ManagedPolicy(
self._stack,
policy_id,
managed_policy_name=f'{self._stack.stack_name}-{role_name}Policy',
managed_policy_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-{role_name}Policy', 'iam_managed_policy'),
statements=policy_statements)
policy_output = core.CfnOutput(
self._stack,
id=f'{policy_id}Output',
description=f'{role_name} policy arn to call service',
export_name=f"{self._application_name}:{policy_id}",
export_name=f'{self._application_name}:{policy_id}',
value=policy.managed_policy_arn)
@@ -8,6 +8,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
from aws_cdk import core
from .aws_metrics_stack import AWSMetricsStack
from .auth import AuthPolicy
from .aws_utils import resource_name_sanitizer
class AWSMetrics(core.Construct):
@@ -23,19 +24,20 @@ class AWSMetrics(core.Construct):
env: core.Environment) -> None:
super().__init__(scope, id_)
# Set-up any stack name(s) to be unique in account
stack_name = f'{project_name}-{feature_name}-{env.region}'
stack_name = resource_name_sanitizer.sanitize_resource_name(
f'{project_name}-{feature_name}-{env.region}', 'cloudformation_stack')
application_name = f'{project_name}-{feature_name}'
# Check context variables to get enabled optional features
optional_features = {
'batch_processing': self.node.try_get_context("batch_processing") == 'true'
'batch_processing': self.node.try_get_context("batch_processing") == 'true',
'server_access_logs_bucket': self.node.try_get_context("server_access_logs_bucket")
}
# Deploy AWS Metrics Stack
self._feature_stack = AWSMetricsStack(
scope,
stack_name,
stack_name=stack_name,
application_name=application_name,
description=f'Contains resources for the AWS Metrics Gem Feature stack as part of the {project_name} project',
optional_features=optional_features,
@@ -43,9 +43,11 @@ class AWSMetricsStack(core.Stack):
)
batch_processing_enabled = optional_features.get('batch_processing', False)
server_access_logs_bucket = optional_features.get('server_access_logs_bucket')
self._data_lake_integration = DataLakeIntegration(
self,
application_name=application_name
application_name=application_name,
server_access_logs_bucket=server_access_logs_bucket
) if batch_processing_enabled else None
self._batch_processing = BatchProcessing(
@@ -0,0 +1,6 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
@@ -0,0 +1,45 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import hashlib
MAX_RESOURCE_NAME_LENGTH_MAPPING = {
'athena_work_group': 128,
'athena_named_query': 128,
'cloudformation_stack': 128,
'cloudwatch_dashboard': 255,
'cloudwatch_log_group': 512,
'firehose_delivery_stream': 64,
'iam_managed_policy': 144,
'iam_role': 64,
'kinesis_application': 128,
'kinesis_stream': 128,
'lambda_function': 64,
's3_bucket': 63
}
def sanitize_resource_name(resource_name: str, resource_type: str) -> str:
"""
Truncate the resource name if its length exceeds the limit.
This is the best effort for sanitizing resource names based on the AWS documents since each AWS service
has its unique restrictions. Customers can extend this function for validation or sanitization.
:param resource_name: Original name of the resource.
:param resource_type: Type of the resource.
:return Sanitized resource name that can be deployed with AWS.
"""
result = resource_name
if not MAX_RESOURCE_NAME_LENGTH_MAPPING.get(resource_type):
return result
if len(resource_name) > MAX_RESOURCE_NAME_LENGTH_MAPPING[resource_type]:
# PYTHONHASHSEED is set to "random" by default in Python 3.3 and up. Cannot use
# the built-in hash function here since it will give a different return value in each session
digest = "-%x" % (int(hashlib.md5(resource_name.encode('ascii', 'ignore')).hexdigest(), 16) & 0xffffffff)
result = resource_name[:MAX_RESOURCE_NAME_LENGTH_MAPPING[resource_type] - len(digest)] + digest
return result
@@ -11,6 +11,7 @@ from aws_cdk import (
)
from . import aws_metrics_constants
from .aws_utils import resource_name_sanitizer
class BatchAnalytics:
@@ -37,7 +38,8 @@ class BatchAnalytics:
self._athena_work_group = athena.CfnWorkGroup(
self._stack,
id='AthenaWorkGroup',
name=f'{self._stack.stack_name}-AthenaWorkGroup',
name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AthenaWorkGroup', 'athena_work_group'),
recursive_delete_option=True,
state='ENABLED',
work_group_configuration=athena.CfnWorkGroup.WorkGroupConfigurationProperty(
@@ -65,7 +67,8 @@ class BatchAnalytics:
athena.CfnNamedQuery(
self._stack,
id='NamedQuery-CreatePartitionedEventsJson',
name=f'{self._stack.stack_name}-NamedQuery-CreatePartitionedEventsJson',
name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-NamedQuery-CreatePartitionedEventsJson', 'athena_named_query'),
database=self._events_database_name,
query_string="CREATE TABLE events_json "
"WITH (format='JSON',partitioned_by=ARRAY['application_id']) "
@@ -78,7 +81,8 @@ class BatchAnalytics:
athena.CfnNamedQuery(
self._stack,
id='NamedQuery-TotalEventsLastMonth',
name=f'{self._stack.stack_name}-NamedQuery-TotalEventsLastMonth',
name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-NamedQuery-TotalEventsLastMonth', 'athena_named_query'),
database=self._events_database_name,
query_string="WITH detail AS "
"(SELECT date_trunc('month', date(date_parse(CONCAT(year, '-', month, '-', day), '%Y-%m-%d'))) as event_month, * "
@@ -93,7 +97,8 @@ class BatchAnalytics:
athena.CfnNamedQuery(
self._stack,
id='NamedQuery-NewUsersLastMonth',
name=f'{self._stack.stack_name}-NamedQuery-NewUsersLastMonth',
name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-NamedQuery-NewUsersLastMonth', 'athena_named_query'),
database=self._events_database_name,
query_string="WITH detail AS ("
"SELECT date_trunc('month', date(date_parse(CONCAT(year, '-', month, '-', day), '%Y-%m-%d'))) as event_month, * "
@@ -16,6 +16,7 @@ from aws_cdk import (
import os
from . import aws_metrics_constants
from .aws_utils import resource_name_sanitizer
class BatchProcessing:
@@ -42,7 +43,8 @@ class BatchProcessing:
"""
Generate the events processing lambda to filter the invalid metrics events.
"""
events_processing_lambda_name = f'{self._stack.stack_name}-EventsProcessingLambda'
events_processing_lambda_name = resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-EventsProcessingLambda', 'lambda_function')
self._create_events_processing_lambda_role(events_processing_lambda_name)
self._events_processing_lambda = lambda_.Function(
@@ -89,7 +91,8 @@ class BatchProcessing:
self._events_processing_lambda_role = iam.Role(
self._stack,
id='EventsProcessingLambdaRole',
role_name=f'{self._stack.stack_name}-EventsProcessingLambdaRole',
role_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-EventsProcessingLambdaRole', 'iam_role'),
assumed_by=iam.ServicePrincipal(
service='lambda.amazonaws.com'
),
@@ -107,8 +110,10 @@ class BatchProcessing:
self._events_firehose_delivery_stream = kinesisfirehose.CfnDeliveryStream(
self._stack,
id=f'{self._stack.stack_name}-EventsFirehoseDeliveryStream',
id=f'EventsFirehoseDeliveryStream',
delivery_stream_type='KinesisStreamAsSource',
delivery_stream_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-EventsFirehoseDeliveryStream', 'firehose_delivery_stream'),
kinesis_stream_source_configuration=kinesisfirehose.CfnDeliveryStream.KinesisStreamSourceConfigurationProperty(
kinesis_stream_arn=self._input_stream_arn,
role_arn=self._firehose_delivery_stream_role.role_arn
@@ -192,7 +197,8 @@ class BatchProcessing:
self._firehose_delivery_stream_log_group = logs.LogGroup(
self._stack,
id='FirehoseLogGroup',
log_group_name=f'{self._stack.stack_name}-FirehoseLogGroup',
log_group_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-FirehoseLogGroup', 'cloudwatch_log_group'),
removal_policy=core.RemovalPolicy.DESTROY,
retention=logs.RetentionDays.ONE_MONTH
)
@@ -299,7 +305,8 @@ class BatchProcessing:
self._firehose_delivery_stream_role = iam.Role(
self._stack,
id='GameEventsFirehoseRole',
role_name=f'{self._stack.stack_name}-GameEventsFirehoseRole',
role_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-GameEventsFirehoseRole', 'iam_role'),
assumed_by=iam.ServicePrincipal(
service='firehose.amazonaws.com'
),
+3 -1
View File
@@ -12,6 +12,7 @@ from aws_cdk import (
from . import aws_metrics_constants
from .layout_widget_construct import LayoutWidget
from .aws_utils import resource_name_sanitizer
class Dashboard:
@@ -28,7 +29,8 @@ class Dashboard:
events_processing_lambda_name: str = '',
) -> None:
self._dashboard_name = f"{stack.stack_name}-Dashboard"
self._dashboard_name = resource_name_sanitizer.sanitize_resource_name(
f'{stack.stack_name}-Dashboard', 'cloudwatch_dashboard')
self._dashboard = cloudwatch.Dashboard(
stack,
id="DashBoard",
@@ -12,10 +12,11 @@ from aws_cdk import (
aws_kinesis as kinesis
)
from . import aws_metrics_constants
import json
from . import aws_metrics_constants
from .aws_utils import resource_name_sanitizer
class DataIngestion:
"""
@@ -29,7 +30,8 @@ class DataIngestion:
self._input_stream = kinesis.Stream(
self._stack,
id='InputStream',
stream_name=f'{self._stack.stack_name}-InputStream',
stream_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-InputStream', 'kinesis_stream'),
shard_count=1
)
@@ -13,15 +13,18 @@ from aws_cdk import (
)
from . import aws_metrics_constants
from .aws_utils import resource_name_sanitizer
class DataLakeIntegration:
"""
Create the AWS resources including the S3 bucket, Glue database, table and crawler for data lake integration
"""
def __init__(self, stack: core.Construct, application_name: str) -> None:
def __init__(self, stack: core.Construct, application_name: str,
server_access_logs_bucket: str = None) -> None:
self._stack = stack
self._application_name = application_name
self._server_access_logs_bucket = server_access_logs_bucket
self._create_analytics_bucket()
self._create_events_database()
@@ -34,19 +37,31 @@ class DataLakeIntegration:
The bucket uses server-side encryption with a CMK managed by S3:
https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html
"""
# Enable server access logging if the server access logs bucket is provided following S3 best practices.
# See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html
server_access_logs_bucket = s3.Bucket.from_bucket_name(
self._stack,
f'{self._stack.stack_name}-ImportedAccessLogsBucket',
self._server_access_logs_bucket,
) if self._server_access_logs_bucket else None
# Bucket name cannot contain uppercase characters
# Do not specify the bucket name here since bucket name is required to be unique globally. If we set
# a specific name here, only one customer can deploy the bucket successfully.
self._analytics_bucket = s3.Bucket(
self._stack,
id=f'{self._stack.stack_name}-AnalyticsBucket'.lower(),
id=f'AnalyticsBucket'.lower(),
bucket_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AnalyticsBucket'.lower(), 's3_bucket'),
encryption=s3.BucketEncryption.S3_MANAGED,
block_public_access=s3.BlockPublicAccess(
block_public_acls=True,
block_public_policy=True,
ignore_public_acls=True,
restrict_public_buckets=True
)
),
server_access_logs_bucket=server_access_logs_bucket,
server_access_logs_prefix=f'{self._stack.stack_name}-AccessLogs' if server_access_logs_bucket else None
)
# For Amazon S3 buckets, you must delete all objects in the bucket for deletion to succeed.
@@ -285,7 +300,8 @@ class DataLakeIntegration:
self._events_crawler_role = iam.Role(
self._stack,
id='EventsCrawlerRole',
role_name=f'{self._stack.stack_name}-EventsCrawlerRole',
role_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-EventsCrawlerRole', 'iam_role'),
assumed_by=iam.ServicePrincipal(
service='glue.amazonaws.com'
),
@@ -16,6 +16,7 @@ from aws_cdk import (
import os
from . import aws_metrics_constants
from .aws_utils import resource_name_sanitizer
class RealTimeDataProcessing:
@@ -44,7 +45,8 @@ class RealTimeDataProcessing:
self._analytics_application = analytics.CfnApplication(
self._stack,
'AnalyticsApplication',
application_name=f'{self._stack.stack_name}-AnalyticsApplication',
application_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AnalyticsApplication', 'kinesis_application'),
inputs=[
analytics.CfnApplication.InputProperty(
input_schema=analytics.CfnApplication.InputSchemaProperty(
@@ -162,7 +164,8 @@ class RealTimeDataProcessing:
kinesis_analytics_role = iam.Role(
self._stack,
id='AnalyticsApplicationRole',
role_name=f'{self._stack.stack_name}-AnalyticsApplicationRole',
role_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AnalyticsApplicationRole', 'iam_role'),
assumed_by=iam.ServicePrincipal(
service='kinesisanalytics.amazonaws.com'
),
@@ -178,7 +181,8 @@ class RealTimeDataProcessing:
"""
Generate the analytics processing lambda to send processed data to CloudWatch for visualization.
"""
analytics_processing_function_name = f'{self._stack.stack_name}-AnalyticsProcessingLambdaName'
analytics_processing_function_name = resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AnalyticsProcessingLambdaName', 'lambda_function')
self._analytics_processing_lambda_role = self._create_analytics_processing_lambda_role(
analytics_processing_function_name
)
@@ -246,7 +250,8 @@ class RealTimeDataProcessing:
analytics_processing_lambda_role = iam.Role(
self._stack,
id='AnalyticsLambdaRole',
role_name=f'{self._stack.stack_name}-AnalyticsLambdaRole',
role_name=resource_name_sanitizer.sanitize_resource_name(
f'{self._stack.stack_name}-AnalyticsLambdaRole', 'iam_role'),
assumed_by=iam.ServicePrincipal(
service='lambda.amazonaws.com'
),
@@ -6,6 +6,8 @@
*
*/
#include <time.h>
#include <AzCore/Debug/AssetTrackingTypesImpl.h>
#include <AzCore/IO/SystemFile.h> // For AZ_MAX_PATH_LEN
#include <AzCore/Serialization/SerializeContext.h>
@@ -1,298 +0,0 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "PassAsset",
"ClassData": {
"PassTemplate": {
"Name": "ReflectionsParentPass_nomsaaTemplate",
"PassClass": "ParentPass",
"Slots": [
{
"Name": "NormalInput",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "SpecularF0Input",
"SlotType": "Input",
"ScopeAttachmentUsage": "Shader"
},
{
"Name": "DepthStencilInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "DepthStencil"
},
{
"Name": "SpecularInputOutput",
"SlotType": "InputOutput",
"ScopeAttachmentUsage": "RenderTarget"
},
{
"Name": "ReflectionOutput",
"SlotType": "Output",
"ScopeAttachmentUsage": "RenderTarget"
}
],
"Connections": [
{
"LocalSlot": "ReflectionOutput",
"AttachmentRef": {
"Pass": "ReflectionProbeRenderInnerPass",
"Attachment": "ReflectionInputOutput"
}
}
],
"PassRequests": [
{
"Name": "ReflectionProbeStencilPass",
"TemplateName": "ReflectionProbeStencilPassTemplate",
"Connections": [
{
"LocalSlot": "DepthStencilInputOutput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "DepthStencilInputOutput"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "reflectionprobestencil",
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "ReflectionProbeBlendWeightPass",
"TemplateName": "ReflectionProbeBlendWeightPassTemplate",
"Connections": [
{
"LocalSlot": "DepthStencilTextureInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "reflectionprobeblendweight",
"PipelineViewTag": "MainCamera",
"PassSrgShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionProbeBlendWeight.shader"
}
}
},
{
"Name": "ReflectionGlobalFullscreenPass",
"TemplateName": "ReflectionGlobalFullscreenPass_nomsaaTemplate",
"Connections": [
{
"LocalSlot": "DepthStencilTextureInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "NormalInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "NormalInput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "SpecularF0Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularF0Input"
}
},
{
"LocalSlot": "ReflectionBlendWeightInput",
"AttachmentRef": {
"Pass": "ReflectionProbeBlendWeightPass",
"Attachment": "Output"
}
}
],
"PassData": {
"$type": "FullscreenTrianglePassData",
"ShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionGlobalFullscreen_nomsaa.shader"
},
"StencilRef": 3, // See RenderCommon.h and ReflectionGlobalFullscreen_nomsaa.shader
"PipelineViewTag": "MainCamera"
}
},
{
"Name": "ReflectionProbeRenderOuterPass",
"TemplateName": "ReflectionProbeRenderOuterPassTemplate",
"Connections": [
{
"LocalSlot": "ReflectionInputOutput",
"AttachmentRef": {
"Pass": "ReflectionGlobalFullscreenPass",
"Attachment": "ReflectionOutput"
}
},
{
"LocalSlot": "DepthStencilTextureInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "NormalInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "NormalInput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "SpecularF0Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularF0Input"
}
},
{
"LocalSlot": "ReflectionBlendWeightInput",
"AttachmentRef": {
"Pass": "ReflectionProbeBlendWeightPass",
"Attachment": "Output"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderouter",
"PipelineViewTag": "MainCamera",
"PassSrgShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionProbeRenderOuter.shader"
}
}
},
{
"Name": "ReflectionProbeRenderInnerPass",
"TemplateName": "ReflectionProbeRenderInnerPassTemplate",
"Connections": [
{
"LocalSlot": "ReflectionInputOutput",
"AttachmentRef": {
"Pass": "ReflectionProbeRenderOuterPass",
"Attachment": "ReflectionInputOutput"
}
},
{
"LocalSlot": "DepthStencilTextureInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "NormalInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "NormalInput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "SpecularF0Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularF0Input"
}
}
],
"PassData": {
"$type": "RasterPassData",
"DrawListTag": "reflectionproberenderinner",
"PipelineViewTag": "MainCamera",
"PassSrgShaderAsset": {
"FilePath": "Shaders/Reflections/ReflectionProbeRenderInner.shader"
}
}
},
{
// Using cut-down version of screen space reflection for handling the attachment format compatibility issues.
// This is used for mobile devices with limited capabilities.
"Name": "ReflectionScreenSpaceMobilePass",
"TemplateName": "ReflectionScreenSpaceMobilePassTemplate",
"Enabled": false,
"Connections": [
{
"LocalSlot": "ReflectionInputOutput",
"AttachmentRef": {
"Pass": "ReflectionProbeRenderInnerPass",
"Attachment": "ReflectionInputOutput"
}
},
{
"LocalSlot": "NormalInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "NormalInput"
}
},
{
"LocalSlot": "DepthStencilInput",
"AttachmentRef": {
"Pass": "ReflectionProbeStencilPass",
"Attachment": "DepthStencilInputOutput"
}
},
{
"LocalSlot": "SpecularF0Input",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularF0Input"
}
},
{
"LocalSlot": "SpecularInput",
"AttachmentRef": {
"Pass": "Parent",
"Attachment": "SpecularInputOutput"
}
}
]
}
]
}
}
}
@@ -9,6 +9,10 @@
#include <Atom/RHI/Device.h>
#if defined(USE_NSIGHT_AFTERMATH)
#include <RHI/NsightAftermathGpuCrashTracker_Windows.h>
#endif
namespace AZ
{
namespace DX12
@@ -261,7 +261,7 @@ namespace AZ
VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME
} };
[[maybe_unused]] uint32_t optionalExtensionCount = sizeof(optionalExtensions) / sizeof(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME);
[[maybe_unused]] uint32_t optionalExtensionCount = aznumeric_cast<uint32_t>(optionalExtensions.size());
AZ_Assert(optionalExtensionCount == static_cast<uint32_t>(OptionalDeviceExtension::Count), "The order and size must match the enum OptionalDeviceExtensions.");
@@ -117,19 +117,24 @@ namespace AtomToolsFramework
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
}
// should the camera system respond to this particular event
static bool ShouldHandle(const AzFramework::ViewportControllerPriority priority, const bool exclusive)
// what priority should the camera system respond to
static AzFramework::ViewportControllerPriority GetPriority(const AzFramework::CameraSystem& cameraSystem)
{
// ModernViewportCameraControllerInstance receives events at all priorities, it should only respond
// to normal priority events if it is not in 'exclusive' mode and when in 'exclusive' mode it should
// only respond to the highest priority events
return !exclusive && priority == AzFramework::ViewportControllerPriority::Normal ||
exclusive && priority == AzFramework::ViewportControllerPriority::Highest;
// ModernViewportCameraControllerInstance receives events at all priorities, when it is in 'exclusive' mode
// or it is actively handling events (essentially when the camera system is 'active' and responding to inputs)
// it should only respond to the highest priority
if (cameraSystem.m_cameras.Exclusive() || cameraSystem.HandlingEvents())
{
return AzFramework::ViewportControllerPriority::Highest;
}
// otherwise it should only respond to normal priority events
return AzFramework::ViewportControllerPriority::Normal;
}
bool ModernViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event)
{
if (ShouldHandle(event.m_priority, m_cameraSystem.m_cameras.Exclusive()))
if (event.m_priority == GetPriority(m_cameraSystem))
{
return m_cameraSystem.HandleEvents(AzFramework::BuildInputEvent(event.m_inputChannel));
}
@@ -88,7 +88,7 @@ namespace AtomToolsFramework
[this](const AzFramework::InputChannel* inputChannel, QEvent* event)
{
AzFramework::NativeWindowHandle windowId = reinterpret_cast<AzFramework::NativeWindowHandle>(winId());
if (m_controllerList->HandleInputChannelEvent({GetId(), windowId, *inputChannel}))
if (m_controllerList->HandleInputChannelEvent(AzFramework::ViewportControllerInputEvent{GetId(), windowId, *inputChannel}))
{
// If the controller handled the input event, mark the event as accepted so it doesn't continue to propagate.
if (event)
@@ -7,6 +7,7 @@
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/createdestroy.h>
namespace AZ
@@ -217,4 +217,79 @@ namespace UnitTest
e.Deactivate();
}
TEST_F(PythonThreadingTest, PythonInterface_DebugTrace_CallsOnTick)
{
enum class LogTypes
{
Skip = 0,
OnPrewarning
};
m_testSink.m_evaluateMessage = [](const char* window, const char* message) -> int
{
if (AzFramework::StringFunc::Equal(window, "python"))
{
if (AzFramework::StringFunc::StartsWith(message, "OnPrewarning"))
{
return aznumeric_cast<int>(LogTypes::OnPrewarning);
}
}
return aznumeric_cast<int>(LogTypes::Skip);
};
AZ::Entity e;
Activate(e);
SimulateEditorBecomingInitialized();
try
{
// prepare handler on this thread
pybind11::exec(R"(
import azlmbr.debug
def on_prewarning(args):
print ('OnPrewarning: ' + args[0])
handler = azlmbr.debug.TraceMessageBusHandler()
handler.connect()
handler.add_callback('OnPreWarning', on_prewarning)
)");
const size_t numWarnings = 64;
auto doWarning = []()
{
AZ_Warning("PythonThreadingTest", false, "This is a warning message");
};
// start threads. In thread issue a warning.
AZStd::vector<AZStd::thread> threads;
threads.reserve(numWarnings);
for (size_t i = 0; i < numWarnings; ++i)
{
threads.emplace_back(doWarning);
}
for (AZStd::thread& thread : threads)
{
thread.join();
}
// No prewarning calls should have happened because all of them were queued
EXPECT_EQ(0, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::OnPrewarning)]);
// Do one tick
const float timeOneFrameSeconds = 0.016f; //approx 60 fps
AZ::TickBus::Broadcast(&AZ::TickEvents::OnTick,
timeOneFrameSeconds,
AZ::ScriptTimePoint(AZStd::chrono::system_clock::now()));
// After one tick all the queued calls should have been processed
EXPECT_EQ(numWarnings, m_testSink.m_evaluationMap[aznumeric_cast<int>(LogTypes::OnPrewarning)]);
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Error("UnitTest", false, "Failed during thread test with %s", e.what());
}
e.Deactivate();
}
}
@@ -6,6 +6,8 @@
*
*/
#include <time.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
@@ -238,7 +238,7 @@ public:
void ProcessAudio(AudioBufferList* bufferList)
{
AudioBuffer sourceBuffer = bufferList->mBuffers[0];
m_captureData->AddData((int16*)sourceBuffer.mData, sourceBuffer.mDataByteSize / 2, m_config.m_numChannels);
m_captureData->AddData((AZ::s16*)sourceBuffer.mData, sourceBuffer.mDataByteSize / 2, m_config.m_numChannels);
}
@@ -176,7 +176,7 @@ RewindableFixedVector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count']
AZStd::fixed_vector<{{ Property.attrib['Type'] }}, {{ Property.attrib['Count'] }}> m_{{ LowerFirst(Property.attrib['Name']) }};
{% endif %}
{% elif Property.attrib['IsRewindable']|booleanTrue %}
Multiplayer::RewindableObject<{{ Property.attrib['Type'] }}, Multiplayer::RewindHistorySize> m_{{ LowerFirst(Property.attrib['Name']) }} = {{ Property.attrib['Init'] }};
Multiplayer::RewindableObject<{{ Property.attrib['Type'] }}, Multiplayer::RewindHistorySize> m_{{ LowerFirst(Property.attrib['Name']) }} { {{ Property.attrib['Init'] }} };
{% else %}
{{ Property.attrib['Type'] }} m_{{ LowerFirst(Property.attrib['Name']) }} = {{ Property.attrib['Init'] }};
{% endif %}
@@ -35,7 +35,7 @@ namespace AZ::MeshBuilder
MeshBuilderSkinningInfo(size_t numOrgVertices);
void AddInfluence(size_t orgVtxNr, const Influence& influence) { mInfluences.resize(AZStd::max(mInfluences.size(), orgVtxNr)); mInfluences.at(orgVtxNr).emplace_back(influence); }
void AddInfluence(size_t orgVtxNr, const Influence& influence) { mInfluences.resize(AZStd::max(mInfluences.size(), orgVtxNr + 1)); mInfluences.at(orgVtxNr).emplace_back(influence); }
void RemoveInfluence(size_t orgVtxNr, size_t influenceNr) { mInfluences.at(orgVtxNr).erase(mInfluences.at(orgVtxNr).begin() + influenceNr); }
const Influence& GetInfluence(size_t orgVtxNr, size_t influenceNr) const { return mInfluences.at(orgVtxNr).at(influenceNr); }
size_t GetNumInfluences(size_t orgVtxNr) const { return mInfluences.at(orgVtxNr).size(); }
@@ -105,7 +105,6 @@ namespace AZ::SceneGenerationComponents
// Vector3 as a key into a unordered_map.
template <class MeshDataType>
class Vector3Map
: private AZStd::unordered_map<AZ::Vector3, AZ::u32>
{
public:
Vector3Map(const MeshDataType* meshData, bool hasBlendShapes, float positionTolerance)
@@ -116,9 +115,6 @@ namespace AZ::SceneGenerationComponents
{
}
using AZStd::unordered_map<AZ::Vector3, AZ::u32>::reserve;
using AZStd::unordered_map<AZ::Vector3, AZ::u32>::size;
AZ::u32 operator[](const AZ::u32 vertexIndex)
{
if (m_hasBlendShapes)
@@ -130,7 +126,7 @@ namespace AZ::SceneGenerationComponents
return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex));
}
const auto& [iter, didInsert] = try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex);
const auto& [iter, didInsert] = m_map.try_emplace(GetPositionForIndex(vertexIndex), m_currentOriginalVertexIndex);
if (didInsert)
{
++m_currentOriginalVertexIndex;
@@ -149,11 +145,32 @@ namespace AZ::SceneGenerationComponents
return m_meshData->GetUsedPointIndexForControlPoint(m_meshData->GetControlPointIndex(vertexIndex));
}
auto iter = find(GetPositionForIndex(vertexIndex));
AZSTD_CONTAINER_ASSERT(iter != end(), "Element with key is not present");
auto iter = m_map.find(GetPositionForIndex(vertexIndex));
AZSTD_CONTAINER_ASSERT(iter != m_map.end(), "Element with key is not present");
return iter->second;
}
[[nodiscard]] size_t size() const
{
if (m_hasBlendShapes)
{
// Since blend shapes are present, the vertex welding is disabled, and the map will always be empty.
// Use the underlying mesh's vertex count instead.
return m_meshData->GetUsedControlPointCount();
}
return m_map.size();
}
void reserve(size_t count)
{
if (m_hasBlendShapes)
{
// Since blend shapes are present, the vertex welding is disabled, and the map will always be empty.
return;
}
m_map.reserve(count);
}
private:
AZ::Vector3 GetPositionForIndex(const AZ::u32 vertexIndex) const
@@ -167,6 +184,7 @@ namespace AZ::SceneGenerationComponents
) * m_positionTolerance;
}
AZStd::unordered_map<AZ::Vector3, AZ::u32> m_map;
const MeshDataType* m_meshData;
bool m_hasBlendShapes;
float m_positionTolerance;
@@ -186,20 +186,6 @@ namespace ScriptCanvas
if (auto editContext = serializeContext->GetEditContext())
{
auto propertyChoices = [] {
AZStd::vector< AZStd::pair<VariableFlags::InitialValueSource, AZStd::string>> choices;
choices.emplace_back(AZStd::make_pair(VariableFlags::InitialValueSource::Graph, s_InitialValueSourceNames[0]));
choices.emplace_back(AZStd::make_pair(VariableFlags::InitialValueSource::Component, s_InitialValueSourceNames[1]));
return choices;
};
auto scopeChoices = [] {
AZStd::vector< AZStd::pair<VariableFlags::Scope, AZStd::string>> choices;
choices.emplace_back(AZStd::make_pair(VariableFlags::Scope::Graph, s_ScopeNames[0]));
choices.emplace_back(AZStd::make_pair(VariableFlags::Scope::Function, s_ScopeNames[1]));
return choices;
};
editContext->Class<GraphVariable>("Variable", "Represents a Variable field within a Script Canvas Graph")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetVisibility)
@@ -208,7 +194,7 @@ namespace ScriptCanvas
->Attribute(AZ::Edit::Attributes::DescriptionTextOverride, &GraphVariable::GetDescriptionOverride)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &GraphVariable::m_InitialValueSource, "Initial Value Source", "Variables can get their values from within the graph or through component properties.")
->Attribute(AZ::Edit::Attributes::GenericValueList, propertyChoices)
->Attribute(AZ::Edit::Attributes::GenericValueList, &GraphVariable::GetPropertyChoices)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &GraphVariable::OnInitialValueSourceChanged)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetInputControlVisibility)
@@ -219,7 +205,7 @@ namespace ScriptCanvas
->DataElement(AZ::Edit::UIHandlers::ComboBox, &GraphVariable::m_scope, "Scope", "Controls the scope of this variable. i.e. If this is exposed as input to this script, or output from this script, or if the variable is just locally scoped.")
->Attribute(AZ::Edit::Attributes::Visibility, &GraphVariable::GetScopeControlVisibility)
->Attribute(AZ::Edit::Attributes::GenericValueList, scopeChoices)
->Attribute(AZ::Edit::Attributes::GenericValueList, &GraphVariable::GetScopeChoices)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &GraphVariable::OnScopeTypedChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &GraphVariable::m_networkProperties, "Network Properties", "Enables whether or not this value should be network synchronized")
@@ -182,6 +182,22 @@ namespace ScriptCanvas
private:
AZStd::vector<AZStd::pair<unsigned char, AZStd::string>> GetPropertyChoices() const
{
AZStd::vector< AZStd::pair<unsigned char, AZStd::string>> choices;
choices.emplace_back(AZStd::make_pair(static_cast<unsigned char>(VariableFlags::InitialValueSource::Graph), s_InitialValueSourceNames[0]));
choices.emplace_back(AZStd::make_pair(static_cast<unsigned char>(VariableFlags::InitialValueSource::Component), s_InitialValueSourceNames[1]));
return choices;
}
AZStd::vector<AZStd::pair<unsigned char, AZStd::string>> GetScopeChoices() const
{
AZStd::vector< AZStd::pair<unsigned char, AZStd::string>> choices;
choices.emplace_back(AZStd::make_pair(static_cast<unsigned char>(VariableFlags::Scope::Graph), s_ScopeNames[0]));
choices.emplace_back(AZStd::make_pair(static_cast<unsigned char>(VariableFlags::Scope::Function), s_ScopeNames[1]));
return choices;
}
bool IsInFunction() const;
void OnScopeTypedChanged();
@@ -12,6 +12,8 @@
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/string/string.h>
namespace AZ::IO
{
+4 -8
View File
@@ -40,22 +40,18 @@ endif()
# If you keep packages after downloading, then they can be moved to a network share
# or checked into source control so that others on the same project can avoid re-downloading
set(LY_PACKAGE_KEEP_AFTER_DOWNLOADING TRUE CACHE BOOL "If enabled, packages will be kept after downloading them for later re-use")
set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION ${LY_3RDPARTY_PATH}/downloaded_packages CACHE PATH "You can make it store the packages in a folder of your choosing")
set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION @LY_3RDPARTY_PATH@/downloaded_packages CACHE PATH "Download location for packages (Defaults to @LY_3RDPARTY_PATH@/downloaded_packages)")
if (DEFINED ENV{LY_PACKAGE_DOWNLOAD_CACHE_LOCATION})
set(LY_PACKAGE_DOWNLOAD_CACHE_LOCATION $ENV{LY_PACKAGE_DOWNLOAD_CACHE_LOCATION})
endif()
string(CONFIGURE ${LY_PACKAGE_DOWNLOAD_CACHE_LOCATION} LY_PACKAGE_DOWNLOAD_CACHE_LOCATION @ONLY)
# LY_PACKAGE_UNPACK_LOCATION - you can change this to any path reachable.
set(LY_PACKAGE_UNPACK_LOCATION ${LY_3RDPARTY_PATH}/packages CACHE PATH "Location to unpack downloaded packages to")
set(LY_PACKAGE_UNPACK_LOCATION @LY_3RDPARTY_PATH@/packages CACHE PATH "Unpack location of downloaded packages (Defaults to @LY_3RDPARTY_PATH@/packages)")
if (DEFINED ENV{LY_PACKAGE_UNPACK_LOCATION})
set(LY_PACKAGE_UNPACK_LOCATION $ENV{LY_PACKAGE_UNPACK_LOCATION})
endif()
# note that sometimes the user configures first without populating LY_3RDPARTY_PATH
# in that case, we'll try overwriting the cache value, only if it is blank:
if (NOT LY_PACKAGE_UNPACK_LOCATION)
set(LY_PACKAGE_UNPACK_LOCATION ${LY_3RDPARTY_PATH}/packages CACHE PATH "Location to unpack downloaded packages to" FORCE )
endif()
string(CONFIGURE ${LY_PACKAGE_UNPACK_LOCATION} LY_PACKAGE_UNPACK_LOCATION @ONLY)
# while developing you can set one or both to true to force auto downloads from your local cache
set(LY_PACKAGE_VALIDATE_CONTENTS FALSE CACHE BOOL "If enabled, will fully validate every file in every package based on the SHA256SUMS file from the package")
+10 -5
View File
@@ -392,10 +392,10 @@ function(ly_delayed_target_link_libraries)
endif()
if(item_type STREQUAL MODULE_LIBRARY)
target_include_directories(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_INCLUDE_DIRECTORIES>)
target_link_libraries(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_LINK_LIBRARIES>)
target_compile_definitions(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_COMPILE_DEFINITIONS>)
target_compile_options(${target} ${visibility} $<TARGET_PROPERTY:${item},INTERFACE_COMPILE_OPTIONS>)
target_include_directories(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_INCLUDE_DIRECTORIES>>)
target_link_libraries(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_LINK_LIBRARIES>>)
target_compile_definitions(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_COMPILE_DEFINITIONS>>)
target_compile_options(${target} ${visibility} $<GENEX_EVAL:$<TARGET_PROPERTY:${item},INTERFACE_COMPILE_OPTIONS>>)
else()
ly_parse_third_party_dependencies(${item})
target_link_libraries(${target} ${visibility} ${item})
@@ -660,7 +660,12 @@ function(ly_get_vs_folder_directory absolute_target_source_dir output_source_dir
if(is_target_prefix_of_engine_root)
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir)
else()
cmake_path(GET absolute_target_source_dir RELATIVE_PART relative_target_source_dir)
cmake_path(IS_PREFIX CMAKE_SOURCE_DIR ${absolute_target_source_dir} is_target_prefix_of_source_dir)
if(is_target_prefix_of_source_dir)
cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE relative_target_source_dir)
else()
cmake_path(GET absolute_target_source_dir RELATIVE_PART relative_target_source_dir)
endif()
endif()
set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE)
+161 -71
View File
@@ -266,32 +266,42 @@ def CheckoutRepo(boolean disableSubmodules = false) {
palRm('commitdate')
}
def HandleDriveMount(String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean recreateVolume = false) {
unstash name: 'incremental_build_script'
def pythonCmd = ''
if(env.IS_UNIX) pythonCmd = 'sudo -E python3 -u '
else pythonCmd = 'python3 -u '
if(recreateVolume) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false)
}
timeout(5) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false)
}
if(env.IS_UNIX) {
sh label: 'Setting volume\'s ownership',
script: """
if sudo test ! -d "${workspace}"; then
sudo mkdir -p ${workspace}
cd ${workspace}/..
sudo chown -R lybuilder:root .
fi
"""
}
}
def PreBuildCommonSteps(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipeline, String branchName, String platform, String buildType, String workspace, boolean mount = true, boolean disableSubmodules = false) {
echo 'Starting pre-build common steps...'
if (mount) {
unstash name: 'incremental_build_script'
def pythonCmd = ''
if(env.IS_UNIX) pythonCmd = 'sudo -E python3 -u '
else pythonCmd = 'python3 -u '
if(env.RECREATE_VOLUME?.toBoolean()) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action delete --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Deleting volume', winSlashReplacement=false)
}
timeout(5) {
palSh("${pythonCmd} ${INCREMENTAL_BUILD_SCRIPT_PATH} --action mount --snapshot ${snapshot} --repository_name ${repositoryName} --project ${projectName} --pipeline ${pipeline} --branch ${branchName} --platform ${platform} --build_type ${buildType}", 'Mounting volume', winSlashReplacement=false)
}
if(env.IS_UNIX) {
sh label: 'Setting volume\'s ownership',
script: """
if sudo test ! -d "${workspace}"; then
sudo mkdir -p ${workspace}
cd ${workspace}/..
sudo chown -R lybuilder:root .
fi
"""
if(env.RECREATE_VOLUME?.toBoolean()){
echo 'Starting to recreating drive...'
HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, true)
} else {
echo 'Starting to mounting drive...'
HandleDriveMount(snapshot, repositoryName, projectName, pipeline, branchName, platform, buildType, workspace, false)
}
}
@@ -399,10 +409,14 @@ def PostBuildCommonSteps(String workspace, boolean mount = true) {
}
}
def CreateSetupStage(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars) {
def CreateSetupStage(Map pipelineConfig, String snapshot, String repositoryName, String projectName, String pipelineName, String branchName, String platformName, String jobName, Map environmentVars, boolean onlyMountEBSVolume = false) {
return {
stage('Setup') {
PreBuildCommonSteps(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
if(onlyMountEBSVolume) {
HandleDriveMount(snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], false)
} else {
PreBuildCommonSteps(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platformName, jobName, environmentVars['WORKSPACE'], environmentVars['MOUNT_VOLUME'])
}
}
}
}
@@ -439,6 +453,128 @@ def CreateTeardownStage(Map environmentVars) {
}
}
def CreateSingleNode(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName, boolean onlyMountEBSVolume = false) {
def nodeLabel = envVars['NODE_LABEL']
return {
node("${nodeLabel}") {
if(isUnix()) { // Has to happen inside a node
envVars['IS_UNIX'] = 1
}
withEnv(GetEnvStringList(envVars)) {
def build_job_name = build_job.key
try {
CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars, onlyMountEBSVolume).call()
if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
build_job.value.steps.each { build_step ->
build_job_name = build_step
CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
}
} else {
CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call()
}
}
catch(Exception e) {
// https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java
// {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED}
def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
if (currentResult == 'FAILURE') {
currentBuild.result = 'FAILURE'
error "FAILURE: ${e}"
} else if (currentResult == 'UNSTABLE') {
currentBuild.result = 'UNSTABLE'
unstable(message: "UNSTABLE: ${e}")
}
}
finally {
def params = platform.value.build_types[build_job_name].PARAMETERS
if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') {
def output_directory = params.OUTPUT_DIRECTORY
def configuration = params.CONFIGURATION
CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call()
}
if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') {
CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call()
}
CreateTeardownStage(envVars).call()
}
}
}
}
}
// Used in CreateBuildJobs() to preprocess the build_job steps to programically create
// Node sections with a set of steps that can run on that node.
class PipeStepJobData {
String m_nodeLabel = ""
def m_steps = []
PipeStepJobData(String label) {
this.m_nodeLabel = label
}
def addStep(def step) {
this.m_steps.add(step)
}
}
def CreateBuildJobs(Map pipelineConfig, def platform, def build_job, Map envVars, String branchName, String pipelineName, String repositoryName, String projectName) {
// if this is a pipeline, split jobs based on the NODE_LABEL
if(build_job.value.steps) {
def defaultLabel = envVars['NODE_LABEL']
def lastNodeLable = ""
def jobList = []
def currentIdx = -1;
// iterate the steps to build the order of node label + steps sets.
// Order matters, as it is executed from first to last.
// example layout.
// node A
// step 1
// step 2
// node B
// step 3
// node C
// step 4
build_job.value.steps.each { build_step ->
//if node label defined
if(platform.value.build_types[build_step] && platform.value.build_types[build_step].PIPELINE_ENV &&
platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL']) {
//if the last node label doen't match the new one, append it.
if(platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL'] != lastNodeLable) {
lastNodeLable = platform.value.build_types[build_step].PIPELINE_ENV['NODE_LABEL']
jobList.add(new PipeStepJobData(lastNodeLable))
currentIdx++
}
}
//no label define, so it needs to run on the default node label
else if(lastNodeLable != defaultLabel) { //if the last node is not the default, append default
lastNodeLable = defaultLabel
jobList.add(new PipeStepJobData(lastNodeLable))
currentIdx++
}
//add the build_step to the current node
jobList[currentIdx].addStep(build_step)
}
return {
jobList.eachWithIndex{ element, idx ->
//update the node label + steps to the discovered data
envVars['NODE_LABEL'] = element.m_nodeLabel
build_job.value.steps = element.m_steps
//no any additional nodes just mount the drive, do not handle clean parameters as that will be done by the first node.
boolean onlyMountEBSVolume = idx != 0;
//add this node
CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName, onlyMountEBSVolume).call()
}
}
} else {
return CreateSingleNode(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName)
}
}
def projectName = ''
def pipelineName = ''
def branchName = ''
@@ -527,55 +663,9 @@ try {
if (IsJobEnabled(branchName, build_job, pipelineName, platform.key)) { // User can filter jobs, jobs are tagged by pipeline
def envVars = GetBuildEnvVars(platform.value.PIPELINE_ENV ?: EMPTY_JSON, build_job.value.PIPELINE_ENV ?: EMPTY_JSON, pipelineName)
envVars['JOB_NAME'] = "${branchName}_${platform.key}_${build_job.key}" // backwards compatibility, some scripts rely on this
def nodeLabel = envVars['NODE_LABEL']
someBuildHappened = true
buildConfigs["${platform.key} [${build_job.key}]"] = {
node("${nodeLabel}") {
if(isUnix()) { // Has to happen inside a node
envVars['IS_UNIX'] = 1
}
withEnv(GetEnvStringList(envVars)) {
def build_job_name = build_job.key
try {
CreateSetupStage(pipelineConfig, snapshot, repositoryName, projectName, pipelineName, branchName, platform.key, build_job.key, envVars).call()
if(build_job.value.steps) { //this is a pipe with many steps so create all the build stages
build_job.value.steps.each { build_step ->
build_job_name = build_step
CreateBuildStage(pipelineConfig, platform.key, build_step, envVars).call()
}
} else {
CreateBuildStage(pipelineConfig, platform.key, build_job.key, envVars).call()
}
}
catch(Exception e) {
// https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/hudson/model/Result.java
// {SUCCESS,UNSTABLE,FAILURE,NOT_BUILT,ABORTED}
def currentResult = envVars['ON_FAILURE_MARK'] ?: 'FAILURE'
if (currentResult == 'FAILURE') {
currentBuild.result = 'FAILURE'
error "FAILURE: ${e}"
} else if (currentResult == 'UNSTABLE') {
currentBuild.result = 'UNSTABLE'
unstable(message: "UNSTABLE: ${e}")
}
}
finally {
def params = platform.value.build_types[build_job_name].PARAMETERS
if (env.MARS_REPO && params && params.containsKey('TEST_METRICS') && params.TEST_METRICS == 'True') {
def output_directory = params.OUTPUT_DIRECTORY
def configuration = params.CONFIGURATION
CreateTestMetricsStage(pipelineConfig, branchName, envVars, build_job_name, output_directory, configuration).call()
}
if (params && params.containsKey('TEST_RESULTS') && params.TEST_RESULTS == 'True') {
CreateExportTestResultsStage(pipelineConfig, platform.key, build_job_name, envVars, params).call()
}
CreateTeardownStage(envVars).call()
}
}
}
}
buildConfigs["${platform.key} [${build_job.key}]"] = CreateBuildJobs(pipelineConfig, platform, build_job, envVars, branchName, pipelineName, repositoryName, projectName)
}
}
}