diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp index a895c04b95..551b561200 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp @@ -173,7 +173,7 @@ namespace AZ { if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type")) { - // Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types + // Requiring explicit type info to differentiate between colors versus vectors and numeric types const AZ::Uuid baseTypeId = azrtti_typeid(); AZ::Uuid typeId = AZ::Uuid::CreateNull(); result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId)); @@ -198,7 +198,7 @@ namespace AZ { outputPropertyValue.SetObject(); - // Storing explicit type info to differentiate be=tween colors versus vectors and numeric types + // Storing explicit type info to differentiate between colors versus vectors and numeric types rapidjson::Value typeValue; result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context)); outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator()); diff --git a/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h b/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h index d08acd0b17..1937341cd2 100644 --- a/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h +++ b/Gems/GraphModel/Code/Include/GraphModel/Integration/GraphCanvasMetadata.h @@ -14,6 +14,7 @@ // AZ #include +#include #include // Graph Model diff --git a/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h b/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h index 50c1287080..f3dd85575f 100644 --- a/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h +++ b/Gems/GraphModel/Code/Include/GraphModel/Model/Graph.h @@ -14,10 +14,10 @@ // AZ #include #include -#include #include // Graph Model +#include #include #include @@ -136,9 +136,9 @@ namespace GraphModel //! Set/gets a bundle of generic metadata that is provided by the node graph UI //! system. This may include node positions, comment blocks, node groupings, and //! bookmarks, for example. - void SetUiMetadata(const AZStd::any& uiMetadata); - const AZStd::any& GetUiMetadata() const; - AZStd::any& GetUiMetadata(); + void SetUiMetadata(const GraphModelIntegration::GraphCanvasMetadata& uiMetadata); + const GraphModelIntegration::GraphCanvasMetadata& GetUiMetadata() const; + GraphModelIntegration::GraphCanvasMetadata& GetUiMetadata(); AZStd::shared_ptr FindSlot(const Endpoint& endpoint); @@ -157,7 +157,7 @@ namespace GraphModel ConnectionList m_connections; //! Used to store and serialize metadata from the graph UI, like node positions, comments, group boxes, etc. - AZStd::any m_uiMetadata; + GraphModelIntegration::GraphCanvasMetadata m_uiMetadata; //! Used to store all of our node <-> wrapper node mappings NodeWrappingMap m_nodeWrappings; diff --git a/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h b/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h index 0ce02ffa9d..0b012fd2cd 100644 --- a/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h +++ b/Gems/GraphModel/Code/Include/GraphModel/Model/Slot.h @@ -12,6 +12,7 @@ #pragma once // AZ +#include #include #include #include @@ -165,6 +166,32 @@ namespace GraphModel ExtendableSlotConfiguration m_extendableSlotConfiguration; }; + //! Custom JSON serializer for Slot because we use an AZStd::any for m_value + class JsonSlotSerializer + : public AZ::BaseJsonSerializer + { + public: + AZ_RTTI(JsonSlotSerializer, "{8AC96D70-7BCD-4D68-8813-269938982D51}", AZ::BaseJsonSerializer); + AZ_CLASS_ALLOCATOR(JsonSlotSerializer, AZ::SystemAllocator, 0); + + 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; + + private: + template + bool LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + template + bool StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + }; //!!! Start in Graph.h for high level GraphModel documentation !!! @@ -180,6 +207,7 @@ namespace GraphModel class Slot : public GraphElement, public AZStd::enable_shared_from_this { friend class Graph; // So the Graph can update the Slot's cache of Connection pointers + friend class JsonSlotSerializer; // So we can set the m_value and m_subId directly from the serializer public: AZ_CLASS_ALLOCATOR(Slot, AZ::SystemAllocator, 0); diff --git a/Gems/GraphModel/Code/Source/Integration/GraphController.cpp b/Gems/GraphModel/Code/Source/Integration/GraphController.cpp index f6ce6282f4..bb5291637e 100644 --- a/Gems/GraphModel/Code/Source/Integration/GraphController.cpp +++ b/Gems/GraphModel/Code/Source/Integration/GraphController.cpp @@ -1338,11 +1338,7 @@ namespace GraphModelIntegration GraphCanvasMetadata* GraphController::GetGraphMetadata() { - if (!m_graph->GetUiMetadata().is()) - { - m_graph->SetUiMetadata(AZStd::any(GraphCanvasMetadata())); - } - GraphCanvasMetadata* graphCanvasMetadata = AZStd::any_cast(&m_graph->GetUiMetadata()); + GraphCanvasMetadata* graphCanvasMetadata = &m_graph->GetUiMetadata(); AZ_Assert(graphCanvasMetadata, "GraphCanvasMetadata not initialized"); return graphCanvasMetadata; } diff --git a/Gems/GraphModel/Code/Source/Model/Graph.cpp b/Gems/GraphModel/Code/Source/Model/Graph.cpp index 809679e9f6..e82b5d8e15 100644 --- a/Gems/GraphModel/Code/Source/Model/Graph.cpp +++ b/Gems/GraphModel/Code/Source/Model/Graph.cpp @@ -37,7 +37,7 @@ namespace GraphModel if (serializeContext) { serializeContext->Class() - ->Version(1) + ->Version(2) ->Field("m_nodes", &Graph::m_nodes) ->Field("m_connections", &Graph::m_connections) ->Field("m_uiMetadata", &Graph::m_uiMetadata) @@ -312,19 +312,19 @@ namespace GraphModel } - void Graph::SetUiMetadata(const AZStd::any& uiMetadata) + void Graph::SetUiMetadata(const GraphModelIntegration::GraphCanvasMetadata& uiMetadata) { m_uiMetadata = uiMetadata; } - const AZStd::any& Graph::GetUiMetadata() const + const GraphModelIntegration::GraphCanvasMetadata& Graph::GetUiMetadata() const { return m_uiMetadata; } - AZStd::any& Graph::GetUiMetadata() + GraphModelIntegration::GraphCanvasMetadata& Graph::GetUiMetadata() { return m_uiMetadata; } diff --git a/Gems/GraphModel/Code/Source/Model/Slot.cpp b/Gems/GraphModel/Code/Source/Model/Slot.cpp index e5fb9f8cb8..6433129af8 100644 --- a/Gems/GraphModel/Code/Source/Model/Slot.cpp +++ b/Gems/GraphModel/Code/Source/Model/Slot.cpp @@ -11,9 +11,14 @@ */ // AZ +#include +#include +#include +#include #include #include #include +#include #include // Graph Model @@ -294,13 +299,164 @@ namespace GraphModel ///////////////////////////////////////////////////////// // Slot + AZ::JsonSerializationResult::Result JsonSlotSerializer::Load( + void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + AZ::JsonDeserializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize Slot from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + Slot* slot = reinterpret_cast(outputValue); + AZ_Assert(slot, "Output value for JsonSlotSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + + auto serializedSlotValue = inputValue.FindMember("m_value"); + if (serializedSlotValue != inputValue.MemberEnd()) + { + AZStd::any slotValue; + if (LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result) || + LoadAny(slotValue, serializedSlotValue->value, context, result)) + { + slot->m_value = slotValue; + } + } + + // Load m_subId normally because it's just an int + { + SlotSubId slotSubId = 0; + result.Combine(ContinueLoadingFromJsonObjectField( + &slotSubId, azrtti_typeid(), inputValue, + "m_subId", context)); + slot->m_subId = slotSubId; + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded Slot information." + : "Failed to load Slot information."); + } + + AZ::JsonSerializationResult::Result JsonSlotSerializer::Store( + rapidjson::Value& outputValue, const void* inputValue, [[maybe_unused]] const void* defaultValue, const AZ::Uuid& valueTypeId, + AZ::JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize Slot because the provided type is %s.", valueTypeId.ToString().c_str()); + + const Slot* slot = reinterpret_cast(inputValue); + AZ_Assert(slot, "Input value for JsonSlotSerializer can't be null."); + + outputValue.SetObject(); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + + { + AZ::ScopedContextPath subPathPropertyOverrides(context, "m_value"); + + if (!slot->m_value.empty()) + { + rapidjson::Value outputPropertyValue; + if (StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result) || + StoreAny(slot->m_value, outputPropertyValue, context, result)) + { + outputValue.AddMember("m_value", outputPropertyValue, context.GetJsonAllocator()); + } + } + } + + { + AZ::ScopedContextPath subSlotId(context, "m_subId"); + SlotSubId defaultSubId = 0; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "m_subId", &slot->m_subId, &defaultSubId, + azrtti_typeid(), context)); + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information." + : "Failed to store MaterialAssignment information."); + } + + template + bool JsonSlotSerializer::LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + auto valueItr = inputPropertyValue.FindMember("Value"); + auto typeItr = inputPropertyValue.FindMember("$type"); + if ((valueItr != inputPropertyValue.MemberEnd()) && (typeItr != inputPropertyValue.MemberEnd())) + { + // Requiring explicit type info to differentiate between colors versus vectors and numeric types + const AZ::Uuid baseTypeId = azrtti_typeid(); + AZ::Uuid typeId = AZ::Uuid::CreateNull(); + result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId)); + + if (typeId == azrtti_typeid()) + { + T value; + result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid(), inputPropertyValue, "Value", context)); + propertyValue = value; + return true; + } + } + return false; + } + + template + bool JsonSlotSerializer::StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (propertyValue.is()) + { + outputPropertyValue.SetObject(); + + // Storing explicit type info to differentiate between colors versus vectors and numeric types + rapidjson::Value typeValue; + result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context)); + outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator()); + + T value = AZStd::any_cast(propertyValue); + result.Combine( + ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid(), context)); + return true; + } + return false; + } + void Slot::Reflect(AZ::ReflectContext* context) { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + + if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(0) + ->Version(1) ->Field("m_value", &Slot::m_value) ->Field("m_subId", &Slot::m_subId) // m_slotDescription is not reflected because that data is populated procedurally by each node diff --git a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp index 3879cd2597..2f3a7d7ab4 100644 --- a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp +++ b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.cpp @@ -460,10 +460,10 @@ namespace LandscapeCanvasEditor GraphCanvas::StyleManagerRequestBus::Event(editorId, &GraphCanvas::StyleManagerRequests::RegisterDataPaletteStyle, LandscapeCanvas::AreaTypeId, "VegetationAreaDataColorPalette"); LandscapeCanvas::LandscapeCanvasRequestBus::Handler::BusConnect(); - AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect(); AzToolsFramework::EditorPickModeNotificationBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId()); AzToolsFramework::EntityCompositionNotificationBus::Handler::BusConnect(); AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusConnect(); + AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect(); CrySystemEventBus::Handler::BusConnect(); AZ::EntitySystemBus::Handler::BusConnect(); @@ -480,6 +480,7 @@ namespace LandscapeCanvasEditor { AZ::EntitySystemBus::Handler::BusDisconnect(); CrySystemEventBus::Handler::BusDisconnect(); + AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect(); AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusDisconnect(); AzToolsFramework::EditorPickModeNotificationBus::Handler::BusDisconnect(); AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect(); @@ -883,6 +884,10 @@ namespace LandscapeCanvasEditor if (landscapeCanvasComponent) { landscapeCanvasComponent->m_graph = *m_serializeContext->CloneObject(graph.get()); + + // Mark the Landscape Canvas entity as dirty so the changes to the graph will be picked up on the next save + AzToolsFramework::ScopedUndoBatch undo("Update Landscape Canvas Graph"); + AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, rootEntityId); } } } @@ -1572,7 +1577,7 @@ namespace LandscapeCanvasEditor void MainWindow::HandleEditorEntityCreated(const AZ::EntityId& entityId, GraphCanvas::GraphId graphId) { - if (m_ignoreGraphUpdates) + if (m_ignoreGraphUpdates || m_prefabPropagationInProgress) { return; } @@ -1622,6 +1627,11 @@ namespace LandscapeCanvasEditor void MainWindow::OnEditorEntityDeleted(const AZ::EntityId& entityId) { + if (m_prefabPropagationInProgress) + { + return; + } + m_queuedEntityDeletes.push_back(entityId); QTimer::singleShot(0, [this, entityId]() { @@ -2456,6 +2466,11 @@ namespace LandscapeCanvasEditor void MainWindow::EntityParentChanged(AZ::EntityId entityId, AZ::EntityId newParentId, AZ::EntityId oldParentId) { + if (m_prefabPropagationInProgress) + { + return; + } + GraphCanvas::GraphId oldGraphId = FindGraphContainingEntity(oldParentId); GraphCanvas::GraphId newGraphId = FindGraphContainingEntity(newParentId); @@ -2482,6 +2497,20 @@ namespace LandscapeCanvasEditor } } + void MainWindow::OnPrefabInstancePropagationBegin() + { + // Ignore graph updates during prefab propagation because the entities will be + // deleted and re-created, which would inadvertantly trigger our logic to close + // the graph when the corresponding entity is deleted. + m_prefabPropagationInProgress = true; + } + + void MainWindow::OnPrefabInstancePropagationEnd() + { + // See comment above in OnPrefabInstancePropagationBegin + m_prefabPropagationInProgress = false; + } + void MainWindow::OnCryEditorEndCreate() { UpdateGraphEnabled(); @@ -2490,6 +2519,15 @@ namespace LandscapeCanvasEditor void MainWindow::OnCryEditorEndLoad() { UpdateGraphEnabled(); + + AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect(); + } + + void MainWindow::OnCryEditorCloseScene() + { + UpdateGraphEnabled(); + + AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect(); } void MainWindow::OnCryEditorSceneClosed() diff --git a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h index eddf998d8d..e6887eecfd 100644 --- a/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h +++ b/Gems/LandscapeCanvas/Code/Source/Editor/MainWindow.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -84,6 +85,7 @@ namespace LandscapeCanvasEditor , private AzToolsFramework::EntityCompositionNotificationBus::Handler , private AzToolsFramework::PropertyEditorEntityChangeNotificationBus::MultiHandler , private AzToolsFramework::ToolsApplicationNotificationBus::Handler + , private AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler , private CrySystemEventBus::Handler { Q_OBJECT @@ -183,10 +185,15 @@ namespace LandscapeCanvasEditor void EntityParentChanged(AZ::EntityId entityId, AZ::EntityId newParentId, AZ::EntityId oldParentId) override; //////////////////////////////////////////////////////////////////////// + //! PrefabPublicNotificationBus overrides + void OnPrefabInstancePropagationBegin() override; + void OnPrefabInstancePropagationEnd() override; + //////////////////////////////////////////////////////////////////////// // CrySystemEventBus overrides void OnCryEditorEndCreate() override; void OnCryEditorEndLoad() override; + void OnCryEditorCloseScene() override; void OnCryEditorSceneClosed() override; //////////////////////////////////////////////////////////////////////// @@ -246,6 +253,7 @@ namespace LandscapeCanvasEditor AZ::SerializeContext* m_serializeContext = nullptr; bool m_ignoreGraphUpdates = false; + bool m_prefabPropagationInProgress = false; bool m_inObjectPickMode = false; using DeletedNodePositionsMap = AZStd::unordered_map;