merging latest dev

Signed-off-by: Gene Walters <genewalt@amazon.com>
This commit is contained in:
Gene Walters
2021-11-23 19:11:02 -08:00
1574 changed files with 24181 additions and 13745 deletions
+1 -1
View File
@@ -131,7 +131,7 @@ ly_add_source_properties(
PROPERTY COMPILE_DEFINITIONS
VALUES
O3DE_COPYRIGHT_YEAR=${LY_VERSION_COPYRIGHT_YEAR}
LY_BUILD=${LY_VERSION_BUILD_NUMBER}
LY_VERSION_BUILD_NUMBER=${LY_VERSION_BUILD_NUMBER}
${LY_PAL_TOOLS_DEFINES}
)
ly_add_source_properties(
+2 -6
View File
@@ -911,13 +911,9 @@ namespace
QWidget* g_splashScreen = nullptr;
}
QString FormatVersion(const SFileVersion& v)
QString FormatVersion([[maybe_unused]] const SFileVersion& v)
{
#if defined(LY_BUILD)
return QObject::tr("Version %1.%2.%3.%4 - Build %5").arg(v[3]).arg(v[2]).arg(v[1]).arg(v[0]).arg(LY_BUILD);
#else
return QObject::tr("Version %1.%2.%3.%4").arg(v[3]).arg(v[2]).arg(v[1]).arg(v[0]);
#endif
return QObject::tr("Version %1").arg(LY_VERSION_BUILD_NUMBER);
}
QString FormatRichTextCopyrightNotice()
@@ -130,10 +130,11 @@ namespace AZStd::Internal
//! Constructors
constexpr fixed_trivial_storage() = default;
fixed_trivial_storage() = default;
template <typename U, typename = enable_if_t<is_convertible_v<U, T>>>
constexpr fixed_trivial_storage(AZStd::initializer_list<U> ilist) noexcept
fixed_trivial_storage(AZStd::initializer_list<U> ilist) noexcept
: m_size(aznumeric_caster(ilist.size()))
{
AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity");
size_t index{};
@@ -141,20 +142,19 @@ namespace AZStd::Internal
{
m_data[index++] = element;
}
resize_no_construct(ilist.size());
}
constexpr pointer data() noexcept
pointer data() noexcept
{
return m_data;
}
constexpr const_pointer data() const noexcept
const_pointer data() const noexcept
{
return m_data;
}
//! Number of elements currently stored.
constexpr size_type size() const noexcept
size_type size() const noexcept
{
return m_size;
}
@@ -164,12 +164,12 @@ namespace AZStd::Internal
return Capacity;
}
//! Is the storage empty?
constexpr bool empty() const noexcept
bool empty() const noexcept
{
return size() == 0;
}
//! Is the storage full?
constexpr bool full() const noexcept
bool full() const noexcept
{
return size() == capacity();
}
@@ -186,7 +186,7 @@ namespace AZStd::Internal
//! Increases size of the storage by one.
//! Always fails for empty storage.
template <typename... Args, typename = enable_if_t<is_constructible_v<T, Args...>>>
constexpr reference emplace_back(Args&&... args) noexcept
reference emplace_back(Args&&... args) noexcept
{
AZSTD_CONTAINER_ASSERT(!full(), "emplace_back cannot be invoked on full storage");
reference new_element = *(data() + size());
@@ -196,7 +196,7 @@ namespace AZStd::Internal
}
//! Removes the last element of the storage.
//! Precondition: size is not empty
constexpr void pop_back() noexcept
void pop_back() noexcept
{
AZSTD_CONTAINER_ASSERT(!empty(), "pop_back cannot be invoked on empty storage");
resize_no_construct(size() - 1);
@@ -205,7 +205,7 @@ namespace AZStd::Internal
//! removing elements (unsafe).
//!
//! Updates the size of the container while checking that the new size is less than capacity
constexpr void resize_no_construct(size_t new_size) noexcept
void resize_no_construct(size_t new_size) noexcept
{
AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity");
m_size = aznumeric_cast<size_type>(new_size);
@@ -215,19 +215,19 @@ namespace AZStd::Internal
//! This does not modify the size of the storage
//! This is a no-op for trivial types
template <typename InputIt, typename = enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void unsafe_destroy(InputIt, InputIt) noexcept
void unsafe_destroy(InputIt, InputIt) noexcept
{
}
//! Destructs all elements of the storage.
//! This does not modify the size of the storage
//! This is a no-op for trivial types
constexpr void unsafe_destroy_all() noexcept
void unsafe_destroy_all() noexcept
{
}
private:
T m_data[Capacity]{};
T m_data[Capacity];
size_type m_size{};
};
@@ -245,7 +245,7 @@ namespace AZStd::Internal
using reference = T&;
using const_reference = const T&;
constexpr fixed_non_trivial_storage() = default;
fixed_non_trivial_storage() = default;
~fixed_non_trivial_storage() noexcept
{
@@ -253,7 +253,7 @@ namespace AZStd::Internal
}
template <typename U, typename = enable_if_t<is_convertible_v<U, T>>>
constexpr fixed_non_trivial_storage(AZStd::initializer_list<U> ilist) noexcept(noexcept(emplace_back(AZStd::declval<U>())))
fixed_non_trivial_storage(AZStd::initializer_list<U> ilist) noexcept(noexcept(emplace_back(AZStd::declval<U>())))
{
AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity");
for (const U& element : ilist)
@@ -272,7 +272,7 @@ namespace AZStd::Internal
}
//! Number of elements currently stored.
constexpr size_type size() const noexcept
size_type size() const noexcept
{
return m_size;
}
@@ -282,12 +282,12 @@ namespace AZStd::Internal
return Capacity;
}
//! Is the storage empty?
constexpr bool empty() const noexcept
bool empty() const noexcept
{
return size() == 0;
}
//! Is the storage full?
constexpr bool full() const noexcept
bool full() const noexcept
{
return size() == capacity();
}
@@ -325,7 +325,7 @@ namespace AZStd::Internal
//! removing elements (unsafe).
//!
//! Updates the size of the container while checking that the new size is less than capacity
constexpr void resize_no_construct(size_t new_size) noexcept
void resize_no_construct(size_t new_size) noexcept
{
AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity");
m_size = aznumeric_cast<size_type>(new_size);
@@ -402,23 +402,23 @@ namespace AZStd
//////////////////////////////////////////////////////////////////////////
// 23.2.4.1 construct/copy/destroy
constexpr fixed_vector() = default;
fixed_vector() = default;
constexpr explicit fixed_vector(size_type numElements, const_reference value = value_type())
explicit fixed_vector(size_type numElements, const_reference value = value_type())
{
resize_no_construct(numElements);
AZStd::uninitialized_fill_n(data(), numElements, value);
}
template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr fixed_vector(InputIt first, InputIt last)
fixed_vector(InputIt first, InputIt last)
{
resize_no_construct(AZStd::distance(first, last));
AZStd::uninitialized_copy(first, last, data());
}
constexpr fixed_vector(const fixed_vector& rhs)
fixed_vector(const fixed_vector& rhs)
{
resize_no_construct(rhs.size());
AZStd::uninitialized_copy(rhs.data(), rhs.data() + rhs.size(), data());
@@ -428,7 +428,7 @@ namespace AZStd
// It performs an AZStd::move on each of the fixed_vector elements instead
// of swapping pointers to the allocted memory address
// as it is unable to perform that operations due to the storage being baked into the container
constexpr fixed_vector(fixed_vector&& rhs)
fixed_vector(fixed_vector&& rhs)
{
resize_no_construct(rhs.size());
AZStd::uninitialized_move(rhs.data(), rhs.data() + rhs.size(), data());
@@ -440,7 +440,7 @@ namespace AZStd
// into a fixed_vector given that the type in question isn't the same type as this fixed_vector type
template <typename VectorContainer, typename = AZStd::enable_if_t<!AZStd::is_same_v<VectorContainer, fixed_vector>
&& !AZStd::is_convertible_v<VectorContainer, size_type>>>
constexpr fixed_vector(VectorContainer&& rhs)
fixed_vector(VectorContainer&& rhs)
{
constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer>
|| AZStd::is_const_v<VectorContainer>;
@@ -459,12 +459,12 @@ namespace AZStd
}
}
constexpr fixed_vector(AZStd::initializer_list<value_type> ilist)
fixed_vector(AZStd::initializer_list<value_type> ilist)
: base_type(ilist)
{
}
constexpr fixed_vector& operator=(const fixed_vector& rhs)
fixed_vector& operator=(const fixed_vector& rhs)
{
if (this == &rhs)
{
@@ -475,7 +475,7 @@ namespace AZStd
return assign_helper(rhs);
}
constexpr fixed_vector& operator=(fixed_vector&& rhs)
fixed_vector& operator=(fixed_vector&& rhs)
{
if (this == &rhs)
{
@@ -487,23 +487,23 @@ namespace AZStd
}
template <typename VectorContainer>
constexpr AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<VectorContainer>, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs)
AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<VectorContainer>, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs)
{
return assign_helper(AZStd::forward<VectorContainer>(rhs));
}
constexpr iterator begin() { return iterator(data()); }
constexpr const_iterator begin() const { return const_iterator(data()); }
constexpr const_iterator cbegin() const { return const_iterator(data()); }
constexpr iterator end() { return iterator(data() + size()); }
constexpr const_iterator end() const { return const_iterator(data() + size()); }
constexpr const_iterator cend() const { return const_iterator(data() + size()); }
constexpr reverse_iterator rbegin() { return reverse_iterator(end()); }
constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); }
constexpr reverse_iterator rend() { return reverse_iterator(begin()); }
constexpr const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
constexpr const_reverse_iterator crend() const { return const_reverse_iterator(begin()); }
iterator begin() { return iterator(data()); }
const_iterator begin() const { return const_iterator(data()); }
const_iterator cbegin() const { return const_iterator(data()); }
iterator end() { return iterator(data() + size()); }
const_iterator end() const { return const_iterator(data() + size()); }
const_iterator cend() const { return const_iterator(data() + size()); }
reverse_iterator rbegin() { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
const_reverse_iterator crend() const { return const_reverse_iterator(begin()); }
// bring in fixed_vector_storage functions into scope
using base_type::data;
@@ -514,7 +514,7 @@ namespace AZStd
// extension method
using base_type::resize_no_construct;
constexpr size_type size() const noexcept
size_type size() const noexcept
{
return base_type::size();
}
@@ -527,12 +527,12 @@ namespace AZStd
return base_type::max_size();
}
constexpr void resize(size_type newSize)
void resize(size_type newSize)
{
return resize(newSize, value_type{});
}
constexpr void resize(size_type newSize, const_reference value)
void resize(size_type newSize, const_reference value)
{
size_type dataSize = size();
if (dataSize < newSize)
@@ -547,7 +547,7 @@ namespace AZStd
// Removes unused capacity - For fixed_vector this only asserts
// that the supplied capacity is not longer than the fixed_vector capacity
constexpr void reserve(size_type newCapacity)
void reserve(size_type newCapacity)
{
// No-op - Implemented to provide consistent std::vector
AZSTD_CONTAINER_ASSERT(newCapacity <= capacity(),
@@ -556,79 +556,79 @@ namespace AZStd
}
// Removes unused capacity - For fixed_vector this does nothing
constexpr void shrink_to_fit()
void shrink_to_fit()
{
// No-op - Implemented to provide consistent std::vector
}
constexpr reference at(size_type position)
reference at(size_type position)
{
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position);
}
constexpr const_reference at(size_type position) const
const_reference at(size_type position) const
{
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position);
}
constexpr reference operator[](size_type position)
reference operator[](size_type position)
{
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position);
}
constexpr const_reference operator[](size_type position) const
const_reference operator[](size_type position) const
{
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position);
}
constexpr reference front()
reference front()
{
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!");
return *data();
}
constexpr const_reference front() const
const_reference front() const
{
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!");
return *data();
}
constexpr reference back()
reference back()
{
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!");
return *(data() + size() - 1);
}
constexpr const_reference back() const
const_reference back() const
{
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!");
return *(data() + size() - 1);
}
constexpr void push_back(const_reference value)
void push_back(const_reference value)
{
emplace_back(value);
}
constexpr void assign(size_type numElements, const_reference value)
void assign(size_type numElements, const_reference value)
{
clear();
insert(end(), numElements, value);
}
template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void assign(InputIt first, InputIt last)
void assign(InputIt first, InputIt last)
{
clear();
insert(end(), first, last);
}
constexpr void assign(AZStd::initializer_list<value_type> ilist)
void assign(AZStd::initializer_list<value_type> ilist)
{
assign(ilist.begin(), ilist.end());
}
template <typename... Args, typename = AZStd::enable_if_t<is_constructible_v<T, Args...>>>
constexpr iterator emplace(const_iterator insertPos, Args&&... args)
iterator emplace(const_iterator insertPos, Args&&... args)
{
AZSTD_CONTAINER_ASSERT(!full(), "Cannot emplace on a full fixed_vector");
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
@@ -645,18 +645,18 @@ namespace AZStd
AZStd::construct_at(insertPosPtr, AZStd::forward<Args>(args)...);
return iterator(insertPosPtr);
}
constexpr iterator insert(const_iterator insertPos, const_reference value)
iterator insert(const_iterator insertPos, const_reference value)
{
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
return emplace(insertPos, value);
}
constexpr iterator insert(const_iterator insertPos, value_type&& value)
iterator insert(const_iterator insertPos, value_type&& value)
{
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
return emplace(insertPos, AZStd::move(value));
}
constexpr void insert(const_iterator insertPos, size_type numElements, const_reference value)
void insert(const_iterator insertPos, size_type numElements, const_reference value)
{
if (numElements == 0)
{
@@ -708,24 +708,24 @@ namespace AZStd
}
template<class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void insert(const_iterator insertPos, InputIt first, InputIt last)
void insert(const_iterator insertPos, InputIt first, InputIt last)
{
// specialize for iterator categories.
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
insert_iter(insertPos, first, last, typename iterator_traits<InputIt>::iterator_category());
};
constexpr void insert(const_iterator insertPos, AZStd::initializer_list<value_type> ilist)
void insert(const_iterator insertPos, AZStd::initializer_list<value_type> ilist)
{
insert(insertPos, ilist.begin(), ilist.end());
}
constexpr iterator erase(const_iterator elementIter)
iterator erase(const_iterator elementIter)
{
return erase(elementIter, elementIter + 1);
}
constexpr iterator erase(const_iterator first, const_iterator last)
iterator erase(const_iterator first, const_iterator last)
{
AZSTD_CONTAINER_ASSERT(first >= cbegin() && last <= cend(), "erase iterator must be inside the range of fixed_vector container");
iterator dataStart = begin();
@@ -741,12 +741,12 @@ namespace AZStd
return dataStart + offset;
}
constexpr void clear()
void clear()
{
base_type::unsafe_destroy_all();
resize_no_construct(0);
}
constexpr void swap(fixed_vector& rhs)
void swap(fixed_vector& rhs)
{
// Fixed containers cannot swap pointers, they need to do full copies.
// The strategy is to extend the smaller fixed_vector to be the size
@@ -776,12 +776,12 @@ namespace AZStd
}
// Validate container status.
constexpr bool validate() const
bool validate() const
{
return size() <= max_size();
}
// Validate iterator.
constexpr int validate_iterator(const_iterator iter) const
int validate_iterator(const_iterator iter) const
{
const_pointer start = data();
const_pointer end = data() + size();
@@ -799,19 +799,19 @@ namespace AZStd
}
// pushes back an empty without a provided instance.
constexpr void push_back()
void push_back()
{
emplace_back();
}
constexpr void leak_and_reset()
void leak_and_reset()
{
resize_no_construct(0);
}
private:
template <typename VectorContainer>
constexpr fixed_vector& assign_helper(VectorContainer&& rhs)
fixed_vector& assign_helper(VectorContainer&& rhs)
{
constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer>
|| AZStd::is_const_v<VectorContainer>;
@@ -872,7 +872,7 @@ namespace AZStd
}
template<class Iterator>
constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&)
void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&)
{
size_type numElements = AZStd::distance(first, last);
if (numElements == 0)
@@ -923,7 +923,7 @@ namespace AZStd
}
template<class Iterator>
constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&)
void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&)
{
iterator dataStart = data();
size_type offset = AZStd::distance(dataStart, insertPos);
@@ -753,7 +753,7 @@ namespace UnitTest
TEST_F(Arrays, FixedVectorCanCopyAndMoveWithDifferentCapacity)
{
constexpr AZStd::fixed_vector<int, 32> sourceVector{ 1,2,3,4,5 };
AZStd::fixed_vector<int, 32> sourceVector{ 1,2,3,4,5 };
AZStd::fixed_vector<int, 8> copyConstructVector{ sourceVector };
EXPECT_EQ(sourceVector, copyConstructVector);
@@ -768,32 +768,32 @@ namespace UnitTest
AZStd::fixed_vector<int, 16> moveAssignVector = AZStd::move(moveConstructVector);
constexpr AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 };
AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 };
EXPECT_EQ(expectedVector, moveAssignVector);
}
TEST_F(Arrays, FixedVectorComparisonOperatorsSucceedAsExpected)
{
constexpr AZStd::fixed_vector<int, 32> testVector{ 1,2,3,4,5 };
constexpr AZStd::fixed_vector<int, 32> equalVector{ 1,2,3,4,5 };
constexpr AZStd::fixed_vector<int, 32> notEqualVectorDifferentSize{ 1,2,3,4,5,6 };
constexpr AZStd::fixed_vector<int, 32> lessVector{ 1,2,3,4,4 };
constexpr AZStd::fixed_vector<int, 32> greaterVectorDifferentSize{ 1,2,3,4,5, 1 };
AZStd::fixed_vector<int, 32> testVector{ 1,2,3,4,5 };
AZStd::fixed_vector<int, 32> equalVector{ 1,2,3,4,5 };
AZStd::fixed_vector<int, 32> notEqualVectorDifferentSize{ 1,2,3,4,5,6 };
AZStd::fixed_vector<int, 32> lessVector{ 1,2,3,4,4 };
AZStd::fixed_vector<int, 32> greaterVectorDifferentSize{ 1,2,3,4,5, 1 };
static_assert(testVector == equalVector);
static_assert(testVector != notEqualVectorDifferentSize);
static_assert(testVector != lessVector);
static_assert(lessVector < testVector);
static_assert(lessVector < greaterVectorDifferentSize);
static_assert(lessVector <= lessVector);
static_assert(lessVector <= testVector);
static_assert(lessVector <= greaterVectorDifferentSize);
static_assert(testVector > lessVector);
static_assert(testVector > lessVector);
static_assert(notEqualVectorDifferentSize > testVector);
static_assert(testVector >= testVector);
static_assert(testVector >= lessVector);
static_assert(greaterVectorDifferentSize > lessVector);
EXPECT_EQ(testVector, equalVector);
EXPECT_NE(testVector, notEqualVectorDifferentSize);
EXPECT_NE(testVector, lessVector);
EXPECT_LT(lessVector, testVector);
EXPECT_LT(lessVector, greaterVectorDifferentSize);
EXPECT_LE(lessVector, lessVector);
EXPECT_LE(lessVector, testVector);
EXPECT_LE(lessVector, greaterVectorDifferentSize);
EXPECT_GT(testVector, lessVector);
EXPECT_GT(testVector, lessVector);
EXPECT_GT(notEqualVectorDifferentSize, testVector);
EXPECT_GE(testVector, testVector);
EXPECT_GE(testVector, lessVector);
EXPECT_GT(greaterVectorDifferentSize, lessVector);
}
TEST_F(Arrays, VectorSwap)
@@ -239,12 +239,14 @@ namespace AzToolsFramework
, m_isInIsolationMode(false)
{
ToolsApplicationRequests::Bus::Handler::BusConnect();
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
m_undoCache.RegisterToUndoCacheInterface();
}
ToolsApplication::~ToolsApplication()
{
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
ToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop();
}
@@ -566,6 +568,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
@@ -585,6 +593,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
EntityIdList entitiesSelected;
entitiesSelected.reserve(entitiesToSelect.size());
@@ -608,6 +621,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
if (foundIter != m_selectedEntities.end())
{
@@ -625,6 +644,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end());
@@ -679,6 +703,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
// We're setting the selection set as a batch from an external caller.
// * Filter out any unselectable entities
// * Calculate selection/deselection delta so we can notify specific entities only on change.
@@ -1569,6 +1598,16 @@ namespace AzToolsFramework
}
}
void ToolsApplication::OnPrefabInstancePropagationBegin()
{
m_freezeSelectionUpdates = true;
}
void ToolsApplication::OnPrefabInstancePropagationEnd()
{
m_freezeSelectionUpdates = false;
}
void ToolsApplication::CreateUndosForDirtyEntities()
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -16,6 +16,7 @@
#include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/Commands/PreemptiveUndoCache.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#pragma once
@@ -29,6 +30,7 @@ namespace AzToolsFramework
class ToolsApplication
: public AzFramework::Application
, public ToolsApplicationRequests::Bus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{
public:
AZ_RTTI(ToolsApplication, "{2895561E-BE90-4CC3-8370-DD46FCF74C01}", AzFramework::Application);
@@ -169,6 +171,14 @@ namespace AzToolsFramework
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// PrefabPublicNotificationBus::Handler
void OnPrefabInstancePropagationBegin() override;
void OnPrefabInstancePropagationEnd() override;
//////////////////////////////////////////////////////////////////////////
void CreateUndosForDirtyEntities();
void ConsistencyCheckUndoCache();
AZ::Aabb m_selectionBounds;
@@ -181,6 +191,7 @@ namespace AzToolsFramework
bool m_isDuringUndoRedo;
bool m_isInIsolationMode;
EntityIdSet m_isolatedEntityIdSet;
bool m_freezeSelectionUpdates = false;
EditorEntityAPI* m_editorEntityAPI = nullptr;
@@ -449,16 +449,23 @@ namespace AzToolsFramework
AZStd::unordered_map<AZ::EntityId, AZStd::pair<AZ::EntityId, AZ::u64>>::const_iterator orderItr = m_savedOrderInfo.find(childId);
if (orderItr != m_savedOrderInfo.end() && orderItr->second.first == parentId)
{
bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second);
m_savedOrderInfo.erase(childId);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
bool isPrefabEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// If prefabs are enabled, rely on the component to do a sanity check instead of restoring the order from the model
if (!isPrefabEnabled)
{
parentInfo.OnChildSortOrderChanged();
bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second);
m_savedOrderInfo.erase(childId);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
{
parentInfo.OnChildSortOrderChanged();
}
}
}
else
@@ -8,11 +8,17 @@
#include "EditorEntitySortComponent.h"
#include "EditorEntityInfoBus.h"
#include "EditorEntityHelpers.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/std/sort.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
static_assert(sizeof(AZ::u64) == sizeof(AZ::EntityId), "We use AZ::EntityId for Persistent ID, which is a u64 under the hood. These must be the same size otherwise the persistent id will have to be rewritten");
@@ -51,6 +57,12 @@ namespace AzToolsFramework
;
}
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonEditorEntitySortComponentSerializer>()->HandlesType<EditorEntitySortComponent>();
}
}
void EditorEntitySortComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
@@ -167,9 +179,6 @@ namespace AzToolsFramework
}
MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true;
}
@@ -187,6 +196,10 @@ namespace AzToolsFramework
else
{
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
retval = AddChildEntityInternal(entityId, false, insertPosition);
}
@@ -220,9 +233,6 @@ namespace AzToolsFramework
MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true;
}
return false;
@@ -272,6 +282,12 @@ namespace AzToolsFramework
void EditorEntitySortComponent::OnPrefabInstancePropagationEnd()
{
m_ignoreIncomingOrderChanges = false;
if (m_shouldSanityCheckStateAfterPropagation)
{
SanitizeOrderEntryArray();
m_shouldSanityCheckStateAfterPropagation = false;
}
}
void EditorEntitySortComponent::MarkDirtyAndSendChangedEvent()
@@ -280,14 +296,8 @@ namespace AzToolsFramework
// one of the event listeners needs to build the InstanceDataHierarchy
m_entityOrderIsDirty = true;
// Force an immediate update for prefabs, which won't receive PrepareSave
bool isPrefabEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled)
{
PrepareSave();
}
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
}
@@ -308,9 +318,8 @@ namespace AzToolsFramework
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled)
{
PostLoad();
m_shouldSanityCheckStateAfterPropagation = true;
}
// Send out that the order for our entity is now updated
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
}
@@ -336,6 +345,73 @@ namespace AzToolsFramework
m_entityOrderIsDirty = false;
}
void EditorEntitySortComponent::SanitizeOrderEntryArray()
{
bool shouldEmitDirtyState = false;
// Remove invalid and duplicate entries that point at non-existent entities
AZStd::unordered_set<AZ::EntityId> duplicateIds;
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end();)
{
if (!it->IsValid() || GetEntityById(*it) == nullptr || duplicateIds.contains(*it))
{
it = m_childEntityOrderArray.erase(it);
shouldEmitDirtyState = true;
}
else
{
duplicateIds.insert(*it);
++it;
}
}
// Append any missing children
EntityIdList children;
AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end(); ++it)
{
if (auto removedChildrenIt = AZStd::remove(children.begin(), children.end(), *it); removedChildrenIt != children.end())
{
children.erase(removedChildrenIt);
}
}
AZStd::sort(children.begin(), children.end(), [](AZ::EntityId lhs, AZ::EntityId rhs)
{
return GetEntityById(lhs)->GetName() < GetEntityById(rhs)->GetName();
});
if (!children.empty())
{
shouldEmitDirtyState = true;
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
m_childEntityOrderArray.insert(insertPosition, children.begin(), children.end());
}
// Clear out the vector to be rebuilt from persistent id
m_childEntityOrderEntryArray.resize(m_childEntityOrderArray.size());
for (size_t i = 0; i < m_childEntityOrderArray.size(); ++i)
{
m_childEntityOrderEntryArray[i] = {
m_childEntityOrderArray[i],
static_cast<AZ::u64>(i)
};
}
RebuildEntityOrderCache();
if (shouldEmitDirtyState)
{
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::Bus::Events::AddDirtyEntity, GetEntityId());
}
m_entityOrderIsDirty = false;
}
void EditorEntitySortComponent::PostLoad()
{
// Clear out the vector to be rebuilt from persistent id
@@ -383,7 +459,7 @@ namespace AzToolsFramework
firstSelectedEntityPos = selectedEntityPos < firstSelectedEntityPos ? selectedEntityPos : firstSelectedEntityPos;
}
return firstSelectedEntityPos == m_childEntityOrderArray.end() ? m_childEntityOrderArray.begin() : firstSelectedEntityPos;
return firstSelectedEntityPos;
}
}
} // namespace AzToolsFramework
@@ -23,6 +23,8 @@ namespace AzToolsFramework
, public EditorEntityContextNotificationBus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{
friend class JsonEditorEntitySortComponentSerializer;
public:
AZ_COMPONENT(EditorEntitySortComponent, "{6EA1E03D-68B2-466D-97F7-83998C8C27F0}", EditorComponentBase);
@@ -64,6 +66,8 @@ namespace AzToolsFramework
void PrepareSave();
void PostLoad();
void SanitizeOrderEntryArray();
class EntitySortSerializationEvents
: public AZ::SerializeContext::IEventHandler
{
@@ -112,6 +116,7 @@ namespace AzToolsFramework
bool m_entityOrderIsDirty = true; ///< This flag indicates our stored serialization order data is out of date and must be rebuilt before serialization occurs
bool m_ignoreIncomingOrderChanges = false; ///< This is set when prefab propagation occurs so that non-authored order changes can be ignored
bool m_shouldSanityCheckStateAfterPropagation = false; //< This is set after activation, to queue a cleanup of any invalid state after the next prefab propagation.
};
}
} // namespace AzToolsFramework
@@ -0,0 +1,137 @@
/*
* 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
*
*/
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/std/sort.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
namespace AzToolsFramework::Components
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEditorEntitySortComponentSerializer, AZ::SystemAllocator, 0);
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Load(
void* outputValue,
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == outputValueTypeId,
"Unable to deserialize EditorEntitySortComponent from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<EditorEntitySortComponent*>(outputValue);
AZ_Assert(sortComponentInstance, "Output value for JsonEditorEntitySortComponentSerializer can't be null.");
JSR::ResultCode result(JSR::Tasks::ReadField);
{
JSR::ResultCode componentIdLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_id, azrtti_typeid<decltype(sortComponentInstance->m_id)>(), inputValue,
"Id", context);
result.Combine(componentIdLoadResult);
}
{
sortComponentInstance->m_childEntityOrderArray.clear();
JSR::ResultCode enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), inputValue, "Child Entity Order",
context);
// Migrate ChildEntityOrderEntryArray -> ChildEntityOrderArray
if (sortComponentInstance->m_childEntityOrderArray.empty())
{
enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderEntryArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderEntryArray)>(), inputValue,
"ChildEntityOrderEntryArray", context);
AZStd::sort(
sortComponentInstance->m_childEntityOrderEntryArray.begin(),
sortComponentInstance->m_childEntityOrderEntryArray.end(),
[](const EditorEntitySortComponent::EntityOrderEntry& lhs,
const EditorEntitySortComponent::EntityOrderEntry& rhs) -> bool
{
return lhs.m_sortIndex < rhs.m_sortIndex;
});
// Sort by index and copy to the order array, any duplicates or invalid entries will be cleaned up by the sanitization pass
sortComponentInstance->m_childEntityOrderArray.resize(sortComponentInstance->m_childEntityOrderEntryArray.size());
for (size_t i = 0; i < sortComponentInstance->m_childEntityOrderEntryArray.size(); ++i)
{
sortComponentInstance->m_childEntityOrderArray[i] = sortComponentInstance->m_childEntityOrderEntryArray[i].m_entityId;
}
}
sortComponentInstance->RebuildEntityOrderCache();
result.Combine(enryLoadResult);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorEntitySortComponent information."
: "Failed to load EditorEntitySortComponent information.");
}
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
[[maybe_unused]] const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == valueTypeId,
"Unable to Serialize EditorEntitySortComponent because the provided type is %s.",
valueTypeId.ToString<AZStd::string>().c_str());
const EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<const EditorEntitySortComponent*>(inputValue);
AZ_Assert(sortComponentInstance, "Input value for JsonEditorEntitySortComponentSerializer can't be null.");
const EditorEntitySortComponent* defaultsortComponentInstance =
reinterpret_cast<const EditorEntitySortComponent*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathName(context, "m_id");
const AZ::ComponentId* componentId = &sortComponentInstance->m_id;
const AZ::ComponentId* defaultComponentId =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_id : nullptr;
JSR::ResultCode resultComponentId = ContinueStoringToJsonObjectField(
outputValue, "Id", componentId, defaultComponentId, azrtti_typeid<decltype(sortComponentInstance->m_id)>(),
context);
result.Combine(resultComponentId);
}
{
AZ::ScopedContextPath subPathName(context, "m_childEntityOrderArray");
const EntityOrderArray* childEntityOrderArray = &sortComponentInstance->m_childEntityOrderArray;
const EntityOrderArray* defaultChildEntityOrderArray =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_childEntityOrderArray : nullptr;
JSR::ResultCode resultParentEntityId = ContinueStoringToJsonObjectField(
outputValue, "Child Entity Order", childEntityOrderArray, defaultChildEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), context);
result.Combine(resultParentEntityId);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorEntitySortComponent information."
: "Failed to store EditorEntitySortComponent information.");
}
} // namespace AzToolsFramework::Components
@@ -0,0 +1,31 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AzToolsFramework::Components
{
class JsonEditorEntitySortComponentSerializer
: public AZ::BaseJsonSerializer
{
public:
AZ_RTTI(JsonEditorEntitySortComponentSerializer, "{5104782E-B34F-4D87-B1DF-BDFB1AF20D58}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
AZ::JsonSerializationResult::Result Load(
void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result Store(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context) override;
};
} // namespace AzToolsFramework::Components
@@ -20,6 +20,7 @@
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
namespace AzToolsFramework
{
@@ -244,10 +245,10 @@ namespace AzToolsFramework
selectedEntityIds.erase(entityIdIterator--);
}
}
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
// Notify Propagation has ended
// Notify Propagation has ended, then update selection (which is frozen during propagation, so this order matters)
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotifications::OnPrefabInstancePropagationEnd);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
}
m_updatingTemplateInstancesInQueue = false;
@@ -28,6 +28,9 @@ namespace AzToolsFramework
inline static const char* EntityIdName = "Id";
inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity";
inline static const char* ComponentsName = "Components";
inline static const char* EntityOrderName = "Child Entity Order";
inline static const char* TypeName = "$type";
/**
* Find Prefab value from given parent value and target value's name.
@@ -212,7 +212,17 @@ namespace AzToolsFramework::Prefab
AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
return m_focusedInstanceContainerEntityId;
if (m_focusedInstanceContainerEntityId.IsValid())
{
return m_focusedInstanceContainerEntityId;
}
if (auto instance = GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId); instance.has_value())
{
return instance->get().GetContainerEntityId();
}
return AZ::EntityId();
}
bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const
@@ -366,7 +376,7 @@ namespace AzToolsFramework::Prefab
size_t index = 0;
size_t maxIndex = m_instanceFocusHierarchy.size() - 1;
for (const AZ::EntityId containerEntityId : m_instanceFocusHierarchy)
for (const AZ::EntityId& containerEntityId : m_instanceFocusHierarchy)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
if (instance.has_value())
@@ -404,7 +414,7 @@ namespace AzToolsFramework::Prefab
return;
}
for (const AZ::EntityId containerEntityId : instances)
for (const AZ::EntityId& containerEntityId : instances)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
@@ -423,7 +433,7 @@ namespace AzToolsFramework::Prefab
return;
}
for (const AZ::EntityId containerEntityId : instances)
for (const AZ::EntityId& containerEntityId : instances)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
@@ -9,6 +9,7 @@
#include <AzCore/Component/TransformBus.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -29,6 +30,7 @@
#include <AzToolsFramework/Prefab/PrefabUndo.h>
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <QString>
@@ -595,8 +597,41 @@ namespace AzToolsFramework
Instance& entityOwningInstance = owningInstanceOfParentEntity->get();
// Get the template for our owning instance from the root prefab DOM and use that to generate our patch
AZStd::vector<InstanceOptionalConstReference> pathOfInstances;
InstanceOptionalReference rootInstance = owningInstanceOfParentEntity;
while (rootInstance->get().GetParentInstance() != AZStd::nullopt)
{
pathOfInstances.emplace_back(rootInstance);
rootInstance = rootInstance->get().GetParentInstance();
}
AZStd::string aliasPathResult = "";
for (auto instanceIter = pathOfInstances.rbegin(); instanceIter != pathOfInstances.rend(); ++instanceIter)
{
aliasPathResult.append("/Instances/");
aliasPathResult.append((*instanceIter)->get().GetInstanceAlias());
}
PrefabDomPath rootPrefabDomPath(aliasPathResult.c_str());
PrefabDom& rootPrefabTemplateDom = m_prefabSystemComponentInterface->FindTemplateDom(rootInstance->get().GetTemplateId());
auto instanceDomFromRootValue = rootPrefabDomPath.Get(rootPrefabTemplateDom);
if (!instanceDomFromRootValue)
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDomValueReference instanceDomFromRoot = *instanceDomFromRootValue;
if (!instanceDomFromRoot.has_value())
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance);
instanceDomBeforeUpdate.CopyFrom(instanceDomFromRoot.value().get(), instanceDomBeforeUpdate.GetAllocator());
ScopedUndoBatch undoBatch("Add Entity");
@@ -674,6 +709,9 @@ namespace AzToolsFramework
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
bool isNewParentOwnedByDifferentInstance = false;
bool isInFocusTree = m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId);
bool isOwnedByFocusedPrefabInstance = m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId);
if (beforeParentId != afterParentId)
{
// If the entity parent changed, verify if the owning instance changed too
@@ -727,7 +765,7 @@ namespace AzToolsFramework
}
}
if (isInstanceContainerEntity)
if (isInFocusTree && !isOwnedByFocusedPrefabInstance)
{
if (isNewParentOwnedByDifferentInstance)
{
@@ -1648,6 +1686,144 @@ namespace AzToolsFramework
return true;
}
void PrefabPublicHandler::AddNewEntityToSortOrder(
Instance& owningInstance,
PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias,
const EntityAlias& entityToAddAlias)
{
// Find the parent entity to get its sort order component
auto findParentEntity = [&]() -> rapidjson::Value*
{
if (auto containerEntityIter = domToAddEntityUnder.FindMember(PrefabDomUtils::ContainerEntityName);
containerEntityIter != domToAddEntityUnder.MemberEnd())
{
if (parentEntityAlias == containerEntityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &containerEntityIter->value;
}
}
if (auto entitiesIter = domToAddEntityUnder.FindMember(PrefabDomUtils::EntitiesName);
entitiesIter != domToAddEntityUnder.MemberEnd())
{
for (auto entityIter = entitiesIter->value.MemberBegin(); entityIter != entitiesIter->value.MemberEnd(); ++entityIter)
{
if (parentEntityAlias == entityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &entityIter->value;
}
}
}
return nullptr;
};
rapidjson::Value* parentEntityValue = findParentEntity();
if (parentEntityValue == nullptr)
{
return;
}
// Get the list of selected entities, we'll insert our duplicated entities after the last selected
// sibling in their parent's list, e.g. for:
// - Entity1
// - Entity2 (selected)
// - Entity3
// - Entity4 (selected)
// - Entity5
// Our duplicate selection command would create duplicate Entity2 and Entity4 and insert them after Entity4:
// - Entity1
// - Entity2
// - Entity3
// - Entity4
// - Entity2 (new, selected after duplicate)
// - Entity4 (new, selected after duplicate)
// - Entity5
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
// Find the EditorEntitySortComponent DOM
auto componentsIter = parentEntityValue->FindMember(PrefabDomUtils::ComponentsName);
if (componentsIter == parentEntityValue->MemberEnd())
{
return;
}
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentIter->value.MemberEnd();
++componentIter)
{
// Check the component type
auto typeFieldIter = componentIter->value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == componentIter->value.MemberEnd())
{
continue;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
if (typeId != azrtti_typeid<Components::EditorEntitySortComponent>())
{
continue;
}
// Check for the entity order field
auto orderMembersIter = componentIter->value.FindMember(PrefabDomUtils::EntityOrderName);
if (orderMembersIter == componentIter->value.MemberEnd() || !orderMembersIter->value.IsArray())
{
continue;
}
// Scan for the last selected entity in the list (if any) to determine where to add our entries
rapidjson::Value newOrder(rapidjson::kArrayType);
auto insertValuesAfter = orderMembersIter->value.End();
for (auto orderMemberIter = orderMembersIter->value.Begin(); orderMemberIter != orderMembersIter->value.End();
++orderMemberIter)
{
if (!orderMemberIter->IsString())
{
continue;
}
const char* value = orderMemberIter->GetString();
for (AZ::EntityId selectedEntity : selectedEntities)
{
auto alias = owningInstance.GetEntityAlias(selectedEntity);
if (alias.has_value() && alias.value().get() == value)
{
insertValuesAfter = orderMemberIter;
break;
}
}
}
// Construct our new array with the new order - insertion may happen at end, so check for that in the loop itself
for (auto orderMemberIter = orderMembersIter->value.Begin();; ++orderMemberIter)
{
if (orderMemberIter != orderMembersIter->value.End())
{
newOrder.PushBack(orderMemberIter->Move(), domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == insertValuesAfter)
{
newOrder.PushBack(
rapidjson::Value(entityToAddAlias.c_str(), domToAddEntityUnder.GetAllocator()),
domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == orderMembersIter->value.End())
{
break;
}
}
// Replace the order with our newly constructed one
orderMembersIter->value.Swap(newOrder);
break;
}
}
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<EntityAlias, EntityAlias>& oldAliasToNewAliasMap)
@@ -1705,6 +1881,73 @@ namespace AzToolsFramework
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
EntityAlias parentEntityAlias;
if (auto componentsIter = entityDomAfter.FindMember(PrefabDomUtils::ComponentsName);
componentsIter != entityDomAfter.MemberEnd())
{
auto checkComponent = [&](const rapidjson::Value& value) -> bool
{
if (!value.IsObject())
{
return false;
}
// Check the component type
auto typeFieldIter = value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == value.MemberEnd())
{
return false;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
// Prefabs get serialized with the Editor transform component type, check for that
if (typeId != azrtti_typeid<Components::TransformComponent>())
{
return false;
}
if (auto parentEntityIter = value.FindMember("Parent Entity");
parentEntityIter != value.MemberEnd())
{
parentEntityAlias = parentEntityIter->value.GetString();
return true;
}
return false;
};
if (componentsIter->value.IsObject())
{
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentsIter->value.MemberEnd();
++componentIter)
{
if (checkComponent(componentIter->value))
{
break;
}
}
}
else if (componentsIter->value.IsArray())
{
for (auto componentIter = componentsIter->value.Begin(); componentIter != componentsIter->value.End();
++componentIter)
{
if (checkComponent(*componentIter))
{
break;
}
}
}
}
// Insert our entity into its parent's sort order
if (!parentEntityAlias.empty())
{
AddNewEntityToSortOrder(commonOwningInstance, domToAddDuplicatedEntitiesUnder, parentEntityAlias, newEntityAlias);
}
// Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast<rapidjson::SizeType>(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
@@ -78,6 +78,8 @@ namespace AzToolsFramework
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
void AddNewEntityToSortOrder(Instance& owningInstance, PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias, const EntityAlias& entityToAddAlias);
/**
* Duplicate a list of entities owned by a common owning instance by directly
@@ -1599,7 +1599,6 @@ namespace AzToolsFramework
for (size_t entityIndex = 1; entityIndex < m_selectedEntityIds.size(); ++entityIndex)
{
entity = GetSelectedEntityById(m_selectedEntityIds[entityIndex]);
AZ_Assert(entity, "Entity id selected for display but no such entity exists");
if (!entity)
{
continue;
@@ -150,10 +150,7 @@ namespace AzToolsFramework
TypeBeingHandled actualValue = instance;
for (int idx = 0; idx < m_common.GetElementCount(); ++idx)
{
if (elements[idx]->wasValueEditedByUser())
{
actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
}
actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
}
instance = actualValue;
}
@@ -27,7 +27,6 @@
#include <AzToolsFramework/Manipulators/ScaleManipulators.h>
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
@@ -893,37 +892,34 @@ namespace AzToolsFramework
prevModifiers = action.m_modifiers;
}
static void HandleAccents(
const bool hasSelectedEntities,
const AZ::EntityId entityIdUnderCursor,
const bool ctrlHeld,
AZ::EntityId& hoveredEntityId,
void HandleAccents(
const AZ::EntityId currentEntityIdUnderCursor,
AZ::EntityId& hoveredEntityIdUnderCursor,
const HandleAccentsContext& handleAccentsContext,
const ViewportInteraction::MouseButtons mouseButtons,
const bool usingBoxSelect)
const AZStd::function<void(AZ::EntityId, bool)>& setEntityAccentedFn)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const bool invalidMouseButtonHeld = mouseButtons.Middle() || mouseButtons.Right();
const bool hasSelectedEntities = handleAccentsContext.m_hasSelectedEntities;
const bool ctrlHeld = handleAccentsContext.m_ctrlHeld;
const bool boxSelect = handleAccentsContext.m_usingBoxSelect;
const bool stickySelect = handleAccentsContext.m_usingStickySelect;
const bool canSelect = stickySelect ? !hasSelectedEntities || ctrlHeld : true;
if ((hoveredEntityId.IsValid() && hoveredEntityId != entityIdUnderCursor) ||
(hasSelectedEntities && !ctrlHeld && hoveredEntityId.IsValid()) || invalidMouseButtonHeld)
const bool removePreviousAccent =
(currentEntityIdUnderCursor != hoveredEntityIdUnderCursor && hoveredEntityIdUnderCursor.IsValid()) || invalidMouseButtonHeld;
const bool addNextAccent = currentEntityIdUnderCursor.IsValid() && canSelect && !invalidMouseButtonHeld && !boxSelect;
if (removePreviousAccent)
{
if (hoveredEntityId.IsValid())
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, hoveredEntityId, false);
hoveredEntityId.SetInvalid();
}
setEntityAccentedFn(hoveredEntityIdUnderCursor, false);
hoveredEntityIdUnderCursor.SetInvalid();
}
if (!invalidMouseButtonHeld && !usingBoxSelect && (!hasSelectedEntities || ctrlHeld))
if (addNextAccent)
{
if (entityIdUnderCursor.IsValid())
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityIdUnderCursor, true);
hoveredEntityId = entityIdUnderCursor;
}
setEntityAccentedFn(currentEntityIdUnderCursor, true);
hoveredEntityIdUnderCursor = currentEntityIdUnderCursor;
}
}
@@ -1781,7 +1777,7 @@ namespace AzToolsFramework
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction);
m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
m_currentEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
@@ -1802,7 +1798,7 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction,
AZ::Aabb::CreateFromMinMax(boxPosition - scaledSize, boxPosition + scaledSize)))
{
m_cachedEntityIdUnderCursor = entityId;
m_currentEntityIdUnderCursor = entityId;
}
}
}
@@ -1822,7 +1818,7 @@ namespace AzToolsFramework
return true;
}
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
const AZ::EntityId entityIdUnderCursor = m_currentEntityIdUnderCursor;
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
@@ -3341,9 +3337,23 @@ namespace AzToolsFramework
m_cursorState.Update();
bool stickySelect = false;
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
stickySelect, viewportInfo.m_viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled);
HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = keyboardModifiers.Ctrl();
handleAccentsContext.m_hasSelectedEntities = !m_selectedEntityIds.empty();
handleAccentsContext.m_usingBoxSelect = m_boxSelect.Active();
handleAccentsContext.m_usingStickySelect = stickySelect;
HandleAccents(
!m_selectedEntityIds.empty(), m_cachedEntityIdUnderCursor, keyboardModifiers.Ctrl(), m_hoveredEntityId,
ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()), m_boxSelect.Active());
m_currentEntityIdUnderCursor, m_hoveredEntityId, handleAccentsContext,
ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()),
[](const AZ::EntityId entityId, bool highlighted)
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityId, highlighted);
});
const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock.value_or(ReferenceFrameFromModifiers(keyboardModifiers));
@@ -3589,7 +3599,8 @@ namespace AzToolsFramework
if (auto prefabFocusPublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabFocusPublicInterface>::Get())
{
AzFramework::EntityContextId editorEntityContextId = GetEntityContextId();
if (AZ::EntityId focusRoot = prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); focusRoot.IsValid())
if (AZ::EntityId focusRoot = prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
focusRoot.IsValid())
{
m_selectedEntityIds.erase(focusRoot);
}
@@ -3721,7 +3732,6 @@ namespace AzToolsFramework
break;
case ViewportEditorMode::Focus:
{
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RemoveViewportBorder);
}
@@ -317,7 +317,7 @@ namespace AzToolsFramework
void SetAllViewportUiVisible(bool visible);
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection.
@@ -357,6 +357,23 @@ namespace AzToolsFramework
bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements.
};
//! Bundles viewport state that impacts how accents are added/removed in HandleAccents.
struct HandleAccentsContext
{
bool m_hasSelectedEntities;
bool m_ctrlHeld;
bool m_usingBoxSelect;
bool m_usingStickySelect;
};
//! Updates whether accents (icon highlights) are added/removed for a given entity based on the cursor position.
void HandleAccents(
AZ::EntityId currentEntityIdUnderCursor,
AZ::EntityId& hoveredEntityIdUnderCursor,
const HandleAccentsContext& handleAccentsContext,
ViewportInteraction::MouseButtons mouseButtons,
const AZStd::function<void(AZ::EntityId, bool)>& setEntityAccentedFn);
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
//! the EditorTransformComponentSelection type. Functions in this namespace are exposed to facilitate testing
//! and should not be used outside of EditorTransformComponentSelection or EditorTransformComponentSelectionTests.
@@ -148,6 +148,8 @@ set(FILES
Entity/EditorEntitySortBus.h
Entity/EditorEntitySortComponent.cpp
Entity/EditorEntitySortComponent.h
Entity/EditorEntitySortComponentSerializer.cpp
Entity/EditorEntitySortComponentSerializer.h
Entity/EditorEntityTransformBus.h
Entity/PrefabEditorEntityOwnershipInterface.h
Entity/PrefabEditorEntityOwnershipService.h
@@ -15,10 +15,10 @@ namespace AzToolsFramework::EmbeddedPython
PythonLoader::PythonLoader()
{
constexpr char libPythonName[] = "libpython3.7m.so.1.0";
if (m_embeddedLibPythonHandle = dlopen(libPythonName, RTLD_NOW | RTLD_GLOBAL);
m_embeddedLibPythonHandle == nullptr)
m_embeddedLibPythonHandle = dlopen(libPythonName, RTLD_NOW | RTLD_GLOBAL);
if (m_embeddedLibPythonHandle == nullptr)
{
char* err = dlerror();
[[maybe_unused]] const char* err = dlerror();
AZ_Error("PythonLoader", false, "Failed to load %s with error: %s\n", libPythonName, err ? err : "Unknown Error");
}
}
@@ -2781,4 +2781,196 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithNoSelectionAndUnstickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId;
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool currentEntityIdAccentAdded = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&currentEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
});
using ::testing::Eq;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId));
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithSelectionAndUnstickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId;
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool currentEntityIdAccentAdded = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&currentEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
});
using ::testing::Eq;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId));
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndInvalidButton)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::Middle),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndDoingBoxSelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = true;
handleAccentsContext.m_usingStickySelect = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
}
// mimics the mouse moving off of hovered entity onto a new entity with sticky select enabled
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionAndStickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = true;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
}
TEST(HandleAccents, CurrentValidEntityIdDoesBecomeHoveredWithSelectionAndStickySelectAndCtrl)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = true;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = true;
bool currentEntityIdAccentAdded = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, &currentEntityIdAccentAdded, currentEntityId,
hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
EXPECT_THAT(hoveredEntityEntityId, Eq(AZ::EntityId(12345)));
}
} // namespace UnitTest
@@ -49,18 +49,17 @@ namespace
rlimit limit;
if (getrlimit(resource, &limit) != 0)
{
AZ_Error("Launcher", false, "[ERROR] Failed to get limit for resource %d. Error: %s", resource, strerror(errno));
return false;
AZ_Warning("Launcher", false, "[WARNING] Unable to get limit for resource %d. Error: %s", resource, strerror(errno));
}
if (updateLimit(limit))
{
if (setrlimit(resource, &limit) != 0)
{
AZ_Error("Launcher", false, "[ERROR] Failed to update resource limit for resource %d. Error: %s", resource, strerror(errno));
return false;
AZ_Warning("Launcher", false, "[WARNING] Unable to update resource limit for resource %d. Error: %s", resource, strerror(errno));
}
}
return true;
}
}
@@ -103,6 +103,8 @@ set(FILES
native/utilities/PlatformConfiguration.cpp
native/utilities/PlatformConfiguration.h
native/utilities/PotentialDependencies.h
native/utilities/StatsCapture.cpp
native/utilities/StatsCapture.h
native/utilities/SpecializedDependencyScanner.h
native/utilities/ThreadHelper.cpp
native/utilities/ThreadHelper.h
@@ -36,6 +36,7 @@ set(FILES
native/tests/platformconfiguration/platformconfigurationtests.h
native/tests/utilities/JobModelTest.cpp
native/tests/utilities/JobModelTest.h
native/tests/utilities/StatsCaptureTest.cpp
native/tests/AssetCatalog/AssetCatalogUnitTests.cpp
native/tests/assetscanner/AssetScannerTests.h
native/tests/assetscanner/AssetScannerTests.cpp
@@ -25,6 +25,7 @@
#include <native/AssetManager/PathDependencyManager.h>
#include <native/utilities/BuilderConfigurationBus.h>
#include <native/utilities/StatsCapture.h>
#include "AssetRequestHandler.h"
@@ -123,6 +124,9 @@ namespace AssetProcessor
{
if (status == AssetProcessor::AssetScanningStatus::Started)
{
// capture scanning stats:
AssetProcessor::StatsCapture::BeginCaptureStat("AssetScanning");
// Ensure that the source file list is populated before a scan begins
m_sourceFilesInDatabase.clear();
m_fileModTimes.clear();
@@ -176,6 +180,8 @@ namespace AssetProcessor
(status == AssetProcessor::AssetScanningStatus::Stopped))
{
m_isCurrentlyScanning = false;
AssetProcessor::StatsCapture::EndCaptureStat("AssetScanning");
// we cannot invoke this immediately - the scanner might be done, but we aren't actually ready until we've processed all remaining messages:
QMetaObject::invokeMethod(this, "CheckMissingFiles", Qt::QueuedConnection);
}
@@ -209,13 +215,24 @@ namespace AssetProcessor
}
else
{
QString statKey = QString("ProcessJob,%1,%2,%3").arg(jobEntry.m_databaseSourceName).arg(jobEntry.m_jobKey).arg(jobEntry.m_platformInfo.m_identifier.c_str());
if (status == JobStatus::InProgress)
{
//update to in progress status
m_jobRunKeyToJobInfoMap[jobEntry.m_jobRunKey].m_status = JobStatus::InProgress;
// stats tracking. Start accumulating time.
AssetProcessor::StatsCapture::BeginCaptureStat(statKey.toUtf8().constData());
}
else //if failed or succeeded remove from the map
{
// note that sometimes this gets called twice, once by the RCJobs thread and once by the AP itself,
// because sometimes jobs take a short cut from "started" -> "failed" or "started" -> "complete
// without going thru the RC.
// as such, all the code in this block should be crafted to work regardless of whether its double called.
AssetProcessor::StatsCapture::EndCaptureStat(statKey.toUtf8().constData());
m_jobRunKeyToJobInfoMap.erase(jobEntry.m_jobRunKey);
Q_EMIT SourceFinished(sourceUUID, legacySourceUUID);
Q_EMIT JobComplete(jobEntry, status);
@@ -3355,8 +3372,13 @@ namespace AssetProcessor
AZStd::string logFileName = AssetUtilities::ComputeJobLogFileName(createJobsRequest);
{
AssetUtilities::JobLogTraceListener jobLogTraceListener(logFileName, runKey, true);
// track the time it takes to createJobs. We can perform analysis later to present it by extension and other stats.
QString statKey = QString("CreateJobs,%1,%2").arg(actualRelativePath).arg(builderInfo.m_name.c_str());
AssetProcessor::StatsCapture::BeginCaptureStat(statKey.toUtf8().constData());
builderInfo.m_createJobFunction(createJobsRequest, createJobsResponse);
AssetProcessor::StatsCapture::EndCaptureStat(statKey.toUtf8().constData());
}
AssetProcessor::SetThreadLocalJobId(0);
bool isBuilderMissingFingerprint = (createJobsResponse.m_result == AssetBuilderSDK::CreateJobsResultCode::Success
@@ -4839,5 +4861,7 @@ namespace AssetProcessor
}
return filesFound;
}
} // namespace AssetProcessor
@@ -0,0 +1,200 @@
/*
* 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
*
*/
#include <native/tests/AssetProcessorTest.h>
#include <native/utilities/StatsCapture.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/StringFunc/StringFunc.h>
// the simple stats capture system has a trivial interface and only writes to printf.
// So the simplest tests we can do is make sure it only asserts when it should
// and doesn't assert in cases when it shouldn't, and that the stats are reasonable
// in printf format.
namespace AssetProcessor
{
// Its okay to talk to this system when unintialized, you can gain some perf
// by not intializing it at all
TEST_F(AssetProcessorTest, StatsCaptureTest_UninitializedSystemDoesNotAssert)
{
AssetProcessor::StatsCapture::BeginCaptureStat("Test");
AssetProcessor::StatsCapture::EndCaptureStat("Test");
AssetProcessor::StatsCapture::Dump();
AssetProcessor::StatsCapture::Shutdown();
}
// Double-intiailize is an error
TEST_F(AssetProcessorTest, StatsCaptureTest_DoubleInitializeIsAnAssert)
{
m_errorAbsorber->Clear();
AssetProcessor::StatsCapture::Initialize();
AssetProcessor::StatsCapture::Initialize();
EXPECT_EQ(m_errorAbsorber->m_numErrorsAbsorbed, 0);
EXPECT_EQ(m_errorAbsorber->m_numAssertsAbsorbed, 1); // not allowed to assert on this
AssetProcessor::StatsCapture::BeginCaptureStat("Test");
AssetProcessor::StatsCapture::Shutdown();
}
class StatsCaptureOutputTest : public AssetProcessorTest, public AZ::Debug::TraceMessageBus::Handler
{
public:
void SetUp() override
{
AssetProcessorTest::SetUp();
AssetProcessor::StatsCapture::Initialize();
}
// dump but also capture the dump as a vector of lines:
void Dump()
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
AssetProcessor::StatsCapture::Dump();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
virtual bool OnPrintf(const char* /*window*/, const char* message)
{
m_gatheredMessages.emplace_back(message);
AZ::StringFunc::TrimWhiteSpace(m_gatheredMessages.back(), true, true);
return false;
}
void TearDown() override
{
m_gatheredMessages = {};
AssetProcessor::StatsCapture::Shutdown();
AssetProcessorTest::TearDown();
}
AZStd::vector<AZStd::string> m_gatheredMessages;
};
// turning off machine and human readable mode, should not dump anything.
TEST_F(StatsCaptureOutputTest, StatsCaptureTest_DisabledByRegset_DumpsNothing)
{
auto registry = AZ::SettingsRegistry::Get();
ASSERT_NE(registry, nullptr);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", false);
AssetProcessor::StatsCapture::BeginCaptureStat("Test");
AssetProcessor::StatsCapture::EndCaptureStat("Test");
Dump();
EXPECT_EQ(m_gatheredMessages.size(), 0);
}
// turning on Human Readable, turn off Machine Readable, should not output any machine readable stats.
TEST_F(StatsCaptureOutputTest, StatsCaptureTest_HumanReadableOnly_DumpsNoMachineReadable)
{
auto registry = AZ::SettingsRegistry::Get();
ASSERT_NE(registry, nullptr);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", true);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", false);
AssetProcessor::StatsCapture::BeginCaptureStat("Test");
AssetProcessor::StatsCapture::EndCaptureStat("Test");
Dump();
EXPECT_GT(m_gatheredMessages.size(), 0);
for (const auto& message : m_gatheredMessages)
{
// we expect to see ZERO "Machine Readable" lines
EXPECT_FALSE(message.contains("MachineReadableStat:")) << "Found unexpected line in output: " << message.c_str();
}
}
// Turn on Machine Readable, Turn off Human Readable, ensure only Machine Readable stats emitted.
TEST_F(StatsCaptureOutputTest, StatsCaptureTest_MachineReadableOnly_DumpsNoHumanReadable)
{
auto registry = AZ::SettingsRegistry::Get();
ASSERT_NE(registry, nullptr);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", true);
AssetProcessor::StatsCapture::BeginCaptureStat("Test");
AssetProcessor::StatsCapture::EndCaptureStat("Test");
Dump();
for (const auto& message : m_gatheredMessages)
{
// we expect to see ONLY "Machine Readable" lines
EXPECT_TRUE(message.contains("MachineReadableStat:")) << "Found unexpected line in output: " << message.c_str();
}
EXPECT_GT(m_gatheredMessages.size(), 0);
}
// The interface for StatsCapture just captures and then dumps.
// For us to test this, we thus have to capture and parse the dump output.
TEST_F(StatsCaptureOutputTest, StatsCaptureTest_Sanity)
{
auto registry = AZ::SettingsRegistry::Get();
ASSERT_NE(registry, nullptr);
// Make it output in "machine raadable" format so that it is simpler to parse.
registry->Set("/Amazon/AssetProcessor/Settings/Stats/HumanReadable", false);
registry->Set("/Amazon/AssetProcessor/Settings/Stats/MachineReadable", true);
AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo,mybuilder");
AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo,mybuilder");
// Intentionally not using sleeps in this test. It means that the
// captured duration will be likely 0 but its not worth it to slow down tests.
// If the durations end up 0 its going to be extremely noticable in day-to-day use.
AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo,mybuilder");
AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo,mybuilder");
// for the second stat, we'll double capture and double end, in order to test debounce
AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo2,mybuilder");
AssetProcessor::StatsCapture::BeginCaptureStat("CreateJobs,foo2,mybuilder");
AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo2,mybuilder2");
AssetProcessor::StatsCapture::EndCaptureStat("CreateJobs,foo2,mybuilder2");
m_gatheredMessages.clear();
Dump();
EXPECT_GT(m_gatheredMessages.size(), 0);
// We'll parse the machine readable stat lines here and make sure that the following is true
// mybuilder appears
// mybuilder appears only once but count is 2
bool foundFoo = false;
bool foundFoo2 = false;
for (const auto& stat : m_gatheredMessages)
{
if (stat.contains("MachineReadableStat:"))
{
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(stat, tokens, ":", false, false);
ASSERT_EQ(tokens.size(), 5); // should be "MachineReadableStat:time:count:average:name)
const auto& countData = tokens[2];
const auto& nameData = tokens[4];
if (AZ::StringFunc::Equal(nameData, "CreateJobs,foo,mybuilder"))
{
EXPECT_FALSE(foundFoo); // should only find one of these
foundFoo = true;
EXPECT_STREQ(countData.c_str(), "2");
}
if (AZ::StringFunc::Equal(nameData, "CreateJobs,foo2,mybuilder2"))
{
EXPECT_FALSE(foundFoo2); // should only find one of these
foundFoo2 = true;
EXPECT_STREQ(countData.c_str(), "1");
}
}
}
EXPECT_TRUE(foundFoo) << "The expected token CreateJobs,foo,mybuilder did not appear in the output.";
EXPECT_TRUE(foundFoo2) << "The expected CreateJobs.foo2.mybuilder2 did not appear in the output";
}
}
@@ -16,7 +16,8 @@
#include <AzFramework/Logging/LoggingComponent.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include "native/resourcecompiler/RCBuilder.h"
#include <native/resourcecompiler/RCBuilder.h>
#include <native/utilities/StatsCapture.h>
#include <QLocale>
#include <QTranslator>
@@ -200,6 +201,10 @@ ApplicationManager::~ApplicationManager()
delete m_appDependencies[idx];
}
// end stats capture (dump and shutdown)
AssetProcessor::StatsCapture::Dump();
AssetProcessor::StatsCapture::Shutdown();
qInstallMessageHandler(nullptr);
//deleting QCoreApplication/QApplication
@@ -571,6 +576,8 @@ bool ApplicationManager::StartAZFramework()
bool ApplicationManager::ActivateModules()
{
AssetProcessor::StatsCapture::BeginCaptureStat("LoadingModules");
// we load the editor xml for our modules since it contains the list of gems we need for tools to function (not just runtime)
connect(&m_frameworkApp, &AssetProcessorAZApplication::AssetProcessorStatus, this,
[this](AssetProcessor::AssetProcessorStatusEntry entry)
@@ -587,6 +594,8 @@ bool ApplicationManager::ActivateModules()
}
m_frameworkApp.LoadDynamicModules();
AssetProcessor::StatsCapture::EndCaptureStat("LoadingModules");
return true;
}
@@ -618,6 +627,9 @@ ApplicationManager::BeforeRunStatus ApplicationManager::BeforeRun()
return ApplicationManager::BeforeRunStatus::Status_Failure;
}
// enable stats capture from this point on
AssetProcessor::StatsCapture::Initialize();
return ApplicationManager::BeforeRunStatus::Status_Success;
}
@@ -1173,6 +1173,7 @@ void ApplicationManagerBase::InitBuilderManager()
{
m_builderManager->ConnectionLost(connId);
});
}
void ApplicationManagerBase::ShutdownBuilderManager()
@@ -0,0 +1,394 @@
/*
* 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
*
*/
#include <native/utilities/StatsCapture.h>
#include <native/assetprocessor.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/chrono/clocks.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <inttypes.h>
namespace AssetProcessor
{
namespace StatsCapture
{
// This class captures stats by storing them in a map of type
// [name of stat] -> Stat struct
// It can then analyze these stats and produce more stats from the original
// Captures, before dumping.
class StatsCaptureImpl final
{
public:
AZ_CLASS_ALLOCATOR(StatsCaptureImpl, AZ::SystemAllocator, 0);
void BeginCaptureStat(AZStd::string_view statName);
void EndCaptureStat(AZStd::string_view statName);
void Dump();
private:
using timepoint = AZStd::chrono::high_resolution_clock::time_point;
using duration = AZStd::chrono::milliseconds;
struct StatsEntry
{
duration m_cumulativeTime = {}; // The total amount of time spent on this.
timepoint m_operationStartTime = {}; // Async tracking - the last time stamp an operation started.
int64_t m_operationCount = 0; // In case there's more than one sample. Used to calc average.
};
AZStd::unordered_map<AZStd::string, StatsEntry> m_stats;
bool m_dumpMachineReadableStats = false;
bool m_dumpHumanReadableStats = true;
// Make a friendly time string of the format nnHnnMhhS.xxxms
AZStd::string FormatDuration(const duration& duration)
{
int64_t milliseconds = duration.count();
constexpr int64_t millisecondsInASecond = 1000;
constexpr int64_t millisecondsInAMinute = millisecondsInASecond * 60;
constexpr int64_t millisecondsInAnHour = millisecondsInAMinute * 60;
int64_t hours = milliseconds / millisecondsInAnHour;
milliseconds -= hours * millisecondsInAnHour;
int64_t minutes = milliseconds / millisecondsInAMinute;
milliseconds -= minutes * millisecondsInAMinute;
int64_t seconds = milliseconds / millisecondsInASecond;
milliseconds -= seconds * millisecondsInASecond;
// omit the sections which dont make sense for readability
if (hours)
{
return AZStd::string::format("%02" PRId64 "h%02" PRId64 "m%02" PRId64 "s%03" PRId64 "ms" , hours, minutes, seconds, milliseconds);
}
else if (minutes)
{
return AZStd::string::format(" %02" PRId64 "m%02" PRId64 "s%03" PRId64 "ms", minutes, seconds, milliseconds);
}
else if (seconds)
{
return AZStd::string::format(" %02" PRId64 "s%03" PRId64 "ms", seconds, milliseconds);
}
return AZStd::string::format(" %03" PRId64 "ms", milliseconds);
}
// Prints out a single stat.
void PrintStat([[maybe_unused]] const char* name, duration milliseconds, int64_t count)
{
// note that name may be unused as it only appears in Trace macros, which are
// stripped out in release builds.
if (count <= 1)
{
count = 1;
}
duration average(static_cast<int64_t>(static_cast<double>(milliseconds.count()) / static_cast<double>(count)));
if (m_dumpHumanReadableStats)
{
if (count > 1)
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, " Time: %s, Count: %4" PRId64 ", Average: %s, EventName: %s\n",
FormatDuration(milliseconds).c_str(),
count,
FormatDuration(average).c_str(),
name);
}
else
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel, " Time: %s, EventName: %s\n",
FormatDuration(milliseconds).c_str(),
name);
}
}
if (m_dumpMachineReadableStats)
{
// machine Readable mode prints raw milliseconds and uses a CSV-like format
// note that the stat itself may contain commas, so we dont acutally separate with comma
// instead we separate with :
// and each "interesting line" is 'MachineReadableStat:milliseconds:count:average:name'
AZ_TracePrintf(AssetProcessor::ConsoleChannel, "MachineReadableStat:%" PRId64 ":%" PRId64 ":%" PRId64 ":%s\n",
milliseconds.count(),
count,
count > 1 ? average.count() : milliseconds.count(),
name);
}
}
// calls PrintStat on each element in the vector.
void PrintStatsArray(AZStd::vector<AZStd::string>& keys, int maxToPrint, const char* header)
{
if ((m_dumpHumanReadableStats)&&(header))
{
AZ_TracePrintf(AssetProcessor::ConsoleChannel,"Top %i %s\n", maxToPrint, header);
}
auto sortByTimeDescending = [&](const AZStd::string& s1, const AZStd::string& s2)
{
return this->m_stats[s1].m_cumulativeTime > this->m_stats[s2].m_cumulativeTime;
};
AZStd::sort(keys.begin(), keys.end(), sortByTimeDescending);
for (int idx = 0; idx < maxToPrint; ++idx)
{
if (idx < keys.size())
{
PrintStat(keys[idx].c_str(), m_stats[keys[idx]].m_cumulativeTime, m_stats[keys[idx]].m_operationCount);
}
}
}
};
void StatsCaptureImpl::BeginCaptureStat(AZStd::string_view statName)
{
StatsEntry& existingStat = m_stats[statName];
if (existingStat.m_operationStartTime != timepoint())
{
// prevent double 'Begins'
return;
}
existingStat.m_operationStartTime = AZStd::chrono::high_resolution_clock::now();
}
void StatsCaptureImpl::EndCaptureStat(AZStd::string_view statName)
{
StatsEntry& existingStat = m_stats[statName];
if (existingStat.m_operationStartTime != timepoint())
{
existingStat.m_cumulativeTime = AZStd::chrono::high_resolution_clock::now() - existingStat.m_operationStartTime;
existingStat.m_operationCount = existingStat.m_operationCount + 1;
existingStat.m_operationStartTime = timepoint(); // reset the start time so that double 'Ends' are ignored.
}
}
void StatsCaptureImpl::Dump()
{
timepoint startTimeStamp = AZStd::chrono::high_resolution_clock::now();
auto settingsRegistry = AZ::SettingsRegistry::Get();
int maxCumulativeStats = 5; // default max cumulative stats to show
int maxIndividualStats = 5; // default max individual files to show
if (settingsRegistry)
{
AZ::u64 cumulativeStats = static_cast<AZ::u64>(maxCumulativeStats);
AZ::u64 individualStats = static_cast<AZ::u64>(maxIndividualStats);
settingsRegistry->Get(m_dumpHumanReadableStats, "/Amazon/AssetProcessor/Settings/Stats/HumanReadable");
settingsRegistry->Get(m_dumpMachineReadableStats, "/Amazon/AssetProcessor/Settings/Stats/MachineReadable");
settingsRegistry->Get(cumulativeStats, "/Amazon/AssetProcessor/Settings/Stats/MaxCumulativeStats");
settingsRegistry->Get(individualStats, "/Amazon/AssetProcessor/Settings/Stats/MaxIndividualStats");
maxCumulativeStats = static_cast<int>(cumulativeStats);
maxIndividualStats = static_cast<int>(individualStats);
}
if ((!m_dumpHumanReadableStats)&&(!m_dumpMachineReadableStats))
{
return;
}
AZStd::vector<AZStd::string> allCreateJobs; // individual
AZStd::vector<AZStd::string> allCreateJobsByBuilder; // bucketed by builder
AZStd::vector<AZStd::string> allProcessJobs; // individual
AZStd::vector<AZStd::string> allProcessJobsByPlatform; // bucketed by platform
AZStd::vector<AZStd::string> allProcessJobsByJobKey; // bucketed by type of job (job key)
AZStd::vector<AZStd::string> allHashFiles;
// capture only existing keys as we will be expanding the stats
// this approach avoids mutating an iterator.
AZStd::vector<AZStd::string> statKeys;
for (const auto& element : m_stats)
{
statKeys.push_back(element.first);
}
for (const AZStd::string& statKey : statKeys)
{
const StatsEntry& statistic = m_stats[statKey];
// Createjobs stats encode like (CreateJobs,sourcefilepath,builderid)
if (AZ::StringFunc::StartsWith(statKey, "CreateJobs,", true))
{
allCreateJobs.push_back(statKey);
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(statKey, tokens, ",", false, false);
// look up the builder so you can get its name:
AZStd::string_view builderName = tokens[2];
// synthesize a stat to track per-builder createjobs times:
{
AZStd::string newStatKey = AZStd::string::format("CreateJobsByBuilder,%.*s", AZ_STRING_ARG(builderName));
auto insertion = m_stats.insert(newStatKey);
StatsEntry& statToSynth = insertion.first->second;
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
if (insertion.second)
{
allCreateJobsByBuilder.push_back(newStatKey);
}
}
// synthesize a stat to track total createjobs times:
{
StatsEntry& statToSynth = m_stats["CreateJobsTotal"];
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
}
}
else if (AZ::StringFunc::StartsWith(statKey, "ProcessJob,", true))
{
allProcessJobs.push_back(statKey);
// processjob has the format ProcessJob,sourcename,jobkey,platformname
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(statKey, tokens, ",", false, false);
AZStd::string_view jobKey = tokens[2];
AZStd::string_view platformName = tokens[3];
// synthesize a stat to record process time accumulated by job key platform
{
AZStd::string newStatKey = AZStd::string::format("ProcessJobsByPlatform,%.*s", AZ_STRING_ARG(platformName));
auto insertion = m_stats.insert(newStatKey);
StatsEntry& statToSynth = insertion.first->second;
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
if (insertion.second)
{
allProcessJobsByPlatform.push_back(newStatKey);
}
}
// synthesize a stat to record process time accumulated job key total across all platforms
{
AZStd::string newStatKey = AZStd::string::format("ProcessJobsByJobKey,%.*s", AZ_STRING_ARG(jobKey));
auto insertion = m_stats.insert(newStatKey);
StatsEntry& statToSynth = insertion.first->second;
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
if (insertion.second)
{
allProcessJobsByJobKey.push_back(newStatKey);
}
}
// synthesize a stat to track total processjob times:
{
StatsEntry& statToSynth = m_stats["ProcessJobsTotal"];
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
}
}
else if (AZ::StringFunc::StartsWith(statKey, "HashFile,", true))
{
allHashFiles.push_back(statKey);
// processjob has the format ProcessJob,sourcename,jobkey,platformname
// synthesize a stat to track total hash times:
StatsEntry& statToSynth = m_stats["HashFileTotal"];
statToSynth.m_cumulativeTime += statistic.m_cumulativeTime;
statToSynth.m_operationCount += statistic.m_operationCount;
}
}
StatsEntry& gemLoadStat = m_stats["LoadingModules"];
PrintStat("LoadingGems", gemLoadStat.m_cumulativeTime, 1);
// analysis-related stats
StatsEntry& totalScanTime = m_stats["AssetScanning"];
PrintStat("AssetScanning", totalScanTime.m_cumulativeTime, totalScanTime.m_operationCount);
StatsEntry& totalHashTime = m_stats["HashFileTotal"];
PrintStat("HashFileTotal", totalHashTime.m_cumulativeTime, totalHashTime.m_operationCount);
PrintStatsArray(allHashFiles, maxIndividualStats, "longest individual file hashes:");
// CreateJobs stats
StatsEntry& totalCreateJobs = m_stats["CreateJobsTotal"];
if (totalCreateJobs.m_operationCount)
{
PrintStat("CreateJobsTotal", totalCreateJobs.m_cumulativeTime, totalCreateJobs.m_operationCount);
PrintStatsArray(allCreateJobs, maxIndividualStats, "longest individual CreateJobs");
PrintStatsArray(allCreateJobsByBuilder, maxCumulativeStats, "longest CreateJobs By builder");
}
// ProcessJobs stats
StatsEntry& totalProcessJobs = m_stats["ProcessJobsTotal"];
if (totalProcessJobs.m_operationCount)
{
PrintStat("ProcessJobsTotal", totalProcessJobs.m_cumulativeTime, totalProcessJobs.m_operationCount);
PrintStatsArray(allProcessJobs, maxIndividualStats, "longest individual ProcessJob");
PrintStatsArray(allProcessJobsByJobKey, maxCumulativeStats, "cumulative time spent in ProcessJob by JobKey");
PrintStatsArray(allProcessJobsByPlatform, maxCumulativeStats, "cumulative time spent in ProcessJob by Platform");
}
duration costToGenerateStats = AZStd::chrono::high_resolution_clock::now() - startTimeStamp;
PrintStat("ComputeStatsTime", costToGenerateStats, 1);
}
// Public interface:
static StatsCaptureImpl* g_instance = nullptr;
//! call this one time before capturing stats.
void Initialize()
{
if (g_instance)
{
AZ_Assert(false, "An instance of StatsCaptureImpl already exists.");
return;
}
g_instance = aznew StatsCaptureImpl();
}
//! Call this one time as part of shutting down.
//! note that while it is an error to double-initialize, it is intentionally
//! not an error to call any other function when uninitialized, allowing this system
//! to essentially be "turned off" just by not initializing it in the first place.
void Shutdown()
{
if (g_instance)
{
delete g_instance;
g_instance = nullptr;
}
}
//! Start the clock running for a particular stat name.
void BeginCaptureStat(AZStd::string_view statName)
{
if (g_instance)
{
g_instance->BeginCaptureStat(statName);
}
}
//! Stop the clock running for a particular stat name.
void EndCaptureStat(AZStd::string_view statName)
{
if (g_instance)
{
g_instance->EndCaptureStat(statName);
}
}
//! Do additional processing and then write the cumulative stats to log.
//! Note that since this is an AP-specific system, the analysis done in the dump function
//! is going to make a lot of assumptions about the way the data is encoded.
void Dump()
{
if (g_instance)
{
g_instance->Dump();
}
}
}
}
@@ -0,0 +1,40 @@
/*
* 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
*
*/
// This is an AssetProcessor-only stats capture system. Its kept out-of-band
// from the rest of the Asset Processor systems so that it can avoid interfering
// with the rest of the processing decision making and other parts of AssetProcessor.
// This is not meant to be used anywhere except in AssetProcessor.
#pragma once
#include <AzCore/std/string/string_view.h>
namespace AssetProcessor
{
namespace StatsCapture
{
//! call this one time before capturing stats.
void Initialize();
//! Call this one time as part of shutting down.
void Shutdown();
//! Start the clock running for a particular stat name.
void BeginCaptureStat(AZStd::string_view statName);
//! Stop the clock running for a particular stat name.
void EndCaptureStat(AZStd::string_view statName);
//! Do additional processing and then write the cumulative stats to log.
//! Note that since this is an AP-specific system, the analysis done in the dump function
//! is going to make a lot of assumptions about the way the data is encoded.
void Dump();
}
}
@@ -10,9 +10,10 @@
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Math/Sha1.h>
#include "native/utilities/PlatformConfiguration.h"
#include "native/AssetManager/FileStateCache.h"
#include "native/AssetDatabase/AssetDatabase.h"
#include <native/utilities/PlatformConfiguration.h>
#include <native/utilities/StatsCapture.h>
#include <native/AssetManager/FileStateCache.h>
#include <native/AssetDatabase/AssetDatabase.h>
#include <utilities/ThreadHelper.h>
#include <QCoreApplication>
#include <QElapsedTimer>
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
{
// Attempt to use the Ninja build system if it is installed (described in the o3de documentation) if possible,
// otherwise default to the the default for Linux (Unix Makefiles)
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment());
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"});
QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles";
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
@@ -38,7 +38,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QStringList, QString> ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const
{
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment());
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"});
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher";
@@ -19,16 +19,15 @@ namespace O3DE::ProjectManager
// The list of clang C/C++ compiler command lines to validate on the host Linux system
const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"};
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
return AZ::Success(currentEnvironment);
return AZ::Success();
}
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{
// Validate that cmake is installed and is in the command line
auto whichCMakeResult = ProjectUtils::ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, QProcessEnvironment::systemEnvironment());
auto whichCMakeResult = ProjectUtils::ExecuteCommandResult("which", QStringList{ProjectCMakeCommand});
if (!whichCMakeResult.IsSuccess())
{
return AZ::Failure(QObject::tr("CMake not found. <br><br>"
@@ -39,8 +38,8 @@ namespace O3DE::ProjectManager
// Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE.
for (const QString& supportClangVersion : SupportedClangVersions)
{
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)});
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)});
if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess())
{
return AZ::Success(QString("clang-%1").arg(supportClangVersion));
@@ -54,7 +53,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
{
AZ::Outcome processEnvResult = GetCommandLineProcessEnvironment();
AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
if (!processEnvResult.IsSuccess())
{
return AZ::Failure(processEnvResult.GetError());
@@ -68,7 +67,6 @@ namespace O3DE::ProjectManager
}
QProcess process;
process.setProcessEnvironment(processEnvResult.GetValue());
// if the project build path is relative, it should be relative to the project path
process.setWorkingDirectory(projectPath);
@@ -88,7 +86,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog(
QString("%1/python/get_python.sh").arg(engineRoot),
{},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
@@ -19,16 +19,14 @@ namespace O3DE::ProjectManager
{
AZ::Outcome<QString, QString> QueryInstalledCmakeFullPath()
{
auto environmentRequest = ProjectUtils::GetCommandLineProcessEnvironment();
auto environmentRequest = ProjectUtils::SetupCommandLineProcessEnvironment();
if (!environmentRequest.IsSuccess())
{
return AZ::Failure(environmentRequest.GetError());
}
auto currentEnvironment = environmentRequest.GetValue();
auto queryCmakeInstalled = ProjectUtils::ExecuteCommandResult("which",
QStringList{ProjectCMakeCommand},
currentEnvironment);
QStringList{ProjectCMakeCommand});
if (!queryCmakeInstalled.IsSuccess())
{
return AZ::Failure(QObject::tr("Unable to detect CMake on this host."));
@@ -18,28 +18,36 @@ namespace O3DE::ProjectManager
{
namespace ProjectUtils
{
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{
// For CMake on Mac, if its installed through home-brew, then it will be installed
// under /usr/local/bin, which may not be in the system PATH environment.
// Add that path for the command line process so that it will be able to locate
// a home-brew installed version of CMake
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
QString pathValue = currentEnvironment.value("PATH");
pathValue += ":/usr/local/bin";
currentEnvironment.insert("PATH", pathValue);
return AZ::Success(currentEnvironment);
QString pathEnv = qEnvironmentVariable("PATH");
QStringList pathEnvList = pathEnv.split(":");
if (!pathEnvList.contains("/usr/local/bin"))
{
pathEnv += ":/usr/local/bin";
if (!qputenv("PATH", pathEnv.toStdString().c_str()))
{
return AZ::Failure(QObject::tr("Failed to set PATH environment variable"));
}
}
return AZ::Success();
}
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
QString pathValue = currentEnvironment.value("PATH");
pathValue += ":/usr/local/bin";
currentEnvironment.insert("PATH", pathValue);
AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
if (!processEnvResult.IsSuccess())
{
return AZ::Failure(processEnvResult.GetError());
}
// Validate that we have cmake installed first
auto queryCmakeInstalled = ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, currentEnvironment);
auto queryCmakeInstalled = ExecuteCommandResult("which", QStringList{ProjectCMakeCommand});
if (!queryCmakeInstalled.IsSuccess())
{
return AZ::Failure(QObject::tr("Unable to detect CMake on this host."));
@@ -47,7 +55,7 @@ namespace O3DE::ProjectManager
QString cmakeInstalledPath = queryCmakeInstalled.GetValue().split("\n")[0];
// Query the version of the installed cmake
auto queryCmakeVersionQuery = ExecuteCommandResult(cmakeInstalledPath, QStringList{"-version"}, currentEnvironment);
auto queryCmakeVersionQuery = ExecuteCommandResult(cmakeInstalledPath, QStringList{"-version"});
if (!queryCmakeVersionQuery.IsSuccess())
{
return AZ::Failure(QObject::tr("Unable to determine the version of CMake on this host."));
@@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
AZ_TracePrintf("Project Manager", "Cmake version %s detected.", queryCmakeVersionQuery.GetValue().split("\n")[0].toUtf8().constData());
// Query for the version of xcodebuild (if installed)
auto queryXcodeBuildVersion = ExecuteCommandResult("xcodebuild", QStringList{"-version"}, currentEnvironment);
auto queryXcodeBuildVersion = ExecuteCommandResult("xcodebuild", QStringList{"-version"});
if (!queryCmakeInstalled.IsSuccess())
{
return AZ::Failure(QObject::tr("Unable to detect XCodeBuilder on this host."));
@@ -104,7 +112,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog(
QString("%1/python/get_python.sh").arg(engineRoot),
{},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
@@ -21,7 +21,7 @@ namespace O3DE::ProjectManager
{
namespace ProjectUtils
{
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{
// Use the engine path to insert a path for cmake
auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
@@ -31,26 +31,34 @@ namespace O3DE::ProjectManager
}
auto engineInfo = engineInfoResult.GetValue();
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
// Append cmake path to PATH incase it is missing
// Append cmake path to the current environment PATH incase it is missing, since if
// we are starting CMake itself the current application needs to find it using Path
// This also takes affect for all child processes.
QDir cmakePath(engineInfo.m_path);
cmakePath.cd("cmake/runtime/bin");
QString pathValue = currentEnvironment.value("PATH");
pathValue += ";" + cmakePath.path();
currentEnvironment.insert("PATH", pathValue);
return AZ::Success(currentEnvironment);
QString pathEnv = qEnvironmentVariable("Path");
QStringList pathEnvList = pathEnv.split(";");
if (!pathEnvList.contains(cmakePath.path()))
{
pathEnv += ";" + cmakePath.path();
if (!qputenv("Path", pathEnv.toStdString().c_str()))
{
return AZ::Failure(QObject::tr("Failed to set Path environment variable"));
}
}
return AZ::Success();
}
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{
// Validate that cmake is installed
auto cmakeProcessEnvResult = GetCommandLineProcessEnvironment();
auto cmakeProcessEnvResult = SetupCommandLineProcessEnvironment();
if (!cmakeProcessEnvResult.IsSuccess())
{
return AZ::Failure(cmakeProcessEnvResult.GetError());
}
auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{"--version"}, cmakeProcessEnvResult.GetValue());
auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{"--version"});
if (!cmakeVersionQueryResult.IsSuccess())
{
return AZ::Failure(QObject::tr("CMake not found. \n\n"
@@ -104,7 +112,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
{
AZ::Outcome processEnvResult = GetCommandLineProcessEnvironment();
AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
if (!processEnvResult.IsSuccess())
{
return AZ::Failure(processEnvResult.GetError());
@@ -118,7 +126,6 @@ namespace O3DE::ProjectManager
}
QProcess process;
process.setProcessEnvironment(processEnvResult.GetValue());
// if the project build path is relative, it should be relative to the project path
process.setWorkingDirectory(projectPath);
@@ -139,7 +146,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog(
"cmd.exe",
QStringList{"/c", batPath},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
@@ -157,7 +163,7 @@ namespace O3DE::ProjectManager
.arg(shortcutPath)
.arg(targetPath)
.arg(arguments.join(' '));
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment());
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg});
if (!createShortcutResult.IsSuccess())
{
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
@@ -117,18 +117,16 @@ namespace O3DE::ProjectManager
// Show some kind of progress with very approximate estimates
UpdateProgress(++m_progressEstimate);
auto currentEnvironmentRequest = ProjectUtils::GetCommandLineProcessEnvironment();
auto currentEnvironmentRequest = ProjectUtils::SetupCommandLineProcessEnvironment();
if (!currentEnvironmentRequest.IsSuccess())
{
QStringToAZTracePrint(currentEnvironmentRequest.GetError());
return AZ::Failure(currentEnvironmentRequest.GetError());
}
QProcessEnvironment currentEnvironment = currentEnvironmentRequest.GetValue();
m_configProjectProcess = new QProcess(this);
m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_configProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeGenerateArgumentsResult = ConstructCmakeGenerateProjectArguments(engineInfo.m_thirdPartyPath);
if (!cmakeGenerateArgumentsResult.IsSuccess())
@@ -181,7 +179,6 @@ namespace O3DE::ProjectManager
m_buildProjectProcess = new QProcess(this);
m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_buildProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeBuildArgumentsResult = ConstructCmakeBuildCommandArguments();
if (!cmakeBuildArgumentsResult.IsSuccess())
@@ -520,12 +520,10 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
const QString& title)
{
QString resultOutput;
QProcess execProcess;
execProcess.setProcessEnvironment(processEnv);
execProcess.setProcessChannelMode(QProcess::MergedChannels);
QProgressDialog dialog(title, QObject::tr("Cancel"), /*minimum=*/0, /*maximum=*/0);
@@ -611,11 +609,9 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds /*= ProjectCommandLineTimeoutSeconds*/)
{
QProcess execProcess;
execProcess.setProcessEnvironment(processEnv);
execProcess.setProcessChannelMode(QProcess::MergedChannels);
execProcess.start(cmd, arguments);
if (!execProcess.waitForStarted())
@@ -47,7 +47,6 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds = ProjectCommandLineTimeoutSeconds);
/**
@@ -61,10 +60,9 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog(
const QString& cmd,
const QStringList& arguments,
const QProcessEnvironment& processEnv,
const QString& title);
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment();
AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment();
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);