Merge pull request #5877 from aws-lumberyard-dev/junbo/gitflow_211123_o3de

Merge stabilization/2110
This commit is contained in:
Junbo Liang
2021-11-23 14:25:41 -08:00
committed by GitHub
52 changed files with 921 additions and 283 deletions
@@ -95,7 +95,8 @@ def EntityOutliner_EntityOrdering():
entity_outliner_model.dropMimeData(
mime_data, QtCore.Qt.MoveAction, target_row, 0, target_index.parent()
)
QtWidgets.QApplication.processEvents()
# Wait after move to let events (i.e. prefab propagation) process
general.idle_wait(1.0)
# Move an entity before another entity in the order by dragging the source above the target
move_entity_before = lambda source_name, target_name: _move_entity(
@@ -119,24 +120,24 @@ def EntityOutliner_EntityOrdering():
# Our new entity should be given a name with a number automatically
new_entity = f"Entity{i+1}"
# The new entity should be added to the top of its parent entity
expected_order = [new_entity] + expected_order
# The new entity should be added to the bottom of its parent entity
expected_order = expected_order + [new_entity]
verify_entities_sorted(expected_order)
# 3) Move "Entity1" to the top of the order
move_entity_before("Entity1", "Entity5")
expected_order = ["Entity1", "Entity5", "Entity4", "Entity3", "Entity2"]
# 3) Move "Entity5" to the top of the order
move_entity_before("Entity5", "Entity1")
expected_order = ["Entity5", "Entity1", "Entity2", "Entity3", "Entity4"]
verify_entities_sorted(expected_order)
# 4) Move "Entity4" to the bottom of the order
move_entity_after("Entity4", "Entity2")
expected_order = ["Entity1", "Entity5", "Entity3", "Entity2", "Entity4"]
# 4) Move "Entity2" to the bottom of the order
move_entity_after("Entity2", "Entity4")
expected_order = ["Entity5", "Entity1", "Entity3", "Entity4", "Entity2"]
verify_entities_sorted(expected_order)
# 5) Add another new entity, ensure the rest of the order is unchanged
create_entity()
expected_order = ["Entity6", "Entity1", "Entity5", "Entity3", "Entity2", "Entity4"]
expected_order = ["Entity5", "Entity1", "Entity3", "Entity4", "Entity2", "Entity6"]
verify_entities_sorted(expected_order)
+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()
@@ -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;
}
@@ -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
@@ -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);
+28 -16
View File
@@ -60,6 +60,9 @@ ly_create_alias(
)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
include(${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME}/PAL_traits_editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
ly_add_target(
NAME AWSCore.Editor.Static STATIC
NAMESPACE Gem
@@ -97,22 +100,31 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::AWSCore.Editor.Static
)
# This target is not a real gem module
# It is not meant to be loaded by the ModuleManager in C++
ly_add_target(
NAME AWSCore.ResourceMappingTool MODULE
NAMESPACE Gem
OUTPUT_SUBDIRECTORY AWSCoreEditorQtBin
FILES_CMAKE
awscore_resourcemappingtool_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Include/Private
BUILD_DEPENDENCIES
PRIVATE
Gem::AWSCore.Editor.Static
)
ly_add_dependencies(AWSCore.Editor AWSCore.ResourceMappingTool)
if (PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL)
# This target is not a real gem module
# It is not meant to be loaded by the ModuleManager in C++
ly_add_target(
NAME AWSCore.ResourceMappingTool MODULE
NAMESPACE Gem
OUTPUT_SUBDIRECTORY AWSCoreEditorQtBin
FILES_CMAKE
awscore_resourcemappingtool_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Include/Private
BUILD_DEPENDENCIES
PRIVATE
Gem::AWSCore.Editor.Static
RUNTIME_DEPENDENCIES
3rdParty::pyside2
)
ly_add_dependencies(AWSCore.Editor AWSCore.ResourceMappingTool)
ly_install_directory(DIRECTORIES Tools/ResourceMappingTool)
endif()
# Builders and Tools (such as the Editor use AWSCore.Editor) use the .Editor module above.
ly_create_alias(
@@ -13,6 +13,8 @@
#include <QAction>
#include <QObject>
#include "AWSCoreEditor_Traits_Platform.h"
namespace AWSCore
{
class AWSCoreResourceMappingToolAction
@@ -22,7 +24,7 @@ namespace AWSCore
static constexpr const char AWSCoreResourceMappingToolActionName[] = "AWSCoreResourceMappingToolAction";
static constexpr const char ResourceMappingToolDirectoryPath[] = "Gems/AWSCore/Code/Tools/ResourceMappingTool";
static constexpr const char ResourceMappingToolLogDirectoryPath[] = "user/log/";
static constexpr const char EngineWindowsPythonEntryScriptPath[] = "python/python.cmd";
static constexpr const char EngineWindowsPythonEntryScriptPath[] = AWSCORE_EDITOR_PYTHON_COMMAND;
AWSCoreResourceMappingToolAction(const QString& text, QObject* parent = nullptr);
@@ -7,4 +7,6 @@
*/
#pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 0
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 1
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT ""
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.sh"
@@ -0,0 +1,9 @@
#
# 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
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL TRUE)
@@ -8,3 +8,5 @@
#pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 0
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT ""
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.sh"
@@ -0,0 +1,9 @@
#
# 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
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL FALSE)
@@ -8,3 +8,5 @@
#pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 1
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT "debug "
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.cmd"
@@ -0,0 +1,9 @@
#
# 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
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL TRUE)
@@ -58,7 +58,7 @@ namespace AWSCore
if (m_isDebug)
{
return AZStd::string::format(
"\"%s\" debug -B \"%s\" --binaries-path \"%s\" --debug --profile \"%s\" --config-path \"%s\" --log-path \"%s\"",
"\"%s\" " AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT "-B \"%s\" --binaries-path \"%s\" --debug --profile \"%s\" --config-path \"%s\" --log-path \"%s\"",
m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(),
profileName.c_str(), m_toolConfigDirectoryPath.c_str(), m_toolLogDirectoryPath.c_str());
}
@@ -39,6 +39,16 @@ Follow cmake instructions to configure your project, for example:
```
$ python\python.cmd debug Gems\AWSCore\Code\Tools\ResourceMappingTool\resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>\bin\debug\AWSCoreEditorQtBin
```
* Linux
* release mode
```
$ python/python.sh Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>/bin/profile/AWSCoreEditorQtBin
```
* debug mode
```
$ python/python.sh Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>/bin/debug/AWSCoreEditorQtBin
```
* Note - Editor is integrated with the same engine python environment to launch Resource Mapping Tool. If it is failed to launch the tool
in Editor, please follow above steps to make sure expected scripts/binaries are present.
@@ -21,6 +21,7 @@ argument_parser.add_argument('--debug', action='store_true', help='Execute on de
argument_parser.add_argument('--log-path', help='Path to resource mapping tool logging directory '
'(if not provided, logging file will be located at tool directory)')
argument_parser.add_argument('--profile', default='default', help='Named AWS profile to use for querying AWS resources')
arguments: Namespace = argument_parser.parse_args()
# logging setup
@@ -5,6 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
import platform
from typing import List
from unittest import TestCase
from unittest.mock import (ANY, call, MagicMock, patch)
@@ -27,14 +28,24 @@ class TestEnvironmentUtils(TestCase):
self.addCleanup(os_pathsep_patcher.stop)
self._mock_os_pathsep: MagicMock = os_pathsep_patcher.start()
def test_setup_qt_environment_global_flag_is_set(self) -> None:
@patch('os.path.exists')
@patch('ctypes.CDLL')
def test_setup_qt_environment_global_flag_is_set(self, mock_os_path_exists, mock_ctype_cdll) -> None:
mock_os_path_exists.return_value = True
environment_utils.setup_qt_environment("dummy")
self._mock_os_environ.copy.assert_called_once()
self._mock_os_pathsep.join.assert_called_once()
assert environment_utils.is_qt_linked() is True
if platform.system() == 'Linux':
mock_os_path_exists.assert_called()
def test_cleanup_qt_environment_global_flag_is_set(self) -> None:
@patch('os.path.exists')
@patch('ctypes.CDLL')
def test_cleanup_qt_environment_global_flag_is_set(self, mock_os_path_exists, mock_ctype_cdll) -> None:
mock_os_path_exists.return_value = True
environment_utils.setup_qt_environment("dummy")
assert environment_utils.is_qt_linked() is True
environment_utils.cleanup_qt_environment()
assert environment_utils.is_qt_linked() is False
if platform.system() == 'Linux':
mock_os_path_exists.assert_called()
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
import logging
import os
import platform
from typing import Dict
from utils import file_utils
@@ -38,6 +39,20 @@ def setup_qt_environment(bin_path: str) -> None:
new_path = os.pathsep.join([binaries_path, path])
os.environ['PATH'] = new_path
# On Linux, we need to load pyside2 and related modules as well
if platform.system() == 'Linux':
import ctypes
preload_shared_libs = [f'{bin_path}/libpyside2.abi3.so.5.14',
f'{bin_path}/libQt5Widgets.so.5']
for preload_shared_lib in preload_shared_libs:
if not os.path.exists(preload_shared_lib):
logger.error(f"Cannot find required shared library at {preload_shared_lib}")
return
else:
ctypes.CDLL(preload_shared_lib)
global qt_binaries_linked
qt_binaries_linked = True
@@ -8,12 +8,16 @@
#include <PrefabGroup/ProceduralAssetHandler.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzQtComponents/Components/Widgets/FileDialog.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <QMenu>
namespace AZ::Prefab
{
@@ -21,6 +25,7 @@ namespace AZ::Prefab
class PrefabGroupAssetHandler::AssetTypeInfoHandler final
: public AZ::AssetTypeInfoBus::Handler
, protected AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(AssetTypeInfoHandler, AZ::SystemAllocator, 0);
@@ -31,15 +36,21 @@ namespace AZ::Prefab
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
// AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries) override;
bool SaveAsAuthoredPrefab(const AZ::Data::AssetId& assetId, const char* destinationFilename);
};
PrefabGroupAssetHandler::AssetTypeInfoHandler::AssetTypeInfoHandler()
{
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<ProceduralPrefabAsset>());
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
}
PrefabGroupAssetHandler::AssetTypeInfoHandler::~AssetTypeInfoHandler()
{
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<ProceduralPrefabAsset>());
}
@@ -68,6 +79,84 @@ namespace AZ::Prefab
extensions.push_back(PrefabGroupAssetHandler::s_Extension);
}
void PrefabGroupAssetHandler::AssetTypeInfoHandler::AddContextMenuActions(
[[maybe_unused]] QWidget* caller,
QMenu* menu,
const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
{
using namespace AzToolsFramework::AssetBrowser;
auto entryIt = AZStd::find_if
(
entries.begin(),
entries.end(),
[](const AssetBrowserEntry* entry) -> bool
{
return entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product;
}
);
if (entryIt == entries.end())
{
return;
}
else if ((*entryIt)->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product)
{
ProductAssetBrowserEntry* product = azrtti_cast<ProductAssetBrowserEntry*>(*entryIt);
if (product->GetAssetType() == azrtti_typeid<ProceduralPrefabAsset>())
{
AZ::Data::AssetId assetId = product->GetAssetId();
menu->addAction("Save as Prefab...", [assetId, this]()
{
QString filePath = AzQtComponents::FileDialog::GetSaveFileName(nullptr, QString("Save to file"), "", QString("Prefab file (*.prefab)"));
if (filePath.isEmpty())
{
return;
}
if (SaveAsAuthoredPrefab(assetId, filePath.toUtf8().data()))
{
AZ_Printf("Prefab", "Prefab was saved to a .prefab file %s", filePath.toUtf8().data());
}
});
}
}
}
bool PrefabGroupAssetHandler::AssetTypeInfoHandler::SaveAsAuthoredPrefab(const AZ::Data::AssetId& assetId, const char* destinationFilename)
{
using namespace AzToolsFramework::Prefab;
using namespace AZ::Data;
auto procPrefabAsset = AssetManager::Instance().GetAsset<ProceduralPrefabAsset>(assetId, AssetLoadBehavior::Default);
const auto status = AssetManager::Instance().BlockUntilLoadComplete(procPrefabAsset);
if (status != AssetData::AssetStatus::Ready)
{
return false;
}
auto* prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (!prefabLoaderInterface)
{
return false;
}
const auto templateId = procPrefabAsset.GetAs<ProceduralPrefabAsset>()->GetTemplateId();
AZStd::string outputJson;
if (prefabLoaderInterface->SaveTemplateToString(templateId, outputJson) == false)
{
return false;
}
const auto fileMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText;
AZ::IO::FileIOStream outputFileStream;
if (outputFileStream.Open(destinationFilename, fileMode) == false)
{
return false;
}
outputFileStream.Write(outputJson.size(), outputJson.data());
return true;
}
// PrefabGroupAssetHandler
AZStd::string_view PrefabGroupAssetHandler::s_Extension{ "procprefab" };
@@ -1,92 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ScriptEventReferencesComponent.h"
namespace ScriptEvents
{
namespace Components
{
void ScriptEventReferencesComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
// The Script Event References component is no longer necessary, as all Script Event assets
// will be properly loaded as needed.
serializeContext->ClassDeprecate("ScriptEventReferencesComponent", "{D0F440AC-32D4-49EC-8B93-860B188266A6}");
}
}
void ScriptEventReferencesComponent::Activate()
{
for (auto& scriptEventReferences : m_scriptEventAssets)
{
const auto& asset = scriptEventReferences.GetAsset();
if (asset)
{
if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(asset.GetId()))
{
AZ::Data::AssetBus::MultiHandler::BusConnect(asset.GetId());
}
// Load the asset if it's not ready
if (!asset.IsReady())
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId());
if (assetInfo.m_assetId.IsValid())
{
AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), azrtti_typeid<ScriptEventsAsset>(), AZ::Data::AssetLoadBehavior::Default)
.BlockUntilLoadComplete();
}
}
}
else
{
AZ_Warning("Script Events", false, "ScriptEventReferencesComponent could not find Script Event asset: %s", scriptEventReferences.GetDefinition() ? scriptEventReferences.GetDefinition()->GetName().c_str() : scriptEventReferences.GetAsset().GetId().ToString<AZStd::string>().c_str());
}
}
}
void ScriptEventReferencesComponent::Deactivate()
{
for (auto& scriptEventReferences : m_scriptEventAssets)
{
const auto& asset = scriptEventReferences.GetAsset();
if (asset)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
}
}
}
void ScriptEventReferencesComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ScriptEventReference", 0x3df92d40));
}
void ScriptEventReferencesComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ScriptEventReference", 0x3df92d40));
}
void ScriptEventReferencesComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("LuaScriptService", 0x21d76c4b));
}
void ScriptEventReferencesComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (ScriptEventsAsset* scriptEventAsset = asset.GetAs<ScriptEventsAsset>())
{
scriptEventAsset->m_definition.RegisterInternal();
}
}
}
}
@@ -1,44 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <ScriptEvents/ScriptEventsAssetRef.h>
#include <AzCore/Asset/AssetCommon.h>
namespace ScriptEvents
{
namespace Components
{
class ScriptEventReferencesComponent
: public AZ::Component
, private AZ::Data::AssetBus::MultiHandler
{
public:
AZ_COMPONENT(ScriptEventReferencesComponent, "{D0F440AC-32D4-49EC-8B93-860B188266A6}", AZ::Component);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
static void Reflect(AZ::ReflectContext* reflection);
AZStd::vector<ScriptEvents::ScriptEventsAssetRef> m_scriptEventAssets;
};
}
}
@@ -9,7 +9,6 @@
#include <ScriptEvents/ScriptEventsGem.h>
#include <Source/Editor/ScriptEventsSystemEditorComponent.h>
#include <ScriptEvents/Components/ScriptEventReferencesComponent.h>
#include <Builder/ScriptEventsBuilderComponent.h>
#include <ScriptEvents/ScriptEventsBus.h>
@@ -74,7 +73,6 @@ namespace ScriptEvents
m_descriptors.insert(m_descriptors.end(), {
ScriptEventsEditor::ScriptEventEditorSystemComponent::CreateDescriptor(),
ScriptEvents::Components::ScriptEventReferencesComponent::CreateDescriptor(),
ScriptEventsBuilder::ScriptEventsBuilderComponent::CreateDescriptor(),
});
}
@@ -12,8 +12,6 @@
#include <ScriptEvents/ScriptEventsGem.h>
#include <ScriptEvents/Components/ScriptEventReferencesComponent.h>
namespace ScriptEvents
{
ScriptEventsModule::ScriptEventsModule()
@@ -23,8 +21,7 @@ namespace ScriptEvents
ScriptEventModuleConfigurationRequestBus::Handler::BusConnect();
m_descriptors.insert(m_descriptors.end(), {
ScriptEvents::ScriptEventsSystemComponent::CreateDescriptor(),
ScriptEvents::Components::ScriptEventReferencesComponent::CreateDescriptor(),
ScriptEvents::ScriptEventsSystemComponent::CreateDescriptor()
});
}
@@ -42,6 +42,4 @@ set(FILES
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventsBindingBus.h
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.h
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.cpp
Include/ScriptEvents/Components/ScriptEventReferencesComponent.h
Include/ScriptEvents/Components/ScriptEventReferencesComponent.cpp
)
@@ -40,15 +40,17 @@ namespace Terrain
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "")
// Temporary constraint until the rest of the Terrain system is updated to support larger worlds.
->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldMin)
->Attribute(AZ::Edit::Attributes::Min, -2048.0f)
->Attribute(AZ::Edit::Attributes::Max, 2048.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "")
// Temporary constraint until the rest of the Terrain system is updated to support larger worlds.
->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldMax)
->Attribute(AZ::Edit::Attributes::Min, -2048.0f)
->Attribute(AZ::Edit::Attributes::Max, 2048.0f)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "")
;
->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldHeight);
}
}
}
@@ -128,4 +130,42 @@ namespace Terrain
}
return false;
}
}
float TerrainWorldConfig::NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery)
{
float numberOfSamples = ((max->GetX() - min->GetX()) / heightQuery->GetX()) * ((max->GetY() - min->GetY()) / heightQuery->GetY());
return numberOfSamples;
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::DetermineMessage(float numSamples)
{
const float maximumSamplesAllowed = 8.0f * 1024.0f * 1024.0f;
if (numSamples < maximumSamplesAllowed)
{
return AZ::Success();
}
return AZ::Failure(AZStd::string("The number of samples exceeds the maximum allowed."));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldMin(void* newValue, [[maybe_unused]]const AZ::Uuid& valueType)
{
AZ::Vector3 minValue = *static_cast<AZ::Vector3*>(newValue);
return DetermineMessage(NumberOfSamples(&minValue, &m_worldMax, &m_heightQueryResolution));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldMax(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType)
{
AZ::Vector3 maxValue = *static_cast<AZ::Vector3*>(newValue);
return DetermineMessage(NumberOfSamples(&m_worldMin, &maxValue, &m_heightQueryResolution));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldHeight(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType)
{
AZ::Vector2 heightValue = *static_cast<AZ::Vector2*>(newValue);
return DetermineMessage(NumberOfSamples(&m_worldMin, &m_worldMax, &heightValue));
}
} // namespace Terrain
@@ -32,6 +32,14 @@ namespace Terrain
AZ::Vector3 m_worldMin{ 0.0f, 0.0f, 0.0f };
AZ::Vector3 m_worldMax{ 1024.0f, 1024.0f, 1024.0f };
AZ::Vector2 m_heightQueryResolution{ 1.0f, 1.0f };
private:
AZ::Outcome<void, AZStd::string> ValidateWorldMin(void* newValue, const AZ::Uuid& valueType);
AZ::Outcome<void, AZStd::string> ValidateWorldMax(void* newValue, const AZ::Uuid& valueType);
AZ::Outcome<void, AZStd::string> ValidateWorldHeight(void* newValue, const AZ::Uuid& valueType);
float NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery);
AZ::Outcome<void, AZStd::string> DetermineMessage(float numSamples);
};
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides height data for a region to the terrain system";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainHeight.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainHeight.svg";
static constexpr const char* const s_helpUrl = "";
static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/height_gradient_list/";
};
}
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Defines a terrain region for use by the terrain system";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg";
static constexpr const char* const s_helpUrl = "";
static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/layer_spawner/";
};
}
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system.";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_helpUrl = "";
static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-gradient-list/";
};
}
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides a mapping between surface tags and render materials.";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceMaterials.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg";
static constexpr const char* const s_helpUrl = "";
static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-material-list/";
};
}
@@ -10,6 +10,7 @@ cmake_policy(SET CMP0012 NEW) # new policy for the if that evaluates a boolean o
function(ly_copy source_file target_directory)
cmake_path(GET source_file FILENAME target_filename)
cmake_PATH(GET source_file EXTENSION target_filename_ext)
cmake_path(APPEND target_file "${target_directory}" "${target_filename}")
cmake_path(COMPARE "${source_file}" EQUAL "${target_file}" same_location)
if(NOT ${same_location})