Fix several Prefab outliner ordering issues (#5747)
* Fix several Prefab outliner ordering issues This change does a few things to address instability in entity order when prefabs are enabled: - Changes ordering behavior on entity add to always be "insert after the last selected sibling of the new entity, or at the end if there is no selected sibling" - Adds some logic to ensure selection stays frozen during prefab propagation to allow this behavior to be used - Alters delta generation in `PrefabPublicHandler::CreateEntity` to use the template instead of reserializing the DOM - this avoids a whole bunch of patching issues caused by EditorEntitySortComponent doing post-hoc order fix-up and should generally be safer/faster as we're producing patches for the actual target for those patches - Because the duplicate action is DOM-driven, and there's some thorniness around making changes that will affect the template during propagation, this adds `PrefabPublicHandler::AddNewEntityToSortOrder` to directly patch the DOM for the duplicate case Two bits of this come from patches from the incredibly helpful @AMZN-daimini - Alters `PrefabPublicHandler::GenerateUndoNodesForEntityChangeAndUpdateCache` behavior for determining what's an override: we now check to see if we're part of the current focused instance but *not* owned by the focus instance directly. This lets entity order for nested prefabs get saved to the owning prefab instead of as an override. I'm putting this up for discussion without a feature flag gating it, but we may wish to make this a toggle or disable it outright for stabilization (in which case entity order won't be saved to the owning prefab) - Adds a custom serializer for EditorEntitySortComponent. This isn't strictly necessary now, but it was very useful for debugging and ended up receiving much more manual testing its migration path and save/load path more than I've tested without using the serializer. Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Add missing serializers Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Address some review feedback Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Generate patch in `PrefabPublicHandler::CreateEntity` using the instance's fully evaluated template in-memory Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Fix up comment Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Fix Linux build Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Try to make test less timing dependent (haven't been able to repro failure locally) Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Fix another build issue Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Fix unit test failures Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * Fix a duplicate issue with sanitization that was causing sporadic test failure (thanks, test!) Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com> * One more Linux fix... Signed-off-by: Nicholas Van Sickle <nvsickle@amazon.com>monroegm-disable-blank-issue-2
parent
4ad35f424e
commit
4ee2f341dc
@ -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
|
||||
Loading…
Reference in New Issue