From 4c2933b38d36dca8e2e0a2d412114a1bd1ab0d92 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Mon, 17 May 2021 18:44:32 -0400 Subject: [PATCH 01/65] Adding gems metadata query --- cmake/Tools/registration.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/cmake/Tools/registration.py b/cmake/Tools/registration.py index 184d2cdb31..70fbc5ba90 100755 --- a/cmake/Tools/registration.py +++ b/cmake/Tools/registration.py @@ -2216,6 +2216,19 @@ def get_gem_data(gem_name: str = None, return None +def get_gems_metadata(): + gem_list = get_gems() + get_engine_gems() + gem_data_dict = {} + for gem in gem_list: + json_path = os.path.join(gem, 'gem.json') + if (os.path.exists(json_path)): + with open(json_path, 'r') as gem_json: + parsed_meta_data = json.loads(gem_json.read()) + gem_data_dict[str(parsed_meta_data['gem_name'])] = parsed_meta_data + else: + logger.error(f'Gem json {gem_json} is not present.') + json_result = json.dumps(gem_data_dict, indent = 4) + return json_result def get_template_data(template_name: str = None, template_path: str or pathlib.Path = None, ) -> dict or None: @@ -2410,6 +2423,12 @@ def print_downloadables(verbose: int) -> None: print_templates_data(downloadable_data['templates']) print_restricted_data(downloadable_data['templates']) +def print_gems_metadata(verbose: int) -> None: + gems_data = get_gems_metadata() + print(gems_data) + if verbose > 0: + gem_list = get_gems() + get_engine_gems() + print(gem_list) def download_engine(engine_name: str, dest_path: str) -> int: @@ -3833,7 +3852,6 @@ def _run_register_show(args: argparse) -> int: if args.this_engine: print_this_engine(args.verbose) return 0 - elif args.engines: print_engines(args.verbose) return 0 @@ -3897,6 +3915,9 @@ def _run_register_show(args: argparse) -> int: elif args.downloadable_templates: print_downloadable_templates(args.verbose) return 0 + elif args.gems_data: + print_gems_metadata(args.verbose) + return 0 else: register_show(args.verbose) return 0 @@ -4168,6 +4189,9 @@ def add_args(parser, subparsers) -> None: group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False, default=False, help='Combine all repos templates into a single list of resources.') + group.add_argument('-gd', '--gems-data', action='store_true', required=False, + default=False, + help='Returns a json formatted string of meta data for all local and engine gems.') register_show_subparser.add_argument('-v', '--verbose', action='count', required=False, default=0, From 4b610058b5eadc9f2885885094eb2858cebb8db5 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Wed, 26 May 2021 15:47:41 -0700 Subject: [PATCH 02/65] implement function DetachPrefabFromParent in PrefabPublicHandler --- .../Prefab/Instance/Instance.cpp | 12 + .../Prefab/Instance/Instance.h | 5 +- .../Instance/InstanceToTemplatePropagator.cpp | 5 + .../Prefab/PrefabPublicHandler.cpp | 224 +++++++++++++++++- .../Prefab/PrefabPublicHandler.h | 2 + .../Prefab/PrefabPublicInterface.h | 8 + .../UI/Prefab/PrefabIntegrationManager.cpp | 32 +++ .../UI/Prefab/PrefabIntegrationManager.h | 1 + 8 files changed, 275 insertions(+), 14 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index c4cb3e0316..8c7604f680 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -401,6 +401,17 @@ namespace AzToolsFramework } } + InstancePtrOptionalReference Instance::GetNestedInstance(const InstanceAlias& instanceAlias) + { + auto nestedInstanceIterator = m_nestedInstances.find(instanceAlias); + if (nestedInstanceIterator != m_nestedInstances.end()) + { + return nestedInstanceIterator->second; + } + + return AZStd::nullopt; + } + void Instance::GetNestedInstances(const AZStd::function&)>& callback) { for (auto& [instanceAlias, instance] : m_nestedInstances) @@ -613,6 +624,7 @@ namespace AzToolsFramework AZStd::unique_ptr Instance::DetachContainerEntity() { + m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId()); return AZStd::move(m_containerEntity); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 821478b706..377be68753 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -48,6 +48,8 @@ namespace AzToolsFramework using EntityAliasOptionalReference = AZStd::optional>; using InstanceOptionalReference = AZStd::optional>; using InstanceOptionalConstReference = AZStd::optional>; + using InstancePtrOptionalReference = AZStd::optional>>; + using InstanceSet = AZStd::unordered_set; using InstanceSetConstReference = AZStd::optional>; using EntityOptionalReference = AZStd::optional>; @@ -85,6 +87,7 @@ namespace AzToolsFramework bool AddEntity(AZ::Entity& entity); bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias); AZStd::unique_ptr DetachEntity(const AZ::EntityId& entityId); + void DetachEntities(const AZStd::function)>& callback); void DetachNestedEntities(const AZStd::function)>& callback); void RemoveNestedEntities(const AZStd::function&)>& filter); @@ -92,6 +95,7 @@ namespace AzToolsFramework Instance& AddInstance(AZStd::unique_ptr instance); AZStd::unique_ptr DetachNestedInstance(const InstanceAlias& instanceAlias); + InstancePtrOptionalReference GetNestedInstance(const InstanceAlias& instanceAlias); /** * Gets the aliases for the entities in the Instance DOM. @@ -182,7 +186,6 @@ namespace AzToolsFramework void ClearEntities(); - void DetachEntities(const AZStd::function)>& callback); void RemoveEntities(const AZStd::function&)>& filter); bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index 6d3ddedd51..cd8fad1725 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -176,10 +176,15 @@ namespace AzToolsFramework { PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); + PrefabDomUtils::PrintPrefabDomValue("providedPatch", providedPatch); + PrefabDomUtils::PrintPrefabDomValue("templateDomReference", templateDomReference); + //apply patch to template AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference, templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch); + PrefabDomUtils::PrintPrefabDomValue("templateDomReference(Patch applied)", templateDomReference); + //trigger propagation if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 0181050a32..315c7b5710 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -986,6 +987,216 @@ namespace AzToolsFramework return AZ::Success(); } + PrefabOperationResult PrefabPublicHandler::DetachPrefabFromParent(const AZ::EntityId& entityId) + { + if (!entityId.IsValid()) + { + return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); + } + + if (IsLevelInstanceContainerEntity(entityId)) + { + return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance.")); + } + + InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(entityId); + if (owningInstance->get().GetContainerEntityId() != entityId) + { + return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); + } + + AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); + + UndoSystem::URSequencePoint* currentUndoBatch = nullptr; + ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch); + + bool createdUndo = false; + if (!currentUndoBatch) + { + createdUndo = true; + ToolsApplicationRequests::Bus::BroadcastResult( + currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Detach Prefab"); + AZ_Assert(currentUndoBatch, "Failed to create new undo batch."); + } + + // In order to undo Prefab Instance detachment, we have to create a selection command which selects the current selection + // and then add the detach as children. + // Commands always execute themselves first and then their children (when going forwards) + // and do the opposite when going backwards. + EntityIdList selectedEntities; + ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities); + SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Detach Prefab"); + + // We insert a "deselect all" command before we detach the Prefab Instance. This ensures the detach operations aren't changing + // selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive + // UI updates once at the start instead of once for each entity. + { + EntityIdList deselection; + SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities"); + deselectAllCommand->SetParent(selCommand); + } + + { + AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture"); + + InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); + const auto parentTemplateId = parentInstance->get().GetTemplateId(); + + Prefab::PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); + + { + auto getInstancePtrResult = parentInstance->get().GetNestedInstance(owningInstance->get().GetInstanceAlias()); + AZ_Assert(getInstancePtrResult, "Can't find selected container entity's owning Instance."); + + auto& instancePtr = getInstancePtrResult->get(); + + AZStd::unordered_map oldEntityAliases; + oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); + + auto containerEntityPtr = instancePtr->DetachContainerEntity(); + auto& containerEntity = *containerEntityPtr.release(); + auto editorPrefabComponent = containerEntity.FindComponent(); + containerEntity.Deactivate(); + const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent); + AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed."); + delete editorPrefabComponent; + containerEntity.Activate(); + + const bool containerEntityAdded = parentInstance->get().AddEntity(containerEntity); + AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed."); + + EntityIdList entityIds; + entityIds.emplace_back(containerEntity.GetId()); + + instancePtr->GetEntities( + [&](AZStd::unique_ptr& entityPtr) + { + oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get()); + return true; + }); + + instancePtr->DetachEntities( + [&](AZStd::unique_ptr entityPtr) + { + auto& entity = *entityPtr.release(); + const bool entityAdded = parentInstance->get().AddEntity(entity); + AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed."); + + entityIds.emplace_back(entity.GetId()); + }); + + Prefab::PrefabDom instanceDomAfter; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance->get()); + + PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); + command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); + command->SetParent(selCommand); + + selCommand->SetParent(currentUndoBatch); + { + AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo"); + selCommand->RunRedo(); + } + + const auto instanceTemplateId = instancePtr->GetTemplateId(); + auto parentContainerEntityId = parentInstance->get().GetContainerEntityId(); + instancePtr->GetNestedInstances( + [&](AZStd::unique_ptr& nestedInstancePtr) + { + //get previous link patch + auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId()); + PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches(); + AZ_Assert( + linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.", + nestedInstancePtr->GetLinkId()); + + PrefabDom linkPatchesCopy; + linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); + + RemoveLink(nestedInstancePtr, instanceTemplateId, currentUndoBatch); + + /*auto getNestedInstanceContainerEntityResult = nestedInstancePtr->GetContainerEntity(); + AZ_Assert(getNestedInstanceContainerEntityResult.has_value(), "Can't get nested instance container entitt."); + + auto& nestedInstanceContainerEntity = getNestedInstanceContainerEntityResult->get(); + auto nestedInstanceContainerEntityId = nestedInstanceContainerEntity.GetId(); + + PrefabDom containerEntityDomBefore; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, nestedInstanceContainerEntity); + + AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformInterface::SetParent, containerEntity.GetId()); + + PrefabDom containerEntityDomAfter; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, nestedInstanceContainerEntity); + + PrefabDom reparentPatch; + m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);*/ + + PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); + + //update aliases + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + linkPatchesCopy.Accept(writer); + QString previousPatchString(buffer.GetString()); + + for (AZ::EntityId entityId : entityIds) + { + AZStd::string oldEntityAlias = oldEntityAliases[entityId]; + EntityAliasOptionalReference newEntityAlias = parentInstance->get().GetEntityAlias(entityId); + AZ_Assert( + newEntityAlias.has_value(), + "Could not fetch entity alias for entity with id '%llu' during prefab creation.", + static_cast(entityId)); + ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); + } + + linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); + + CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); + + //update links? + //// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step + //m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter)); + + //// Save these changes as patches to the link + //PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(nestedInstanceContainerEntityId))); + //linkUpdate->SetParent(undoBatch.GetUndoBatch()); + //linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId()); + + //linkUpdate->Redo(); + }); + + RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); + } + } + + if (createdUndo) + { + ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); + } + + return AZ::Success(); + } + + void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) + { + QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); + QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); + + stringToReplace.replace(oldAliasQuotes, newAliasQuotes); + + QString oldAliasPathRef = QString("/%1").arg(oldAlias.data()); + QString newAliasPathRef = QString("/%1").arg(newAlias.data()); + + stringToReplace.replace(oldAliasPathRef, newAliasPathRef); + } + void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation) { @@ -1236,18 +1447,5 @@ namespace AzToolsFramework return true; } - - void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) - { - QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); - QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); - - stringToReplace.replace(oldAliasQuotes, newAliasQuotes); - - QString oldAliasPathRef = QString("/%1").arg(oldAlias.data()); - QString newAliasPathRef = QString("/%1").arg(newAlias.data()); - - stringToReplace.replace(oldAliasPathRef, newAliasPathRef); - } } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 7e2357dd44..f1c32ee35c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,6 +64,8 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) override; + private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 0750c4d264..f12bc359f2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -150,6 +150,14 @@ namespace AzToolsFramework * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + + /** + * Detaches target container entity's owning instance from its parent instance. + * Bails if the entity is not a container entity or belongs to the level prefab instance. + * @param entityId The container entity whose instance to detach. + * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. + */ + virtual PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) = 0; }; } // namespace Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 3edc190fb7..090c7bdc54 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -237,6 +237,27 @@ namespace AzToolsFramework { deleteAction->setDisabled(true); } + + QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); + if (selectedEntities.size() != 1) + { + detachPrefabAction->setDisabled(true); + } + else + { + AZ::EntityId selectedEntity = selectedEntities[0]; + + if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) && + !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity)) + { + QObject::connect(detachPrefabAction, &QAction::triggered, detachPrefabAction, + [this, selectedEntity] { ContextMenu_DetachPrefab(selectedEntity); }); + } + else + { + detachPrefabAction->setDisabled(true); + } + } } void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const @@ -392,6 +413,17 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) + { + PrefabOperationResult detachPrefabResult = + s_prefabPublicInterface->DetachPrefabFromParent(containerEntity); + + if (!detachPrefabResult.IsSuccess()) + { + WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError()); + } + } + void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index c9b846aa5b..69ec3013cc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -93,6 +93,7 @@ namespace AzToolsFramework static void ContextMenu_EditPrefab(AZ::EntityId containerEntity); static void ContextMenu_SavePrefab(AZ::EntityId containerEntity); static void ContextMenu_DeleteSelected(); + static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity); // Prompt and resolve dialogs static bool QueryUserForPrefabSaveLocation( From 4fc7d72b2b755fc8ccd7a1f4baee601015a96f4d Mon Sep 17 00:00:00 2001 From: chiyteng Date: Wed, 26 May 2021 16:49:09 -0700 Subject: [PATCH 03/65] modify DetachPrefabFromParent function for debugging --- .../Prefab/PrefabPublicHandler.cpp | 44 +++---------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 315c7b5710..d3553c78e1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1042,14 +1042,14 @@ namespace AzToolsFramework InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); const auto parentTemplateId = parentInstance->get().GetTemplateId(); - Prefab::PrefabDom instanceDomBefore; - m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); - { - auto getInstancePtrResult = parentInstance->get().GetNestedInstance(owningInstance->get().GetInstanceAlias()); - AZ_Assert(getInstancePtrResult, "Can't find selected container entity's owning Instance."); + auto instancePtr = parentInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); + AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); - auto& instancePtr = getInstancePtrResult->get(); + RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + + Prefab::PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); AZStd::unordered_map oldEntityAliases; oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); @@ -1115,25 +1115,6 @@ namespace AzToolsFramework linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); RemoveLink(nestedInstancePtr, instanceTemplateId, currentUndoBatch); - - /*auto getNestedInstanceContainerEntityResult = nestedInstancePtr->GetContainerEntity(); - AZ_Assert(getNestedInstanceContainerEntityResult.has_value(), "Can't get nested instance container entitt."); - - auto& nestedInstanceContainerEntity = getNestedInstanceContainerEntityResult->get(); - auto nestedInstanceContainerEntityId = nestedInstanceContainerEntity.GetId(); - - PrefabDom containerEntityDomBefore; - m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, nestedInstanceContainerEntity); - - AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformInterface::SetParent, containerEntity.GetId()); - - PrefabDom containerEntityDomAfter; - m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, nestedInstanceContainerEntity); - - PrefabDom reparentPatch; - m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter); - m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);*/ - PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); //update aliases @@ -1156,21 +1137,8 @@ namespace AzToolsFramework linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); - - //update links? - //// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step - //m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter)); - - //// Save these changes as patches to the link - //PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(nestedInstanceContainerEntityId))); - //linkUpdate->SetParent(undoBatch.GetUndoBatch()); - //linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId()); - - //linkUpdate->Redo(); }); - RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } From 053e273b97bcacb191886afcad15c917addfa45c Mon Sep 17 00:00:00 2001 From: srikappa Date: Wed, 26 May 2021 18:24:49 -0700 Subject: [PATCH 04/65] Clear dirty entities at the end of InstantiatePrefab logic --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index d3553c78e1..1b98711352 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -384,7 +384,10 @@ namespace AzToolsFramework CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch)); // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes - m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); + //m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(); From 78afc45709a26a03c30a1d8159671a91482840c8 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 12:39:14 -0700 Subject: [PATCH 05/65] [ftue_auto_register] add logic to auto register engine if it is not already --- .../ProjectManager/Source/PythonBindings.cpp | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 8c79a153c8..0672e02ff4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -286,6 +286,30 @@ namespace O3DE::ProjectManager m_registration = pybind11::module::import("cmake.Tools.registration"); m_engineTemplate = pybind11::module::import("cmake.Tools.engine_template"); + // register the current engine if it isn't already + bool registerThis = true; + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } + } + + if (registerThis) + { + auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); + + AZ_Error("ProjectManagerWindow", registrationResult.cast() == 0, + "Registration of this engine failed!"); + } + return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) { From ecc18338fa0ef9f71c9ee4956ae7275a5cbf91af Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 14:19:31 -0700 Subject: [PATCH 06/65] [ftue_auto_register] move engine registration check into private helper function --- .../ProjectManager/Source/PythonBindings.cpp | 56 +++++++++++-------- .../ProjectManager/Source/PythonBindings.h | 2 + 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 0672e02ff4..90e18f5f27 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -286,29 +286,8 @@ namespace O3DE::ProjectManager m_registration = pybind11::module::import("cmake.Tools.registration"); m_engineTemplate = pybind11::module::import("cmake.Tools.engine_template"); - // register the current engine if it isn't already - bool registerThis = true; - auto allEngines = m_registration.attr("get_engines")(); - if (pybind11::isinstance(allEngines)) - { - for (const auto& engine : allEngines) - { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); - if (enginePath.Compare(m_enginePath) == 0) - { - registerThis = false; - break; - } - } - } - - if (registerThis) - { - auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); - - AZ_Error("ProjectManagerWindow", registrationResult.cast() == 0, - "Registration of this engine failed!"); - } + // make sure the engine is registered + RegisterThisEngine(); return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) @@ -332,6 +311,37 @@ namespace O3DE::ProjectManager return !PyErr_Occurred(); } + bool PythonBindings::RegisterThisEngine() + { + bool registerThis = true; + + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } + } + + bool result = true; + if (registerThis) + { + auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); + result = (registrationResult.cast() == 0); + } + + AZ_Error("ProjectManagerWindow", result, "Registration of this engine failed!"); + return result; + } + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 892e13a65b..71616de4e0 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -60,9 +60,11 @@ namespace O3DE::ProjectManager GemInfo GemInfoFromPath(pybind11::handle path); ProjectInfo ProjectInfoFromPath(pybind11::handle path); ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path); + bool RegisterThisEngine(); bool StartPython(); bool StopPython(); + AZ::IO::FixedMaxPath m_enginePath; pybind11::handle m_engineTemplate; AZStd::recursive_mutex m_lock; From 3137b961bf860f03aac17bd5192477380c7056e3 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 14:54:51 -0700 Subject: [PATCH 07/65] [ftue_auto_register] elevated error to assert if registration fails --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 90e18f5f27..7158bfefb7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -338,7 +338,7 @@ namespace O3DE::ProjectManager result = (registrationResult.cast() == 0); } - AZ_Error("ProjectManagerWindow", result, "Registration of this engine failed!"); + AZ_Assert(result, "Registration of this engine failed!"); return result; } From e1dfac34fc7fba0d5478c1a412e368a1805aad86 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 15:24:39 -0700 Subject: [PATCH 08/65] [ftue_auto_register] wrapped registration pybind calls with ExecuteWithLock --- .../ProjectManager/Source/PythonBindings.cpp | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 7158bfefb7..1a3e30f99f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -313,33 +313,38 @@ namespace O3DE::ProjectManager bool PythonBindings::RegisterThisEngine() { - bool registerThis = true; - - // check current engine path against all other registered engines - // to see if we are already registered - auto allEngines = m_registration.attr("get_engines")(); - if (pybind11::isinstance(allEngines)) - { - for (const auto& engine : allEngines) + bool registrationResult = true; // already registered is considered successful + bool pythonResult = ExecuteWithLock( + [&] { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); - if (enginePath.Compare(m_enginePath) == 0) + bool registerThis = true; + + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) { - registerThis = false; - break; + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } } - } - } - bool result = true; - if (registerThis) - { - auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); - result = (registrationResult.cast() == 0); - } + if (registerThis) + { + auto result = m_registration.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); + } + }); - AZ_Assert(result, "Registration of this engine failed!"); - return result; + bool finalResult = (registrationResult && pythonResult); + AZ_Assert(finalResult, "Registration of this engine failed!"); + return finalResult; } bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) From c21a59af50933d1d0541de154b2ef154de3c6c44 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Thu, 27 May 2021 17:19:32 -0700 Subject: [PATCH 09/65] Remove print dom for debug previously --- .../Prefab/Instance/InstanceToTemplatePropagator.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index cd8fad1725..6d3ddedd51 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -176,15 +176,10 @@ namespace AzToolsFramework { PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); - PrefabDomUtils::PrintPrefabDomValue("providedPatch", providedPatch); - PrefabDomUtils::PrintPrefabDomValue("templateDomReference", templateDomReference); - //apply patch to template AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference, templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch); - PrefabDomUtils::PrintPrefabDomValue("templateDomReference(Patch applied)", templateDomReference); - //trigger propagation if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) { From 57913d837854fafe4b47725266d62aaabc694f6e Mon Sep 17 00:00:00 2001 From: srikappa Date: Thu, 27 May 2021 18:19:28 -0700 Subject: [PATCH 10/65] A couple of bug fixes --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 6 +++--- .../AzToolsFramework/Prefab/PrefabSystemComponent.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 1b98711352..f6ded4ae45 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1141,12 +1141,12 @@ namespace AzToolsFramework CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); }); - - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( - &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } } + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); + if (createdUndo) { ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index c4e6415b02..1d478ad581 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -652,7 +652,8 @@ namespace AzToolsFramework if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd()) { instancesValue->get().AddMember( - rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator()); + rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(), + targetTemplateDom.GetAllocator()); } Template& sourceTemplate = sourceTemplateRef->get(); From 802943bbb3bf0553bfaf311608587ed295ff3866 Mon Sep 17 00:00:00 2001 From: karlberg Date: Thu, 27 May 2021 19:54:56 -0700 Subject: [PATCH 11/65] Bug fixes, naming changes to make variables more clear, and adds a cvar to adjust client window size --- Code/LauncherUnified/Launcher.cpp | 16 +++ .../Multiplayer/Components/NetBindComponent.h | 4 + .../Components/NetworkTransformComponent.h | 10 ++ .../EntityReplication/ReplicationRecord.h | 8 +- .../LocalPredictionPlayerInputComponent.cpp | 32 +++--- .../Source/Components/NetBindComponent.cpp | 10 ++ .../Components/NetworkTransformComponent.cpp | 48 ++++++--- .../Source/MultiplayerSystemComponent.cpp | 98 +++++++++++++++++-- .../Code/Source/MultiplayerSystemComponent.h | 4 + .../EntityReplicationManager.cpp | 2 +- .../EntityReplication/PropertyPublisher.cpp | 10 +- .../EntityReplication/ReplicationRecord.cpp | 26 ++--- 12 files changed, 210 insertions(+), 58 deletions(-) diff --git a/Code/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index 1f29478399..acb5cb9dba 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -9,6 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ + #include #include @@ -22,6 +23,8 @@ #include #include #include +#include +#include #include @@ -45,6 +48,19 @@ extern "C" void CreateStaticModules(AZStd::vector& modulesOut); namespace { + void OnViewportResize(const AZ::Vector2& value); + + AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate, + "The default size for the launcher viewport, 0 0 means full screen"); + + void OnViewportResize(const AZ::Vector2& value) + { + AzFramework::NativeWindowHandle windowHandle = nullptr; + AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle); + AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast(value.GetX()), aznumeric_cast(value.GetY())); + AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize); + } + void ExecuteConsoleCommandFile(AzFramework::Application& application) { const AZStd::string_view customConCmdKey = "console-command-file"; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 4fe60f14a3..7d9b7d4086 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -39,6 +39,7 @@ namespace Multiplayer using EntityMigrationStartEvent = AZ::Event; using EntityMigrationEndEvent = AZ::Event<>; using EntityServerMigrationEvent = AZ::Event; + using EntityPreRenderEvent = AZ::Event; //! @class NetBindComponent //! @brief Component that provides net-binding to a networked entity. @@ -97,6 +98,7 @@ namespace Multiplayer void NotifyMigrationStart(ClientInputId migratedInputId); void NotifyMigrationEnd(); void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); + void NotifyPreRender(float deltaTime, float blendFactor); void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler); void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler); @@ -104,6 +106,7 @@ namespace Multiplayer void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler); void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler); void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler); + void AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler); bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer); @@ -152,6 +155,7 @@ namespace Multiplayer EntityMigrationStartEvent m_entityMigrationStartEvent; EntityMigrationEndEvent m_entityMigrationEndEvent; EntityServerMigrationEvent m_entityServerMigrationEvent; + EntityPreRenderEvent m_entityPreRenderEvent; AZ::Event<> m_onRemove; RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle; AZ::Event<>::Handler m_handleMarkedDirty; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h index 2a3b5fb3cc..0bf913a89a 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h @@ -13,6 +13,7 @@ #pragma once #include +#include #include namespace Multiplayer @@ -32,13 +33,22 @@ namespace Multiplayer void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; private: + void OnPreRender(float deltaTime, float blendFactor); + void OnRotationChangedEvent(const AZ::Quaternion& rotation); void OnTranslationChangedEvent(const AZ::Vector3& translation); void OnScaleChangedEvent(const AZ::Vector3& scale); + void OnResetCountChangedEvent(); + + AZ::Transform m_previousTransform = AZ::Transform::CreateIdentity(); + AZ::Transform m_targetTransform = AZ::Transform::CreateIdentity(); AZ::Event::Handler m_rotationEventHandler; AZ::Event::Handler m_translationEventHandler; AZ::Event::Handler m_scaleEventHandler; + AZ::Event::Handler m_resetCountEventHandler; + + EntityPreRenderEvent::Handler m_entityPreRenderEventHandler; }; class NetworkTransformComponentController diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h index 3dfc4b8016..33e1e0bde6 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h @@ -45,10 +45,10 @@ namespace Multiplayer static constexpr uint32_t MaxRecordBits = 2048; ReplicationRecord() = default; - ReplicationRecord(NetEntityRole netEntityRole); + ReplicationRecord(NetEntityRole remoteNetEntityRole); - void SetNetworkRole(NetEntityRole netEntityRole); - NetEntityRole GetNetworkRole() const; + void SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole); + NetEntityRole GetRemoteNetworkRole() const; bool AreAllBitsConsumed() const; void ResetConsumedBits(); @@ -92,6 +92,6 @@ namespace Multiplayer // Sequence number this ReplicationRecord was sent on AzNetworking::PacketId m_sentPacketId = AzNetworking::InvalidPacketId; - NetEntityRole m_netEntityRole = NetEntityRole::InvalidRole;; + NetEntityRole m_remoteNetEntityRole = NetEntityRole::InvalidRole;; }; } diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 612601883c..99e19a89fd 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -25,6 +25,7 @@ namespace Multiplayer AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay"); #ifndef AZ_RELEASE_BUILD AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat"); + AZ_CVAR(bool, cl_EnableDesyncDebugging, true, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs"); #endif AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs"); @@ -214,11 +215,12 @@ namespace Multiplayer // Send correction SendClientInputCorrection(GetLastInputId(), correction); -#ifdef _DEBUG - // In debug, show which states caused the correction +#ifndef AZ_RELEASE_BUILD AZStd::string clientStateString; AZStd::string serverStateString; + if (cl_EnableDesyncDebugging) { + // In debug, show which states caused the correction // Write in client state AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize()); GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer); @@ -236,11 +238,13 @@ namespace Multiplayer GetNetBindComponent()->SerializeEntityCorrection(serverValues); AZStd::map> mapComparison; + // put the server value in the first part of the pair for (const auto& pair : serverValues.GetValueMap()) { mapComparison[pair.first].first = pair.second; } + // put the client value in the second part of the pair for (const auto& pair : clientValues.GetValueMap()) { @@ -266,12 +270,13 @@ namespace Multiplayer } } } -#else - const AZStd::string clientStateString = "available in debug only"; - const AZStd::string serverStateString = "available in debug only"; -#endif - + else + { + clientStateString = "available in debug only"; + serverStateString = "available in debug only"; + } AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str()); +#endif } } } @@ -416,7 +421,7 @@ namespace Multiplayer ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const { - return m_clientInputId; + return m_lastClientInputId; } HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const @@ -520,10 +525,13 @@ namespace Multiplayer // In debug, send the entire client output state to the server to make it easier to debug desync issues AzNetworking::PacketEncodingBuffer processInputResult; -#ifdef _DEBUG - AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity()); - GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer); - processInputResult.Resize(processInputResultSerializer.GetSize()); +#ifndef AZ_RELEASE_BUILD + if (cl_EnableDesyncDebugging) + { + AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity()); + GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer); + processInputResult.Resize(processInputResultSerializer.GetSize()); + } #endif // Save this input and discard move history outside our client rewind window diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index d91bba2e0c..0847d42dd6 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -390,6 +390,11 @@ namespace Multiplayer m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); } + void NetBindComponent::NotifyPreRender(float deltaTime, float blendFactor) + { + m_entityPreRenderEvent.Signal(deltaTime, blendFactor); + } + void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler) { eventHandler.Connect(m_entityStopEvent); @@ -420,6 +425,11 @@ namespace Multiplayer eventHandler.Connect(m_entityServerMigrationEvent); } + void NetBindComponent::AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler) + { + eventHandler.Connect(m_entityPreRenderEvent); + } + bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer) { m_predictableRecord.ResetConsumedBits(); diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 0cc4cb131e..81b756de50 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -33,6 +33,8 @@ namespace Multiplayer : m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); }) , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) , m_scaleEventHandler([this](const AZ::Vector3& scale) { OnScaleChangedEvent(scale); }) + , m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); }) + , m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) { ; } @@ -47,6 +49,11 @@ namespace Multiplayer RotationAddEvent(m_rotationEventHandler); TranslationAddEvent(m_translationEventHandler); ScaleAddEvent(m_scaleEventHandler); + ResetCountAddEvent(m_resetCountEventHandler); + GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); + + // When coming into relevance, reset all blending factors so we don't interpolate to our start position + OnResetCountChangedEvent(); } void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -56,23 +63,37 @@ namespace Multiplayer void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetRotation(rotation); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetRotation(m_targetTransform.GetRotation()); + m_targetTransform.SetRotation(rotation); } void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetTranslation(translation); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetTranslation(m_targetTransform.GetTranslation()); + m_targetTransform.SetTranslation(translation); } void NetworkTransformComponent::OnScaleChangedEvent(const AZ::Vector3& scale) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetScale(scale); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetScale(m_targetTransform.GetScale()); + m_targetTransform.SetScale(scale); + } + + void NetworkTransformComponent::OnResetCountChangedEvent() + { + m_previousTransform = m_targetTransform; + } + + void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime, float blendFactor) + { + if (!HasController()) + { + AZ::Transform blendTransform; + blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor)); + blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); + blendTransform.SetScale(m_previousTransform.GetScale().Lerp(m_targetTransform.GetScale(), blendFactor)); + GetTransformComponent()->SetWorldTM(blendTransform); + } } @@ -96,11 +117,8 @@ namespace Multiplayer void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm) { - if (IsAuthority()) - { - SetRotation(worldTm.GetRotation()); - SetTranslation(worldTm.GetTranslation()); - SetScale(worldTm.GetScale()); - } + SetRotation(worldTm.GetRotation()); + SetTranslation(worldTm.GetTranslation()); + SetScale(worldTm.GetScale()); } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index ef8627fe54..485a3719ad 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -12,7 +12,6 @@ #include #include - #include #include #include @@ -24,12 +23,19 @@ #include #include #include +#include #include #include +#include #include #include #include + +#include +#include +#include #include + #include namespace AZ::ConsoleTypeHelpers @@ -74,6 +80,7 @@ namespace Multiplayer AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); + AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update"); AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) @@ -156,10 +163,26 @@ namespace Multiplayer AZ::TickBus::Handler::BusDisconnect(); } - void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) + void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { - AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); - AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs(); + const AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); + const AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs(); + const AZ::TimeMs serverRateMs = static_cast(sv_serverSendRateMs); + const float serverRateSeconds = static_cast(serverRateMs) / 1000.0f; + + TickVisibleNetworkEntities(deltaTime, serverRateSeconds); + + if (GetAgentType() == MultiplayerAgentType::ClientServer + || GetAgentType() == MultiplayerAgentType::DedicatedServer) + { + m_serverSendAccumulator += deltaTime; + if (m_serverSendAccumulator < serverRateSeconds) + { + return; + } + m_serverSendAccumulator -= serverRateSeconds; + m_networkTime.IncrementHostFrameId(); + } // Handle deferred local rpc messages that were generated during the updates m_networkEntityManager.DispatchLocalDeferredRpcMessages(); @@ -365,13 +388,21 @@ namespace Multiplayer } EntityReplicationManager& replicationManager = reinterpret_cast(connection->GetUserData())->GetReplicationManager(); - - // Ignore a_Request.GetServerGameTimePoint(), clients can't affect the server gametime + + if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId)) + { + // Update client to latest server time + m_renderBlendFactor = 0.0f; + m_lastReplicatedHostTimeMs = packet.GetHostTimeMs(); + m_lastReplicatedHostFrameId = packet.GetHostFrameId(); + m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId); + } + for (AZStd::size_t i = 0; i < packet.GetEntityMessages().size(); ++i) { const NetworkEntityUpdateMessage& updateMessage = packet.GetEntityMessages()[i]; handledAll &= replicationManager.HandleEntityUpdateMessage(connection, packetHeader, updateMessage); - AZ_Assert(handledAll, "GameServerToClientNetworkRequestHandler EntityUpdates Did not handle all updates"); + AZ_Assert(handledAll, "EntityUpdates did not handle all update messages"); } return handledAll; @@ -439,7 +470,7 @@ namespace Multiplayer } if (GetAgentType() == MultiplayerAgentType::ClientServer - || GetAgentType() == MultiplayerAgentType::DedicatedServer) + || GetAgentType() == MultiplayerAgentType::DedicatedServer) { PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str()), 1); INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity()); @@ -594,6 +625,57 @@ namespace Multiplayer AZLOG_INFO("Total RPCs received bytes: %llu", aznumeric_cast(rpcsRecv.m_totalBytes)); } + void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds) + { + const float targetAdjustBlend = AZStd::clamp(deltaTime / serverRateSeconds, 0.0f, 1.0f); + m_renderBlendFactor += targetAdjustBlend; + + // Linear close to the origin, but asymptote at y = 1 + const float adjustedBlendFactor = 1.0f - (std::powf(0.2f, m_renderBlendFactor)); + AZLOG(NET_Blending, "Computed blend factor of %f", adjustedBlendFactor); + + AZ::Transform activeCameraTransform; + Camera::Configuration activeCameraConfiguration; + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform); + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration); + + const AZ::ViewFrustumAttributes frustumAttributes + ( + activeCameraTransform, + activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth, + activeCameraConfiguration.m_fovRadians, + activeCameraConfiguration.m_nearClipDistance, + activeCameraConfiguration.m_farClipDistance + ); + const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes); + + // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system + AZStd::vector gatheredEntities; + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, + [&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData) + { + gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); + for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) + { + if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) + { + AZ::Entity* entity = static_cast(visEntry->m_userData); + NetBindComponent* netBindComponent = entity->template FindComponent(); + if (netBindComponent != nullptr) + { + gatheredEntities.push_back(netBindComponent); + } + } + } + }); + + for (NetBindComponent* netBindComponent : gatheredEntities) + { + netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + } + } + void MultiplayerSystemComponent::OnConsoleCommandInvoked ( AZStd::string_view command, diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index db83c50fb5..a38bb935a2 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -102,6 +102,7 @@ namespace Multiplayer private: + void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); @@ -123,6 +124,9 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; + double m_serverSendAccumulator = 0.0; + float m_renderBlendFactor = 0.0f; + #if !defined(AZ_RELEASE_BUILD) MultiplayerEditorConnection m_editorConnectionListener; #endif diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index bd30c5e37f..5d0284dfb2 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -132,7 +132,7 @@ namespace Multiplayer EntityReplicatorList replicatorUpdatedList; MultiplayerPackets::EntityUpdates entityUpdatePacket; entityUpdatePacket.SetHostTimeMs(hostTimeMs); - entityUpdatePacket.SetHostFrameId(InvalidHostFrameId); + entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId()); // Serialize everything while (!toSendList.empty()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp index dfa324f76b..8af636870b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp @@ -27,7 +27,7 @@ namespace Multiplayer , m_sentRecords(net_EntityReplicatorRecordsMax) { AZ_Assert(m_netBindComponent, "NetBindComponent is nullptr"); - m_pendingRecord.SetNetworkRole(remoteNetworkRole); + m_pendingRecord.SetRemoteNetworkRole(remoteNetworkRole); } bool PropertyPublisher::IsDeleting() const @@ -67,7 +67,7 @@ namespace Multiplayer void PropertyPublisher::SetRebasing() { - AZ_Assert(m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity"); + AZ_Assert(m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity"); m_replicatorState = EntityReplicatorState::Rebasing; } @@ -118,7 +118,7 @@ namespace Multiplayer m_sentRecords.clear(); m_netBindComponent->FillTotalReplicationRecord(m_pendingRecord); // Don't send predictable properties back to the Autonomous unless we correct them - if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous) + if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord()); } @@ -137,7 +137,7 @@ namespace Multiplayer // We need to clear out old records, and build up a list of everything that has changed since the last acked packet m_sentRecords.push_front(m_pendingRecord); auto iter = m_sentRecords.begin(); - ++iter; // consider everything after the record we are going to send + ++iter; // Consider everything after the record we are going to send for (; iter != m_sentRecords.end(); ++iter) { // Sequence wasn't acked, so we need to send these bits again @@ -145,7 +145,7 @@ namespace Multiplayer } // Don't send predictable properties back to the Autonomous unless we correct them - if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous) + if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord()); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp index 7fe0efd323..47360bfba6 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp @@ -49,19 +49,19 @@ namespace Multiplayer } ReplicationRecord::ReplicationRecord(NetEntityRole netEntityRole) - : m_netEntityRole(netEntityRole) + : m_remoteNetEntityRole(netEntityRole) { ; } - void ReplicationRecord::SetNetworkRole(NetEntityRole netEntityRole) + void ReplicationRecord::SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole) { - m_netEntityRole = netEntityRole; + m_remoteNetEntityRole = remoteNetEntityRole; } - NetEntityRole ReplicationRecord::GetNetworkRole() const + NetEntityRole ReplicationRecord::GetRemoteNetworkRole() const { - return m_netEntityRole; + return m_remoteNetEntityRole; } bool ReplicationRecord::AreAllBitsConsumed() const @@ -196,26 +196,26 @@ namespace Multiplayer bool ReplicationRecord::ContainsAuthorityToClientBits() const { - return (m_netEntityRole != NetEntityRole::Authority) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole != NetEntityRole::Authority) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAuthorityToServerBits() const { - return (m_netEntityRole == NetEntityRole::Server) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Server) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAuthorityToAutonomousBits() const { - return (m_netEntityRole == NetEntityRole::Autonomous || m_netEntityRole == NetEntityRole::Server) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Autonomous || m_remoteNetEntityRole == NetEntityRole::Server) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAutonomousToAuthorityBits() const { - return (m_netEntityRole == NetEntityRole::Authority) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Authority) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } uint32_t ReplicationRecord::GetRemainingAuthorityToClientBits() const From dc4a15628f4702d54035a7680dc3c68a478fcdf9 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 00:43:31 -0700 Subject: [PATCH 12/65] Remove unused GetNestedInstance method in Prefab Instance class --- .../AzToolsFramework/Prefab/Instance/Instance.cpp | 11 ----------- .../AzToolsFramework/Prefab/Instance/Instance.h | 1 - 2 files changed, 12 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index 8c7604f680..8f483ec818 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -401,17 +401,6 @@ namespace AzToolsFramework } } - InstancePtrOptionalReference Instance::GetNestedInstance(const InstanceAlias& instanceAlias) - { - auto nestedInstanceIterator = m_nestedInstances.find(instanceAlias); - if (nestedInstanceIterator != m_nestedInstances.end()) - { - return nestedInstanceIterator->second; - } - - return AZStd::nullopt; - } - void Instance::GetNestedInstances(const AZStd::function&)>& callback) { for (auto& [instanceAlias, instance] : m_nestedInstances) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 377be68753..31dfb7b8b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -95,7 +95,6 @@ namespace AzToolsFramework Instance& AddInstance(AZStd::unique_ptr instance); AZStd::unique_ptr DetachNestedInstance(const InstanceAlias& instanceAlias); - InstancePtrOptionalReference GetNestedInstance(const InstanceAlias& instanceAlias); /** * Gets the aliases for the entities in the Instance DOM. From be6cee806ddcc1b629cfcf3fad51c5429091a504 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 02:14:28 -0700 Subject: [PATCH 13/65] Show detach prefab only when a single instance is selected --- .../Prefab/PrefabPublicHandler.cpp | 26 +++++++++---------- .../UI/Prefab/PrefabIntegrationManager.cpp | 21 +++++++-------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index f6ded4ae45..653b7d4878 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1155,19 +1155,6 @@ namespace AzToolsFramework return AZ::Success(); } - void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) - { - QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); - QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); - - stringToReplace.replace(oldAliasQuotes, newAliasQuotes); - - QString oldAliasPathRef = QString("/%1").arg(oldAlias.data()); - QString newAliasPathRef = QString("/%1").arg(newAlias.data()); - - stringToReplace.replace(oldAliasPathRef, newAliasPathRef); - } - void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation) { @@ -1418,5 +1405,18 @@ namespace AzToolsFramework return true; } + + void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) + { + QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); + QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); + + stringToReplace.replace(oldAliasQuotes, newAliasQuotes); + + QString oldAliasPathRef = QString("/%1").arg(oldAlias.data()); + QString newAliasPathRef = QString("/%1").arg(newAlias.data()); + + stringToReplace.replace(oldAliasPathRef, newAliasPathRef); + } } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 090c7bdc54..45499a4071 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -238,24 +238,21 @@ namespace AzToolsFramework deleteAction->setDisabled(true); } - QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); - if (selectedEntities.size() != 1) - { - detachPrefabAction->setDisabled(true); - } - else + // Detach Prefab + if (selectedEntities.size() == 1) { AZ::EntityId selectedEntity = selectedEntities[0]; if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity)) { - QObject::connect(detachPrefabAction, &QAction::triggered, detachPrefabAction, - [this, selectedEntity] { ContextMenu_DetachPrefab(selectedEntity); }); - } - else - { - detachPrefabAction->setDisabled(true); + QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); + QObject::connect( + detachPrefabAction, &QAction::triggered, detachPrefabAction, + [this, selectedEntity] + { + ContextMenu_DetachPrefab(selectedEntity); + }); } } } From 55e1da64bb2e95a6668fd919619334a74ebb8d90 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 02:29:09 -0700 Subject: [PATCH 14/65] Renamed a function and improved comments --- .../AzToolsFramework/Prefab/Instance/Instance.h | 1 - .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 2 +- .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- .../AzToolsFramework/Prefab/PrefabPublicInterface.h | 9 ++++++--- .../UI/Prefab/PrefabIntegrationManager.cpp | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 31dfb7b8b4..68bc395012 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -48,7 +48,6 @@ namespace AzToolsFramework using EntityAliasOptionalReference = AZStd::optional>; using InstanceOptionalReference = AZStd::optional>; using InstanceOptionalConstReference = AZStd::optional>; - using InstancePtrOptionalReference = AZStd::optional>>; using InstanceSet = AZStd::unordered_set; using InstanceSetConstReference = AZStd::optional>; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 653b7d4878..7ec0ddf8ad 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -990,7 +990,7 @@ namespace AzToolsFramework return AZ::Success(); } - PrefabOperationResult PrefabPublicHandler::DetachPrefabFromParent(const AZ::EntityId& entityId) + PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& entityId) { if (!entityId.IsValid()) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f1c32ee35c..f3b3b242dd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,7 +64,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) override; private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index f12bc359f2..ec13a852ee 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -152,12 +152,15 @@ namespace AzToolsFramework virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; /** - * Detaches target container entity's owning instance from its parent instance. + * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting + * the container entity into a regular entity and putting it under the parent prefab, removing the link between this + * instance and the parent, removing links between this instance and it's nested instances, adding entities directly + * owned by this instance under the parent instance. * Bails if the entity is not a container entity or belongs to the level prefab instance. - * @param entityId The container entity whose instance to detach. + * @param entityId The container entity id of the instance to detach. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) = 0; + virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) = 0; }; } // namespace Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 45499a4071..b7feb1a8c1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -413,7 +413,7 @@ namespace AzToolsFramework void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) { PrefabOperationResult detachPrefabResult = - s_prefabPublicInterface->DetachPrefabFromParent(containerEntity); + s_prefabPublicInterface->DetachPrefab(containerEntity); if (!detachPrefabResult.IsSuccess()) { From 984db9c1b48571998f5a19381262ce391ed2727d Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 10:56:03 -0700 Subject: [PATCH 15/65] Remove selection command and update undo batch in DetachPrefab function --- .../Prefab/PrefabPublicHandler.cpp | 53 ++++--------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 7ec0ddf8ad..e223775add 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1010,38 +1010,11 @@ namespace AzToolsFramework AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); - UndoSystem::URSequencePoint* currentUndoBatch = nullptr; - ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch); - - bool createdUndo = false; - if (!currentUndoBatch) - { - createdUndo = true; - ToolsApplicationRequests::Bus::BroadcastResult( - currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Detach Prefab"); - AZ_Assert(currentUndoBatch, "Failed to create new undo batch."); - } - - // In order to undo Prefab Instance detachment, we have to create a selection command which selects the current selection - // and then add the detach as children. - // Commands always execute themselves first and then their children (when going forwards) - // and do the opposite when going backwards. - EntityIdList selectedEntities; - ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities); - SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Detach Prefab"); - - // We insert a "deselect all" command before we detach the Prefab Instance. This ensures the detach operations aren't changing - // selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive - // UI updates once at the start instead of once for each entity. - { - EntityIdList deselection; - SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities"); - deselectAllCommand->SetParent(selCommand); - } - { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture"); + ScopedUndoBatch undoBatch("Detach Prefab"); + InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); const auto parentTemplateId = parentInstance->get().GetTemplateId(); @@ -1049,7 +1022,7 @@ namespace AzToolsFramework auto instancePtr = parentInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); - RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch()); Prefab::PrefabDom instanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); @@ -1094,12 +1067,10 @@ namespace AzToolsFramework PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); - command->SetParent(selCommand); - - selCommand->SetParent(currentUndoBatch); + command->SetParent(undoBatch.GetUndoBatch()); { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo"); - selCommand->RunRedo(); + command->RunRedo(); } const auto instanceTemplateId = instancePtr->GetTemplateId(); @@ -1117,7 +1088,7 @@ namespace AzToolsFramework PrefabDom linkPatchesCopy; linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); - RemoveLink(nestedInstancePtr, instanceTemplateId, currentUndoBatch); + RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); //update aliases @@ -1139,17 +1110,13 @@ namespace AzToolsFramework linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); - CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); + CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), + AZStd::move(linkPatchesCopy), true); }); } - } - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( - &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); - - if (createdUndo) - { - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(); From de4cfdb5d7d7ef8c736763eb366013640e6d00b5 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 11:33:58 -0700 Subject: [PATCH 16/65] Add helper function to update entity aliases in link patch --- .../Prefab/PrefabPublicHandler.cpp | 67 +++++++++++-------- .../Prefab/PrefabPublicHandler.h | 7 +- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index e223775add..1c645100d1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -189,24 +189,7 @@ namespace AzToolsFramework if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - previousPatch.Accept(writer); - QString previousPatchString(buffer.GetString()); - - for (AZ::Entity* entity : entities) - { - AZ::EntityId entityId = entity->GetId(); - AZStd::string oldEntityAlias = oldEntityAliases[entityId]; - EntityAliasOptionalReference newEntityAlias = instanceToCreate->get().GetEntityAlias(entityId); - AZ_Assert( - newEntityAlias.has_value(), - "Could not fetch entity alias for entity with id '%llu' during prefab creation.", - static_cast(entityId)); - ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); - } - - previousPatch.Parse(previousPatchString.toUtf8().constData()); + UpdateLinkPatchForNewParent(previousPatch, oldEntityAliases, instanceToCreate->get()); } // These link creations shouldn't be undone because that would put the template in a non-usable state if a user @@ -1015,17 +998,20 @@ namespace AzToolsFramework ScopedUndoBatch undoBatch("Detach Prefab"); - InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); - const auto parentTemplateId = parentInstance->get().GetTemplateId(); + InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance(); + AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity."); + + auto& parentInstance = getParentInstanceResult->get(); + const auto parentTemplateId = parentInstance.GetTemplateId(); { - auto instancePtr = parentInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); + auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias()); AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch()); Prefab::PrefabDom instanceDomBefore; - m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); AZStd::unordered_map oldEntityAliases; oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); @@ -1039,7 +1025,7 @@ namespace AzToolsFramework delete editorPrefabComponent; containerEntity.Activate(); - const bool containerEntityAdded = parentInstance->get().AddEntity(containerEntity); + const bool containerEntityAdded = parentInstance.AddEntity(containerEntity); AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed."); EntityIdList entityIds; @@ -1056,14 +1042,14 @@ namespace AzToolsFramework [&](AZStd::unique_ptr entityPtr) { auto& entity = *entityPtr.release(); - const bool entityAdded = parentInstance->get().AddEntity(entity); + const bool entityAdded = parentInstance.AddEntity(entity); AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed."); entityIds.emplace_back(entity.GetId()); }); Prefab::PrefabDom instanceDomAfter; - m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance->get()); + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance); PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); @@ -1074,7 +1060,7 @@ namespace AzToolsFramework } const auto instanceTemplateId = instancePtr->GetTemplateId(); - auto parentContainerEntityId = parentInstance->get().GetContainerEntityId(); + auto parentContainerEntityId = parentInstance.GetContainerEntityId(); instancePtr->GetNestedInstances( [&](AZStd::unique_ptr& nestedInstancePtr) { @@ -1089,8 +1075,8 @@ namespace AzToolsFramework linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); - PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); + UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); //update aliases rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1100,7 +1086,7 @@ namespace AzToolsFramework for (AZ::EntityId entityId : entityIds) { AZStd::string oldEntityAlias = oldEntityAliases[entityId]; - EntityAliasOptionalReference newEntityAlias = parentInstance->get().GetEntityAlias(entityId); + EntityAliasOptionalReference newEntityAlias = parentInstance.GetEntityAlias(entityId); AZ_Assert( newEntityAlias.has_value(), "Could not fetch entity alias for entity with id '%llu' during prefab creation.", @@ -1385,5 +1371,30 @@ namespace AzToolsFramework stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } + + void PrefabPublicHandler::UpdateLinkPatchForNewParent( + PrefabDom& linkPatch, + const AZStd::unordered_map& oldEntityAliases, + Instance& newParent) + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + linkPatch.Accept(writer); + QString previousPatchString(buffer.GetString()); + + for (const auto& [entityId, oldEntityAlias] : oldEntityAliases) + { + EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId); + AZ_Assert( + newEntityAlias.has_value(), + "Could not fetch entity alias for entity with id '%llu' during prefab creation.", + static_cast(entityId)); + + ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); + } + + linkPatch.Parse(previousPatchString.toUtf8().constData()); + } + } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f3b3b242dd..c339c17a48 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -134,7 +134,12 @@ namespace AzToolsFramework bool IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); - void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias); + static void UpdateLinkPatchForNewParent( + PrefabDom& linkPatch, + const AZStd::unordered_map& oldEntityAliases, + Instance& newParent); + + static void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias); static Instance* GetParentInstance(Instance* instance); static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant); From da0ab84f1cbd279294eab50ea832e5122f06e147 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 11:42:36 -0700 Subject: [PATCH 17/65] Add helper function to update entity aliases in link patch --- .../Prefab/PrefabPublicHandler.cpp | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 1c645100d1..fdc4302c1b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1077,25 +1077,7 @@ namespace AzToolsFramework RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); - //update aliases - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - linkPatchesCopy.Accept(writer); - QString previousPatchString(buffer.GetString()); - - for (AZ::EntityId entityId : entityIds) - { - AZStd::string oldEntityAlias = oldEntityAliases[entityId]; - EntityAliasOptionalReference newEntityAlias = parentInstance.GetEntityAlias(entityId); - AZ_Assert( - newEntityAlias.has_value(), - "Could not fetch entity alias for entity with id '%llu' during prefab creation.", - static_cast(entityId)); - ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); - } - - linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); - + CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(linkPatchesCopy), true); }); From 50c6c9b1c62131f1075b8b06d86bef82699eba10 Mon Sep 17 00:00:00 2001 From: clujames Date: Fri, 28 May 2021 13:23:28 -0700 Subject: [PATCH 18/65] Added an optional variable to the cdk deploy function to allow additional flags and arguments. --- AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py index 455b3f94cb..a45ce3f49e 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -82,16 +82,19 @@ class Cdk: env=self._cdk_env, shell=True) - def deploy(self, context_variable: str = '') -> List[str]: + def deploy(self, context_variable: str = '', additonal_params: List[str] = None) -> List[str]: """ Deploys all the CDK stacks. :param context_variable: Context variable for enabling optional features. + :param additonal_params: Additonal parameters like --all can be passed in this way. :return List of deployed stack arns. """ if not self._cdk_path: return [] deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] + if additonal_params: + deploy_cdk_application_cmd += additonal_params if context_variable: deploy_cdk_application_cmd.extend(['-c', f'{context_variable}']) From 17024d6cf4bc30343604ebeb9bfb4e3727998436 Mon Sep 17 00:00:00 2001 From: clujames Date: Fri, 28 May 2021 14:21:55 -0700 Subject: [PATCH 19/65] Updating according to feedback --- AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py index a45ce3f49e..ea40001c31 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -94,7 +94,7 @@ class Cdk: deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] if additonal_params: - deploy_cdk_application_cmd += additonal_params + deploy_cdk_application_cmd.extend(additonal_params) if context_variable: deploy_cdk_application_cmd.extend(['-c', f'{context_variable}']) From e8e9096dda7ea79d6275f26db4eb0dbf8eb82932 Mon Sep 17 00:00:00 2001 From: srikappa Date: Tue, 1 Jun 2021 11:11:13 -0700 Subject: [PATCH 20/65] Changed a function name and removed a comment --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 9 +++------ .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index fdc4302c1b..3187b55721 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -189,7 +189,7 @@ namespace AzToolsFramework if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); - UpdateLinkPatchForNewParent(previousPatch, oldEntityAliases, instanceToCreate->get()); + UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->get()); } // These link creations shouldn't be undone because that would put the template in a non-usable state if a user @@ -366,9 +366,6 @@ namespace AzToolsFramework CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch)); - // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes - //m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } @@ -1076,7 +1073,7 @@ namespace AzToolsFramework RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); - UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); + UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance); CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(linkPatchesCopy), true); @@ -1354,7 +1351,7 @@ namespace AzToolsFramework stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } - void PrefabPublicHandler::UpdateLinkPatchForNewParent( + void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, Instance& newParent) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index c339c17a48..acaf9b5753 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -134,7 +134,7 @@ namespace AzToolsFramework bool IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); - static void UpdateLinkPatchForNewParent( + void UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, Instance& newParent); From 9fd690f0048a2d84abdec52271346ab86c28d553 Mon Sep 17 00:00:00 2001 From: srikappa Date: Tue, 1 Jun 2021 17:03:50 -0700 Subject: [PATCH 21/65] Changed a function parameter name --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 12 ++++++------ .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- .../AzToolsFramework/Prefab/PrefabPublicInterface.h | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index afda2aef09..b44d8fde14 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -973,20 +973,20 @@ namespace AzToolsFramework return AZ::Success(); } - PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& entityId) + PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId) { - if (!entityId.IsValid()) + if (!containerEntityId.IsValid()) { return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); } - if (IsLevelInstanceContainerEntity(entityId)) + if (IsLevelInstanceContainerEntity(containerEntityId)) { return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance.")); } - InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(entityId); - if (owningInstance->get().GetContainerEntityId() != entityId) + InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId); + if (owningInstance->get().GetContainerEntityId() != containerEntityId) { return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); } @@ -1014,7 +1014,7 @@ namespace AzToolsFramework m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); AZStd::unordered_map oldEntityAliases; - oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); + oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get()); auto containerEntityPtr = instancePtr->DetachContainerEntity(); auto& containerEntity = *containerEntityPtr.release(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f73af93195..e7b6f8c932 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,7 +64,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index ad6a28cb3e..1dbed53223 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -157,10 +157,10 @@ namespace AzToolsFramework * instance and the parent, removing links between this instance and it's nested instances, adding entities directly * owned by this instance under the parent instance. * Bails if the entity is not a container entity or belongs to the level prefab instance. - * @param entityId The container entity id of the instance to detach. + * @param containerEntityId The container entity id of the instance to detach. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) = 0; + virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0; }; } // namespace Prefab From 3fcc1b64fce369a6129b9419187f2d78d7d39339 Mon Sep 17 00:00:00 2001 From: mnaumov Date: Tue, 1 Jun 2021 20:24:23 -0700 Subject: [PATCH 22/65] [ATOM-15692] Ebus for registering custom feature processors for thumbnail generation --- .../ThumbnailFeatureProcessorProviderBus.h | 37 +++++++++++++++ .../Rendering/CommonThumbnailRenderer.cpp | 28 ++++++++++++ .../Rendering/CommonThumbnailRenderer.h | 11 ++++- .../ThumbnailRendererSteps/InitializeStep.cpp | 45 ++++++++++--------- 4 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Thumbnails/ThumbnailFeatureProcessorProviderBus.h diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Thumbnails/ThumbnailFeatureProcessorProviderBus.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Thumbnails/ThumbnailFeatureProcessorProviderBus.h new file mode 100644 index 0000000000..71e101bcef --- /dev/null +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Include/AtomLyIntegration/CommonFeatures/Thumbnails/ThumbnailFeatureProcessorProviderBus.h @@ -0,0 +1,37 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include + +namespace AZ +{ + namespace LyIntegration + { + namespace Thumbnails + { + //! ThumbnailFeatureProcessorProviderRequests allows registering custom Feature Processors for thumbnail generation + //! Duplicates will be ignored + //! You can check minimal feature processors that are already registered in CommonThumbnailRenderer.cpp + class ThumbnailFeatureProcessorProviderRequests + : public AZ::EBusTraits + { + public: + //! Get a list of custom feature processors to register with thumbnail renderer + virtual const AZStd::vector& GetCustomFeatureProcessors() const = 0; + }; + + using ThumbnailFeatureProcessorProviderBus = AZ::EBus; + } // namespace Thumbnails + } // namespace LyIntegration +} // namespace AZ diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.cpp index 06d54a20b3..b002a0bb66 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.cpp @@ -34,12 +34,34 @@ namespace AZ AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusConnect(RPI::MaterialAsset::RTTI_Type()); AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusConnect(RPI::ModelAsset::RTTI_Type()); SystemTickBus::Handler::BusConnect(); + ThumbnailFeatureProcessorProviderBus::Handler::BusConnect(); m_steps[Step::Initialize] = AZStd::make_shared(this); m_steps[Step::FindThumbnailToRender] = AZStd::make_shared(this); m_steps[Step::WaitForAssetsToLoad] = AZStd::make_shared(this); m_steps[Step::Capture] = AZStd::make_shared(this); m_steps[Step::ReleaseResources] = AZStd::make_shared(this); + + m_minimalFeatureProcessors = + { + "AZ::Render::TransformServiceFeatureProcessor", + "AZ::Render::MeshFeatureProcessor", + "AZ::Render::SimplePointLightFeatureProcessor", + "AZ::Render::SimpleSpotLightFeatureProcessor", + "AZ::Render::PointLightFeatureProcessor", + // There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow + // flickering [ATOM-13568] + // as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now. + // Possibly re-enable with [GFX TODO][ATOM-13639] + // "AZ::Render::DirectionalLightFeatureProcessor", + "AZ::Render::DiskLightFeatureProcessor", + "AZ::Render::CapsuleLightFeatureProcessor", + "AZ::Render::QuadLightFeatureProcessor", + "AZ::Render::DecalTextureArrayFeatureProcessor", + "AZ::Render::ImageBasedLightFeatureProcessor", + "AZ::Render::PostProcessFeatureProcessor", + "AZ::Render::SkyBoxFeatureProcessor" + }; } CommonThumbnailRenderer::~CommonThumbnailRenderer() @@ -50,6 +72,7 @@ namespace AZ } AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler::BusDisconnect(); SystemTickBus::Handler::BusDisconnect(); + ThumbnailFeatureProcessorProviderBus::Handler::BusDisconnect(); } void CommonThumbnailRenderer::SetStep(Step step) @@ -77,6 +100,11 @@ namespace AZ AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::ExecuteQueuedEvents(); } + const AZStd::vector& CommonThumbnailRenderer::GetCustomFeatureProcessors() const + { + return m_minimalFeatureProcessors; + } + AZStd::shared_ptr CommonThumbnailRenderer::GetData() const { return m_data; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.h index 50e73f4391..249a1f1343 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/CommonThumbnailRenderer.h @@ -17,6 +17,8 @@ #include #include +#include + // Disables warning messages triggered by the Qt library // 4251: class needs to have dll-interface to be used by clients of class // 4800: forcing value to bool 'true' or 'false' (performance warning) @@ -34,9 +36,10 @@ namespace AZ //! Provides custom rendering of material and model thumbnails class CommonThumbnailRenderer - : private AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler + : public ThumbnailRendererContext + , private AzToolsFramework::Thumbnailer::ThumbnailerRendererRequestBus::MultiHandler , private SystemTickBus::Handler - , public ThumbnailRendererContext + , private ThumbnailFeatureProcessorProviderBus::Handler { public: AZ_CLASS_ALLOCATOR(CommonThumbnailRenderer, AZ::SystemAllocator, 0) @@ -57,9 +60,13 @@ namespace AZ //! SystemTickBus::Handler interface overrides... void OnSystemTick() override; + //! Render::ThumbnailFeatureProcessorProviderBus::Handler interface overrides... + const AZStd::vector& GetCustomFeatureProcessors() const override; + AZStd::unordered_map> m_steps; Step m_currentStep = Step::None; AZStd::shared_ptr m_data; + AZStd::vector m_minimalFeatureProcessors; }; } // namespace Thumbnails } // namespace LyIntegration diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp index 322c765f1b..c35c33017a 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp @@ -11,10 +11,16 @@ */ +#include +#include + +#include + #include #include #include #include + #include #include #include @@ -23,10 +29,11 @@ #include #include #include + #include #include -#include -#include +#include + #include #include #include @@ -37,7 +44,6 @@ namespace AZ { namespace Thumbnails { - InitializeStep::InitializeStep(ThumbnailRendererContext* context) : ThumbnailRendererStep(context) { @@ -50,24 +56,23 @@ namespace AZ data->m_entityContext = AZStd::make_unique(); data->m_entityContext->InitContext(); - // Create and register a scene with minimum required feature processors + // Create and register a scene with all required feature processors RPI::SceneDescriptor sceneDesc; - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::TransformServiceFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::MeshFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SimplePointLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SimpleSpotLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PointLightFeatureProcessor"); - // There is currently a bug where having multiple DirectionalLightFeatureProcessors active can result in shadow flickering [ATOM-13568] - // as well as continually rebuilding MeshDrawPackets [ATOM-13633]. Lets just disable the directional light FP for now. - // Possibly re-enable with [GFX TODO][ATOM-13639] - // sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DirectionalLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DiskLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::CapsuleLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::QuadLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::DecalTextureArrayFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::ImageBasedLightFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::PostProcessFeatureProcessor"); - sceneDesc.m_featureProcessorNames.push_back("AZ::Render::SkyBoxFeatureProcessor"); + + AZ::EBusAggregateResults> results; + ThumbnailFeatureProcessorProviderBus::BroadcastResult(results, &ThumbnailFeatureProcessorProviderBus::Handler::GetCustomFeatureProcessors); + + AZStd::set featureProcessorNames; + for (auto& resultCollection : results.values) + { + for (auto& featureProcessorName : resultCollection) + { + if (featureProcessorNames.emplace(featureProcessorName).second) + { + sceneDesc.m_featureProcessorNames.push_back(featureProcessorName); + } + } + } data->m_scene = RPI::Scene::CreateScene(sceneDesc); From d7ae88c17b1ce1c5c59f293a0575cee9f4ea0937 Mon Sep 17 00:00:00 2001 From: mnaumov Date: Tue, 1 Jun 2021 20:24:52 -0700 Subject: [PATCH 23/65] Adding cmake change --- .../Code/atomlyintegration_commonfeatures_editor_files.cmake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake index e58f72a121..9072cd54f2 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/atomlyintegration_commonfeatures_editor_files.cmake @@ -10,11 +10,12 @@ # set(FILES + Include/AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h + Include/AtomLyIntegration/CommonFeatures/ReflectionProbe/EditorReflectionProbeBus.h + Include/AtomLyIntegration/CommonFeatures/Thumbnails/ThumbnailFeatureProcessorProviderBus.h Source/Module.cpp Source/Animation/EditorAttachmentComponent.h Source/Animation/EditorAttachmentComponent.cpp - Include/AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h - Include/AtomLyIntegration/CommonFeatures/ReflectionProbe/EditorReflectionProbeBus.h Source/EditorCommonFeaturesSystemComponent.h Source/EditorCommonFeaturesSystemComponent.cpp Source/CoreLights/EditorAreaLightComponent.h From 39d1f2702192090408d59746b78d59870495f38f Mon Sep 17 00:00:00 2001 From: scottr Date: Wed, 2 Jun 2021 08:39:57 -0700 Subject: [PATCH 24/65] [ftue_auto_register] post merge and linux build fixes --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index ab13e67591..ca8e571de5 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -324,10 +324,10 @@ namespace O3DE::ProjectManager // check current engine path against all other registered engines // to see if we are already registered - auto allEngines = m_registration.attr("get_engines")(); + auto allEngines = m_manifest.attr("get_engines")(); if (pybind11::isinstance(allEngines)) { - for (const auto& engine : allEngines) + for (auto engine : allEngines) { AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); if (enginePath.Compare(m_enginePath) == 0) @@ -340,7 +340,7 @@ namespace O3DE::ProjectManager if (registerThis) { - auto result = m_registration.attr("register")(m_enginePath.c_str()); + auto result = m_register.attr("register")(m_enginePath.c_str()); registrationResult = (result.cast() == 0); } }); From 5554bdf329d69ba7f7180fa6c2eb0cacd55697f3 Mon Sep 17 00:00:00 2001 From: scottr Date: Wed, 2 Jun 2021 08:46:57 -0700 Subject: [PATCH 25/65] [ftue_auto_register] use early return in registered engines loop instead of break --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index ca8e571de5..2859a8fc9d 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -320,8 +320,6 @@ namespace O3DE::ProjectManager bool pythonResult = ExecuteWithLock( [&] { - bool registerThis = true; - // check current engine path against all other registered engines // to see if we are already registered auto allEngines = m_manifest.attr("get_engines")(); @@ -332,17 +330,13 @@ namespace O3DE::ProjectManager AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); if (enginePath.Compare(m_enginePath) == 0) { - registerThis = false; - break; + return; } } } - if (registerThis) - { - auto result = m_register.attr("register")(m_enginePath.c_str()); - registrationResult = (result.cast() == 0); - } + auto result = m_register.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); }); bool finalResult = (registrationResult && pythonResult); From ee0ecc2fa03b73cb95e3987d56a63441cfc8fe0a Mon Sep 17 00:00:00 2001 From: mnaumov Date: Wed, 2 Jun 2021 11:49:08 -0700 Subject: [PATCH 26/65] [ATOM-15711] Changing thumbmail sphere to polar sphere --- Gems/Atom/Feature/Common/Assets/Models/sphere.fbx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Atom/Feature/Common/Assets/Models/sphere.fbx b/Gems/Atom/Feature/Common/Assets/Models/sphere.fbx index 5c8f550c8c..eb02c68394 100644 --- a/Gems/Atom/Feature/Common/Assets/Models/sphere.fbx +++ b/Gems/Atom/Feature/Common/Assets/Models/sphere.fbx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a476e99b55cf2a76fef6775c5a57dad29f8ffcb942c625bab04c89051a72a560 -size 62626 +oid sha256:838830c99f344f5b68e5e85c9bc52751350caf48e662c9c2b767ab77039bbd8f +size 103472 From 7631cdc11e6e0b23dba10c16b774bb0dc6825757 Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:06:04 -0700 Subject: [PATCH 27/65] ATOM-15659 Add changing StencilState support to DynamicDrawContext (#1009) --- .../DynamicDraw/DynamicDrawContext.h | 8 +-- .../DynamicDraw/DynamicDrawContext.cpp | 54 ++++++++----------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h index 2dd0865688..81b23068a1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h @@ -43,7 +43,7 @@ namespace AZ { PrimitiveType = AZ_BIT(0), DepthState = AZ_BIT(1), - EnableStencil = AZ_BIT(2), + StencilState = AZ_BIT(2), FaceCullMode = AZ_BIT(3), BlendMode = AZ_BIT(4) }; @@ -110,8 +110,8 @@ namespace AZ //! Set DepthState if DrawStateOptions::DepthState option is enabled void SetDepthState(RHI::DepthState depthState); - //! Enable/disable stencil if DrawStateOptions::EnableStencil option is enabled - void SetEnableStencil(bool enable); + //! Set StencilState if DrawStateOptions::StencilState option is enabled + void SetStencilState(RHI::StencilState stencilState); //! Set CullMode if DrawStateOptions::FaceCullMode option is enabled void SetCullMode(RHI::CullMode cullMode); //! Set TargetBlendState for target 0 if DrawStateOptions::BlendMode option is enabled @@ -188,7 +188,7 @@ namespace AZ // states available for change RHI::CullMode m_cullMode; RHI::DepthState m_depthState; - bool m_enableStencil; + RHI::StencilState m_stencilState; RHI::PrimitiveTopology m_topology; RHI::TargetBlendState m_blendState0; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp index 4e4a7a5e71..00c2122825 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp @@ -30,25 +30,7 @@ namespace AZ constexpr const char* PerContextSrgName = "PerContextSrg"; constexpr const char* PerDrawSrgName = "PerDrawSrg"; }; - - bool CompareTargetBlendState(const RHI::TargetBlendState& firstState, const RHI::TargetBlendState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_blendOp != secondState.m_blendOp - || firstState.m_blendDest != secondState.m_blendDest - || firstState.m_blendSource != secondState.m_blendSource - || firstState.m_blendAlphaDest != secondState.m_blendAlphaDest - || firstState.m_blendAlphaOp != secondState.m_blendAlphaOp - || firstState.m_blendAlphaSource != secondState.m_blendAlphaSource); - } - - bool CompareDepthState(const RHI::DepthState& firstState, const RHI::DepthState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_func != secondState.m_func - || firstState.m_writeMask != secondState.m_writeMask); - } - + void DynamicDrawContext::MultiStates::UpdateHash(const DrawStateOptions& drawStateOptions) { if (!m_isDirty) @@ -70,9 +52,19 @@ namespace AZ seed = TypeHash64(m_depthState.m_writeMask, seed); } - if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::StencilState)) { - seed = TypeHash64(m_enableStencil, seed); + seed = TypeHash64(m_stencilState.m_enable, seed); + seed = TypeHash64(m_stencilState.m_readMask, seed); + seed = TypeHash64(m_stencilState.m_writeMask, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_func, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_func, seed); } if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::FaceCullMode)) @@ -203,7 +195,7 @@ namespace AZ m_currentStates.m_cullMode = m_pipelineState->ConstDescriptor().m_renderStates.m_rasterState.m_cullMode; m_currentStates.m_topology = m_pipelineState->ConstDescriptor().m_inputStreamLayout.GetTopology(); m_currentStates.m_depthState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_depth; - m_currentStates.m_enableStencil = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil.m_enable; + m_currentStates.m_stencilState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil; m_currentStates.m_blendState0 = m_pipelineState->ConstDescriptor().m_renderStates.m_blendState.m_targets[0]; m_currentStates.UpdateHash(m_drawStateOptions); @@ -291,7 +283,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::DepthState)) { - if (!CompareDepthState(m_currentStates.m_depthState, depthState)) + if (!(m_currentStates.m_depthState == depthState)) { m_currentStates.m_depthState = depthState; m_currentStates.m_isDirty = true; @@ -303,19 +295,19 @@ namespace AZ } } - void DynamicDrawContext::SetEnableStencil(bool enable) + void DynamicDrawContext::SetStencilState(RHI::StencilState stencilState) { - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - if (m_currentStates.m_enableStencil != enable) + if (!(m_currentStates.m_stencilState == stencilState)) { - m_currentStates.m_enableStencil = enable; + m_currentStates.m_stencilState = stencilState; m_currentStates.m_isDirty = true; } } else { - AZ_Warning("RHI", false, "Can't set SetEnableStencil if DrawVariation::EnableStencil wasn't enabled"); + AZ_Warning("RHI", false, "Can't set SetStencilState if DrawVariation::StencilState wasn't enabled"); } } @@ -340,7 +332,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::BlendMode)) { - if (!CompareTargetBlendState(m_currentStates.m_blendState0, blendState)) + if (!(m_currentStates.m_blendState0 == blendState)) { m_currentStates.m_blendState0 = blendState; m_currentStates.m_isDirty = true; @@ -695,9 +687,9 @@ namespace AZ { m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_depth = m_currentStates.m_depthState; } - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil.m_enable = m_currentStates.m_enableStencil; + m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil = m_currentStates.m_stencilState; } if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::FaceCullMode)) { From a69db3bf7681e2e6cef653919544ec3828b195c4 Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 14:44:02 -0700 Subject: [PATCH 28/65] Converts physx console commands from cry console to az console, fixes some bugs in the multiplayer gem --- .../Components/NetworkTransformComponent.cpp | 12 +++ .../Source/MultiplayerSystemComponent.cpp | 79 +++++++++++-------- .../Code/Source/SystemComponent.cpp | 49 ++++-------- Gems/PhysXDebug/Code/Source/SystemComponent.h | 3 - 4 files changed, 75 insertions(+), 68 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 9a49724fb8..d4abf6e789 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -81,6 +81,9 @@ namespace Multiplayer void NetworkTransformComponent::OnResetCountChangedEvent() { + m_targetTransform.SetRotation(GetRotation()); + m_targetTransform.SetTranslation(GetTranslation()); + m_targetTransform.SetUniformScale(GetScale()); m_previousTransform = m_targetTransform; } @@ -93,6 +96,15 @@ namespace Multiplayer blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); blendTransform.SetScale(m_previousTransform.GetScale().Lerp(m_targetTransform.GetScale(), blendFactor)); GetTransformComponent()->SetWorldTM(blendTransform); + + //AZLOG + //( + // NET_Movement, + // "Blending entity to position %f x %f x %f", + // blendTransform.GetTranslation().GetX(), + // blendTransform.GetTranslation().GetY(), + // blendTransform.GetTranslation().GetZ() + //); } } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 485a3719ad..38f0fda94e 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -634,45 +634,62 @@ namespace Multiplayer const float adjustedBlendFactor = 1.0f - (std::powf(0.2f, m_renderBlendFactor)); AZLOG(NET_Blending, "Computed blend factor of %f", adjustedBlendFactor); - AZ::Transform activeCameraTransform; - Camera::Configuration activeCameraConfiguration; - Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform); - Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration); - - const AZ::ViewFrustumAttributes frustumAttributes - ( - activeCameraTransform, - activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth, - activeCameraConfiguration.m_fovRadians, - activeCameraConfiguration.m_nearClipDistance, - activeCameraConfiguration.m_farClipDistance - ); - const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes); - - // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system - AZStd::vector gatheredEntities; - AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); - AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, - [&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData) + if (Camera::ActiveCameraRequestBus::HasHandlers()) { - gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); - for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) + // If there's a camera, update only what's visible + AZ::Transform activeCameraTransform; + Camera::Configuration activeCameraConfiguration; + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform); + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration); + + const AZ::ViewFrustumAttributes frustumAttributes + ( + activeCameraTransform, + activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth, + activeCameraConfiguration.m_fovRadians, + activeCameraConfiguration.m_nearClipDistance, + activeCameraConfiguration.m_farClipDistance + ); + const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes); + + // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system + AZStd::vector gatheredEntities; + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, + [&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData) { - if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) + gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); + for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) { - AZ::Entity* entity = static_cast(visEntry->m_userData); - NetBindComponent* netBindComponent = entity->template FindComponent(); - if (netBindComponent != nullptr) + if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) { - gatheredEntities.push_back(netBindComponent); + AZ::Entity* entity = static_cast(visEntry->m_userData); + NetBindComponent* netBindComponent = entity->FindComponent(); + if (netBindComponent != nullptr) + { + gatheredEntities.push_back(netBindComponent); + } } } - } - }); + }); - for (NetBindComponent* netBindComponent : gatheredEntities) + for (NetBindComponent* netBindComponent : gatheredEntities) + { + netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + } + } + else { - netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + // If there's no camera, fall back to updating all net entities + for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker())) + { + AZ::Entity* entity = iter.second; + NetBindComponent* netBindComponent = entity->FindComponent(); + if (netBindComponent != nullptr) + { + netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + } + } } } diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index 08bf71753d..24a28b8f43 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -183,9 +184,7 @@ namespace PhysXDebug void SystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, const SSystemInitParams&) { InitPhysXColorMappings(); - RegisterCommands(); ConfigurePhysXVisualizationParameters(); - } void SystemComponent::Reflect(AZ::ReflectContext* context) @@ -537,12 +536,13 @@ namespace PhysXDebug } } - static void CmdEnableWireFrame([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_EnableWireFrame([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::ToggleCullingWireFrame); } + AZ_CONSOLEFREEFUNC(physx_EnableWireFrame, AZ::ConsoleFunctorFlags::DontReplicate, "Enables physx wireframe view"); - static void CmdConnectToPvd([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_ConnectToPvd([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -550,8 +550,9 @@ namespace PhysXDebug debug->ConnectToPvd(); } } + AZ_CONSOLEFREEFUNC(physx_ConnectToPvd, AZ::ConsoleFunctorFlags::DontReplicate, "Connects to the physx visual debugger"); - static void CmdDisconnectFromPvd([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_DisconnectFromPvd([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -559,13 +560,14 @@ namespace PhysXDebug debug->DisconnectFromPvd(); } } + AZ_CONSOLEFREEFUNC(physx_DisconnectFromPvd, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects from the physx visual debugger"); - static void CmdSetPhysXDebugCullingBoxSize(IConsoleCmdArgs* args) + static void physx_SetPhysXDebugCullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { - const int argumentCount = args->GetArgCount(); + const int argumentCount = arguments.size(); if (argumentCount == 2) { - float newCullingBoxSize = (float)strtol(args->GetArg(1), nullptr, 10); + float newCullingBoxSize = (float)strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10); PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::SetCullingBoxSize, newCullingBoxSize); } else @@ -574,16 +576,17 @@ namespace PhysXDebug "Please use physx_SetDebugCullingBoxSize e.g. physx_SetDebugCullingBoxSize 100."); } } + AZ_CONSOLEFREEFUNC(physx_SetPhysXDebugCullingBoxSize, AZ::ConsoleFunctorFlags::DontReplicate, "Sets physx debug culling box size"); - static void CmdTogglePhysXDebugVisualization(IConsoleCmdArgs* args) + static void physx_TogglePhysXDebugVisualization([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { using namespace CryStringUtils; - const int argumentCount = args->GetArgCount(); + const int argumentCount = arguments.size(); if (argumentCount == 2) { - const auto userPreference = static_cast(strtol(args->GetArg(1), nullptr, 10)); + const auto userPreference = static_cast(strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10)); switch (userPreference) { @@ -609,29 +612,7 @@ namespace PhysXDebug AZ_Warning("PhysXDebug", false, "Invalid physx_Debug Arguments. Please use physx_Debug 1 to enable, physx_Debug 0 to disable or physx_Debug 2 to enable all configuration settings."); } } - - void SystemComponent::RegisterCommands() - { - if (m_registered) - { - return; - } - - if (gEnv) - { - IConsole* console = gEnv->pSystem->GetIConsole(); - if (console) - { - console->AddCommand("physx_Debug", CmdTogglePhysXDebugVisualization); - console->AddCommand("physx_CullingBox", CmdEnableWireFrame); - console->AddCommand("physx_CullingBoxSize", CmdSetPhysXDebugCullingBoxSize); - console->AddCommand("physx_PvdConnect", CmdConnectToPvd); - console->AddCommand("physx_PvdDisconnect", CmdDisconnectFromPvd); - } - - m_registered = true; - } - } + AZ_CONSOLEFREEFUNC(physx_TogglePhysXDebugVisualization, AZ::ConsoleFunctorFlags::DontReplicate, "Toggles physx debug visualization"); void SystemComponent::ConfigurePhysXVisualizationParameters() { diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.h b/Gems/PhysXDebug/Code/Source/SystemComponent.h index 631354c034..f4d033fd4c 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.h +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.h @@ -161,9 +161,6 @@ namespace PhysXDebug /// Initialise the PhysX debug draw colors based on defaults. void InitPhysXColorMappings(); - /// Register debug drawing PhysX commands with Open 3D Engine console during game mode. - void RegisterCommands(); - /// Draw the culling box being used by the viewport. /// @param cullingBoxAabb culling box Aabb to debug draw. void DrawDebugCullingBox(const AZ::Aabb& cullingBoxAabb); From 8a7f156e2c66235546589744d07622da69483d1a Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:45:43 -0700 Subject: [PATCH 29/65] LYN-4133 | Prefab Container Transform stores non-default values to template on Create Prefab (#1038) * Show container transforms, reset container transform to zero before saving a prefab after create. * Fix order of operations to prevent patching issues * Reset the entity to the Identity Transform instead of the default constructor to correctly set the scale to 1.0 --- .../Prefab/PrefabPublicHandler.cpp | 26 +++++++++++++++++-- .../PropertyEditor/EntityPropertyEditor.cpp | 3 ++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index fadcc1b81f..6501986231 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -243,11 +243,33 @@ namespace AzToolsFramework instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch)); + // Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom. + // The new values were saved in the link, so propagation will apply them correctly. + { + AZ::Entity* containerEntity = GetEntityById(containerEntityId); + + PrefabDom containerBeforeReset; + m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity); + + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId()); + AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity()); + + PrefabDom containerAfterReset; + m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity); + + // Update the state of the entity + PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(containerEntityId))); + state->SetParent(undoBatch.GetUndoBatch()); + state->Capture(containerBeforeReset, containerAfterReset, containerEntityId); + + state->Redo(); + } + // This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab. - // We are doing this so that the changes in those enities are not queued up twice for propagation. + // We are doing this so that the changes in those entities are not queued up twice for propagation. AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); - + // Select Container Entity { auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity"); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp index d0f3fa452a..60a53aca42 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp @@ -969,7 +969,8 @@ namespace AzToolsFramework { // Build up components to display SharedComponentArray sharedComponentArray; - BuildSharedComponentArray(sharedComponentArray, selectionEntityTypeInfo != SelectionEntityTypeInfo::OnlyStandardEntities); + BuildSharedComponentArray(sharedComponentArray, + !(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities)); if (sharedComponentArray.size() == 0) { From 8ef2bd751821c56ed841bb15d6b9765a05d75b8a Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 14:47:05 -0700 Subject: [PATCH 30/65] Turn off desync debug by default, as this explodes network input sizes --- .../Source/Components/LocalPredictionPlayerInputComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 99e19a89fd..97e194dccb 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -25,7 +25,7 @@ namespace Multiplayer AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay"); #ifndef AZ_RELEASE_BUILD AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat"); - AZ_CVAR(bool, cl_EnableDesyncDebugging, true, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs"); + AZ_CVAR(bool, cl_EnableDesyncDebugging, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs"); #endif AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs"); From b013d7ac6780c61607792bb291627da4a08154e0 Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 14:53:33 -0700 Subject: [PATCH 31/65] Minor cleanup --- .../Components/NetworkTransformComponent.cpp | 9 --------- .../Code/Source/SystemComponent.cpp | 20 +++++++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index d4abf6e789..2de838df93 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -96,15 +96,6 @@ namespace Multiplayer blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); blendTransform.SetScale(m_previousTransform.GetScale().Lerp(m_targetTransform.GetScale(), blendFactor)); GetTransformComponent()->SetWorldTM(blendTransform); - - //AZLOG - //( - // NET_Movement, - // "Blending entity to position %f x %f x %f", - // blendTransform.GetTranslation().GetX(), - // blendTransform.GetTranslation().GetY(), - // blendTransform.GetTranslation().GetZ() - //); } } diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index 24a28b8f43..34315eb11e 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -536,13 +536,13 @@ namespace PhysXDebug } } - static void physx_EnableWireFrame([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + static void physx_CullingBox([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::ToggleCullingWireFrame); } - AZ_CONSOLEFREEFUNC(physx_EnableWireFrame, AZ::ConsoleFunctorFlags::DontReplicate, "Enables physx wireframe view"); + AZ_CONSOLEFREEFUNC(physx_CullingBox, AZ::ConsoleFunctorFlags::DontReplicate, "Enables physx wireframe view"); - static void physx_ConnectToPvd([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + static void physx_PvdConnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -550,9 +550,9 @@ namespace PhysXDebug debug->ConnectToPvd(); } } - AZ_CONSOLEFREEFUNC(physx_ConnectToPvd, AZ::ConsoleFunctorFlags::DontReplicate, "Connects to the physx visual debugger"); + AZ_CONSOLEFREEFUNC(physx_PvdConnect, AZ::ConsoleFunctorFlags::DontReplicate, "Connects to the physx visual debugger"); - static void physx_DisconnectFromPvd([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + static void physx_PvdDisconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -560,9 +560,9 @@ namespace PhysXDebug debug->DisconnectFromPvd(); } } - AZ_CONSOLEFREEFUNC(physx_DisconnectFromPvd, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects from the physx visual debugger"); + AZ_CONSOLEFREEFUNC(physx_PvdDisconnect, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects from the physx visual debugger"); - static void physx_SetPhysXDebugCullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + static void physx_CullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { const int argumentCount = arguments.size(); if (argumentCount == 2) @@ -576,9 +576,9 @@ namespace PhysXDebug "Please use physx_SetDebugCullingBoxSize e.g. physx_SetDebugCullingBoxSize 100."); } } - AZ_CONSOLEFREEFUNC(physx_SetPhysXDebugCullingBoxSize, AZ::ConsoleFunctorFlags::DontReplicate, "Sets physx debug culling box size"); + AZ_CONSOLEFREEFUNC(physx_CullingBoxSize, AZ::ConsoleFunctorFlags::DontReplicate, "Sets physx debug culling box size"); - static void physx_TogglePhysXDebugVisualization([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + static void physx_Debug([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { using namespace CryStringUtils; @@ -612,7 +612,7 @@ namespace PhysXDebug AZ_Warning("PhysXDebug", false, "Invalid physx_Debug Arguments. Please use physx_Debug 1 to enable, physx_Debug 0 to disable or physx_Debug 2 to enable all configuration settings."); } } - AZ_CONSOLEFREEFUNC(physx_TogglePhysXDebugVisualization, AZ::ConsoleFunctorFlags::DontReplicate, "Toggles physx debug visualization"); + AZ_CONSOLEFREEFUNC(physx_Debug, AZ::ConsoleFunctorFlags::DontReplicate, "Toggles physx debug visualization"); void SystemComponent::ConfigurePhysXVisualizationParameters() { From 82f9d08cfd7539482dd137e5c66d181f1ce11013 Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 15:21:43 -0700 Subject: [PATCH 32/65] Build fix for uniform scale changes --- .../Code/Source/Components/NetworkTransformComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 2de838df93..bb256701ff 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -94,7 +94,7 @@ namespace Multiplayer AZ::Transform blendTransform; blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor)); blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); - blendTransform.SetScale(m_previousTransform.GetScale().Lerp(m_targetTransform.GetScale(), blendFactor)); + blendTransform.SetUniformScale(AZ::Lerp(m_previousTransform.GetUniformScale(), m_targetTransform.GetUniformScale(), blendFactor)); GetTransformComponent()->SetWorldTM(blendTransform); } } From 38853eb2c2426dfed99be3384b2923fce5d0116e Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 15:34:35 -0700 Subject: [PATCH 33/65] Linux build fix --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 074e9af86c..f30c8912de 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -38,6 +38,8 @@ #include +#include // for std::powf on linux + namespace AZ::ConsoleTypeHelpers { template <> From f1dbeb584af8e2671a056baee74e645bab5382f0 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:50:18 -0700 Subject: [PATCH 34/65] LYN-4206 CMake bakes install prefix during configure (#1100) --- cmake/Platform/Common/Install_common.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index b18aed6fb4..710a8b266f 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -15,7 +15,11 @@ ly_set(LY_DEFAULT_INSTALL_COMPONENT Core) file(RELATIVE_PATH runtime_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) file(RELATIVE_PATH library_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) -set(install_output_folder "${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") +# Anywhere CMAKE_INSTALL_PREFIX is used, it has to be escaped so it is baked into the cmake_install.cmake script instead +# of baking the path. This is needed so `cmake --install --prefix ` works regardless of the CMAKE_INSTALL_PREFIX +# used to generate the solution. +# CMAKE_INSTALL_PREFIX is still used when building the INSTALL target +set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") #! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target From c752b9d0fc19ca1f5f5cbc713184b0aeaedd1d9a Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 3 Jun 2021 01:04:01 +0100 Subject: [PATCH 35/65] Fixed ctrl+g port number. Enabled server spawn for levels with no network entities since we can spawn net entities from the scripts --- .../Editor/MultiplayerEditorConnection.cpp | 41 +++++++++++-------- .../Editor/MultiplayerEditorConnection.h | 4 +- .../MultiplayerEditorSystemComponent.cpp | 2 +- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index f684e1f12f..710a051cd9 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -109,11 +109,7 @@ namespace Multiplayer AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); - uint16_t serverPort = DefaultServerPort; - if (auto console = AZ::Interface::Get(); console) - { - console->GetCvarValue("sv_port", serverPort); - } + uint16_t serverPort = GetGameServerPort(); networkInterface->Listen(serverPort); AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); @@ -138,18 +134,20 @@ namespace Multiplayer if (auto console = AZ::Interface::Get(); console) { AZ::CVarFixedString remoteAddress; - uint16_t remotePort; - if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && - console->GetCvarValue("editorsv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) - { - // Connect the Editor to the editor server for Multiplayer simulation - AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); - INetworkInterface* networkInterface = - AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + uint16_t remotePort = GetGameServerPort(); + if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound) + { + // Connect the Editor to the editor server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); - const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); - networkInterface->Connect(ipAddress); - } + // Connecting to DefaultServerPort here + const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); + networkInterface->Connect(ipAddress); + + AZ::Interface::Get()->SendReadyForEntityUpdates(true); + } } } return true; @@ -184,4 +182,15 @@ namespace Multiplayer { ; } + + uint16_t MultiplayerEditorConnection::GetGameServerPort() + { + uint16_t serverPort = DefaultServerPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("sv_port", serverPort); + } + return serverPort; + } + } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index d803a60744..aeafc09861 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -39,7 +39,8 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); - + + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; @@ -50,6 +51,7 @@ namespace Multiplayer //! @} private: + uint16_t GetGameServerPort(); AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZStd::vector m_buffer; diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 523ffd90de..3ac4e98d42 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -165,7 +165,7 @@ namespace Multiplayer // BeginGameMode and Prefab Processing have completed at this point IMultiplayerTools* mpTools = AZ::Interface::Get(); - if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) + if (editorsv_enabled && mpTools != nullptr) { const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); From 3b519c64756df01777dc89b134308338794a4e72 Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 3 Jun 2021 01:06:15 +0100 Subject: [PATCH 36/65] removed whitespace --- .../Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index aeafc09861..40eb38af1d 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -40,7 +40,6 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); - //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; From bffb7d1b2876633780c728969906c656f813105e Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 3 Jun 2021 01:20:22 +0100 Subject: [PATCH 37/65] Simplified the change to rely on sv_port cvar --- .../Editor/MultiplayerEditorConnection.cpp | 41 ++++++++----------- .../Editor/MultiplayerEditorConnection.h | 3 +- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index 710a051cd9..847d1caadf 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -109,7 +109,11 @@ namespace Multiplayer AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::DedicatedServer); INetworkInterface* networkInterface = AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); - uint16_t serverPort = GetGameServerPort(); + uint16_t serverPort = DefaultServerPort; + if (auto console = AZ::Interface::Get(); console) + { + console->GetCvarValue("sv_port", serverPort); + } networkInterface->Listen(serverPort); AZLOG_INFO("Editor Server completed asset receive, responding to Editor..."); @@ -134,20 +138,20 @@ namespace Multiplayer if (auto console = AZ::Interface::Get(); console) { AZ::CVarFixedString remoteAddress; - uint16_t remotePort = GetGameServerPort(); - if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound) - { - // Connect the Editor to the editor server for Multiplayer simulation - AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); - INetworkInterface* networkInterface = - AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); + uint16_t remotePort; + if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && + console->GetCvarValue("sv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) + { + // Connect the Editor to the editor server for Multiplayer simulation + AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); + INetworkInterface* networkInterface = + AZ::Interface::Get()->RetrieveNetworkInterface(AZ::Name(MPNetworkInterfaceName)); - // Connecting to DefaultServerPort here - const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); - networkInterface->Connect(ipAddress); + const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); + networkInterface->Connect(ipAddress); - AZ::Interface::Get()->SendReadyForEntityUpdates(true); - } + AZ::Interface::Get()->SendReadyForEntityUpdates(true); + } } } return true; @@ -182,15 +186,4 @@ namespace Multiplayer { ; } - - uint16_t MultiplayerEditorConnection::GetGameServerPort() - { - uint16_t serverPort = DefaultServerPort; - if (auto console = AZ::Interface::Get(); console) - { - console->GetCvarValue("sv_port", serverPort); - } - return serverPort; - } - } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h index 40eb38af1d..d803a60744 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.h @@ -39,7 +39,7 @@ namespace Multiplayer bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerInit& packet); bool HandleRequest(AzNetworking::IConnection* connection, const AzNetworking::IPacketHeader& packetHeader, MultiplayerEditorPackets::EditorServerReady& packet); - + //! IConnectionListener interface //! @{ AzNetworking::ConnectResult ValidateConnect(const AzNetworking::IpAddress& remoteAddress, const AzNetworking::IPacketHeader& packetHeader, AzNetworking::ISerializer& serializer) override; @@ -50,7 +50,6 @@ namespace Multiplayer //! @} private: - uint16_t GetGameServerPort(); AzNetworking::INetworkInterface* m_networkEditorInterface = nullptr; AZStd::vector m_buffer; From e445c643211322d9c4193669200ebc1965bc777e Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 3 Jun 2021 01:21:27 +0100 Subject: [PATCH 38/65] Fixed TimedThread bled %d ms logging to not spam the console --- .../AzNetworking/AzNetworking/Utilities/TimedThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp b/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp index d078149996..7e1f41f745 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp @@ -46,7 +46,7 @@ namespace AzNetworking } else if (m_updateRate < updateTimeMs) { - AZLOG_INFO("TimedThread bled %d ms", aznumeric_cast(updateTimeMs - m_updateRate)); + AZLOG(NET_TimedThread, "TimedThread bled %d ms", aznumeric_cast(updateTimeMs - m_updateRate)); } } OnStop(); From 312c704ba65d2b2d447ba332fc948e301e5a34c5 Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Wed, 2 Jun 2021 17:39:50 -0700 Subject: [PATCH 39/65] ATOM-15352 Find a solution to modify a render pipeline when enable a feature gem (#960) * Added atom_rpi_tools python module in Atom_RPI gem. The tool includes functions to modify pass template data and some utility functions. * Added pytest tests for atom_rpi_tools --- Gems/Atom/RPI/CMakeLists.txt | 2 + Gems/Atom/RPI/Tools/CMakeLists.txt | 23 ++ Gems/Atom/RPI/Tools/README.txt | 39 +++ Gems/Atom/RPI/Tools/__init__.py | 10 + .../RPI/Tools/atom_rpi_tools/pass_data.py | 210 ++++++++++++ .../Tools/atom_rpi_tools/tests/__init__.py | 10 + .../tests/test_pass_template.py | 303 ++++++++++++++++++ .../Tools/atom_rpi_tools/tests/test_utils.py | 53 +++ .../tests/testdata/pass_requests.json | 116 +++++++ .../tests/testdata/pass_slots.json | 21 ++ .../tests/testdata/pass_test_bad.json | 6 + Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py | 31 ++ Gems/Atom/RPI/Tools/setup.py | 33 ++ 13 files changed, 857 insertions(+) create mode 100644 Gems/Atom/RPI/Tools/CMakeLists.txt create mode 100644 Gems/Atom/RPI/Tools/README.txt create mode 100644 Gems/Atom/RPI/Tools/__init__.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py create mode 100644 Gems/Atom/RPI/Tools/setup.py diff --git a/Gems/Atom/RPI/CMakeLists.txt b/Gems/Atom/RPI/CMakeLists.txt index 20a680bce9..8cea783633 100644 --- a/Gems/Atom/RPI/CMakeLists.txt +++ b/Gems/Atom/RPI/CMakeLists.txt @@ -10,3 +10,5 @@ # add_subdirectory(Code) +add_subdirectory(Tools) + diff --git a/Gems/Atom/RPI/Tools/CMakeLists.txt b/Gems/Atom/RPI/Tools/CMakeLists.txt new file mode 100644 index 0000000000..35df664ab2 --- /dev/null +++ b/Gems/Atom/RPI/Tools/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools) + + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_pytest( + NAME RPI::atom_rpi_tools_tests + PATH ${CMAKE_CURRENT_LIST_DIR}/atom_rpi_tools/tests/ + TIMEOUT 30 + ) + endif() +endif() + diff --git a/Gems/Atom/RPI/Tools/README.txt b/Gems/Atom/RPI/Tools/README.txt new file mode 100644 index 0000000000..ee2e9264da --- /dev/null +++ b/Gems/Atom/RPI/Tools/README.txt @@ -0,0 +1,39 @@ +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + +INTRODUCTION +------------ + +atom_rpi_tools is a Python project that contains a collection of tools +developed by the Atom team. The project contains the following tools: + + * Render pipeline merge tool: + A library to manipulate .pass asset files and help gems create scripts to update render pipeline + + +REQUIREMENTS +------------ + + * Python 3.7.5 (64-bit) + +It is recommended that you completely remove any other versions of Python +installed on your system. + + +INSTALL +----------- +It is recommended to set up these these tools with Lumberyard's CMake build commands. + + +UNINSTALLATION +-------------- + +The preferred way to uninstall the project is: +(engine install root)/python/python -m pip uninstall atom_rpi_tools diff --git a/Gems/Atom/RPI/Tools/__init__.py b/Gems/Atom/RPI/Tools/__init__.py new file mode 100644 index 0000000000..79f8fa4422 --- /dev/null +++ b/Gems/Atom/RPI/Tools/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py new file mode 100644 index 0000000000..e5e7a839bd --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py @@ -0,0 +1,210 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +""" + +import sys, os +import json +import shutil + +class PassTemplate: + # This class provide necessary functions for insert pass requests and update connections + # which are common functions required for adding features. + # It doesn't include the remove/delete furnctions since that's not common case for merging render pipeline + def __init__(self, filePath: str): + self.initialized = False + self.file_path: str = filePath + #load the json file + json_data = open(filePath, "r") + self.file_data = json.load(json_data) + + if 'ClassName' not in self.file_data or 'ClassData' not in self.file_data or self.file_data['ClassName']!='PassAsset' or 'PassTemplate' not in self.file_data['ClassData']: + raise KeyError('the json file is not a PassAsset file') + return + + if 'PassRequests' in self.file_data['ClassData']['PassTemplate']: + self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests'] + + if 'Slots' in self.file_data['ClassData']['PassTemplate']: + self.slots = self.file_data['ClassData']['PassTemplate']['Slots'] + + self.initialized = True + print('PassTemplate is loaded from ', filePath) + + def find_pass(self, passName): + # return pass's index in PassRequests if a PassRequest with input passName exists + if not hasattr(self, 'passRequests'): + return -1 + index = 0 + for passRequest in self.passRequests: + if passRequest['Name'] == passName: + return index + index += 1 + return -1 + + def get_pass_count(self): + if not hasattr(self, 'passRequests'): + return 0 + return len(self.passRequests) + + def __validate_pass_request_data(self, passRequest): + if ('Name' not in passRequest or 'TemplateName' not in passRequest): + raise KeyError('invalid pass request data') + + def __ensure_pass_requests_key(self): + if not hasattr(self, 'passRequests'): + self.file_data['ClassData']['PassTemplate']['PassRequests'] = [] + self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests'] + + def __ensure_pass_slots_key(self): + if not hasattr(self, 'slots'): + self.file_data['ClassData']['PassTemplate']['Slots'] = [] + self.slots = self.file_data['ClassData']['PassTemplate']['Slots'] + + def insert_pass_request(self, location, passRequest): + self.__validate_pass_request_data(passRequest) + + if (self.find_pass(passRequest['Name']) >= 0): + raise ValueError('pass request ', passRequest['Name'], ' is already exist') + # insert a passRequest before the specified location + self.__ensure_pass_requests_key() + self.passRequests.insert(location, passRequest) + + def replace_references_after(self, startPassRequest, oldPass, oldSlot, newPass, newSlot): + if not hasattr(self, 'passRequests'): + return 0 + # from all pass requests after startPassRequest + # replace all attachment references which uses oldPass and oldSlot + # with newPass and newSlot + started = False + replaced_count = 0 + for request in self.passRequests: + if started: + if ('Connections' in request): + for connection in request['Connections']: + if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot: + connection['AttachmentRef']['Pass'] = newPass + connection['AttachmentRef']['Attachment'] = newSlot + replaced_count += 1 + if request['Name'] == startPassRequest and not started: + started = True + return replaced_count + + def replace_references_for(self, passRequest, oldPass, oldSlot, newPass, newSlot): + if not hasattr(self, 'passRequests'): + return 0 + #replace pass reference for the specified passRequest + replaced_count = 0 + for request in self.passRequests: + if request['Name'] == passRequest: + if ('Connections' in request): + for connection in request['Connections']: + if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot: + connection['AttachmentRef']['Pass'] = newPass + connection['AttachmentRef']['Attachment'] = newSlot + replaced_count += 1 + return replaced_count #return when the specified pass request is updated. + return replaced_count + + def __validate_slot_data(self, slotData): + if ('Name' not in slotData or 'SlotType' not in slotData): + raise KeyError('invalid slot data') + + def get_slot_count(self): + if not hasattr(self, 'slots'): + return 0 + return len(self.slots) + + def find_slot(self, slotName): + # return slot's index in Slots if a PassRequest with input passName exists + if not hasattr(self, 'slots'): + return -1 + index = 0 + for slot in self.slots: + if slot['Name'] == slotName: + return index + index += 1 + return -1 + + def insert_slot(self, location, newSlotData): + # insert a new slot at specified location + self.__validate_slot_data(newSlotData) + # check if the slot already exist + if (self.find_slot(newSlotData['Name']) >= 0): + raise ValueError('Slot ', newSlotData['Name'], ' is already exist') + + self.__ensure_pass_slots_key() + self.slots.insert(location, newSlotData) + + def add_slot(self, newSlotData): + # append a new slot to slots + self.__validate_slot_data(newSlotData) + # check if the slot already exist + if (self.find_slot(newSlotData['Name']) >= 0): + raise ValueError('Slot ', newSlotData['Name'], ' is already exist') + + self.__ensure_pass_slots_key() + self.slots.append(newSlotData) + + def get_pass_request(self, passName): + if not hasattr(self, 'passRequests'): + return + # Get the pass request from PassRequests with matching pass name + for passRequest in self.passRequests: + if passRequest['Name'] == passName: + return passRequest + + def save(self): + # backup the original file + backupFilePath = self.file_path +'.backup' + shutil.copyfile(self.file_path, backupFilePath) + # save and overwrite file + with open(self.file_path, 'w') as json_file: + json.dump(self.file_data, json_file, indent = 4) + print('File [', self.file_path, '] is updated. Old version is saved in [', backupFilePath, ']') + + +class PassRequest: + + def __init__(self, passRequest: object): + self.pass_request = passRequest + if 'Connections' in passRequest: + self.connections = passRequest['Connections'] + + def __validate_connection(self, connection): + if ('LocalSlot' not in connection or 'AttachmentRef' not in connection): + raise KeyError('invalid connection data') + + def __ensure_connections_key(self): + if not hasattr(self, 'connections'): + self.pass_request['Connections'] = [] + self.connections = self.pass_request['Connections'] + + def get_connection_count(self): + if not hasattr(self, 'connections'): + return 0 + return len(self.connections) + + def find_connection(self, localSlotName): + if not hasattr(self, 'connections'): + return -1 + index = 0 + for connection in self.connections: + if connection['LocalSlot'] == localSlotName: + return index + index += 1 + return -1 + + def add_connection(self, newConnection): + self.__validate_connection(newConnection) + if self.find_connection(newConnection['LocalSlot']) >= 0: + raise ValueError('connection ', newConnection['LocalSlot'], ' already exists') + self.__ensure_connections_key() + self.connections.append(newConnection) \ No newline at end of file diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py new file mode 100644 index 0000000000..79f8fa4422 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py new file mode 100644 index 0000000000..d8cfc4840a --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py @@ -0,0 +1,303 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Unit tests for pass_data.py +""" +import os +import pytest +import shutil +import json +from atom_rpi_tools.pass_data import PassTemplate +from atom_rpi_tools.pass_data import PassRequest + +good_pass_requests_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_requests.json') +good_pass_slots_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_slots.json') +bad_test_data_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_test_bad.json') + +@pytest.fixture +def pass_requests_template(tmpdir): + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + destFilePath = os.path.join(tmpdir, 'pass_requests.json') + shutil.copyfile(source_path, destFilePath) + return PassTemplate(destFilePath) + +@pytest.fixture +def pass_slots_template(tmpdir): + filename = 'pass_slots.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + destFilePath = os.path.join(tmpdir, 'pass_requests.json') + shutil.copyfile(source_path, destFilePath) + return PassTemplate(destFilePath) + +@pytest.fixture +def new_pass_request(): + pass_request = json.loads('{\"Name\": \"InsertPass\",\"TemplateName\": \"InsertPassTemplate\"}') + return pass_request + +@pytest.fixture +def new_slot(): + slot = json.loads('{\"Name\": \"NewSlot\",\"SlotType\": \"Input\"}') + return slot + +@pytest.fixture +def new_connection(): + connection = json.loads('{\"LocalSlot\": \"color\", \"AttachmentRef\": { \"Pass\": \"Parent\", \"Attachment\": \"DepthStencil\"}}') + return connection + +def test_PassTemplate_Initialize_BadPassTemplateData_ExceptionThrown(): + with pytest.raises(KeyError): + PassTemplate(bad_test_data_file) + +def test_PassTemplate_FindPass_Success(pass_requests_template): + assert pass_requests_template.find_pass('OpaquePass') == 0 + assert pass_requests_template.find_pass('ImGuiPass') == 4 + assert pass_requests_template.find_pass('NotExistPass') == -1 + +def test_PassTemplate_InsertPassRequest_AtBegining_Success(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(0, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == 0 + assert template.get_pass_count() == pass_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_pass(new_pass_request['Name'])== 0 + assert saved_tamplate.get_pass_count() == pass_count+1 + +def test_PassTemplate_InsertPassRequest_AtEnd_Success(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(pass_count, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == pass_count + assert template.get_pass_count() == pass_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_pass(new_pass_request['Name']) == pass_count + assert saved_tamplate.get_pass_count() == pass_count+1 + +def test_PassTemplate_InsertPassRequest_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_pass_request): + template = pass_requests_template + # insert new pass request + template.insert_pass_request(0, new_pass_request) + pass_count = template.get_pass_count() + # exception when insert the same pass again + with pytest.raises(ValueError): + template.insert_pass_request(2, new_pass_request) + # pass count doesn't change + assert template.get_pass_count() == pass_count + +def test_PassTemplate_InsertPassRequest_WithBadData_ExceptionThrown(pass_requests_template): + template = pass_requests_template + pass_count = template.get_pass_count() + bad_pass_request = json.loads('{\"name\":\"value\"}') + with pytest.raises(KeyError): + template.insert_pass_request(2, bad_pass_request) + assert template.get_pass_count() == pass_count + +def test_PassTemplate_InsertPassRequest_AtOutOfRange_AppendSuccess(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(pass_count+2, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == pass_count + assert template.get_pass_count() == pass_count+1 + +def test_PassTemplate_ReplaceReferencesAfter_Success(pass_requests_template): + # replace OpaquePass.DepthStencil with Parent.DepthStencil' + refPass = 'OpaquePass' + # there are 2 passes after OpaquePass which use OpaquePass.DepthStencil as attachment reference + assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 2 + # after the previous replacement, there it no OpaquePass.DepthStencil reference + refPass = 'TransparentPass' + assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + + # verify changes are saved + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + assert saved_tamplate.replace_references_after('OpaquePass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + +def test_PassTemplate_ReplaceReferencesFor_Success(pass_requests_template): + refPass = 'TransparentPass' + assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 1 + refPass = '2DPass' + assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + + # verify changes are saved + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + # no reference of OpaquePass.DepthStencil in TransparentPass + assert saved_tamplate.replace_references_for('TransparentPass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + +def test_PassTemplate_FindSlot_Success(pass_slots_template): + assert pass_slots_template.find_slot('Color') == -1 + assert pass_slots_template.find_slot('DepthStencil') == 0 + assert pass_slots_template.find_slot('ColorInputOutput') == 1 + +def test_PassTemplate_InsertSlot_AtBegining_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.insert_slot(0, new_slot) + assert template.find_slot(new_slot['Name']) == 0 + assert template.find_slot('DepthStencil') == depth_stencil_slot+1 # DepthStencil moved back by 1 + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == 0 + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_InsertSlot_AtEnd_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.insert_slot(slot_count, new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.find_slot('DepthStencil') == depth_stencil_slot + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == slot_count + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_AddSlot_GoodSlotData_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.add_slot(new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.find_slot('DepthStencil') == depth_stencil_slot + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == slot_count + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_InsertSlot_OutOfRange_AppendSuccess(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + template.insert_slot(slot_count+3, new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.get_slot_count() == slot_count+1 + +def test_PassTemplate_AddDuplicateSlot_ExceptionThrown(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + template.add_slot(new_slot) + + with pytest.raises(ValueError): + template.insert_slot(0, new_slot) + with pytest.raises(ValueError): + template.add_slot(new_slot) + +def test_PassTemplate_InsertOrAddSlot_WithBadSlotData_ExceptionThrown(pass_slots_template): + template = pass_slots_template + slot_count = template.get_slot_count() + bad_slot = json.loads('{\"slot\": \"xxx\"}') + + with pytest.raises(KeyError): + template.insert_slot(0, bad_slot) + with pytest.raises(KeyError): + template.add_slot(bad_slot) + +def test_PassReqeuest_Initialize_WithExistPassReqeuestFromPassTemplate_Success(pass_requests_template): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + connection_count = request.get_connection_count() + assert connection_count == 2 + +def test_PassTemplate_GetPassRequest_NotExist_ReturnNull(pass_requests_template): + assert not pass_requests_template.get_pass_request('NotExistPass') + +def test_PassReqeuest_AddConnection_WithExistingConnections_Success(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + connection_count = request.get_connection_count() + request.add_connection(new_connection) + connection_count += 1 + assert request.get_connection_count() == connection_count + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + saved_request = PassRequest(saved_tamplate.get_pass_request('OpaquePass')) + assert saved_request.get_connection_count() == connection_count + +def test_PassReqeuest_AddConnection_WithNoExistingConnections_Success(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('ImGuiPass')) + assert request.get_connection_count() == 0 + request.add_connection(new_connection) + assert request.get_connection_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + saved_request = PassRequest(saved_tamplate.get_pass_request('ImGuiPass')) + assert saved_request.get_connection_count() == 1 + +def test_PassReqeuest_AddConnection_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + request.add_connection(new_connection) + with pytest.raises(ValueError): + request.add_connection(new_connection) + +def test_PassReqeuest_AddConnect_BadConnectionData_ExceptionThrown(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + bad_connection = json.loads('{\"xxx\": \"xxx\"}') + with pytest.raises(KeyError): + request.add_connection(bad_connection) + +def test_PassTemplate_InsertSlot_ToEmptyList_Success(pass_requests_template, new_slot): + template = pass_requests_template + # test insert slot function to pass template which doesn't have any slots + slot_count = template.get_slot_count() + assert slot_count == 0 + + assert template.find_slot(new_slot['Name'])==-1 + pass_requests_template.insert_slot(0, new_slot) + assert template.find_slot(new_slot['Name']) == 0 + assert template.get_slot_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.get_slot_count() == 1 + +def test_PassTempalte_InsertPassRequest_ToEmptyList_Success(pass_slots_template, new_pass_request): + template = pass_slots_template + # test insert pass function to pass template which doesn't have any pass requests + pass_count = template.get_pass_count() + assert pass_count == 0 + template.insert_pass_request(0, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == 0 + assert template.get_pass_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.get_pass_count() == 1 + +def test_PassTemplate_Save_Success(pass_requests_template): + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + assert os.path.exists(pass_requests_template.file_path) + assert os.path.exists(pass_requests_template.file_path +'.backup') diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py new file mode 100644 index 0000000000..ac1bb6edf9 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py @@ -0,0 +1,53 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Unit tests for utils.py +""" +import pytest +import os +import atom_rpi_tools.utils as utils + + +def test_FindOrCopyFile_DestFileNotExist_CopySuccess(tmpdir): + # created dir and copied + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json') + assert not os.path.exists(dest_path) + utils.find_or_copy_file(dest_path, source_path) + assert os.path.exists(dest_path) + source_size = os.path.getsize(source_path) + dest_size = os.path.getsize(dest_path) + assert source_size == dest_size + +def test_FindOrCopyFile_DestFileAlreadyExists_Skip(tmpdir): + # copy %cur_dir%/testdata/pass_requests.json to tempdir/testdata/pass_requests.json + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json') + utils.find_or_copy_file(dest_path, source_path) + + # skip if dest_path already exists + assert os.path.exists(dest_path) + before_size = os.path.getsize(dest_path) + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', 'pass_slots.json') + before_source_size = os.path.getsize(source_path) + assert before_size != source_path + utils.find_or_copy_file(dest_path, source_path) + after_size = os.path.getsize(dest_path) + assert before_size == after_size + + +def test_FindOrCopyFile_SourceFileNotExists_ExceptionThrown(tmpdir): + # report error if source doesn't exist + bad_source_path = 'notexist.dat' + dest_path = os.path.join(tmpdir, 'notexist.dat') + with pytest.raises(ValueError): + utils.find_or_copy_file(dest_path, bad_source_path) diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json new file mode 100644 index 0000000000..f3dd3f7a19 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json @@ -0,0 +1,116 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "PipelineTemplate", + "PassClass": "ParentPass", + "PassRequests": [ + { + "Name": "OpaquePass", + "TemplateName": "OpaquePassTemplate", + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "TransparentPass", + "TemplateName": "TransparentPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Color" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "transparent", + "DrawListSortType": "KeyThenReverseDepth", + "PipelineViewTag": "MainCamera", + "PassSrgAsset": { + "FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg" + } + } + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "ColorInputOutput" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "2DPass", + "TemplateName": "UIPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "ColorInputOutput" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "2dpass", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "ImGuiPass", + "TemplateName": "ImGuiPassTemplate", + "PassData": { + "$type": "ImGuiPassData", + "IsDefaultImGui": true + } + } + ] + } + } +} diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json new file mode 100644 index 0000000000..7274b0968a --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json @@ -0,0 +1,21 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "PipelineTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "DepthStencil", + "SlotType": "InputOutput" + }, + { + "Name": "ColorInputOutput", + "SlotType": "InputOutput" + } + ] + } + } +} diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json new file mode 100644 index 0000000000..a0e5ffcc90 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json @@ -0,0 +1,6 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassData": { + } +} \ No newline at end of file diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py new file mode 100644 index 0000000000..90037a8287 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py @@ -0,0 +1,31 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import os.path +from os import path +import shutil +import json + + +def find_or_copy_file(destFilePath, sourceFilePath): + if path.exists(destFilePath): + return + if not path.exists(sourceFilePath): + raise ValueError('find_or_copy_file: source file [', sourceFilePath, '] doesn\'t exist') + return + + dstDir = path.dirname(destFilePath) + if not path.isdir(dstDir): + os.makedirs(dstDir) + shutil.copyfile(sourceFilePath, destFilePath) + +def load_json_file(filePath): + file_stream = open(filePath, "r") + return json.load(file_stream) diff --git a/Gems/Atom/RPI/Tools/setup.py b/Gems/Atom/RPI/Tools/setup.py new file mode 100644 index 0000000000..ab494ec773 --- /dev/null +++ b/Gems/Atom/RPI/Tools/setup.py @@ -0,0 +1,33 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import os +import platform + +from setuptools import setup, find_packages + +PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) + +PYTHON_64 = platform.architecture()[0] == '64bit' + +if __name__ == '__main__': + if not PYTHON_64: + raise RuntimeError("32-bit Python is not a supported platform.") + + with open(os.path.join(PROJECT_ROOT, 'README.txt')) as f: + long_description = f.read() + + setup( + name="atom_rpi_tools", + version="1.0.0", + description='Python interface to Atom RPI tools', + long_description=long_description, + packages=find_packages(exclude=['tests']) + ) From bb92e4c0b86963d59916bbfc844d5d998215ae1f Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Wed, 2 Jun 2021 19:49:38 -0500 Subject: [PATCH 40/65] Font update to dynamic draw per view (#1085) * Move AtomFont over to use the new per viewport dynamic draw context. Remove scene tracking and listening for bootstrap scene created. Remove build dependency on the Bootstrap gem. Add build dependency on the AtomBridge gem. FFont's are now initialized with a viewport Id. Remove previous DynamicDraw context per scene system. Verify FFont can get a dynamic draw context before attempting initialization. Ensure a render scene exists before attempting font initialization (as a proxy for rendering has begun) * Move AtomFont FFont to use ShaderInputNameIndex's This allowed removing all of the InitFont function as no longer need to query compiled shader info for constant data offsets. * cache the AZ::Name used to find the dynamic draw context rather than recreate it each use --- .../AtomFont/Code/CMakeLists.txt | 3 +- .../AtomLyIntegration/AtomFont/AtomFont.h | 15 +-- .../AtomLyIntegration/AtomFont/FFont.h | 22 +--- .../AtomFont/Code/Source/AtomFont.cpp | 74 +++--------- .../AtomFont/Code/Source/FFont.cpp | 105 ++++-------------- 5 files changed, 51 insertions(+), 168 deletions(-) diff --git a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt index d3b8c8cde7..0045b37687 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt @@ -29,7 +29,8 @@ ly_add_target( Legacy::CryCommon Gem::Atom_RHI.Reflect Gem::Atom_RPI.Public - Gem::Atom_Bootstrap.Headers + PUBLIC + Gem::Atom_AtomBridge.Static ) ################################################################################ diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h index d5463fc15c..d21796cf17 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h @@ -27,11 +27,14 @@ #include #include +#include namespace AZ { class FFont; + static constexpr char AtomFontDynamicDrawContextName[] = "AtomFont"; + //! AtomFont is the font system manager. //! AtomFont manages the lifetime of FFont instances, each of which represents an individual font (e.g Courier New Italic) @@ -90,13 +93,6 @@ namespace AZ AzFramework::FontDrawInterface* GetFontDrawInterface(AzFramework::FontId fontId) const override; AzFramework::FontDrawInterface* GetDefaultFontDrawInterface() const override; - void SceneAboutToBeRemoved(AzFramework::Scene& scene); - - - // Atom DynamicDraw interface management - AZ::RHI::Ptr GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene); - - public: void UnregisterFont(const char* fontName); @@ -108,8 +104,6 @@ namespace AZ using FontFamilyMap = AZStd::unordered_map>; using FontFamilyReverseLookupMap = AZStd::unordered_map; - using SceneToDynamicDrawMap = AZStd::unordered_map>; - private: //! Convenience method for loading fonts IFFont* LoadFont(const char* fontName); @@ -145,9 +139,6 @@ namespace AZ int r_persistFontFamilies = 1; //!< Persist fonts for application lifetime to prevent unnecessary work; enabled by default. AZStd::vector m_persistedFontFamilies; //!< Stores persisted fonts (if "persist font families" is enabled) - - SceneToDynamicDrawMap m_sceneToDynamicDrawMap; - AZStd::shared_mutex m_sceneToDynamicDrawMutex; }; } #endif diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h index 1e224d6090..78d74a784a 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h @@ -42,11 +42,9 @@ #include #include #include +#include #include -#include -#include - struct ISystem; namespace AZ @@ -68,7 +66,6 @@ namespace AZ : public IFFont , public AZStd::intrusive_refcount , public AzFramework::FontDrawInterface - , private AZ::Render::Bootstrap::NotificationBus::Handler { using ref_count = AZStd::intrusive_refcount; friend FontDeleter; @@ -168,8 +165,8 @@ namespace AZ struct FontShaderData { - AZ::RHI::ShaderInputImageIndex m_imageInputIndex; - AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex; + AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture"; + AZ::RHI::ShaderInputNameIndex m_viewProjInputIndex = "m_worldToProj"; }; public: @@ -230,7 +227,6 @@ namespace AZ private: virtual ~FFont(); - bool InitFont(AZ::RPI::Scene* renderScene); bool InitTexture(); bool InitCache(); @@ -281,8 +277,6 @@ namespace AZ void ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) const; - void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override; - RPI::WindowContextSharedPtr GetDefaultWindowContext() const; RPI::ViewportContextPtr GetDefaultViewportContext() const; @@ -303,6 +297,8 @@ namespace AZ string m_name; string m_curPath; + AZ::Name m_dynamicDrawContextName = AZ::Name(AZ::AtomFontDynamicDrawContextName); + FontTexture* m_fontTexture = nullptr; size_t m_fontBufferSize = 0; @@ -315,13 +311,6 @@ namespace AZ AtomFont* m_atomFont = nullptr; bool m_fontTexDirty = false; - enum class InitializationState : AZ::u8 - { - Uninitialized, - Initializing, - Initialized - }; - AZStd::atomic m_fontInitializationState = InitializationState::Uninitialized; FontEffects m_effects; @@ -356,6 +345,7 @@ namespace AZ if (font && font->m_atomFont) { font->m_atomFont->UnregisterFont(font->m_name); + font->m_atomFont = nullptr; } delete font; diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp index 623a931ac9..fd8a4f26ed 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp @@ -354,17 +354,26 @@ AZ::AtomFont::AtomFont(ISystem* system) #endif AZ::Interface::Register(this); - m_sceneEventHandler = AzFramework::ISceneSystem::SceneEvent::Handler( - [this](AzFramework::ISceneSystem::EventType eventType, const AZStd::shared_ptr& scene) + // register font per viewport dynamic draw context. + static const char* shaderFilepath = "Shaders/SimpleTextured.azshader"; + AZ::AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext( + AZ::Name(AZ::AtomFontDynamicDrawContextName), + [](RPI::Ptr drawContext) { - if (eventType == AzFramework::ISceneSystem::EventType::ScenePendingRemoval) - { - SceneAboutToBeRemoved(*scene); - } + Data::Instance shader = AZ::RPI::LoadShader(shaderFilepath); + AZ::RPI::ShaderOptionList shaderOptions; + shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false"))); + shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true"))); + drawContext->InitShaderWithVariant(shader, &shaderOptions); + drawContext->InitVertexFormat( + { + {"POSITION", RHI::Format::R32G32B32_FLOAT}, + {"COLOR", RHI::Format::B8G8R8A8_UNORM}, + {"TEXCOORD0", RHI::Format::R32G32_FLOAT} + }); + drawContext->EndInit(); }); - auto sceneSystem = AzFramework::SceneSystemInterface::Get(); - AZ_Assert(sceneSystem, "Font created before the scene system is available."); - sceneSystem->ConnectToEvents(m_sceneEventHandler); + } AZ::AtomFont::~AtomFont() @@ -860,52 +869,5 @@ XmlNodeRef AZ::AtomFont::LoadFontFamilyXml(const char* fontFamilyName, string& o return root; } - -void AZ::AtomFont::SceneAboutToBeRemoved(AzFramework::Scene& scene) -{ - AZ::RPI::ScenePtr* rpiScene = scene.FindSubsystem(); - if (rpiScene) - { - AZStd::lock_guard lock(m_sceneToDynamicDrawMutex); - if (auto it = m_sceneToDynamicDrawMap.find(rpiScene->get()); it != m_sceneToDynamicDrawMap.end()) - { - m_sceneToDynamicDrawMap.erase(it); - } - } -} - -AZ::RHI::Ptr AZ::AtomFont::GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene) -{ - static const char* shaderFilepath = "Shaders/SimpleTextured.azshader"; - - { - // shared lock while reading - AZStd::shared_lock lock(m_sceneToDynamicDrawMutex); - - if (auto it = m_sceneToDynamicDrawMap.find(scene); it != m_sceneToDynamicDrawMap.end()) - { - return it->second; - } - } - - // Create and initialize DynamicDrawContext for font draw - AZ::RHI::Ptr dynamicDraw = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene); - - Data::Instance shader = AZ::RPI::LoadShader(shaderFilepath); - AZ::RPI::ShaderOptionList shaderOptions; - shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false"))); - shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true"))); - dynamicDraw->InitShaderWithVariant(shader, &shaderOptions); - dynamicDraw->InitVertexFormat({{"POSITION", RHI::Format::R32G32B32_FLOAT}, {"COLOR", RHI::Format::B8G8R8A8_UNORM}, {"TEXCOORD0", RHI::Format::R32G32_FLOAT}}); - dynamicDraw->EndInit(); - - // exclusive lock while writing - AZStd::lock_guard lock(m_sceneToDynamicDrawMutex); - m_sceneToDynamicDrawMap.insert(AZStd::make_pair(scene, dynamicDraw)); - - return dynamicDraw; -} - - #endif diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index 31eb089803..1976c69b45 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -60,14 +60,7 @@ static const size_t MaxVerts = 8 * 1024; // 2048 quads static const size_t MaxIndices = (MaxVerts * 6) / 4; // 6 indices per quad, 6/4 * MaxVerts static const char DrawList2DPassName[] = "2dpass"; -namespace ShaderInputs -{ - static const char TextureIndexName[] = "m_texture"; - static const char WorldToProjIndexName[] = "m_worldToProj"; - static const char SamplerIndexName[] = "m_sampler"; -} - -AZ::FFont::FFont(AtomFont* atomFont, const char* fontName) +AZ::FFont::FFont(AZ::AtomFont* atomFont, const char* fontName) : m_name(fontName) , m_atomFont(atomFont) { @@ -78,9 +71,14 @@ AZ::FFont::FFont(AtomFont* atomFont, const char* fontName) FontEffect* effect = AddEffect("default"); effect->AddPass(); - AddRef(); + // Create cpu memory to cache the font draw data before submit + m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts]; + m_indexBuffer = new u16[MaxIndices]; - AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect(); + m_vertexCount = 0; + m_indexCount = 0; + + AddRef(); } AZ::RPI::ViewportContextPtr AZ::FFont::GetDefaultViewportContext() const @@ -98,55 +96,10 @@ AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const return {}; } -bool AZ::FFont::InitFont(AZ::RPI::Scene* renderScene) -{ - if (!renderScene) - { - return false; - } - - auto initializationState = InitializationState::Uninitialized; - // Do an atomic transition to Initializing if we're in the Uninitialized state. - // Otherwise, check the current state. - // If we're Initialized, there's no more work to be done, return true to indicate we're good to go. - // If we're Initializing (on another thread), return false to let the consumer know it's not safe for us to be used yet. - if (!m_fontInitializationState.compare_exchange_strong(initializationState, InitializationState::Initializing)) - { - return initializationState == InitializationState::Initialized; - } - - // Create and initialize DynamicDrawContext for font draw - AZ::RPI::Ptr dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(renderScene); - - // Save draw srg input indices for later use - Data::Instance drawSrg = dynamicDraw->NewDrawSrg(); - const RHI::ShaderResourceGroupLayout* layout = drawSrg->GetAsset()->GetLayout(); - - m_fontShaderData.m_imageInputIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::TextureIndexName)); - AZ_Error("AtomFont::FFont", m_fontShaderData.m_imageInputIndex.IsValid(), "Failed to find shader input constant %s.", - ShaderInputs::TextureIndexName); - - m_fontShaderData.m_viewProjInputIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::WorldToProjIndexName)); - AZ_Error("AtomFont::FFont", m_fontShaderData.m_viewProjInputIndex.IsValid(), "Failed to find shader input constant %s.", - ShaderInputs::WorldToProjIndexName); - - // Create cpu memory to cache the font draw data before submit - m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts]; - m_indexBuffer = new u16[MaxIndices]; - - m_vertexCount = 0; - m_indexCount = 0; - - m_fontInitializationState = InitializationState::Initialized; - return true; -} - AZ::FFont::~FFont() { AZ_Assert(m_atomFont == nullptr, "The font should already be unregistered through a call to AZ::FFont::Release()"); - AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect(); - delete[] m_vertexBuffer; delete[] m_indexBuffer; @@ -303,7 +256,8 @@ void AZ::FFont::DrawStringUInternal( const TextDrawContext& ctx) { // Lazily ensure we're initialized before attempting to render. - if (!viewportContext || !InitFont(viewportContext->GetRenderScene().get())) + // Validate that there is a render scene before attempting to init. + if (!viewportContext || !viewportContext->GetRenderScene()) { return; } @@ -323,12 +277,6 @@ void AZ::FFont::DrawStringUInternal( return; } - // if the font is about to be deleted then m_atomFont can be nullptr - if (!m_atomFont) - { - return; - } - const bool orthoMode = ctx.m_overrideViewProjMatrices; const float viewX = viewport.m_minX; @@ -406,14 +354,17 @@ void AZ::FFont::DrawStringUInternal( if (numQuads) { - auto dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(viewportContext->GetRenderScene().get()); - //setup per draw srg - auto drawSrg = dynamicDraw->NewDrawSrg(); - drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat); - drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView()); - drawSrg->Compile(); + AZ::RPI::Ptr dynamicDraw = AZ::AtomBridge::PerViewportDynamicDraw::Get()->GetDynamicDrawContextForViewport(m_dynamicDrawContextName, viewportContext->GetId()); + if (dynamicDraw) + { + //setup per draw srg + auto drawSrg = dynamicDraw->NewDrawSrg(); + drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat); + drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView()); + drawSrg->Compile(); - dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg); + dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg); + } m_indexCount = 0; m_vertexCount = 0; } @@ -694,12 +645,6 @@ uint32_t AZ::FFont::WriteTextQuadsToBuffers(SVF_P2F_C4B_T2F_F4B* verts, uint16_t return numQuadsWritten; } - // if the font is about to be deleted then m_atomFont can be nullptr - if (!m_atomFont) - { - return numQuadsWritten; - } - SVF_P2F_C4B_T2F_F4B* vertexData = verts; uint16_t* indexData = indices; size_t vertexOffset = 0; @@ -1523,7 +1468,7 @@ bool AZ::FFont::UpdateTexture() { using namespace AZ; - if (m_fontInitializationState != InitializationState::Initialized || !m_fontImage) + if (!m_fontImage) { return false; } @@ -1591,7 +1536,7 @@ void AZ::FFont::Prepare(const char* str, bool updateTexture, const AtomFont::Gly const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender; const AtomFont::GlyphSize usedGlyphSize = rerenderGlyphs ? glyphSize : AtomFont::defaultGlyphSize; bool texUpdateNeeded = m_fontTexture->PreCacheString(str, nullptr, m_sizeRatio, usedGlyphSize, m_fontHintParams) == 1 || m_fontTexDirty; - if (m_fontInitializationState == InitializationState::Initialized && updateTexture && texUpdateNeeded && m_fontImage) + if (updateTexture && texUpdateNeeded && m_fontImage) { UpdateTexture(); m_fontTexDirty = false; @@ -1625,12 +1570,6 @@ void AZ::FFont::ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) co y *= height / WindowScaleHeight; } - -void AZ::FFont::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) -{ - InitFont(bootstrapScene); -} - static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::TextDrawParameters& params) { if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center) From 35fed7722305c8a3eaadb91f45836628d5d2ba33 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Wed, 2 Jun 2021 21:56:03 -0400 Subject: [PATCH 41/65] Adding CLI script for modifying project properties (LYN-3918). Updating O3de to support it. Fixing some typo errors in manifest.py and minor optimizations --- scripts/o3de.py | 7 ++- scripts/o3de/o3de/manifest.py | 12 +--- scripts/o3de/o3de/project_properties.py | 82 +++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 scripts/o3de/o3de/project_properties.py diff --git a/scripts/o3de.py b/scripts/o3de.py index 8d7532878c..85d49ec268 100755 --- a/scripts/o3de.py +++ b/scripts/o3de.py @@ -32,7 +32,7 @@ def add_args(parser, subparsers) -> None: # add the scripts/o3de directory to the front of the sys.path sys.path.insert(0, str(o3de_package_dir)) from o3de import engine_template, global_project, register, print_registration, get_registration, \ - enable_gem, disable_gem, sha256 + enable_gem, disable_gem, project_properties, sha256 # Remove the temporarily added path sys.path = sys.path[1:] @@ -55,7 +55,10 @@ def add_args(parser, subparsers) -> None: # remove a gem from a project disable_gem.add_args(subparsers) - + + # modify project properties + project_properties.add_args(subparsers) + # sha256 sha256.add_args(subparsers) diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 2a7e5bba11..edcd44c525 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -573,9 +573,7 @@ def get_registered(engine_name: str = None, return engine_path elif isinstance(project_name, str): - enging_projects = get_engine_projects() - projects = json_data['projects'].copy() - projects.extend(engine_object['projects']) + projects = get_all_projects() for project_path in projects: project_path = pathlib.Path(project_path).resolve() project_json = project_path / 'project.json' @@ -605,9 +603,7 @@ def get_registered(engine_name: str = None, return gem_path elif isinstance(template_name, str): - engine_templates = get_engine_templates() - templates = json_data['templates'].copy() - templates.extend(engine_templates) + templates = get_all_templates() for template_path in templates: template_path = pathlib.Path(template_path).resolve() template_json = template_path / 'template.json' @@ -622,9 +618,7 @@ def get_registered(engine_name: str = None, return template_path elif isinstance(restricted_name, str): - engine_restricted = get_engine_restricted() - restricted = json_data['restricted'].copy() - restricted.extend(engine_restricted) + restricted = get_all_restricted() for restricted_path in restricted: restricted_path = pathlib.Path(restricted_path).resolve() restricted_json = restricted_path / 'restricted.json' diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py new file mode 100644 index 0000000000..10153ff833 --- /dev/null +++ b/scripts/o3de/o3de/project_properties.py @@ -0,0 +1,82 @@ +import argparse +import json +import os +import pathlib +import sys +import logging + +from o3de import manifest + +logger = logging.getLogger() +logging.basicConfig() + +def get_project_props(name: str = None, path: pathlib.Path = None) -> dict: + proj_json = manifest.get_project_json_data(project_name=name, project_path=path) + if not proj_json: + logger.error('Could not retrieve project.json file') + return None + return proj_json + +def edit_project_props(proj_path, proj_name, new_origin, new_display, + new_summary, new_icon, new_tag) -> int: + proj_json = get_project_props(proj_name, proj_path) + + try: + if new_origin and 'origin' in proj_json: + proj_json['origin'] = new_origin + if new_display and 'display_name' in proj_json: + proj_json['display_name'] = new_display + if new_summary and 'summary' in proj_json: + proj_json['summary'] = new_summary + if new_icon and 'icon_path' in proj_json: + proj_json['icon_path'] = new_icon + if new_tag and 'user_tags' in proj_json: + proj_json['user_tags'].append(new_tag) + except Exception as e: + logger.error(e) + return 1 + + manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path)/'project.json') + return 0 + +def _edit_project_props(args: argparse) -> int: + return edit_project_props(args.project_path, + args.project_name, + args.project_origin, + args.project_display, + args.project_summary, + args.project_icon, + args.project_tag) + +def add_parser_args(parser): + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.') + group.add_argument('-po', '--project-origin', type=str, required=False, + help='Sets description or url for project origin.') + group.add_argument('-pd', '--project-display', type=str, required=False, + help='Sets the project display name.') + group.add_argument('-ps', '--project-summary', type=str, required=False, + help='Sets the summary description of the project.') + group.add_argument('-pi', '--project-icon', type=str, required=False, + help='Sets the path to the projects icon resource.') + group.add_argument('-pt', '--project-tag', type=str, required=False, + help='Adds a tag to canonical user tags.') + parser.set_defaults(func=_edit_project_props) + +def add_args(subparsers) -> None: + enable_project_props_subparser = subparsers.add_parser('edit-project-props') + add_parser_args(enable_project_props_subparser) + +def main(): + the_parser = argparse.ArgumentParser() + add_parser_args(the_parser) + the_args = the_parser.parse_args() + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + sys.exit(ret) + +if __name__ == "__main__": + main() \ No newline at end of file From afeea878627b29f465eb0da648f06f4e9dbc69b0 Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 19:21:39 -0700 Subject: [PATCH 42/65] Fix for linux being a banned keyword --- Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index f30c8912de..069ac49d96 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -38,7 +38,7 @@ #include -#include // for std::powf on linux +#include namespace AZ::ConsoleTypeHelpers { @@ -641,7 +641,7 @@ namespace Multiplayer m_renderBlendFactor += targetAdjustBlend; // Linear close to the origin, but asymptote at y = 1 - const float adjustedBlendFactor = 1.0f - (std::powf(0.2f, m_renderBlendFactor)); + const float adjustedBlendFactor = 1.0f - (std::pow(0.2f, m_renderBlendFactor)); AZLOG(NET_Blending, "Computed blend factor of %f", adjustedBlendFactor); if (Camera::ActiveCameraRequestBus::HasHandlers()) From 89b1afc50e00e7dd78488feb9ba6770544412d9d Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Wed, 2 Jun 2021 19:37:35 -0700 Subject: [PATCH 43/65] Adding Multiplayer:: namespace to RpcIndex so components outside the Multiplayer gem can compile --- Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 5cfd0bfc4d..917b1058a7 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -311,7 +311,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop {{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramDefines) }}) { - constexpr RpcIndex rpcId = static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ UpperFirst(Property.attrib['Name']) }}); + constexpr Multiplayer::RpcIndex rpcId = static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ UpperFirst(Property.attrib['Name']) }}); {% if Property.attrib['IsReliable']|booleanTrue %} constexpr AzNetworking::ReliabilityType isReliable = Multiplayer::ReliabilityType::Reliable; {% else %} From f2a7cd9a2da207c118ae56f58dd8077f3d1960dd Mon Sep 17 00:00:00 2001 From: Terry Michaels Date: Thu, 3 Jun 2021 08:29:24 -0500 Subject: [PATCH 44/65] Fixed monolithic build warning/error (#1116) --- Gems/LyShine/Code/Source/LyShineSystemComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp index 0eab7705f6..1290683145 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp @@ -375,7 +375,7 @@ namespace LyShine } /////////////////////////////////////////////////////////////////////////////////////////////// - void LyShineSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& startupParams) + void LyShineSystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, [[maybe_unused]] const SSystemInitParams& startupParams) { #if !defined(AZ_MONOLITHIC_BUILD) // When module is linked dynamically, we must set our gEnv pointer. From cf08f4dab1e7206a8b69e7d79b5f1f0a4b51e398 Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Thu, 3 Jun 2021 15:48:44 +0100 Subject: [PATCH 45/65] Improve camera orbit behavior (#1060) --- Code/Sandbox/Editor/EditorViewportWidget.cpp | 18 +++++++-------- .../SandboxIntegration.cpp | 7 +++--- .../ModularViewportCameraController.h | 5 ++++- ...odularViewportCameraControllerRequestBus.h | 7 +++++- .../ModularViewportCameraController.cpp | 22 +++++++++++++++++-- 5 files changed, 43 insertions(+), 16 deletions(-) diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 24d1590808..667179e3cd 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -1233,7 +1233,7 @@ void EditorViewportWidget::SetViewportId(int id) auto controller = AZStd::make_shared(); controller->SetCameraListBuilderCallback( - [](AzFramework::Cameras& cameras) + [id](AzFramework::Cameras& cameras) { auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::CameraFreeLookButton); auto firstPersonPanCamera = @@ -1243,17 +1243,17 @@ void EditorViewportWidget::SetViewportId(int id) auto orbitCamera = AZStd::make_shared(); orbitCamera->SetLookAtFn( - [](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional + [id](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional { - AZStd::optional manipulatorTransform; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - manipulatorTransform, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); + AZStd::optional lookAtAfterInterpolation; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + lookAtAfterInterpolation, id, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation); - // initially attempt to use manipulator transform if one exists (there is a selection) - if (manipulatorTransform) + // initially attempt to use the last set look at point after an interpolation has finished + if (lookAtAfterInterpolation.has_value()) { - return manipulatorTransform->GetTranslation(); + return *lookAtAfterInterpolation; } const float RayDistance = 1000.0f; diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 5ff2debe3d..8161d07547 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -1732,13 +1732,14 @@ void SandboxIntegrationManager::GoToEntitiesInViewports(const AzToolsFramework:: // compute new camera transform const float fov = AzFramework::RetrieveFov(viewportContext->GetCameraProjectionMatrix()); const float fovScale = (1.0f / AZStd::tan(fov * 0.5f)); - const float distanceToTarget = selectionSize * fovScale * centerScale; + const float distanceToLookAt = selectionSize * fovScale * centerScale; const AZ::Transform nextCameraTransform = - AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToTarget), aabb.GetCenter()); + AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToLookAt), aabb.GetCenter()); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( viewportContext->GetId(), - &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform); + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform, + distanceToLookAt); } } } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 1318deb355..b88b340926 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -51,7 +51,8 @@ namespace AtomToolsFramework void UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) override; // ModularViewportCameraControllerRequestBus overrides ... - void InterpolateToTransform(const AZ::Transform& worldFromLocal) override; + void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override; + AZStd::optional LookAtAfterInterpolation() const override; private: // AzFramework::ViewportDebugDisplayEventBus overrides ... @@ -71,6 +72,8 @@ namespace AtomToolsFramework AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); float m_animationT = 0.0f; CameraMode m_cameraMode = CameraMode::Control; + AZStd::optional m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished. + //!< Will be cleared when the view changes (camera looks away). bool m_updatingTransform = false; AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h index 5b90119372..a7f067cdf4 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h @@ -32,7 +32,12 @@ namespace AtomToolsFramework static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; //! Begin a smooth transition of the camera to the requested transform. - virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal) = 0; + //! @param worldFromLocal The transform of where the camera should end up. + //! @param lookAtDistance The distance between the camera transform and the imagined look at point. + virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) = 0; + + //! Look at point after an interpolation has finished and no translation has occurred. + virtual AZStd::optional LookAtAfterInterpolation() const = 0; protected: ~ModularViewportCameraControllerRequests() = default; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 896d9f8043..082dc8f272 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -140,6 +140,18 @@ namespace AtomToolsFramework m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count()); m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, event.m_deltaTime.count()); + // if there has been an interpolation, only clear the look at point if it is no longer + // centered in the view (the camera has looked away from it) + if (m_lookAtAfterInterpolation.has_value()) + { + if (const float lookDirection = + (*m_lookAtAfterInterpolation - m_camera.Translation()).GetNormalized().Dot(m_camera.Transform().GetBasisY()); + !AZ::IsCloseMag(lookDirection, 1.0f, 0.001f)) + { + m_lookAtAfterInterpolation = {}; + } + } + viewportContext->SetCameraTransform(m_camera.Transform()); } else if (m_cameraMode == CameraMode::Animation) @@ -148,8 +160,8 @@ namespace AtomToolsFramework { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; - const float transitionT = smootherStepFn(m_animationT); + const float transitionT = smootherStepFn(m_animationT); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( m_transformStart.GetRotation().Slerp(m_transformEnd.GetRotation(), transitionT), m_transformStart.GetTranslation().Lerp(m_transformEnd.GetTranslation(), transitionT)); @@ -185,11 +197,17 @@ namespace AtomToolsFramework } } - void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal) + void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { m_animationT = 0.0f; m_cameraMode = CameraMode::Animation; m_transformStart = m_camera.Transform(); m_transformEnd = worldFromLocal; + m_lookAtAfterInterpolation = m_transformEnd.GetTranslation() + m_transformEnd.GetBasisY() * lookAtDistance; + } + + AZStd::optional ModernViewportCameraControllerInstance::LookAtAfterInterpolation() const + { + return m_lookAtAfterInterpolation; } } // namespace AtomToolsFramework From cd619e14dc9b74f845cb4897780ea8aa6004f10b Mon Sep 17 00:00:00 2001 From: Gene Walters Date: Thu, 3 Jun 2021 08:31:38 -0700 Subject: [PATCH 46/65] Allow script canvas users to send RPCs via entityId --- .../Source/AutoGen/AutoComponent_Common.jinja | 2 +- .../Source/AutoGen/AutoComponent_Source.jinja | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index 61dcacaa94..05403a00ef 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -175,7 +175,7 @@ void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ //! {{ PropertyName }} Handler //! {{ Property.attrib['Description'] }} //! HandleOn {{ HandleOn }} -virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) = 0; +virtual void Handle{{ PropertyName }}([[maybe_unused]] AzNetworking::IConnection* invokingConnection, [[maybe_unused]] {{ ', [[maybe_unused]] '.join(paramDefines) }}) {} {% endif %} {% endmacro %} {# diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 917b1058a7..259f469020 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -368,6 +368,31 @@ void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.jo ->Method("{{ UpperFirst(Property.attrib['Name']) }}", [](const {{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { self->m_controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); }) + ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntity", [](AZ::EntityId id, {{ ', '.join(paramDefines) }}) { + + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return; + } + + {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); + if (!networkComponent) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) + return; + } + + {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); + if (!controller) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This RemoteProcedure can only be invoked from {{InvokeFrom}} network entities, because this entity doesn't have a controller, it must not be a {{InvokeFrom}} entity. Please check your network context before attempting to call {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) + return; + } + + controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); + }) {% endif %} {% endcall %} {% endmacro %} From 29c71b4e530861d07426521a574f168d781a2ca3 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Thu, 3 Jun 2021 09:30:33 -0700 Subject: [PATCH 47/65] SPEC-2513 Fixes to enable w4701 (#1105) * Some fixes * more fixes * fixes for debug --- Code/CryEngine/CrySystem/DebugCallStack.cpp | 2 +- Code/CryEngine/CrySystem/System.cpp | 2 +- Code/CryEngine/CrySystem/SystemInit.cpp | 2 +- Code/Framework/AzCore/Tests/Jobs.cpp | 3 ++- .../Windowing/NativeWindow_Windows.cpp | 1 + Code/Sandbox/Editor/LogFile.cpp | 2 +- Code/Sandbox/Editor/Util/AffineParts.cpp | 4 ++-- Code/Sandbox/Editor/Util/FileUtil.cpp | 3 ++- Code/Sandbox/Editor/Util/ImageBT.cpp | 1 + Code/Sandbox/Editor/Util/StringHelpers.cpp | 2 +- .../Code/Source/Converters/FIR-Weights.cpp | 2 +- .../External/CubeMapGen/CCubeMapProcessor.cpp | 2 +- .../Code/Source/RHI/FrameGraphCompiler.cpp | 1 + .../Code/Tests/BoolLogicNodeTests.cpp | 4 ++-- .../Code/External/FastNoise/FastNoise.cpp | 21 +++++++++++++------ .../GraphCanvas/Utils/GraphUtils.cpp | 2 +- .../Code/Source/Animation/AzEntityNode.cpp | 2 +- .../Code/Source/UiLayoutGridComponent.cpp | 6 ++++-- .../Code/Source/UiNavigationHelpers.cpp | 2 ++ .../Source/Optimization/LineSearch.cpp | 2 +- .../Common/MSVC/Configurations_msvc.cmake | 1 - 21 files changed, 42 insertions(+), 25 deletions(-) diff --git a/Code/CryEngine/CrySystem/DebugCallStack.cpp b/Code/CryEngine/CrySystem/DebugCallStack.cpp index 2a219ce674..eea4725639 100644 --- a/Code/CryEngine/CrySystem/DebugCallStack.cpp +++ b/Code/CryEngine/CrySystem/DebugCallStack.cpp @@ -561,7 +561,7 @@ void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex) if (pex) { - MINIDUMP_TYPE mdumpValue; + MINIDUMP_TYPE mdumpValue = MiniDumpNormal; bool bDump = true; switch (g_cvars.sys_dump_type) { diff --git a/Code/CryEngine/CrySystem/System.cpp b/Code/CryEngine/CrySystem/System.cpp index 6fcbc32b72..bf34700c39 100644 --- a/Code/CryEngine/CrySystem/System.cpp +++ b/Code/CryEngine/CrySystem/System.cpp @@ -66,7 +66,7 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } if (pSystem && !pSystem->IsQuitting()) { - LRESULT result; + LRESULT result = 0; bool bAny = false; for (std::vector::const_iterator it = pSystem->m_windowMessageHandlers.begin(); it != pSystem->m_windowMessageHandlers.end(); ++it) { diff --git a/Code/CryEngine/CrySystem/SystemInit.cpp b/Code/CryEngine/CrySystem/SystemInit.cpp index 52744519bb..4a4296bbb5 100644 --- a/Code/CryEngine/CrySystem/SystemInit.cpp +++ b/Code/CryEngine/CrySystem/SystemInit.cpp @@ -634,7 +634,7 @@ ICVar* CSystem::attachVariable (const char* szVarName, int* pContainer, const ch IConsole* pConsole = GetIConsole(); ICVar* pOldVar = pConsole->GetCVar (szVarName); - int nDefault; + int nDefault = 0; if (pOldVar) { nDefault = pOldVar->GetIVal(); diff --git a/Code/Framework/AzCore/Tests/Jobs.cpp b/Code/Framework/AzCore/Tests/Jobs.cpp index 553123496e..664b163417 100644 --- a/Code/Framework/AzCore/Tests/Jobs.cpp +++ b/Code/Framework/AzCore/Tests/Jobs.cpp @@ -395,7 +395,8 @@ namespace UnitTest } else { - int result1, result2; + int result1 = 0; + int result2 = 0; Job* job1 = aznew FibonacciJob2(m_n - 1, &result1, m_context); Job* job2 = aznew FibonacciJob2(m_n - 2, &result2, m_context); StartAsChild(job1); diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp index fd49f37dc8..b96ec81b5f 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp @@ -353,6 +353,7 @@ namespace AzFramework // Get the dimensions of the display device on which the window is currently displayed. MONITORINFO monitorInfo; + memset(&monitorInfo, 0, sizeof(MONITORINFO)); // C4701 potentially uninitialized local variable 'monitorInfo' used monitorInfo.cbSize = sizeof(MONITORINFO); const BOOL success = monitor ? GetMonitorInfo(monitor, &monitorInfo) : FALSE; if (!success) diff --git a/Code/Sandbox/Editor/LogFile.cpp b/Code/Sandbox/Editor/LogFile.cpp index 0841a6b288..0bbfd09a21 100644 --- a/Code/Sandbox/Editor/LogFile.cpp +++ b/Code/Sandbox/Editor/LogFile.cpp @@ -553,7 +553,7 @@ void CLogFile::OnWriteToConsole(const char* sText, bool bNewLine) // remember selection and the top row int len = m_hWndEditBox->document()->toPlainText().length(); - int top; + int top = 0; int from = m_hWndEditBox->textCursor().selectionStart(); int to = from + m_hWndEditBox->textCursor().selectionEnd(); bool keepPos = false; diff --git a/Code/Sandbox/Editor/Util/AffineParts.cpp b/Code/Sandbox/Editor/Util/AffineParts.cpp index e294f93089..139519c078 100644 --- a/Code/Sandbox/Editor/Util/AffineParts.cpp +++ b/Code/Sandbox/Editor/Util/AffineParts.cpp @@ -157,7 +157,7 @@ static Quatern Qt_FromMatrix(HMatrix mat) * |w| is greater than 1/2, which is as small as a largest component can be. * Otherwise, the largest diagonal entry corresponds to the largest of |x|, * |y|, or |z|, one of which must be larger than |w|, and at least 1/2. */ - Quatern qu; + Quatern qu = { 0.0f, 0.0f, 0.0f, 1.0f }; double tr, s; tr = mat[X][X] + mat[Y][Y] + mat[Z][Z]; @@ -531,7 +531,7 @@ Quatern snuggle(Quatern q, HVect* k) #define swap(a, i, j) {a[3] = a[i]; a[i] = a[j]; a[j] = a[3]; } #define cycle(a, p) if (p) {a[3] = a[0]; a[0] = a[1]; a[1] = a[2]; a[2] = a[3]; } \ else {a[3] = a[2]; a[2] = a[1]; a[1] = a[0]; a[0] = a[3]; } - Quatern p; + Quatern p = { 0.0f, 0.0f, 0.0f, 1.0f }; float ka[4]; int i, turn = -1; ka[X] = k->x; diff --git a/Code/Sandbox/Editor/Util/FileUtil.cpp b/Code/Sandbox/Editor/Util/FileUtil.cpp index 8dd379f096..ece516659e 100644 --- a/Code/Sandbox/Editor/Util/FileUtil.cpp +++ b/Code/Sandbox/Editor/Util/FileUtil.cpp @@ -2239,7 +2239,8 @@ uint32 CFileUtil::GetAttributes(const char* filename, bool bUseSourceControl /*= bool CFileUtil::CompareFiles(const QString& strFilePath1, const QString& strFilePath2) { // Get the size of both files. If either fails we say they are different (most likely one doesn't exist) - uint64 size1, size2; + uint64 size1 = 0; + uint64 size2 = 0; if (!GetDiskFileSize(strFilePath1.toUtf8().data(), size1) || !GetDiskFileSize(strFilePath2.toUtf8().data(), size2)) { return false; diff --git a/Code/Sandbox/Editor/Util/ImageBT.cpp b/Code/Sandbox/Editor/Util/ImageBT.cpp index 30c4911cb8..79ce4bba35 100644 --- a/Code/Sandbox/Editor/Util/ImageBT.cpp +++ b/Code/Sandbox/Editor/Util/ImageBT.cpp @@ -116,6 +116,7 @@ bool CImageBT::Load(const QString& fileName, CFloatImage& image) // Get the BT header data BtHeader header; + memset(&header, 0, sizeof(BtHeader)); // C4701 potentially uninitialized local variable 'header' used bool validData = true; validData = validData && (fread(&header, sizeof(BtHeader), 1, file) != 0); diff --git a/Code/Sandbox/Editor/Util/StringHelpers.cpp b/Code/Sandbox/Editor/Util/StringHelpers.cpp index 5e44c3b0bd..12865dfe73 100644 --- a/Code/Sandbox/Editor/Util/StringHelpers.cpp +++ b/Code/Sandbox/Editor/Util/StringHelpers.cpp @@ -419,7 +419,7 @@ static inline bool MatchesWildcardsIgnoreCaseExt_Tpl(const TS& str, const TS& wi const typename TS::value_type* savedStrBegin = 0; const typename TS::value_type* savedStrEnd = 0; const typename TS::value_type* savedWild = 0; - size_t savedWildCount; + size_t savedWildCount = 0; const typename TS::value_type* pStr = str.c_str(); const typename TS::value_type* pWild = wildcards.c_str(); diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp index 192c82c165..b1d0cfbda0 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp @@ -88,7 +88,7 @@ namespace ImageProcessingAtom int dstPosition; signed short int n; bool trimZeros = true, stillzero; - int lastnonzero, hWeight, highest; + int lastnonzero = 0, hWeight, highest = 0; signed int sumiWeights, iWeight; signed short int* weightsPtr; signed short int* weightsMem; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp index 26768f8f2e..df31067af0 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp @@ -1106,7 +1106,7 @@ namespace ImageProcessingAtom //fractional amount to apply change in tap intensity along edge to taps // in a perpendicular direction to edge CP_ITYPE fixupFrac = (CP_ITYPE)(fixupDist - iFixup) / (CP_ITYPE)(fixupDist); - CP_ITYPE fixupWeight; + CP_ITYPE fixupWeight = 0.0f; switch(a_FixupType ) { diff --git a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp index 7fc1db179a..e4fff11d32 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp @@ -484,6 +484,7 @@ namespace AZ } D3D12_RESOURCE_TRANSITION_BARRIER transition; + memset(&transition, 0, sizeof(D3D12_RESOURCE_TRANSITION_BARRIER)); // C4701 potentially unitialized local variable 'transition' used transition.pResource = image.GetMemoryView().GetMemory(); Scope& firstScope = static_cast(scopeAttachment->GetScope()); diff --git a/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp b/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp index 30950790d9..218bea2169 100644 --- a/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp +++ b/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp @@ -165,8 +165,8 @@ namespace EMotionFX const AZ::Outcome boolYParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolY); success = boolXParamIndexOutcome.IsSuccess() && boolYParamIndexOutcome.IsSuccess(); - uint32 boolXOutputPortIndex; - uint32 boolYOutputPortIndex; + uint32 boolXOutputPortIndex = InvalidIndex32; + uint32 boolYOutputPortIndex = InvalidIndex32; const int portIndicesTosetCount = 2; int portIndicesFound = 0; const AZStd::vector& parameterNodeOutputPorts = parameterNode->GetOutputPorts(); diff --git a/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp b/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp index fce7d6498a..3fe7bf45fe 100644 --- a/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp +++ b/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp @@ -612,7 +612,9 @@ FN_DECIMAL FastNoise::SingleValue(unsigned char offset, FN_DECIMAL x, FN_DECIMAL int y1 = y0 + 1; int z1 = z0 + 1; - FN_DECIMAL xs, ys, zs; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; + FN_DECIMAL zs = 0.0f; switch (m_interp) { case Linear: @@ -726,7 +728,8 @@ FN_DECIMAL FastNoise::SingleValue(unsigned char offset, FN_DECIMAL x, FN_DECIMAL int x1 = x0 + 1; int y1 = y0 + 1; - FN_DECIMAL xs, ys; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; switch (m_interp) { case Linear: @@ -840,7 +843,9 @@ FN_DECIMAL FastNoise::SinglePerlin(unsigned char offset, FN_DECIMAL x, FN_DECIMA int y1 = y0 + 1; int z1 = z0 + 1; - FN_DECIMAL xs, ys, zs; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; + FN_DECIMAL zs = 0.0f; switch (m_interp) { case Linear: @@ -962,7 +967,8 @@ FN_DECIMAL FastNoise::SinglePerlin(unsigned char offset, FN_DECIMAL x, FN_DECIMA int x1 = x0 + 1; int y1 = y0 + 1; - FN_DECIMAL xs, ys; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; switch (m_interp) { case Linear: @@ -1699,7 +1705,9 @@ FN_DECIMAL FastNoise::SingleCellular(FN_DECIMAL x, FN_DECIMAL y, FN_DECIMAL z) c int zr = FastRound(z); FN_DECIMAL distance = 999999; - int xc, yc, zc; + int xc = 0; + int yc = 0; + int zc = 0; switch (m_cellularDistanceFunction) { @@ -1923,7 +1931,8 @@ FN_DECIMAL FastNoise::SingleCellular(FN_DECIMAL x, FN_DECIMAL y) const int yr = FastRound(y); FN_DECIMAL distance = 999999; - int xc, yc; + int xc = 0; + int yc = 0; switch (m_cellularDistanceFunction) { diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp index 28fb760c20..c2574a69f6 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp @@ -1239,7 +1239,7 @@ namespace GraphCanvas bool GraphUtils::IsValidModelConnection(const GraphId& graphId, const Endpoint& sourceEndpoint, const Endpoint& targetEndpoint) { - bool validConnection; + bool validConnection = false; AZStd::unordered_set< Endpoint > finalSourceEndpoints = RemapEndpointForModel(sourceEndpoint); AZStd::unordered_set< Endpoint > finalTargetEndpoints = RemapEndpointForModel(targetEndpoint); diff --git a/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp b/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp index 7ff46f285f..2d24a84c71 100644 --- a/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp +++ b/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp @@ -691,7 +691,7 @@ IUiAnimTrack* CUiAnimAzEntityNode::CreateTrackForAzField(const UiAnimParamData& return nullptr; } - EUiAnimValue valueType; + EUiAnimValue valueType = eUiAnimValue_Unknown; switch (numElements) { case 2: diff --git a/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp b/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp index e39b9cc684..4c5d48911f 100644 --- a/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp +++ b/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp @@ -107,7 +107,8 @@ void UiLayoutGridComponent::ApplyLayoutHeight() AZStd::vector childEntityIds; EBUS_EVENT_ID_RESULT(childEntityIds, GetEntityId(), UiElementBus, GetChildEntityIds); int childIndex = 0; - int columnIndex, rowIndex; + int columnIndex = 0; + int rowIndex = 0; for (auto child : childEntityIds) { // Set the anchors @@ -627,7 +628,8 @@ AZ::Vector2 UiLayoutGridComponent::GetChildrenBoundingRectSize(const AZ::Vector2 UiLayoutHelpers::GetSizeInsidePadding(GetEntityId(), m_padding, layoutRectSize); // Calculate number of rows and columns - int numColumns, numRows; + int numColumns = 0; + int numRows = 0; switch (m_startingDirection) { case StartingDirection::HorizontalOrder: diff --git a/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp b/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp index 0c1b6765ce..79084c8bbe 100644 --- a/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp +++ b/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp @@ -151,6 +151,8 @@ namespace UiNavigationHelpers } UiTransformInterface::Rect parentRect; + parentRect.Set(0.0f, 0.0f, 0.0f, 0.0f); + AZ::Matrix4x4 parentTransformFromViewport; if (parentElement.IsValid() && !isCurElementDescendantOfParentElement) { diff --git a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp index abc2154228..1325638dd2 100644 --- a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp +++ b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp @@ -72,7 +72,7 @@ namespace NumericalMethods::Optimization for (AZ::u32 iteration = 0; iteration < lineSearchIterations; iteration++) { - ScalarVariable alphaNew; + ScalarVariable alphaNew = 0.0; if (iteration > 0) { // first try selecting a new alpha value based on cubic interpolation through the most recent points diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index 25b6e63ab9..24dffe56a6 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -75,7 +75,6 @@ ly_append_configurations_options( /wd4450 # declaration hides global declaration /wd4457 # declaration hides function parameter /wd4459 # declaration hides global declaration - /wd4701 # potentially unintialized local variable # Enabling warnings that are disabled by default from /W4 # https://docs.microsoft.com/en-us/cpp/preprocessor/compiler-warnings-that-are-off-by-default?view=vs-2019 From d56688e6cd9e620e384f8233431fa8180294da5a Mon Sep 17 00:00:00 2001 From: Tommy Walton <82672795+amzn-tommy@users.noreply.github.com> Date: Thu, 3 Jun 2021 09:41:31 -0700 Subject: [PATCH 48/65] Adding a file that was missed when merging PR 481 from 1.0->main. Also, updated the comment a bit (#912) --- .../RPI/Code/Source/RPI.Public/Culling.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index c64f08e4f8..c5e9d949aa 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -239,16 +239,26 @@ namespace AZ void CullingScene::RegisterOrUpdateCullable(Cullable& cullable) { - m_cullDataConcurrencyCheck.soft_lock(); + // Multiple threads can call RegisterOrUpdateCullable at the same time + // since the underlying visScene is thread safe, but if you're inserting or + // updating between BeginCulling and EndCulling, you'll get non-deterministic + // results depending on a race condition if you happen to update before or after + // the culling system starts Enumerating, so use soft_lock_shared here + m_cullDataConcurrencyCheck.soft_lock_shared(); m_visScene->InsertOrUpdateEntry(cullable.m_cullData.m_visibilityEntry); - m_cullDataConcurrencyCheck.soft_unlock(); + m_cullDataConcurrencyCheck.soft_unlock_shared(); } void CullingScene::UnregisterCullable(Cullable& cullable) { - m_cullDataConcurrencyCheck.soft_lock(); + // Multiple threads can call RegisterOrUpdateCullable at the same time + // since the underlying visScene is thread safe, but if you're inserting or + // updating between BeginCulling and EndCulling, you'll get non-deterministic + // results depending on a race condition if you happen to update before or after + // the culling system starts Enumerating, so use soft_lock_shared here + m_cullDataConcurrencyCheck.soft_lock_shared(); m_visScene->RemoveEntry(cullable.m_cullData.m_visibilityEntry); - m_cullDataConcurrencyCheck.soft_unlock(); + m_cullDataConcurrencyCheck.soft_unlock_shared(); } uint32_t CullingScene::GetNumCullables() const From eef5122ce54dc44f97d852b4bf11b9a6a6cfc51b Mon Sep 17 00:00:00 2001 From: Chris Galvan Date: Thu, 3 Jun 2021 11:56:20 -0500 Subject: [PATCH 49/65] [LYN-3008] Only register the Slice Relationship View when prefabs are disabled. --- .../ComponentEntityEditorPlugin.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp index 54ac71db16..ed21b5b6e9 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp @@ -179,11 +179,11 @@ ComponentEntityEditorPlugin::ComponentEntityEditorPlugin([[maybe_unused]] IEdito LyViewPane::EntityOutliner, LyViewPane::CategoryTools, outlinerOptions); - } - AzToolsFramework::ViewPaneOptions options; - options.preferedDockingArea = Qt::NoDockWidgetArea; - RegisterViewPane(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options); + AzToolsFramework::ViewPaneOptions options; + options.preferedDockingArea = Qt::NoDockWidgetArea; + RegisterViewPane(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options); + } RegisterModuleResourceSelectors(GetIEditor()->GetResourceSelectorHost()); From 5838975d626fc632d74f6069bfa329b5092ab062 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Thu, 3 Jun 2021 13:29:51 -0400 Subject: [PATCH 50/65] Incorporating review comments. Minor formatting changes and changes to parameter descriptions. Removed try-catch for property modification and added creation of user_tags element if it does not exist but is modified through CLI --- scripts/o3de/o3de/project_properties.py | 37 +++++++++++++------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py index 10153ff833..d8453a2c4f 100644 --- a/scripts/o3de/o3de/project_properties.py +++ b/scripts/o3de/o3de/project_properties.py @@ -21,22 +21,23 @@ def edit_project_props(proj_path, proj_name, new_origin, new_display, new_summary, new_icon, new_tag) -> int: proj_json = get_project_props(proj_name, proj_path) - try: - if new_origin and 'origin' in proj_json: - proj_json['origin'] = new_origin - if new_display and 'display_name' in proj_json: - proj_json['display_name'] = new_display - if new_summary and 'summary' in proj_json: - proj_json['summary'] = new_summary - if new_icon and 'icon_path' in proj_json: - proj_json['icon_path'] = new_icon - if new_tag and 'user_tags' in proj_json: - proj_json['user_tags'].append(new_tag) - except Exception as e: - logger.error(e) + if not proj_json: return 1 - manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path)/'project.json') + if new_origin: + proj_json['origin'] = new_origin + if new_display: + proj_json['display_name'] = new_display + if new_summary: + proj_json['summary'] = new_summary + if new_icon: + proj_json['icon_path'] = new_icon + if new_tag: + if 'user_tags' not in proj_json: + proj_json['user_tags'] = [] + proj_json['user_tags'].append(new_tag) + + manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json') return 0 def _edit_project_props(args: argparse) -> int: @@ -56,7 +57,7 @@ def add_parser_args(parser): help='The name of the project.') group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.') group.add_argument('-po', '--project-origin', type=str, required=False, - help='Sets description or url for project origin.') + help='Sets description or url for project origin (such as project host, repository, owner...etc).') group.add_argument('-pd', '--project-display', type=str, required=False, help='Sets the project display name.') group.add_argument('-ps', '--project-summary', type=str, required=False, @@ -64,11 +65,11 @@ def add_parser_args(parser): group.add_argument('-pi', '--project-icon', type=str, required=False, help='Sets the path to the projects icon resource.') group.add_argument('-pt', '--project-tag', type=str, required=False, - help='Adds a tag to canonical user tags.') + help='Adds a tag to canonical user tags. These tags are intended for documentation and filtering.') parser.set_defaults(func=_edit_project_props) def add_args(subparsers) -> None: - enable_project_props_subparser = subparsers.add_parser('edit-project-props') + enable_project_props_subparser = subparsers.add_parser('edit-project-properties') add_parser_args(enable_project_props_subparser) def main(): @@ -79,4 +80,4 @@ def main(): sys.exit(ret) if __name__ == "__main__": - main() \ No newline at end of file + main() From c009e7d50bc47daa1a3a19c775d66c031c5b8a5f Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:42:20 -0700 Subject: [PATCH 51/65] ATOM-4782 [Material] Transparent pass is using StandardPBR_Forwardpass shader with incorrect SRG (#1103) Removed TransparentPassSrg until we have pbr shaders TransparentPassSrg --- .../Materials/Special/ShadowCatcher.azsl | 2 +- .../Assets/Passes/TransparentParent.pass | 2 +- .../Atom/Features/PBR/ForwardPassSrg.azsli | 1 + .../Features/PBR/TransparentPassSrg.azsli | 39 ------------------- .../atom_feature_common_asset_files.cmake | 1 - 5 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl index 942db3ed5f..4a228f076c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl @@ -37,7 +37,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial } #include -#include +#include #include #include diff --git a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass index b278f2bcb4..a9db59c646 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass @@ -124,7 +124,7 @@ "DrawListSortType": "KeyThenReverseDepth", "PipelineViewTag": "MainCamera", "PassSrgAsset": { - "FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg" + "FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg" } } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli index 14cff21739..d9367f9d03 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli @@ -35,4 +35,5 @@ ShaderResourceGroup PassSrg : SRG_PerPass Texture2D m_tileLightData; StructuredBuffer m_lightListRemapped; + Texture2D m_linearDepthTexture; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli deleted file mode 100644 index d9367f9d03..0000000000 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli +++ /dev/null @@ -1,39 +0,0 @@ -/* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ - -#pragma once - -#include - -ShaderResourceGroup PassSrg : SRG_PerPass -{ - // [GFX TODO][ATOM-2012] adapt to multiple shadowmaps - Texture2DArray m_directionalLightShadowmap; - Texture2DArray m_directionalLightExponentialShadowmap; - Texture2DArray m_projectedShadowmaps; - Texture2DArray m_projectedExponentialShadowmap; - Texture2D m_brdfMap; - - Sampler LinearSampler - { - MinFilter = Linear; - MagFilter = Linear; - MipFilter = Linear; - AddressU = Clamp; - AddressV = Clamp; - AddressW = Clamp; - }; - - Texture2D m_tileLightData; - StructuredBuffer m_lightListRemapped; - Texture2D m_linearDepthTexture; -} diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 359c0b9b20..a9ba765329 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -246,7 +246,6 @@ set(FILES ShaderLib/Atom/Features/PBR/Hammersley.azsli ShaderLib/Atom/Features/PBR/LightingOptions.azsli ShaderLib/Atom/Features/PBR/LightingUtils.azsli - ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli From 8704b9233a699af223cfd0964bedd5271b79db45 Mon Sep 17 00:00:00 2001 From: mnaumov Date: Thu, 3 Jun 2021 10:53:00 -0700 Subject: [PATCH 52/65] build fix --- .../Rendering/ThumbnailRendererSteps/InitializeStep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp index c35c33017a..8bf157e5f3 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/InitializeStep.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include From 54fdca353b9d3a1108e4a5dd2f63a23b171cdb3a Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Thu, 3 Jun 2021 12:54:14 -0500 Subject: [PATCH 53/65] Fix editor axis gizmo text rendering above gizmo Add AzToolsFramework utility function to query the display scale for a viewport. Use new function to fix the text location on the axis gizmo. --- .../ViewportSelection/EditorSelectionUtil.cpp | 11 +++++++++++ .../ViewportSelection/EditorSelectionUtil.h | 3 +++ .../EditorTransformComponentSelection.cpp | 7 ++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp index 88b92e8c41..d0143c5517 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp @@ -124,4 +124,15 @@ namespace AzToolsFramework return cameraState; } + + float GetScreenDisplayScaling(const int viewportId) + { + float scaling = 1.0f; + ViewportInteraction::ViewportInteractionRequestBus::EventResult( + scaling, viewportId, + &ViewportInteraction::ViewportInteractionRequestBus::Events::DeviceScalingFactor); + + return scaling; + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h index 9936fb9afd..e904277078 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h @@ -60,6 +60,9 @@ namespace AzToolsFramework /// Wrapper for EBus call to return the CameraState for a given viewport. AzFramework::CameraState GetCameraState(int viewportId); + /// Wrapper for EBus call to return the DPI scaling for a given viewport. + float GetScreenDisplayScaling(const int viewportId); + /// A utility to return the center of several points. /// Take several positions and store the min and max of each in /// turn - when all points have been added return the center/midpoint. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 91ac495e0e..5acfa5df59 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -3573,9 +3573,10 @@ namespace AzToolsFramework debugDisplay.SetLineWidth(1.0f); const float labelOffset = cl_viewportGizmoAxisLabelOffset; - const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize; - const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize; - const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize; + const float screenScale = GetScreenDisplayScaling(viewportId); + const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale; + const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale; + const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale; // draw the label of of each axis for the gizmo const float labelSize = cl_viewportGizmoAxisLabelSize; From ba02652e6377a022b7588a1c2f59d38063a0646a Mon Sep 17 00:00:00 2001 From: Vincent Liu <5900509+onecent1101@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:59:11 -0700 Subject: [PATCH 54/65] [LYN-4200] Fail and log error if required aws config file is not found (#1099) --- .../manager/configuration_manager.py | 7 +- .../resource_mapping_tool.py | 11 +- .../style/editormainwindow_resources.py | 2362 +++++++++-------- .../ResourceMappingTool/utils/aws_utils.py | 13 +- 4 files changed, 1261 insertions(+), 1132 deletions(-) diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py index b679921196..fc188582c7 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py @@ -48,7 +48,8 @@ class ConfigurationManager(object): def configuration(self, new_configuration: ConfigurationManager) -> None: self._configuration = new_configuration - def setup(self, config_path: str) -> None: + def setup(self, config_path: str) -> bool: + result: bool = True logger.info("Setting up default configuration ...") try: normalized_config_path: str = file_utils.normalize_file_path(config_path); @@ -63,5 +64,7 @@ class ConfigurationManager(object): self._configuration.account_id = aws_utils.get_default_account_id() self._configuration.region = aws_utils.get_default_region() except (RuntimeError, FileNotFoundError) as e: - logger.exception(e) + logger.error(e) + result = False logger.debug(self._configuration) + return result diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py index e99bf5d441..6a56491b24 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py @@ -74,11 +74,18 @@ if __name__ == "__main__": logger.warning("Failed to load style sheet for resource mapping tool") logger.info("Initializing boto3 default session ...") - aws_utils.setup_default_session(arguments.profile) + try: + aws_utils.setup_default_session(arguments.profile) + except RuntimeError as error: + logger.error(error) + environment_utils.cleanup_qt_environment() + exit(-1) logger.info("Initializing configuration manager ...") configuration_manager: ConfigurationManager = ConfigurationManager() - configuration_manager.setup(arguments.config_path) + if not configuration_manager.setup(arguments.config_path): + environment_utils.cleanup_qt_environment() + exit(-1) logger.info("Initializing thread manager ...") thread_manager: ThreadManager = ThreadManager() diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py index fb819cac34..67181fa36d 100644 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py @@ -3649,968 +3649,1082 @@ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ Z\x80d\xdf%\x00\x7f\x12T\x1b\x97qJ\x10\x92\xa7\ \x22:BG\x84z\xfa\x9d{\x88\xac\x1d\xf5-\x8f\xc3\ r\xe1\x95\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00;\xfb\ +\x00\x00C\x13\ \x00\ -\x01\xa2\x08x\x9c\xed]\x09\x5cL\xdf\x17\x7f-\xb4 \ -\xc9\xde\xa2\xc5RD%\x7f\x89HB\x96(;E\x14\ -\xd1\x0fQ\xb4\x92(B\xd6V*d/E*[Q\ -hAE\x08\xadZ\x94\x92\xd4\xb4\xafS\xcd\xf4\xfe\xf7\ -\xbey\xed\x13M\xcd\xd4\xc4\xdc\xcf\xe7+\xaff\xde;\ -\xf7\x9e\xfb\xce=\xf7l\x17A\xd8\x90\xfe\x08ll\x88\ -\x18rI\x18Av\x80\xff\xdb\xd8P\xae\xa5\xb8\xd9\x10\ -D\x04ATT\xf0kU\x04\x91\x1a\xcf\x86\xc8\xc9Q\ -\xae}\xc6#\xc8\x0a\x03\xf0?1\xfc\x9a\x1fA\x88g\ -\xd9\x10~~\xca\xf5\x7f\x9c\x08r\xdd\x93\x0d9\xb5B\ -c\xd1 ^A^p\xebAK\x16/X\x05\xff\x0a\ -\xc1\x0d\x1f\xbdi\xbf\x0fx\xa6\xb0\xde\x92\x05\xf3\xd6X\ -\xa6\x16e\x1c\xb8\xe2\x9a\xe8YR\xa69E\xe7E\xc0\ -\x0a[\xb5\xc9*Zk9\xfb\xb1\x9f\x97\xf2\xd9\xf6\xfa\ -\xbf\xb3[\xcfN\x10/\x1eU5)w\x94\xe4\x0c\x0f\ -\xb9%\x13.]~\x9b%\xad]p\xc1naN\x22\ -\xff\xca[Q*\xe3.}\x9e\xa72o\xdeW\xd7\xbd\ -*\xb6\xec;\x9f\x5c\xcf\xb7\xf6\xf5\xcfmX\x99\xb8A\ -\xc5(qm\xdeL\xcd\xc4\x0a\xab\xb0\xcd\xa4\xb1\xab\xcc\ -\xae\xde)8D0\x8c\x9b\xa90\xf9u\xce\xa0\x1f\x1c\ -96\xabm\x87\x84\x1bg\xe9\xeb\xaaD\x8a\x8e\x5c\xe1\ -\x17\xea\xa7zJM\x8c[X\x83\xf8H4e@!\ -\x9br\x7fs\xa2u\xf1\xba\x8c~\xd3O/\xb7`[\ -Sf\xc9\xf1,|\x9c\xee,\xb9\xd1\xee\xc8Y\xce\xc2\ -\x90\xd8\x09\xcb\xa6\xb0\x17\xf6\x0fB\x84l\x9f\x84O\x08\ -\x8b\xcb\xf0\x0f\xd5\x9d\xa4\xbf\x1c\xb1|\xce&\xf4`\xe9\ -\xe1\x89r\x03\xdcy\x7f\x14\xc8\xb8\xc5\xf0\x16\x85\xf7\xd7\ -\x1d^:)\xf0M\x89\xc9\x22\xb3\x91\x93D\xe4g!\ -\xb3\xbf\xd9\x88(\x8e|:\xc2}\xd0\x9a\x02\x19\x97@\ -;\xc1\xac\x85\xba3\x03?\x82\x0f\x98\x8e\xf4\x09\x93\x97\ -\x07\x1f\x08\xff/k\x9d.\xdf\xaa\xfc\xe3\x9b\xa7\x87O\ -\x0b\x17\x08\x8b{\xb1\xdaB\xcf\xf9\xc2T\xb6\xac\xb2\xfe\ -W\xc2\x13\xc3E\xc2\x07f\xe4\x0e\xb99\xe0\x9e\xed\xf1\ -\xf0kde\x8b\xc5\x16zu\x91\x95\xf0\xaf\xa7m\x9e\ -\xd9\x8c\x1c\xe5\x1d:[\xb52B9\x9ckru}\ -\xfa\xf2\x90\x01\xf1C\xe3\xfb\xab<\xcf\xce\xecG\x92^\ -q\x03\xfem\xfe\x94@\xc4\xf2\xbd\xdf<\xdd\x10\xa4\xd4\ -\x92Cf\xeb\x07Rm\xb8\x0a\xb8\xa5\xc4 >\xb6\xc1\ -\xc8\x00p\xcb\x15\x16z\x02\x1e\x93 -\xb9\xe1F\xb6\ -+Cb%\xec\xd8Cm\xac\xc8\xca5\xbc\x19\xfd\xf2\ -\xf5O\xc3\xef\x8d\xb5\x89>\xf5!s\x80S\xd1:6\ -\x8b:\xeb\xe29!\x03nN\x87\xdd\xe3p_\xe4w\ -`\xcd\xf5\xfa\x1d\xdcz\xd7Gl\xcdH_DR\xb6\ -\xd8\xb0^\xfcq?\x9bh!\xc5\x0b)\xa4\xfc\xff\xe9\ -l\xe7\x8f\xe4L\xe20!)\xd7D\xcf\xfa\xb6)6\ -\xc4\xd2a\x86\xf1\xd8\xc0\xd1\xbe$\xd5e\x1c\x0a\xd1\x04\ -\xbb\xef\x03\xf7\xb0Y>\xb7U\xd5\x1dR\xaa#2R\ -\xda\xee\xed\x14\x8e\xbd\x15\xf5\xe9\x1f\xd4^\xdc\xd0\x83\xfd\ -T\x13\xd88w\xce7\x9f\x9aa\x12\x1c{8v\xd4\ -[\x17g\xa8=\xf7\x10\xd7A\xc0m\xa6\x96\x8eYU\ -{\xfc\xd7\x07\xcb\xac\x17\x96\x9e+5\x0e\x00\xae\xd8^\ -\xb6\xbd\xa0\xe21d\xe4\xb8\x05#\xa48\xd6E\x11\xce\ -\xafZ\xca\x11\x9e3\x88\xc4\x158,f\xa5\xb5\xb8\xaa\ -\xbe\x87\xb0\xaeo\xb4\x10\xd1\xeb\xdd\x8c\x95\xf2\xfd\xf93\ -\xaf\xe4\xe8m5!\xa9\xda'\xccfO\xbf\xb5u\xa8\ -\xf3c.\x95C\xfb\xe2J\xec\xe7\x04\xc8\xda-v\x18\ -\x1f^\x94\xbf\xf9\x7f\xaeI\x88=Z.\xab:)\x8c\ -\x7f\xd2dq\x85\xe5\xeeA\x17UB\xa2\x911j\xe0\ -\x17w\xd3\x97\xfa\x9d\xf7Z\x9e\xca.\xa6\xbcA\xdc\xfa\ -\xb0x\xc2%\xb7\x8f\xb6\x11\x8fM\xd9\xb2\xe3\x87\xab?\ -\xc9\x10\x10\xf7\xcb\x1b\x11\x16G^pW\xd6y?\xf2\ -z\xe4d\xbf\x05\xdeuvr\xab\xb8\x8c=2\xf3\x0e\ -\xcf\xb90r\xc9D$;\xd6*s\xfe\x88Z\xee\x1b\ -\xf3E\xe4\xc6_\xd8\xfe\x8a<\x86\xc3~nJ\x85\x04\ -\xe8\xda'\xaf\x81\x96S7\x8f\xac\x1f\x7fa^\xf8\xc2\ -\x98\xd4\xc3\xe2\xa6B\x9a~2\xd5\xeaa\xecb\x0d\x85\ -\x0fg\x7f\x91\xb6\x9by(8\xac\x9eg\xdc\x1c\xd0\x95\ -\xb7\x06\xd3V\xdc\xae\xbf{\x9c\x7f\xd1t\xb6\xa1.\x19\ -ZR\xbe#T\x0ey\x94L[\x9c;2\xd2\xf0\xb4\ -\x8dA\xb6\xd9\xe38\xe7\x22\xf6\xf7\xf3\x83\xca\x04\x22\xfd\ -\xbc\x9d~l}\xael\xc1\x96\xbd\xd1\xc2h\x91y\xc9\ -\xf0\xc8\xf7\xda6\xd3\xeaBr-\xe5\xa7\xd8D;\xbd\ -\x93\xb5\xfb:\x9c\xff\xde=\xeb/w\x10\xfbk)c\ -O\x9as;\x8d\xde\xf1\x86`\x18p\xb0\x88\xfdu\x5c\ -A\xday\xe1\xe4\xf1\x0b&|]\xe8'\xfd)G\xcb\ -\xcez'[\xba\xed\x9a\xc3\xe2\xf2\x1f\xa4\xb6\x9a\x05/\ -\x9a\x13\x84d/2-\x99\xa7\x91?\x5cb\xa8\x08\xbb\ -Z\xa5\xe9\xff\x0e\x18M\xb1\x09u|'\xbb\xc0[p\ -\xb1\x1f\x9f\xc5\xbd\xb4\x11*\xa4\xe4#\xa2+n\xd4O\ -\xb2\xe3'\x9bs\x96\x9a&\xf3\xfe\xf2\xed'7\x12\xcc\ -\x0b\xfd\x90\xfd[\xcd\x9e\xa9\xeb\x04\xc1\x196\x948\xaa\ -\xf4\x97\xcc\x0b~\x89\xed\x87\xb3\xd6\x84\xc5\x89\xccj\x90\ -\x0d\xd9\xcd\xb6\xe9\x1cx\xda\xc3\xe3'7x*\xcf\xb3\ -`\xfbD\xa8\xb4\xe5\x97\xb8e\xaauG\xf3\x17;\x98\ -\x03SU.\x14\x7f\xe1\x15\xff\xb2\xd7/3\xcf_\x83\ -4X\xe7\x09\xb2\xf7\xb3\xceRq;\x01\xc9\x84\xa2m\ -\xda\xc8\x18\xa3p>ad\x92\x99\xdb\x1an'\xf3\xe9\ -\xe1+\xc0m\xf3I\x83\xa3\x83\x91\x8f\x0b\x01K\xbf\xde\ -u\xde 2\xb7\x80\x88<\xd5\xb0\xe09\xce\xbfTd\ -u\xb4\x839[6\x97\xd5\xe1\xd3\x8f\x11\xbb\x98\x1d\xd3\ -\xc3\x97xr\x9d\x9d\x10\x84L\x10\xbb\xb0\xc5\xdep\xfd\ -\x12?\x7f\x8e\xace\xc43\xeafn~\xd2^\xfd\x14\ -\x9e\xbb\x8d\x0b\x1b\xf2\xd9h\xdd\xe3O\xdf\x9c\x82V\xbb\ -\x1e\x8f\x99\xe0&\x90\xf6\xb5\xde\x1d\x91\xcb=?w\xe1\ -\x01\x85\xbd\x1eN\xd7*\xc3\xac~%\x8eHU2u\ -\xdb\xcc\xbd\xfaR?\x92`6\x9c\xd0\x8e\x03\x96%\xea\ -L\x12\xb7\x13\xf7\x8e\x22\x90>O\xb5Y\xa2\xe2_\xbc\ -\x8aG|\xa0\x93\xba\xdfG\xd1A6\xf9!\xea\xe3\xec\ -\xe4\x12L\x89\xf2\xc8\x04\x82\xcc9\xfe\xc8\x15.\xdfc\ -\x11w\xc7\xc8\x01<\xe2\xf2q\x9cHh\xb4\x90\x14\xa2\ -V\x80\xfde\xec\x9co*\xb6\x22\xa3\xd4\xe7:\xf0\x8c\ -T\xd5\x9f\xac$\x97t\x8aT\xb71\xcaq\xbf\x92\xe4\ -\x05%\x9f\xc9\x8f\xa6=}\xff1k\x95\xd2\xb1\xa5\x85\ -?\x14\xf7\x0c\xcb\xfbj\x9crCQ6h\xd6la\ -\xdb\x9f\xd5R\xdf\xb9\x85w\x9e_\x91&\xec\xe5?3\ -\xd2\xa0j\x08\xe9\xe4+a\x9fs\x81a\x0a\xcb\xa4\xfc\ -\xea\xef.\xe0U\x9be\xfb=D]\xfc\x8b\x91\xa1\x97\ -\xcb\xa3E\x8a\xa3g\xbc~\x9b\x22\xce\x17\xb5n\xd6\xc4\ -\xb7%\x15Z\x81\xd6\x02\xbc\xaf\xa2\xb7\xd8\xf9\xce\x1a\x95\ -}\xeb\x98\xb8B\x11\xcf\x837\xa2\x12\xa3\x9c\x87\xbb\x8f\ -\xde\xfat\xe8\xa8\x1b\xa1\xc3$\xcc8\xce\x14\x1d\xb2>\ -z\xc6D-W\xae*A\xb8\xff0\xdf\x92\xc4\x99\x22\ -r\xde_\x22\xdc\x97re^\x19\xb6Q\xed\xcb\x90\x13\ -{\xea\xe7\xfb\xf9\xf2\x95\xff\xf6{\ -\x83\xd1\xf0|\x99\xfe3\xf9\x14\xad\x9e\xba<:\xa9T\ -\xb6yU\xb8\xd3O\xa9\x80=\xbb\x0dr\x7f\x8a\x11\xcc\ -\xfb\xf3,\x5cud\xb0\x93\xc2|\xcfp$\xc3\xeb\xd7\ -\x12u+\xd7\xbc\xcc=\xeau\x99f\x95\xb7\x8cBH\ -\xfe\xab\xbc\xcf\xbd\xffl~\xdf\xc4M\xa145H\xb7\ -@V_\x22\xf7\xb8\x82\xbf\xe7\xc5\xe8'\xf1\xe5>\xbb\ -O\x95\x97G)\x5c|\x11\xaffvn\xe0\x18\xf7p\ -$\x9e\x0b\xde\xbcf\xca\xee\xa5\xe4\xe3v\xe6\xdb\xfb)\ -\xec\x9d=\xf7\x9aP\x5c\x9c\xc1\xa3\xb9\xaf]\xeem\xd4\ -*\x9b\x17\xfa\x98\xb7r\xb4n\xf4\x0f6\xb1\x87\xf1\x0b\ -\x1c\x86f\xd6\x9c\xd1\xaax\xc4\xbb7T\xee\x1a\xfb\xfa\ -\x83\x5cR\x0b\xaf\x0d\x0c\xe4\x90\xc9\xf0\xe2O\x9a~\xfb\ -\xfdG\xcd\x00)\xfd\x8c\xb8\xd8\xda\x88\xe3\xb5?~.\ -\xe6\x1f\x15\xb3\x9d\xeb\xb4\x99\x9b\xaa\xfdiU%\xd4|\ -\xa1\xf8n\xf4\xc5\x95\xddwr\xf5\x5c\x84\xe5T\x1f\xf8\ -!\xc3\xbc\xcb/\x08(\xa1f:C\xa4\x0fo\x88\xfa\ -1\xa4\xae\xbf\xfe}Uv\x15\xf7/\xa3\xe7{>\x97\ -\x0bpE\x9f\x07\xfd\xfaU\xb0\xc0\xe9\xb0\x00\x12\xf9\xd8\ -P\x81\xf8q\xe6\x01R\x8ed.\xa9\x22_~\x8d\xd7\ -QN\x1b\xb9\x17\x11^\xe6\x17?N\xf9\xbe>y\x98\ -\x93\xe41\xbb\x9d\xe3\xd9\xc4\xe4\xa3\xae\x98Tl8\xb0\ -e\xf4\xb6I\x22!g\x9c\xa7\x8fC\x16\xa8\x8d\x9e\x19\ -\x13\x11\x8b\xae\x8a\xca5x;\xe2b\xbe\x18\xf8\x90\x86\ -ml\xd4\xb6\xe4\x91\x15\xf1\xd2\xbc\x0f\xd4\x1d\xc4\xc6\x0b\ - \x12c\xbf\x1a\x1a\xa7\x1d\xaf\x9d\xfc\xdcwAT\xbf\ -U\xf0C\xa9~\xd3t&\x87>\xb2;\xadZ;\xa9\ -\xe6d\xfat\xa7\xb2\x09\xe7b\xbekZ\x5c\x90\xf3Y\ -\xf3\xf2\xe3[g\xa5\x09\x0f#S\x96J\xc6e\xe8\xbc\ -\xd3\xe5\xb1\xbd\x1d\x93!\xa0\xb5\x8d\xa8\xc2\x15;\x9e\xd3\ -Y\xc2b\xa9\xfe\x85\xef\x1c\x22\xb3\xad\x17\xf9\x9e\xe5\xa9\ -tJ\x1f\xefd\xa66\xd8\xbc\xf4\xa4\xa8k\xcc\x18\x0e\ -\xeb\xace\xe4\xf1\xabf\xecw<&lP\xa7\xec\xb7\ -x\xdc\xa4\x8d\x1aU\xd7\x83fF\x16\xc7\xbd*\x1cd\ -W[\xb9\xd8Ne\xbe\xdd\xab\xe5CR\xe7\x7f\x12<\ -[\x94\xb5\xf3\xd1\x9b n\xd9\xb7\xc1+W\xea\x1b,\ -\xf6\xf0\xbc1\x9cOm\x90y\xd6\xe8,92\xa9\x9e\ -\xd3\xc1%z\xaf\xd4\xcf\xa4\xf1\x83\x1eN\xdc9x\xd9\ -\x10D\x93|\xdd\xee\xc6\xf0\xfc\x8d\xdf\xbe~\x12\xdcN\ -\xd2\xe4#\xbc\x9c\x1c\xef\xe3\xcf\xf3h\xaf\x81\xe3 Q\ -\x8b\xc0D\xd1l]\x9d]V\xc6\xb9\xa1\x86gE\x0b\ -\xf4\xbfpK\xe6\x9d7/K\x5c}\xc5;=&\xe7\ -f\xbc\xce\xfb\xb0\x87\x8e[^&\xf8\x85Jyo?\ -\x8d\xf0\xe7\x07\xef\xd4\xe4\x92V\xe0\xd2z\xe25'\x97\ -\xf7\xc8\xf3$]W\x8b\xb9\xf9Y\x19\x9a\xb36\x103\ -\xef\xff\xf8\x99\xf2Tw\x13\x9f\xdb\xf2\xf7\xb9\xd9\xd36\ -\xfd\xb8\xec\x93o\x1d{\xf6\xd0]\xbe\x13\x07\xf6\xce/\ -\x17\x8a\xd5\xb6\xe5T\x19\xe1kt\xaa\xbc\xb0N\xa1\xa1\ -RC\xc8\x8a=\xca\xcc6U\xe9\xa6_\xaa\xa0~b\ -\x92\xd8\xf1g\x0a\xfeW\x5cc\x07\xe5\xe5^\xfey\xc3\ -\xb3>lmL\x10\x87\xb8\xf9!\xc1/:\xc3\xe2\x9d\ -\x90\x18\x81\xddw\x95\xdc\xeb\xce\xa4\xde\xb2\xb6\xd4\xb9\x94\ -v\x8bTV\xa4\xd0@4\x17\x9a\x16\x15R\xba\xd99\ -n\xdc\x07\xa5\xfa\x1f\xb9)D\xf2\xf27\x87\xc8\x11\xb3\ -.\xa5\xea\xab\x889\xbdg\xd7?\x15G\x18\x9b*\x8d\ -\x96\xad[\xdf\xcf\xa3\xe8^\x9e\x95C\xf2\xa3\x9c\xeb\xdf\ -\xb6d\xeaq\x88\xa4\x1fy\xd2pl\xf5{5v\x15\ -\xa9`s\x03.7\x19\x9f\x9c;q3>\x7f\xba+\ -{{\x87\xe6\xdb\xdc\x87\xa7\xcaG\xed\x93\xe0A\xf4\xec\ -\xff\x1bvU+9\xaa\x1f\x9ag\x9f\x14\xefp\xd8l\ -\xb9\x93x\xde\x84\x1d\xbc\xcf\x86F\xae8)8\xf6\xbd\ -Nj\xb8\xc3\xd5=_\xbel\x9f(\xfb\xb8\xac\xe2\x1e\ -\xafYf\xffxG\x84_{\xf2\x96\xcc\xa8\x19\xa2w\ -\xa7\x99\x06\xdd\x8c\x9f!\x99+o\xfd\xd8p&\x9f\x00\ -\xa2z\xab\xe0\x92\xdczR\xd0\xf9`\x0b\xb3Q\x0d\xdf\ -j\xb6\xac\xb6[\xfd\x92\x0c~\x1fp\x22I*\x13\xd5\ -\x99#k\xb4kF\xa5\xb5\xe6>%\xf7is\xb6\xd4\ -\xe4\xee\x96d\x13{<\xe0xD\xbd\xe9X\xcd\x80i\ -\xe6O\xbe\xcdq\x1b\xb6\xa1\xe1\xd3\x10-n\xd7,\xef\ -\x85\x83\x14\xff\xa7v\xf3\xeb\xcf\xb2s\x0ahm\xca\xd4\ -\xd2\xcc\xb93\xe2\x1ck\xa4\x01!\xc7\x06T\x1c(5\ -\x9a\x12\x22\xab#\xc4c\xc39eD\x80\xcd\x0e9\xb4\ -v\xc2\xe9\x8a\xad\xb1\xa7\xce\x19\xbf:PKj8\xb7\ -NpJ\x88\xea\x8a\x10$\xd2\xf4\xf0S\xbf\xcf\x81\xb6\ -\x11\xce\x87\x84\x82\xdf?.\xde`\x03\xde%\xcf\x15\xa3\ -\xf6\x1ey\xc8\x86\xeam\xf3\xe66\x9a-\x9b\xf9p\xe3\ -\x19u\x84{\xf5]\xa3S\xf2\x16Kn\x89\xe7`b\ -\xe4\x92\xe1\x87\xf1\x91\xf0m\xbc\xbbK5\x1d}8\xf5\ -\xf2\x81\xf4\xd9\xaf.e `\xd4v\x1d9\x14\xfa\xc1\ -\xd1\x80\x0bLs9\xad\x8am\xb1\xb3\x16#\xdc\xf1\x97\ -\xd1\xa3S5\x87l\x9e\x96x0\x03Y\xbd\x8cC&\ -\xf3\x93\x86\xd6\xbc\xba\xfez\xa9g\xb7hM\x1aV\xf7\ -\x5c9\xf2\xc8\xfd\xff~\xda\xeeNP\x8eC\xcf=\xda\ -_\xfb\xe6\xcc\x87\x8b\xda\x0ez\x15\x96\x1cI\xcf\xb4\x9d\ -\x1e\x08\x9b\xc379\xe7\x84\x0f\xf9CH\xc5\xe0s~\ -\xe9\x86\x85\x0f\xf5=o\x0f\x10\x93WUd\x0f;\xbc\ -\x90|\xfa\x85\x96A\x88\xe9\x96\xbb7\xc0k\x1e\xb5A\ -g\xf8\x8f\x0c\xcd\x84\xea\x12\x99Y\x19\x0b\xcf\xcf\xac\x96\ -\x9fn\xc7i\x13#\xe5\xf0\x22\xe9ne\xd5\x00\x93=\ -\xcb\x84vz~\x05\xa4:\xad\x04\x93\xe3\x82\x91\xdc\xc1\ -\x86Q\x04S\x0e\xf8\xcd\x03\xbb\xce\xa5F\x9f\xb8\xe3\xf0\ -@c@\xf8\xb0x\x9b\x1d\xe5\xaf\xd3\x05xM\x12\xfd\ -\x068\xa8}\x0f\x92\x14|\x5c|\xf6\xf2\xa0\xcd\x07\xc2\ -<\x8f\x84=\x5c\xf15\xef\x7f{\xed]\x9c\x0f]\x1e\ -\xaf,\x09Fz\xfe\xbb\xa2M\xa6!!?3\xa3\xe6\ -\x88\x5c$\xae\xf2MJ\x1f\x93[\xb2\xf1\xd8\xfb\xab7\ -w|?\xff\xa1\xbf\xc1\xa0X=\xb3\x0b/3\xce\x9c\ -\x93\xd0\xb1\xe1\x1c:g\xd7\xe5\xc3\x01\xcf\x8d\xde\xe6f\xa2\x16;\ -_\xa6\xefX\xfb\x8e7\xb1\x9f\xcdps#g\xa3\x1d\ -\x9b\xceD\x0e\xda\xf1n\xb8\x8c\xfc\xc3\x10[\xded\xa3\ -\xffI\x93V\xca\x0d\xe0\xb1yz\xf0\xb8I\xd5\xfc\xd8\ -\xed\xa7\xe7]\x10\xcf}h#=\x5c\x00\xd9w-Q\ -z 2`\xf6\xba\xbbg\x0clB\x8c\xef\xef\xb98\ -\xc2f{\x82T\xba\xff\xeeJ\xdd\xdd\x85\xb2w^\xf9\ -D\x14\xb0\xab\xe4:\x1c\xd5\x02\x14\xf8\x92\x02\xd5\x07\x18\ -\xae\xd1\xce\xfa\xbc\xeaV\xae\xa2\xee\x0f1\xb3\xef\x07\xc3\ -\xd95\xf7\xbd\x0b\x96\xd7\xc8\xdd\xe6\x85\x18h\xeb\xbbi\ -\x8d\xf5<\xec\xbc:\x7f\xde\xd5\xc3<\x08\xe1?\xc33\ -\x93\xf6\xf4[6Z\xcf3\xc7\xfb\xac|\xed0\xb3\x9a\ -\xff\x09\x83\xd71\x9f}\xc3;\x99\xe3J5\xa4x)\ -\x1e\x1b\xd3\xcb\xca\xda\x19s2,\x14\xfdL\xf7\xb9\x83\ -\xa7\x8c\xf3\xf1_\x19\x1c\x96\xbeSr\xe6\x85\xbdJ;\ -\xa6?\x9b\xb9\xcb\xa1\xcc\x7f\xd5!\xf6\x07Z13\xe4\ -#N^\xbb[\xffi\x8f\xc2S\xfd\xe7f\xe7=\xd7\ -\x15=\xff\xcb\xc9\xb1\x13\xee\xce|\xff\xe5\xb4\xcc\ -\x5c\xf5\xa2\x17\xe9O\xd7gxp\xec\x90`s\xd4\xcd\ -\xbc!g\xdb\xdf\x8ccn\xbc\xda\xcf\x18S]\xc1a\ -?S\xdd\xcd\xbcC\x92\xd6H\xb1_\xaeY22\xdc\ -\xf9\xdbZ\xcb\xd4\xb3\xaf\xf82\xebv]-V\x7fo\ -\xb9^\xe3\xdd\xdaT\x12Z\xaa\x12#\xefpR\x1d\x11\ -6 \xef}\xb2<\xac\x84\xbcz\x8c\xc6\x81\xff\x89\x8d\ -\x9f\xe1y-\xa0\xb2*\xf3n\xd0\xc7\x99\xe7\x05\x90\xe8\ -\xda\x12\x87\x84\xc3\xc4\x0b\xdb\xcde\xdc\x1e\xd8\x1c\xbe<\ -\xf3\xa3\x97i\xadl\xf8F%\xbf\xcf(\x17\x8f\xcd\xd0\ -C\xdaBz'\xb3'^\x0f\x95\x91Yq\x90OO\ -!4\xcf\x99\xdd+ \x13,g.\x0b\xfb#\xf3\xd8\ -\xaf\xab\xa3\xc4\x15{\xa4u\x13t\xb9\xd6_\x99r\xdf\ -F_\xc7\xf8\xd2\x99G'\xee\xe9\xac\x22;\x14\xcd\x8b\ -\xfc\x1a\x86\x94\x82\xa1\x92y\x1e\xb4\xd6Z\xb4t\x91\xdb\ -p\xc7\xdd\xb6\x15y\xf1o\xf4N\x1e\x09\xbd\xb9\xa2\xb6\ -\xde/\xc5\xcf\xd8`\xd4Pg\xe4Wd\x12Y\xdc+\ -\xf6(\xdb\x89u\x8aO\x8b\xd8\x9f\x1c\xd3\xfa\xfa\xa3.\ -\xbf\xf6\xec\xba\xf4\x1a#\xc33\xe5\xeb.\x98aO\xdb\ -(\xfb<\xd9\x9b4\xa7\xf8\xa5\xe2\xb5\xd5\xe9\xf2\xe3?\ -]~'\xafTc\xad\xb3'S4\xcb\xeb\x5c\x91\xe9\ -\x86mlY+\x05\x90\x0c\xcb\x9d\xdbB\xcb_\xea\xee\ -=\x7f.\xf3\xd0\x1d\xee\xd0J\xce\x83\x95\xd7\x92G\xac\ -\xbcm\x5c\x93\xa9\x11\xe7\xbf^\xb4n\xc7X\xb6O\xe7\ -\xc0\xeaD\x8a\xe3\xab\xfa\x10\x11p\xd5\xf9\x95\xc8\xc8%\ -\xeb\xaf+%k\xc6m\xfb\xa9X\x15\xb7\xc20M4\ -\xfb\x15G\xd2\x06\xb2\xdbb\xf6\xeb\x5c\xea\xeb\x83\x05\xcb\ -\x95k6k\xc7\xe8\x7f\x95\xcd\xe2\xbc3\xd8L\x1e\xbd\ -\x5c\xc0\xd5\x804\x04F\xb9\xc7\xa5\x1a\x8eC\x0e\x06\xee\ -5\xe0\x12\x9dxH5\xd0%\x9e-,\xd4\xd7}\x9e\ -W\xf9>\xd3I\xb2\x8f\x1f\xfc\xfa\xb9u\xfe\x8e\xa5\xca\ -\x01\x0e\xa5\xe5\x13v\x9fk\xb0p6\xda$;\xe2\xc5\ -\xce\xf7\xb2\xc7B_\x7f\x1cj\xa2.\xe58\x8d\xf7\xed\ -\xbe\xf0\xec\x8dKGi\xeb\xbdG\xf2W\xef\xd6\xe6\xd1\ -2,\x0dp\xe1T\x91m\xa8\x9b\xa7\xc4\xb5\xeb\x5c\xad\ -\xf9\x95\xa9\x09o#/\x9f\x9eB\xf6\xb3\xc9\xe8?\xf8\ -\xc3\xc4\x1d\xa7#\x8b^\x0a$*,\xb2\xdcu\xbf\xaa\ -?:k\x09R)1\xecD\xe6\xcb\xd4\xad)\xf5\x1f\ -\x84\xf3\xb2\xcb\x84\x96NV\x9aVz\x99p\x9ax\x22\ -\xdb\x80}\x9a\ -\xb4\xc5\xcaq\x84\xca\x17\xa0\x13\xa5W,\xbb\x15\xb4|\ -,\xd40K\xe6N\xca\xb9\xaa\xe5\xf1\xe4\x9a\xcd+\xbe\ -\x19q1y\xa2se\x92\xd7\xd5\x8a'\x9dsy\x87\ -\x8e_\x94\xffX\x94\xacj!.\xbf|\xb9\x0eg\xe8\ -\xa0=\xe4\xfd'3\xa2f\x8e5yzp\xe9\x98\x93\ -\x97%\xd6\x08).\xfc*:-\xfa\xd9\x86\xfd\xbby\ -\x1eI\xe6e\xef/\x18p\x7f\xbbQp\x99\xd5\xb1u\ -\xe6;x\xf3\xd9+]^\xf1\xc9\xbe\x97\xce1|\xb3\ -qM\xd5\x1b\xff\xb8\xc8\xd9u\xda\x03\xa4\x15\xee\x1d\xb1\ -\x9b9z\xe4\xd5\x9f[\xbf%\xf3\xba\x1a\xe5$\x8e\xbc\ -\xc6\x19\xb9)\xe8\xfa\xc7\xf2\xf8\xc3\xd1`\xad\x89x\xcb\ -!\xecG\xd2\x1b\xed\xd5\xef\xe7\x7f\xa7\x93\x13\x16Z\x1e\ -\xdc#\x1d\xf7\x22\xcd\xbf\xd2\xc6\xe5*\xd8b\xb9\xcf\x19\ -\xef&\xe4\x9dgR f\xe2\xa7\xbf\x8bG\xedY\xc1\ -\x8d\x83w\x87]%\xbb\xfb\x8eCV\xfd\xcf\xe4\xca\x19\ -I\xa3i\xc9_2Vp~\xbb\xcb[\x1e\xfca\xde\ -\xe6\xb4o\x1a\xd6gR\x1d\x143\xb7o\x8bJ\x11\x96\ -\xb9\xec\x1e;>G\xc7[\xe8\xb2{\x0d*X\x1b[\ -\x9ft(5`\x86\x00\xb2\xd38za-\x89(\x14\ -5;\xf8\xaa\x9f1\xa7B\x09A\xb7\xe8#\x9fBq\ -~\xd6\xb1\x17\xe2\x82\xab7\xd5_-y8\xf5\xb0\x8f\ -\xcb\xdb;\x8a\x1e\xca\xa9o\x5cW_\xd1\xae\xaf\x9e\xa3\ -E\xda!=\x98\xf4&e\xed\x15\x8f\x05\xecb\xcb\xcf\ -n\x8e\xbezZs\xf7\xd8B\xd2d;\xd4\xd8N\xd3\ -\xc1\xae\xf6\xa6\xb8\xaa\x8e\xba+Pe\x17s\xaa:I\ -\xc9\xdc9\xb7\xe9\xce\xb9\x87\xaaB\xc5[\x95\xc0j>\ -\xf0\xbe\xb1\xdc\xc1\xc0c@_\xf4\xdeUf\x9a\x07\xd6\ -\xf2\xf4\x06\xfdc\x93\x89\xf9\xb6\x0d\x9f=&\x97\xb9\xe4\ -]\x22.=\x95\xe1Xto\x95\xd9\xaa\xe3\x9e.\x93\ -\xdeo\xf9\xb8\xe6P\xa0\xec\xeb\xe7w\x84\xbc\xcakT\ -\x93\xd8n/\x91\x1a\x18<\xf7k\x0c\xd7\x88\x99k\x93\ -\xdeCE\xe9\xb9\x8fm\x10\x9f/\xf9\x85\x13\xb2\xf8\x16\ -x\xea\xcc\x01\x84\xaf{#>\x8c\xff\xb0\xb5\xe4\x86\x9c\ -\xc9\x08\x85\x15i\x867kW\x0c\x05\xc3\xfc\xc8;H\ -*\xd3\xfa\xf9fmv\xc1\xc29@\xe6.\x22\ -f\xb7\x0b\xbc\x94q\xf8Z\xa8\x97I\xf1\x7fg\x8a\xbf\ -p\x8b\x0f|\xd2\xefe:\xbf\xfe-\xca\xff\x87\xbc\x1c\ -h\xf3$D}\xac\x9d\x5c\x826Q\x1eQ`{\xf8\ -\xf6\x12\x7f\xd2'\x0d\xbf\x8f\xd1\xec\xe1%&\xc1\x1cB\ -\xa6nK\xb9\xf5\xec\x07\x95\xcd:\xf2\xde\xfb\xfa=\xca\ -\xd5-\xb39S\x91\xcd\x14#\xd5\xf9\xa4\xaa\xd7H\x0a\ -{\xc2/\x19\x0f~U_A\x01o\xb2C\x7f\x9b\xcc\ -\xbc\xf3\xec\xb2\xf92\x9e\xe07n\xd9f\x22\x8ae\xba\ -\x8e\x06fn\x1a\xdc7\x12\xa6\x9a\x91\x88\xc8\x86\x93\xa6\ -\x1cv1\xeb\xa6\xbf. \xae\x8c\xe6\x84\x9fF\xe2~\ -\xc9x\xf1\xab\x9e2\xc86#\xf9\xf8W;i\xdf\x0d\ -uU\xe5\x1e\xaf\xe1\x93&\x09\xff\xee\xdf\x7f\x1dA\xc6\ -\x97_\xe2\xfe\xbahB\xf5\xd4\x90\xb4%\xfbo`\xa6\ -\xbd\xbb\xb1\xb32t\x10\xd2\xe8R\x82\xcc\x03~\x89)\ -\xa4\xd7\x05\x95\x8c\xb38\xd1z\xba\xaa\xfd\ -\xedt\xa1\x80\xd09\x05\x15z\xfb\xc0\xbc\x5c\xab{\xb1\ -\xf8\x9e\x80j\xe5\xae\x0b\x99y/\x1f\xcb\xa2\xdb\xe4\x0d\ -\x96f\xf2<\xfe\xf5R^|\x81\x83\xb6}b\xa2r\ -\xbe\x09B\xb4.\x9e\x13~(c\xbd\xa4\xdd\xaf\xcf$\ -K\x9d\x22\x17}m\x04q@\x08f[n\xf0\xdfT\ -'\x9cJ\xfc\xac\xac^\x17\x0em\xe5\x93\x02GK}\ -\xb1~\xa2j\x7f\xeb\x92\xcd\xa9\xc3\xd7\xeas\xf4\x1bV\ ->\x9c\x82d\x1b\x19\x07\x17\xdf\x1b\xae*\x9cS\xac/\ -=\xb7\xf2V\xbfOos\x06\x91\x86\x10\xfd\x8b\xef\x8d\ -P\x0d|_br$`.*\x97?B\xe5h\x9a\ -\xcd\x9e\x90\x80M\xdcg\xd2\xd40\xd3\xf1F\x04\x19c\ -\xa4;\xaet\xcc\x0a\xefz\x19n\xbdyf\xe1\xc3\xc2\ -\xe2^\x84\x86\xc9:.\x1d`C\x14:\x22\xf4\xeb\x93\ -\xb5\xa4]\xa0;_V\x9a\xef\x9a\xc3\x8b\xa7\x22\x08I\ -\xb9\x86\x10^\x14Rr\x82\x7f\x84\x89\xd5\xf3b\xafq\ -\xb9\x08\x1cDM$,\xc1z\x91]\xa0}}\x8e\x99\ -\xcc\xf6\x89\xe7\xd8\x84\xe3r\x06\x11m\x0eh\xef=\xc5\ -\x9fTj\x9f\x99W\xedMx\xf3\xe5\x0b\xfbb\xe3\xc1\ -\x81\xaf?\xa5\x89\xdb\x99M\x16_\x17\xfde\xc2}6\ -\xfe\xb9\xa6\xa5\xe6\xe38\xed\x16\x07\x8f\x7f]G\xfe\xec\ -\xbae\xe2\xbc\x90\x8dpH\xe4\x1e\x1cx{;i\xdc\ -\x00\xf1\x90*n\xe3\x8d\xcf\xf7z\xad\xd5\x8eF\xce\xe7\ -\x95\xf5o\xe0 z\xbc\xab\xe1\x97\x88%\x7f)1A\ -7%\x9e\x91\x1e\x84@\x0f\xc0\xb4R%\xb1EUV\ -;r^F\xe8\xf0\x90QF\ -\x09V\xbcN\xb6Al\x8fV\xeb|O^\xbfS\x94\ -\xb3P\x83t\xf4#\xdb\xc5\xd1UWU\xf5\xdd\xb7\x87\ -\xcfT[{X.\xb0\x9fcL\xce\xa0t\xee\xd9\xa7\ -\x0d'Ip\xbeV\xa8x\x9a\x06\x1e\x1c\xf1kF\x92\ -\x00bljp\xc3\xc0fg\xc8;1;\x1e?\xae\ -\x9f\x95\x06\xd3\x1cL\xe5\xfc\x06\x18?'\xf6\xd3\x944\ -\xffiS\xa80\xd9\xcf\xfbx\x8a\xc5\x91\x10\xffw\x83\ -\x057\x22\xa7\x80\xac\xe4\xce\xda\xf7#\x9dMo\xd0=\ -\x1b%\x97\xaf/\xd6\x0f_\xcd\xa9\x22\x09\xe8\x9fY\xba\ -Q\xd7\xb5\xf8!\xf7\x8dp\x99\xad\x92\xd5~\x9ft\x1c\ -5RC\x10\x9dS\x99\xee\xd3\xec\xe7\x94\x8a\xdd\x118\ -c\xb8\xcf>\xc5\xa2n\xe04@\x93\xd6\x001\xa7h\ -!)\xf6b\xf6\x86\xe1>\x9ft\xc6\x88\xd7\xdd\x18\xb1\ -\x01\x99\x9d\x946\xf1E\x88\xbb\x022\xeb\xcd1]\x99\ -\xac~'\xf2e\x1c\xf8\xad\x03\x07\x97\xb2\x9d\xf9\xa0]\ -\xa6\xe4\xa1\x8c\xcc:\xf0\x9cM\xd2x\xba1\x97\xdc|\ -\x8b\x03v\xbf\x22wm]\xb4\x1f\xfdZbr\xd2t\ -\xa4\xb7\xac\xfc\x04D\xe0\xc0s\xdb\xcf\xe1\xf7\xc3\x85\xaf\ -\xaf\x02\x7f\xf7J\xe4\xdf\x18\xf7\xf0u\x89II\x82\x8e\ -\xbb\xfev}\xc4\xb4\xdc\x92\xc3$\ -\xa3_A\xc0Y>\x9b\x05c\x8c\xc2\xfb/Cd\xc6\ -\x07]\xd5\x9d\xe4x/t\x93\x84\xf4c\x0e!\xfb\x0f\ -\x99a\xbc\x86Kj>\xce\x0e\xd1_\x8e\xc0\xb9\xf1\x83\ -\xdd\xc5\xfe\xbe\xc2\x95e\x93\x85\xd5\xb6>\xf4\xd5\xd4\x9b\ -5DwF`\xbf\x14n\xbf~\x0a\xb5\xd6\xc5\xe6\x19\ -\xfdV\x8b\xef\xe6\xb8.=\xc6HLt\x14\xd7\x15\xde\ -\xb3\xc8\x09\x0eI\x0e\x0e\x81\x86\xa76[3\xb6\x0f_\ -\xfa?\xf6yY\xc6\x91\xd7\xc8\xe1\x9bu\x07\x97N\x0d\ -|\x05\x84T\x81\x8c\xcf\xca\x86\xb9b:\xff\x138\xa9\ -\xb8}3\xf4v.Y\xa8\xb1\xc0_U\xf7\xa8\x14\xf4\ -vR\xdc\x9f\x98\xc7\x12\xc1}\xa1=\xd1\x08Fc\xa8\ -a\x08\x80\x0c\xc0f\x00{\x00?\x80w\x00\xd9\x00\xe5\ -\x00$\x00\xf4\x1f\x01\x09\xefs6>\x06~\xf8\x98l\ -\xc6\xc7h\x08\xb51d\xe6\xd6\x01\xcf\x87\x01,\x008\ -\x06\x10\x0a\x90\x03P\xc9\x04\xe3\xcf\xac\xa8\xc4\xc7(\x14\ -\x1f\xb3\x05\xf8\x182\xed\x5c\xa0B\x1b'>\x87\x0f\x02\ -D\x01\x941\xc1\xb8\xf6U\x94\xe1cx\x10\x1fSN\ -f\x9a\x07T\xf8>\x03\xc0\x15\xe0;\x13\x8c\xdd\xdf\x86\ -\xef\xf8\xd8\xceh;\x0fz\x99\xef\x10\x13\x01\x1c\x00r\ -\x99`\x9c\xfev\xe4\xe2c=\xb1-\x1fz\x81\xf7|\ -\x00;\x01\x92\x99`\x5c\xfe5$\xe3c\xcf\xd7Ss\ -\xa0\x0d\xef\xa7\x00\xf8\x00\x10\x99`,\xfeU\x10q\x1e\ -La\xf4\x1chq\x7f\x0e\x80\x95\x00\xf1L\xd0\x7f\x16\ -(\x88\xc7y\xc2A\xef9@h\xfd\xces\x03\x18\x01\ -\x140A\x9fYh\x8d\x02\x9c7\xdc\x04:\xca\x82\x16\ -\xf7\x1a\x08p\x14\xa0\x8a\x09\xfa\xca\x02uT\xe1<\x1a\ -H\x0f\xfe\xb7\xe1\xfd)\x02k\xad\xef\x0b \xe2\xbc\xea\ -\xd6\x1c \xb4\x96\xf9GY\xbc\xefS\x80\xbc:Fh\ -\xb1\x16t\x91\xf7P\x9f\x80k\x0aK\xe6\xf7=T\xe1\ -\xbc\xa3I'$\xb4\xd6\xf7\xa0N\xc9\xd2\xf5\xfa.\x0a\ -p\x1evZ\x1fl\xf1Y\xb8\xa7d\xed\xf1\xfa>\xe2\ -\x09-\xec\x03\x9d\xe4=\xb4)\xf90\x01\xed,\xd0\x07\ ->\x84\x16v\xc2N\xc8}hWd\xe9{\x7f\x0f\x88\ -8O;\x5c\x07Z\xfc\x0d\xfa\x15X\xf6\xfc\xbf\x0f\xc9\ -\x84\x16>\xa3\x0ex\x0f\xfd\x8a\x0eL@+\x0b\x8c\x81\ -\x03\xa1\x85\xef\x98\x0a\xff\xa1o\x99\xe5\xc3\xfd{\x91\x8b\ -\xf3\x98\x1a\xff\xe1\xbcpe\x02\x1aY`,\x5cq^\ -\xb7}\xf7a|\x11+n\xe7\xef\xc7w\x9c\xd7mu\ -~+&\xa0\x8d\x85\x9e\x81U\x1b\xde\xc3\x18\xd3(&\ -\xa0\x8b\x85\x9eA\x14\xa1u\x5c\xf1B\x02+N\xf7_\ -B\x19\xce\xf3F\xfe\x1fc\x02\x9aX\xe8Y\x1c#4\ -\xe7\xe5\x842\x01=,\xf4,B\x09\xcd9Y9L\ -@\x0f\x0b=\x8b\x1cBs>\x1e+'\xeb\xdfC%\ -\xa19\x17\xb3\xb7i\xe9!\x88\xa0\x84\xbd\x10\xc2(a\ -\x0f\x84\x10\x0e\xc1\x16\x10j\x06\xfc\x1c\xfc<\xfc^\xaf\ -\xd3\xce\x10@\xde\xdfg\x02:\x18\x07\xc8\xbfF~\xee\ -\x97@\x0b\x0f\xc8\xa0\xc5Gg\xa3\xa5\x8e\xab\xd0\xb2+\ -\xdb\xd1\x0a/\x13\xb4\xe2\xeeA\xb4\xc2\xef0Zq\xcf\ -\x0a\xad\xf06A\xcb\xae\x1a\xa0\xa5\xcek\xd1b;e\ -\xb4\xe8\xa0,Zh2\xae\xc5\x9c\xf8\xab\xe6\x02\xe4\xfd\ -;&\xa0\x831<\x07\xff/\x04\xfc+uZ\x85V\ -\xfa\xdb\xa251\xbeh]\xe6\x07\x94\x5c\xf2\x13m\xa8\ -*E\x1bj\xabQ\x94T\x87\xa2\x0dd\x80\x06\xcaO\ -p\x0d\x7f\xdfP]\x8a\x92K\xf3\xd0\xfa\xef\x9fPb\ -\xec}\xb4\xf2\xc1q\xb4\xd4u=Zd5\x15%\x18\ -\x8b\xfe-s\xa11\xff\xbe\xb7\xe9\xa0+\xdf\x0b\xcd\xa5\ -\x00\xcfW\xa3U\xcf\x5c\xd0z\xc0\xef\x86\xaa\x12\x140\ -\x17\xednk\xa8.C\xeb\xb3\xbf\xa0\xd5/=\xd0\xd2\ -\x0bZh\xa1\x854e\x1d\xe9\xbb\xf3\xa0\xb1\xf6Bo\ -\xd3A\x07\xbe\x0bc\xb2\xbd\xfc\x86!Z\x1b\x1f\x82\x92\ -+\x8b\xbb\xcd\xef?\xcd\x85\xda\xe4\x08\xb4\x1c\xac\x1fE\ -V\xf2-t\x05&\x18\x8f\xce\xa3\xef\xd7\xdd\x00\xe3^\ -h>\x11-\xbf\xbe\x0b\xad\xfb\xfa\x1am\xa8\xaba(\ -\xdf\xdb\xcd\x83\xfaZ\xb4.\xe3\x1d\x98\x07\xfb\xd1B\xcb\ -\xc9\x94y\xd0\xdbc\xd2y\xf4]\xdec\xef\x9a(Z\ -r^\x13%~|\x04\xd6\xec\xaa\x1e\xe5{\xbbyP\ -G\x04r'\x14-uY\x07\xf4\x03\xb1\xbe(\x0b\xfa\ -\x0e\xe0;\x0f\xd6\xde\xca\x80\xa3(\xb9\xf8G\xaf\xf2\xbd\ -m#\x97\xe5\xa3UO\xce`kQ\x1f\x93\x05}\x03\ -`L\xe1\xbe\x8c\xf8>\x00E\xeb\xebz\x9b\xdd\xd4\x1b\ -\x99\x84\x12?\x07\xa3\xc5'\x17\xb0\xe6\x00]y/\x02\ -\xf6\xed+\x81N\xff\xbe\xb79\xdc\xa9V\x9f\x13\x0f\xf6\ -\x8c\x1bXk\x01] \x82\x96\xb9oFI\xf9\xe9\xbd\ -\xcdV\x9a\x1a\xa9\xf0;fg\xea\xfd\xf1\xeb\xc3\x00\xef\ -O\xd9%=\x94T\x94\xdd\xdb\xec\xecR\x83v\xa4\xb2\ -\xab\xff\xf5\xfe8\xf6E\x80\xf5\xb3\xf4\xa26\xf6\x1e\xd1\ -\xbd\x81\xbd[CM9\xdaPY\x8c\x92+\x8b\xb0\x9f\ -\xd85\xf8=\xbd\x1b\xb98\x17-\xf3\xd0e\xe9\x034\ -\xf2\xbe\xe4\xccR\xb4>7\x99.Vz\ -qS\xef\xc9R8\x07\x5c\xd7\xa3$\x02\xed6\x8a\x9a\ -W7)\xbaio\x8f}o\x03\xfauN\xcc\x03c\ -\x98E\xd3\xf8A\x1d\x91\xa2K\xf5\xb2.\x0d\x9e\x0fc\ -O`\xdc\x18-\x0d\xc6\xa2\x95\x9c^\xcc\xd2\x03@\xff\ -a\x8c\x1dM\xeb>\xf8,\x94\x17\x84\xfdc\xd1\xde\x8f\ -\xd1\x15\xc1\xde\xe3\xaaPWJ\x1c!\x0d\xad*\xf8|\ -\xef\x8f\x7f\xaf\xf2^\x04-<0\x05\xb3\x8d\xd0\xd2\xea\ -\xb3>\x82\xbd\x82\x22\xf3\xbc;\x98\xcdJ\x01\xad\xcb\x88\ -\xa5\xad\x1f`\xafStH\xbe\xf7eXo\x01\xec\xd3\ -`\xdc\x0c-\xb2\x13\xc6yA\xfb-\xd3\xf0\xbe\xa9/\ -\xc2X,\x1a-6\xe3\x06b%\xb6\x8f`\xd8~\x95\ -\xd9\x01x\x08\xed\xf1\xb4\xb4\xba\x94H\xb4\xd0r\x0a\xf3\ -\xbd3P\x96YLBk\x13_\xd0\xd4\x9f\xea\xe7n\ -\xbdO{o\x8d\x97\xf9D0^/;?X\xa4:\ -\xb4\xc2\xdb\x94y\xdf\x17@W\xf9\xcd=hC}\xe7\ -\xed\x83u\xe91x\xfc(\x93\xcdg\x86\xf3_\x18\xcb\ -\xc7\xa1\xc5\xa6\x0e\xed-L\xedC\x01<,\xb2\x9e\x8e\ -\xd6\xffH\xe8t\x9f`\xdc`\xb1\xfd?\x18/\x06\xde\ -\x15\x18\xd7\x03\xd7\xc0\xce\xb6\x9a7^\xcco73\x16\ -E\xab\xc3\xaft\xbaO\x98>sm'\xf3\xca4\x86\ -\xf1_\x10\xad\xf4;\xdc\xf9}\x1f\x90\xfd0\xd6\x9e\xe9\ -\xc7\x09\xae\x01\xd0\x87EC>B\xd5\xa3S\xcc\xdf/\ -\xbaC\x84&\x9b\x1f\xb9\xa2\x08\x8b\x09az9\x89\xd9\ -\xb3Ti\xf2\x0f\xc1\x18\x03\xc2>\xb1\xde\xa7\xbd\x07y\ -\x0fc,`~eg\x1b\x8c\xc7):4\x8d\xf9\xf5\ -$\xa8\xd7\x1e\x94\xa5\xc9\x8fY\xfb\xe5)Zh&\xc9\ -\xfc}\xa3\xe7\x18\xd1\xa8\xfbc\xfb>\xb0\xbfb\xfe1\ -\x12\xc1xY\xfb9\xb8\xf3}\xc3\xf6\x00L\xb8\xa7e\ -$\xff\xc1\x9e\x07\xf6\xbb\xb3\x8d\xf8\xf1!Zh:\x1e\ -\xed}{o'\xb0O\x02\x8b;\xebl\x83\xf9\x02E\ -Vr\xff\x16\xff\xa1\x8c\xcc\xfa\xd8\xe91\xaay{\x97\ -I\xec\xfd\x9d\x00\xd8\x03\xd4D^\xeb<\xff\xb1\xb5\xed\ -\x1f\xb2\x03cv\x7f\x19\x9a\xf2y\xfej\xfe\xffL\xf9\ -\xf7\xf8\x0f\xd6;\x96\xfc\xc7\xf9\xff/\xca\x7f\xa8\xff%\ -t\xdeVN\xd1\xff\xa4\xfb\xc0\x18uE\xff{\x8b\xf9\ -A\x99\xbfot\x1c#\x9a\xf7\x7f\xa9}l\xff\x17\xd7\ -\xe9\xbe\xd5~y\x06\xe6\x8c\x14\xf3\xf7\x8d\xce\xe3T\xfd\ -\xc2\xad\xd3c\x04\xf3\xb4`\xfcu\x9f\xb0\xff\x1c\x9fG\ -\x9b\xfd\xe7\xcdm\xe6\xb7k\xd3\x1b\x98\xfd\xd7\x9a\x06\xfb\ -o=s\xfb\xfe\x9a\xfa\xd5\x05\xfb\xef\xe3\xd3\xcc\xdf/\ -\x06\x8cS\x99\x9b\x0eM\xf1\x12\xb0\x8e\x1b\xd3\xc7LB\ -\xffO\xd8\xe5N\xf7\x09\xe6\x92\x94_\xdf\xfd\xef\xf1\x1f\ -\xfaJ\x8f(b6@X[\xad\xfe\xfb\xe7\xdf\x03|\ -\x06\xe6o\xc2\xb5\x95i\xd7I\xcc\xff\xfb?\xa0\xcf\xd3\ -\xe0\xff-/@KN\xa91\xff\xba\xc6\x08\x80w\x05\ -\xee\x03a\xad\xcd\xce\x00\xab\xad\xc3\xcc\xeb$\x94\xfd0\ -\x16\x98\x86\xfc0XC\xac\xcf\xea\xfe{E:\x07z\ -\xdc\xa33\xf7\xea\xe5\xb1\xc0\xe2\xbf\x12\x9ew\x9a\xf7\xb0\ -aq\xccF\xa2\xbdO?\xad\xd8?\x16\xeb\xef\x1fa\ -.E\xc9\xcb\xeamz\x19\x8d=x\xee*\x91\x86\xf8\ -O\xa0\xfb\x94]\xda\xda\xf7\xd6~\xa8\xbb]5@k\ -\x93\xc3\x7f\x8f\x94\x08\x94\xf8\xf6.^\x13\x8d\x89\xdf\xdd\ -\xee\xa2\xb1fA\xc6[\x9a\xde}\xa8\xd3\x14Y\xf7\x01\ -\x9bF[\x18\x0aRr6:\xd1`|\x1f\x1c\x9b\xbf\ -W\xbf\x11\xc1\xec\xbdU!.\xb4\xe7\x7f\xc8\xd4v\x8c\x9e\xe2\xbf\xef\x01\ -\xacfnW[\x8f\xc7\x84\xc0\xfa/fR\xd8y1\ -\xb0\xc6TW\x1a\xe9g\x0aZ|\x5c\xa5\xf7\xd7\xae\x7f\ -\x81\xff\xf4:\xc3\x09\xa3]\x14\xe8kK\x80\xcc\xb9\xdf\ -\xe5\xb3\x06\xe0\xf92\xf0\xdc\xa1>\xb7\xdf\xeb\x8b\xfc7\ -\x16\xc3\xce\xec\xc1b\x86\xbaT\xffM\xa4\xb9\xfe\xdb\x89\ -\xf9\xd8^\xad\xbbu\x00a\xfeR\xa1\xe9_\x10\xe7M\ -\xcb\xfe\xbf\xf8G/\xf0\x9f\x12_\x02\xd7Y\xe83\x82\ -6\xf9b[%\xbc\xf6\x9ep\x8b\xf3\xde:Q\xff\xf1\ -\xd5M\xba\xd4\xa0\x85\xb1nE\xb6\xb3\xfa\xb6\xdco\x04\ -V\xf7f\x03\x16\xe3\xf8{\x5cG\xab\x82\xceQ\x8f\xdb\ -\xea\x01\xfeC\xde\xc3\x06\xf3q\xe1\x1e\x8d\xf8)\x08\xab\ -1V\x0e\xcfzs\xdf\x8c\xd5\x94\x87{\x10\xa8\x8b\xc3\ -z\xb0\xad\xea\xbf\xfe\xfcJ\x93-\xf7w\x0d\xd6.\xc7\ -\xf4\xfd\xbf\x81\xf7-\x01\xed\xfa\x9d\x01\xb5\xef\xf6\x04\xff\ -\xe1Y\x11T\x19R\x87\xe5\x9d\xc2\xf3\xc1\xe0yQ\xf0\ -'\xc3\xea?\x97\xe4br\xa4\xcf\xcb|z\xa37\xf9\ -\xdfC\x0d\xd6\x7f/g\xd5\x7f\xff'\xf9\x8f\x9d\xff\xe0\ -\xc9:\xff\xe1_\xe4?\xf4\xeb\xb1\xce\x7f\xf9\x07\xf9O\ -\xae\xc7t\xcc\xe2\x93\xf3\xff>]\x8f\xc5\xff\xdf6\xe8\ -\xcf\x83{\x07\xd6\xf9o\xff\x16\xff)\xe7?\x86`\xe7\ -\x86\xf7\xb1\xf3\x1f\xe9|\x06(\x8d1{}\x9c\xffM\ -\xe7\xbf\xde\xde\x87\x16ZJ\xf7\xb5w\x1e\xf2\x9e\xae\xe7\ -?C;+\xcc\xe9o\x07X\xb3\xc1\x98\x8a\x8f\x96.\ -\xfe\xbf\xfb\xbf\xe1\xffx\x94\xf8!\x90N\xdcnn\x94\ -\xf3\x9f\xc3\xb1ZD\xd8\x99\xf0}\xf7\xfcg:\x9e\xff\ -.\x82\xf1\x12\xda8\xebR\xdf4#-\x0a%\xc6=\ -\xc6b\xe2\xdb\x8d\x11\xf4\xff\x9fU\xc7\xec\x83U\xc1\x0e\ -\xb4\xe3\xa9#\xa5\xe6?U\xff?\xe5\x1c\x19Zrp\ -\xff\xc4\xf3\xfa\xec\xcfX\xdd~\xa8\xd7\xff%\xe7\xbf\xbf\ -\xa3'\xff;\xcay\x81\xe7r\xc0:\x7f\xd4\xe3\x7f\x84\ -1_B\x97\xf1\xbb\xf8\x09cQ\xb4\xd8~!Z\xe1\ -g\x8d\xcd\x03(\xab\xa1/\x02\xb3\xf5\xc1\x9c#R\x1d\ -e\xed\x81\xf9g0n\x0f\xda\x04k\xab\xb1Z\xb4\xd0\ -n\x03\xeb\xf7\xc0\xfcT\xe8\xe7\x801>\xd8\xbb\x0e\xe3\ -\xb41_R\x9f\xe5{# \xef\xef\xd3\x95\xff\x1d\xd4\ -\xf2\x82zq\x87\xfcg4\x9a\xcep\x92\xc0r,\x8a\ -\x8f*av\xf8\xb2\xcb\xdb\xc0\xba\xbd\x1f\xd3?\xe0Y\ -.\x15w\xad\xb0\xdc\xc22O\x03\xa0\xc7\xadA\x8b\x8f\ -)S\xf2M\x80\x0ci\x8e5\xeb\xf3\xd2\xea\ -\xeb\xd8\x0dJ[^\x0fF\xd1\xac\x96\xd7\x5c\xadn\x8f\ -=\xa0-9m\xae\x1bZ_\x1f!\xb5\xbe\x9eKl\ -}-Z\xda\xfazp\xdb\xeb\xac\xd6\xd7\x5c\xe1\xad\xaf\ -9\xda^\xdb\xb4\xbef\xfb\xd35\xc2j\xac\xc6j\xac\ -\xc6j\x0ci6\xad/\xff(\x8f\xc3[_\xb7\x93\xef\ -Y\xad\xaf\xb9\xfe\xb4~\xb4]o\xda\xaeGm\xd7\xab\ -?\xado\xed\xd6\xc3V\x04q\xb5_O\xdb\xae\xb7m\ -\xd7\xe3\xb6\xebu\xbb\xf5\xbc\xf5z/\x05~\xa8 \x94\ -qeC\xc4(\xbfWAhj\x1d\xe8zj\xb8N\ -\x13\x86\xeb9\xd5\x00\x0d\xbd\xa0k5\xe0\xcf\xce\xc6i\ -\xb1\xc7ik\xa7c\xd1\xda\xa8\xf4[\x08\xc0\x10 \x02\ -\xa0\x94\x09\xf4\xcc\x8eP\x8a\xd3h\x88\xd3L\xf38\xb4\ -\xf9\x0e/\x80.@\x5c/\xf1\xb8;s#\x0e\xa7\x9d\ -\xb7\xb3c\xd0\xa6\xef\xe2\x007\x01j\x99\xa0?]E\ --\xde\x07\xf1?\x8dA\x9b\xbe\xcb\x13\xfe\xae\xbc\xe1(\ -\xbcOT\xc7\x80J\xdf?1\x01\xcd\xf4\xc6\xa7\x8e\xc6\ -\x80\xd0z\xce\xffM|\xa76\x0f\x9a\xde\x856}\x87\ -r\xe2&\x13\xd0\xc8h\xdc$\xb4\x91\x898\xa0\xac\xec\ -\xcb\xb2\xae\xb3\xa8\xc5\xfb\xdav}\x8fc\x02\xdaz\x0a\ -q\x84\xd6\xfa\x01\xd4\x17\xfa\xd2\xfa\xde]4\xe0}n\ -\xd4i#\x98\x80\xa6\x9eF\x04\xa1Y\x9fgf\x9d\x96\ -Q(%4\xefez\x9b\x96\xdeB\xe3>\xae\xe7\x9e\ -\xb9\xb7E}Fx\x0dkG\xc1\xb3R\xe19R-\ -\xcfn\xeb\x99\xbaB\xb0\xeft\xac\x93\xdcQ\x9f[\x9c\ -5xT\x09\xabOZ\xf9\xc8\x1e\xady}\x0b%~\ -x\x88\x12?=\xc1\xce\xba\x85\xf5\x87+|-\xd1R\ -\x87\x95X=SJ\x9dJ\x86\xd6\xd5k\xdc\xbf3\xee\ -\x19\xf0\xbc=\xf3\x89X\x9f\xb1s\xd7\x8a\xb2)\xb5y\ -\x7f\xd3\xe0Y]\xf5YqX\xed\xe8\xe2\x93\x0b\x9a\xe7\ -\x0d\xfd\xe9c\x9c\xed\x02\xf2\x1c\xcc\xed\xd2\x8b\x9b\xd0\xda\ -\xa4\xb0.\x9f)J.\xceE\xabB\x5c\xd0\xa2#3\ -\x18q\x9e4\x83\xfa.\x8c\x9d\x93\x01\xcf\xf1\x86\xf5\x96\ -\xe9\xd1`\xbdfx\x86\x1f\xe5\x19L\x5cs\x0c\xf4\xbd\ -\xc8F\x11;'\x86\xde\x0d\xd6\xda\x84u\x8c)\xf5\xf6\ -\x98p\x0c`\xdf\xc1<\x85\xf3\xbd3\x0d\xd6\xe3&W\ -\x14\x82~\x15\xa0\x0dU`\x9e\x90\xea\xff\xfc\x1d \x1b\ -*|,z\xbf\xaf\xed\xfa.\x82\xd5Cl<\x93\xb2\ -C\xfa\xabJ\xd0\xda/\xcf\xd0\xca\xfb\x87\xd1R\xd7\xf5\ -h\x89\xbd\x1aZ|B\x15-9\xbb\x0c-\xf3\xdc\x81\ -\xad\x01\xf5?\x12\x01\xb3\xc9\x1d\xdf\xa3\xb2\x18\x93\xa7\xcc\ -Vs\xb5\xf2\xe1IJ\xaduj\x0d\xc8|b\xdc#\ -\xb4\xd4iu\xeb\xba\xdbMg\xb4\xe2\xf5\x9a\xc18\xc2\ -z\xa2\xb0\xae;\xa9\xe0[\x87c\x00\xcffg\x9as\ -\xb6\x01\xed\xf0\x9cQrY~\x87<\xaf\xf4\xb7E\x0b\ -\xcd$;'\xc31}A\x18\xabo\xff\xbbw\xa9&\ -\xca\x0b%\xec\x97\xe8\xfd\xfe\x03\x1a\xe0Y-T\xfb^\ -]\x86\x9d\xa5\xd1\xd4/\x1a\xc7\x15\x9e\xd3[\x9b\x10\xda\ -\xe1\xbda\xed~\x06\xac\x8b\xb4\xf1\x1e\xbc\xbb\xe4\x8a\xa2\ -\xf6\x04\x92I\xd8\x99Q\xd8\xb9\x02]\x95\xd7\xe0\xfe\xc5\ -v\xca\x14\x99@m\x0e\xc4\xf8R\xf4\xe7\xde\xea?x\ -\xff\xaa\x82\xcfS\xa5\x0d\xd6\x0b.\xb4\x9c\xd2}]\x1e\ -\x8cA\xf9\xf5]Tu(\xa8\x1f\x15\xdb\xcd\xed%9\ - \x82\xe9\xb6\xf0\x5c\x94vs\xb3\xbe\x96r6(=\ -\xe6&\x5c[\xcc\xa4\xa8\xcb\x02 o\xcbo\x1b\xf7\xce\ -;\x00\xc6\x1c\xae]\xd4\xe4^}N\x02Zd%G\ -\xbf}\x1c\xe8\x1f\x5c\xf7\xb1\xf3<\xda\xbe\x03\x91\xd7\xd1\ -^\xd1\x87\x00Me\xee:T\xcf\x15\xaeys\x9b\xce\ -\xcf\x12FKN\xa9a\xfaR\xbb\xf7,%\x02;\x9f\ -\xa9\xe7\xfb/\x08\xf4Q\x13\xaa<\x81\xeb\x1d]\xe7$\ -\xd4\x0b\x0e\xca\xa1\xf5\xb9I\xed\xe7\xda\x8f\x04\xb4\xf0\xa0\ -l\xcf\xd7\x22\x06\xfd\xaf\xbc\x7f\x84\xfa;y\xcb\x98\xa2\ -\xcf\xd0\xedy\x14\x19@M\xd6\x90\xf23\xa8\x9f\xc1\xc4\ -\xea?\xc3\xfb\xdf\xe1\xfc\x0f\xe8\xd9\xf9_\xd4+\xf3\x1f\ -\xc8?7\x1d\xaa\xf6\x1cx&\x13E\xef\xa1\xd7\xb3\x84\ -\xb1\xbd\x12u\xf9\x17\xd9;\xf2\x0f\xae\x7f@7\xab\xcb\ -\x88\x05s0\x1d%\xfdJ\xa3\xa0 \x03;\x17\x0d;\ -G\x8c\x9e\xeb\xdf\x1ds\xea\xeb\xdf\xab\x1b\x0c\xe4q\xa3\ -M\xb6\x05Z\xeaZ\xc6b\xd8:\x0f\xf7lE\x87p\ -\xc0\xff\xc3\xf3\xb4\xe8\xa5\x97\xfeQ\xff\xd9\xcf\x18\xfd\xc7\ -X\x14\x9b\xdf\xd8\xf9Q\xf0,)\x88\xbb\x07\xd1\x923\ -K[\xf3\x95\xd1u\xf1\xa1\xfe{m'u\xfd\xb7$\ -\x97q\xfb`\xc0?h\xabh\xdb\xaa\x9e\x9ci\x1eo\ -l\xaf*\xf8\x1bt\x93/p\xffsL\x19\x93q\xd4\ -\x1a\x11\x9e\x11\xca\xa8\xfdO\x87\xfd?\xdb\xe4\xa3\x80v\ -\xce2\xf7\xcdh\xd9%=*\xd8\x8a\x96:\xae\xec:\ -}p\xff\x0b\xd65x\xfe,\xb5\x86\xed\x7f]\xd73\ -N\xf7\xff\x13\xff\x01J\x9d\xd6`t`g\xedA\xfb\ -]K\x00Y\x05\xd7k\xec\x8c\x90&\xfd\xbc\x13\xe7n\ -\xe1\xfe\x92b\xfb\x05\xe0\x9d\x7fI\xb5\xef\xb0\xd5D\xdd\ -a\xac\xfd\x03\xf6\x9f\x8a\xed\x01\xfa%\x9a\xfa\xef\xbc\x96\ -r\xde`\x07\xad\xee[l\xab\xfeC\x1bP\xa1\xe9\x84\ -\xf6\xe7\x91\xb5\xb4\x7f\x01\x19Zq\xef\x10\xa6\xd7t\xd4\ -Hy\xa9\xe0\xbd\x9f\xc7\xd8}/\xe8\x7f\xd53'\xfc\ -\xdc\xcf(\x0a\xc0\xff)\xba]'\xfb\x9f\xd1\xa2\xffP\ -_\xf0\xdc\x0e\xe6s(\xb6?(\xbd\xa0\x05\xf64\x8b\ -\xb0\xf3\xd4K\xce5\xda?\xddp\xfbg\xc7\xe7\x06c\ -\xf6Ox6h\x0f\xec\xf9\xb1sop\x9ea\x806\ -\xbc\xc69Gs\xff\x05\xb1\xf3\x84\x9b\xfa\x01\xe49\xb9\ -\xb2\x08\xb3\xebc~\x92N\xdb\xbf-\x19\xde\xef\xd6\x10\ -i\x83f\xf9D{\xff\x8d\xa9\xea0\x9diL\xe7\xff\ -\xe8\xc1\xfeC\x7f(\x83\xfc_\x9d\xf3\x01\xb6\xfa\xcc\x1ea\xccw\x00i\ -\x86\xf1\x81\xe4\x92<\xea>\x7fR\x1dJ.\xfa\x81\xe5\ -\xcb\xc2\xf83,_\xaa{\xfc\xe8>\xed\xe0\xf9%\xa7\ -\x97\xa0\xb5\x9f\x82\xd0\x86:b\xa7\xfd\xb7\x0d\xc4J\xcc\ -\xb7\xd7\xbb\xb9Z\x22\xd8\x98\x93\x08Y\x9d\xa6\xbbm\x83\ -~/,\xd6\xaa\xa7\xfb\x00\x9e\x07\xe3b`\xec\x03\xd5\ -\xf1\xad\xa9\xc0b\x81\xb0\x18\x9d\x8c\xb7X\x1cjG\xf1\ -\x080\xf7\x9a\x92/\xd5C}\x801\xbbg\xd5QR\ -av;Z`\xceHu\xd8%,\x1f\xb0\xe8\x90<\ -%\xc6\xc6L\x0a\x8bw+u\xdd\x80\xe5\xc6\xc3\xbc\xb9\ -v|\xf8\x91\x88\xc5\xdc2\x9c\x0f{)\xb9=\xd4b\ -\xef\xea\xd2\xdfb>jJ\x1f\x85Z\xf8\xc4D\x9a\xfd\ -\xbbX\xee\xf7F\xaaq\x8b5\xd1>\x98\xfcb\xec\xd8\ -\xc3\x98\xd1]\xedb3\xea\xd2\xa2\xb1\x1a\x04\x9d\xf2_\ -\xc3\xdc\xb6Sj\xedr\xcf`|\x02\xe4\x11#s\x8e\ -\x0aM\xc6\xb7\x1b{(/a\x9e%M\xcf\xc5rt\ -6\xb7\xcbC\xc7\xe2\xd3\xb18\x22F\xcc\x1da\xacn\ -\x01\xcckn\xd9\xaaC]\xbb\xe6\xd7\x07\xeb\x18\xcc\xf3\ -k\xf5.\x17|\xa3\xd4\x04`\xc4{\x00\xe3tn\xec\ -n\x95\x83\x0c\xf3\xb3i\x1e\xfb\x16\xf7+s\xdf\xd2j\ -\xdd\x80100\xfe\x8f!s\x08\xdc\x13\xe6]\xb6\x92\ -\x1b9\x09X\xecI\x97\xc6\x1f\xcb\xe9\x9f\xd9N\x8eU\ -\xf8\x98\xd39\xc7\xa5\xf9y\xd5//\xb5z\x16\x8c%\ -\xc2\xf4\x80.\xdd\x0f\xc82\xcb\xc9h}\xf6\xe7V\xf7\ -\xac|x\x821\xe3\xdf!\xfd]\xcc\xd1\xe8i\xfa\xa9\ -\xcd\x9f\xdc\xa4\xae\xe7\xd8t8\x7f,\xe84\x7f\xda\xc7\ -e\xc2x5,'\x05\xc8\x09\x08\x18?\x08s5\xbb\ -$/:|\x7f\xb5\xbb=\xfeX|mcl-\x8c\ -\x93\xc3e2\xfc=\xd4\x05Z\xa2\xcb\xf3\x07\x93\x9f>\ -\xad\xe5'!\x13\xc8O\xc5\xee\xc9O@#\xd4a\x1a\ -\xe3\xa3\xe1zU|lNs\xdc\x9a\xe1\xe8\xd6\xe8\xca\ -\xbe\x0a\xcf\xb1j\xbf~\xf9t{\xfd\x82\xe3\xdd2\x9f\ -\x0e\xea\x92P\xc7\x87t\x16YO\xc7t\xc5\xd6X\x0f\ -t/\x15\x9ah\xa7\xae?T\xd0E\xf6c\xf4\x83q\ -o\xbaou\x19F\x7f\xc1\xeeQX\xae\x14\x9c\xa3-\ -\x01\xf7U\xadr\xa5\x1a\xe3\xee\xa9\xeao\xa2\xd8\xfc\x86\ -\xebF\xdb\x86\xe5\xdc\xd3A\x7f\xc3t\x9c/O1\xba\ -\xa1\xae\x0b\xf7'0\x9e\x14\xa3\xdf\xf7@\xbb\xe7b\xcf\ -\x8e\xf2\xc6i\x17\xc1tP\xb8'\x81\xf9\x02Pw.\ -4o\xa3?W\x16\xb7\xfb>\x94ct\xcb\x97\x07c\ -\x04\xf7\xd90\x86\x1f\xf2\x19\xc6\xaab\xef\xb0\xa1\xe0\x9f\ -\xe9\x872\x16\xec\xd7!_`\xce\x1c\xdc\xbb\xd4e\xbc\ -\xeb\xc4\xfee=}\xf7/-\xeb\x915\xdewOg\ -\xe9w\xa4\xfa\x19j\x8d\xa1\xfb\xc7\x969Xx\x9c5\ -\xccS\xa3J?\x94\x1b4\xd0\x8f\xed\xdf\xdf\xde\xa3\xc7\ -\xfe\x9d\xba\x0d\x02\xe6\xbb\x80\xb5\x11\xe6\xb7\xc1=:\x16\ -\xf7\x0b\xd7\x1b\xb0~\xc1\xf9@\x99\x1782\xdf\xa3\x95\ -\x0fN4\xd1\x0fk\xa8Q\xad7\x83\xd9Or\xe8m\ -?\xa1n\xbf\x82\xba\xf2\xcd=\x18\x1d\x98\x8e\x0c\xf6\xbb\ -\x94Zo\x12\xf8\x9e\xb65\x9a\xe5\x86\x08\xf6\xaeR\xec\ -W\xb6=a\xbf\x0a\xeb\x90~\x18\x93\x0e\xf8L.\xfd\ -\x85\x96\x9cY\xd2B.\xb7\xcd\xfbhCG\xd3\xbck\ -c;d\x8c\xfd\x90\xba\xfd\x16\xce\x1f+9,\xcf\x0e\ -\xc6\xe5C\x19\xc8d\xf1\xa9\x8d\xf6\xdb\x8e\xed\xe7\xadb\ -\xc2\x99\x8av\x94\xd0l?\xef\xeb\xfe\x8b\xbe\xee?\xfa\ -\x1b\xfcw}\xd6\x7f\xda\xd7\xfd\xd7\x7fC\xfc@_\x8f\ -\xdf\xe8\xcb\xf13X\xfc\xd0`\x04\x09\x87?9\xf0x\ -#\xb6\xe6\x9f\xac\xd6\xbdf\x03\xffi1\x9e\xe1\xf0'\ -G\xf3\xb8\xc38-1\x84r\x84OS\x9c\x16?\xf5\ -{\xb5\x99[C\x01\xb6\x01<\x05( t\xef4\xces\xda\xbf\xdf\x14\x0b\xd1\xa2n\x09\xed\xb6\xde\ -\xc6w\xac\xf3\xdf\x81\xf5\xb1\xc1\xfe\x14\xdaN`\xbd\x10\ -X[\x01\xd6\x0d\x84\xf5\x10\xb0\xfaA\xb4\xd1\xd1\xf9\xf7\ -\x1b\xdes\xbf\x04\xb6\xd7\x845>0_\x15\x16\x8b\xd0\ -\x80\xd5^\x806)X\xbb\x02\xda\x11\xa0\xed\xbd\x93\xfb\ -\xb9N?\xbb\xd0R\x1a\xcb\x8dn v\x9c;O1\ -@\x90\xb1z\x11t\xb3Ya\xb5\x84$1\xfbX+\ -\x93A\xc17\x94\x18{\x1f\xad~~\x91\x92\xeb\xfa\xfd\ -S\xab\xbaV\xd0\xfePd;\xab\xfb4\x80\xefc\xb6\ -\x192%\xee\x03\xda\xba\xaaC/`6\x10,\x07\x18\ -\xab\x87K\xc9\x93\x866LX\xdf\xa7\xc9v\xd3]\xbb\ -#\xf4C\x1cV@I?\xbf\xe2\x9d\xae\xc7\xf2\xb01\ -;r\xdb~\xe1{\xd22\x8f-M5\xb7\xa0\x8d\xa0\ -[v\xdbF[\x09\xeeW\xacM\x0e\xa7\xd4\x06\xf8\xdd\ -\xfc\x06\x7f\x83\xf9\xd6Mc\x80\xd5z\xec:\xef\xab\xc3\ -=\x9b\xee\xd5)\xff\x0b\xb4a\x9fY\xda\x14\x8b@\xa9\ -g0\x09\xed\xd2>}\x9f\x18\x16\xf7C\xe1{5V\ -;\xe2\x8fc\x89\xd97\xe41\x1b)e\x9efb5\ -q\xbb\xe4\x87\x02|\x86g\x0d`\xcf\xaf'\xe2\xb6T\ -\x06?\xbfe\x8d\x1b\xc8\xcb\xe0\xf3\x98m\x0e\xde\xa7\xcc\ -\xd3\xe0\xcf\xcf\xa76\xfe\x16\x9d\x1c\xff}\xe2\xd8w\xa1\ -\xdd\x15\xda\xd5\xe1\xfbE\x891\xa1\xd4(\xc3\xecq\x7f\ -z\x9f!\xcd\xcf\x9c\x9b\xe7_\xa7k\xadQ\xea\x81C\ -\xb9\x01\xe5i\xed\xe7`L\xdeB\x9f=\xa4\x09\xc3Y\ -uJ\x9d4j9\xd5\x8d\xef\x9f\xfb\xe6\xa6\xd8\x1f\xca\ -\xfb\xd7I\x9f\x1bV\x8f\x5c\x1a\xad\xcf\xfeBy\xd7\x92\ -^b\xe3\x81\xc9\x5c s\x1a\xaa\xcb1?\x0cVK\ -\x06;\xa3A\x8a\x22\x07\x9a\xe4\xcf\x14\x8a\xfc)n-\ -\x7fh\xf2A\x03YU~\xd5\x00\xab\xe5\x0d\xe5\x08\xcc\ -\xa3\x87\xe3\xd7\xb2\xc1z\x1aX\x0d\xe1\x10W\x94\xf8\xf1\ -!\x90\xbfn\x1d\xca\xdf\xe2\xae\xc8_\xd0\xb7\x82\xdd#\ -)\xfe7\xd0\xaf\xb6r\x1f\xd6a\x87\xf7\x84\xcf\xa6\xda\ -h_\x7fZ\xaf\x81\x80\xe70n\xa2\xe4\xf4\x22\xec\x1a\ -\xca[\xe8\x83\xa9\xcf\x89\xc7b\x83\xca\xaf\xef\xc6\xee\x0b\ -\xfd\x0aX]\x94\x86n\xaf\xbf\xcd\xfa\x07\xceG\xf8\xac\ -\xda\xc4\x97\x18?0?+\xb8_\x13p~\x16\xdb*\ -\xd1K\xffh\xa5\x7f\xc1\xda\x19\xe5^&\x98}\x1f\xca\ -\xbf&\x1f[\xdb\x1ay\xf4\xd3\xbf\xda\xeb\x9f-\xebm\ -0\x0e\x8d\xfago\xeb\xdf\xbd\xbd\xff`\x86\xfdW\xaf\ -\xed?{{\xffm\xd3\x8b\xd6\x18\xf8lh\xa7\x80f\ -\x091\xa4\x85\x9d\x82\xb3\xfdgq\x9a\xc7\x00\xb8\xe1}\ -\xfa\x9d\xed\xae\x01\xff\x8c\x1b\xfe\x9d\xc6\xef\x06ua\x0c\ -\x83Z<\x97\xca\xdf)g/4\xe9,\xd4\xef\xd1H\ -s\xeb\xdf\xc3so\x80|\x85\xb1\x7f%\x0e\x9a\xcd:\ -}{9\xd6\xbe\xbf0\xbe\xfa\xec2\xac\xde l\x18\x9b\ -\x02\xfd\x96\xd8\xd9\x22\x8dgl\x18R|\xe1\xf0\xb9\x94\ -x\xd56\xf47\xeeA#\xaea\xeb\x1e<\xaf\x07>\ -\xa7\xd4U\x0b\xd3\xb9\xe0zU\x1d\xe1\xf9\xbb\xf1kh\ -\xf4\xe1\xc28\x22\xe2\xbb\xfb\xd8\x99$\xf0\xfb\xb5\x89/\ -(\xfe\xd4\xdf\xf3\xaf\x00\x8b\xedv\xd3\xc1\xc6\x16\xae\xb5\ -X\x1c4\xb4\x05\x00]\x03\xc6v\xfca\xfeP\xe6/\ -\xd4\xd9\xb0\xfd[\x8b\xf5to\xa7\xe6ow\xdf\x9fn\ -\xbd\xbf\xddm\xff\x07:J\xaf{\ +\x01\x9f\xd1x\x9c\xed\x1d\x09\x5c\x8c\xdb\xf7\x9bR!\xd4\ +\xc3#)e\x8dl\xf9{\xc8Z\xf6}_\x1f\x22<\ +\xbb\x92\x84,\xd5\x90%d\x97\x9d,E$B\xa4\x92\ +\x22\x22k\x12\xd9[H4\xed{\xd3\xcc|\xff{\xee\ +\xf7MM\xd3LM3\x93&\xba\xbf\xdfy\xbdI\xf3\ +}\xf7\xdes\xee\xd9\xef9\x04\xc1 T\x09\x18\x0cB\ +\x9f\x98\xa7M\x10\x0b\xd0\xff3\x99\xd4\xe7\xb65\x19D\ +\x18\xfa\x9d\xa9)\xfdy\x00A\xa4\xb7`\x10FF\xd4\ +g\x8f\xd6\x04\xd1d>\xfa?}\xfa\xb3&A\xdc\xdb\ +\xc1 45\xa9\xcf\x8bj\x10\xc4\xe2\xe3\x0cb\xfb\xb8\ +1C\xeb\xd6\xd6\xae\x8d\x1e]w\xf8\xb0A\x13\xe0_\ +\x01j\xc2\xabgZ{\xa0wj\x1b\x0e\x1f\xd4\x7f\x92\ +\xed\x87\xe4\xcf\xa3\x975\x88\xfa\xa2\x9a\xf6\xa8\xd5\x18+\ +\xa2\xc1\x86\xe8e\xdeM\xe3w\x5c>1\xe4\xb9\xd9?\ +\xb5/\xd5\xb09p\xd0\xccP\xe5\xcc\xe5'\xdb\xa6]\ +\xf3h9\xcd\xac\xd6\xf8I?\xaf\x98\xaeh|\xd9P\ +\xc9\xac\xf9 \xb7\x05C\x0f\x8c\xaeU\xbf\xc5\x88\xc6\xcf\ +'\xef\x98\xda\xff\xf9\xf1\xd0eg{t\xeb\xb4\xeb\x80\ +\xf1\xf6\xe8\x87\xcf\xb4\xe6-{\xa4\xd75\xd3\xa4k&\ +\xa7\xbb\xe9\xf4\xb4\xf4w\x09G3\xbb%\xe4Y\xfb\xe9\ +8=>~\x8c\x11\x9c\x909\xfcp\xc8\x94\x93\xd7n\ +\xae&\xec\x08\xe7\xd9\x8c\x98\x06\xdc\x97\xdf\xec\xdb\x11\xe4\ +\x1b\x87\xa0\xd3\xe4\xda9\xad\xbf\xec\xdb\xdf\xe9\xc8I\xf4\ +\xf7\x1b\xfcf\xbex\xc3\x08o2\x83\xe58%\xcc\xe4\ +\x95C\xb2r\xfb^.\x8765\xaa\xa9\x1e\xae4\x17\ +\xfd\xdd5}\xb3\xb6\x9e\xb7\xffG\xf4\x9a\xb7\xd4\x8c\xd8\ +\xca8\xa1j\xa5z9)\xf2HC\xe6\x05b\x01c\ +\x94\xd7\x83\xde\x87\xdc\xd5\xffk\x14U\x83i\x7f\x98\xac\ +=\xfda\xea\xfeN\x1a~\x1f\xfe\xc7lG\x98\x18\x07\ +\xae\x8a\xf9A\x84\xa7f5>=\x99\xd3\xe9g\xc6^\ +\x83\x8e\xcay\x8c`e\x83[ut\x9a\x10hns\ +\x88\xd9\x03\x97~ip1\xae\x1d3uJ\x9c\xee\xc4\ +\x08\x95\x8b\x87\xe6)\x99\xea\xdd\x0c\x22\xbc\xe2\xd6\x0c?\ +\xbc\xcdc\xd3|\x22\xba\xff\xd2\xbf\xdd\xdc\xd4'n\xcf\ +`\x9c\x9e\xca\xd1\xf2\xd9\x11\xdd\xc9\xac\xd9Ym\xb7\xd3\ +*\xfdg\xbe\xd1\x1f\xd7\x0b&\x16\xcd\xfc\xdb\xf5Dg\ +\x15\xaf\xa5i\xbbl\xfa\xefh\xd7\x84X\xa7\xe7\xa0\xb3\ +,\xf1@@\x07e\xad\x89\x8b\x9dM/\x12\xe1C\xb3\ +S\x1d\x1d\xd7\x7f\xb3\x1c\xcb\x989i\xce[\xfd\xc3O\ +c\x996\xcd\x99\x1bV\x92k\xbc3\x0e\x14\x9cd\xa4\ +\x05\xf0N66\xf8\xa0\x14\xde8\xed\xd3\xa3\x80\xec\xbc\ +A\xb3\xae\x13^\xdd\xbc\xf5\xd3\xe3\xcevd\xbex7\ +\xbdN\xf0K\xf3x\x83\xd4D\xe5\xb4\x1b\xb3\x1a\x0fz\ +\xf7E)\xfcBN\xca\xc0\x19\xb7F\x0e:\xd6n\x91\ +\xd7\xc6\x83f\x9d\x17\xadW7]\x9eA\xd6\x99b\x7f\ +vnH\xf8te\xbb\xd5w=,\x18\xa7\xc7\xa6^\ +\x0b\x1b;i\x96\x9e\x8f\xa6\xd6-\x96\x8aE-\xf4\xb0\ +\x0f*\x84\xc9\xa6\x9d\xeb\x9cr^\xd7\x8b\x19\x1a\xf8b\ +\xda\x8b\x0c\xe2\x87\xb3\xd7\xe7mo\x13\x0c\xce&~\x8e\ +b\xedv\xefP\x97\xe0\xd6\x9d\xe9\xcd\x1e\xad\xe9\x17\xa9\ +\xe290\xf8:\x81^\xfe\xbf-\xee\xcd\xcd\x87\x13\xde\ +\x19\x0d\x8e>\xb9\xaei\xd4\x97\x98\x96\xbf\xbd\xc3\x9c\xe1\ +5\x8fh\x13\x979\x83\x0e\xfd\xafo\x8dQ\xea\x84\xdd\ +\x87\xb8M\xcf\x1bo~\xbc\xb4\xd3\x91\x9e\x83\x17=\x99\ +`n\x17{\x7f\xcf\xf5\xa8-\x8f'\xde]f\xb3\x9c\ +11G\xb9\xed\xb4/\x1f;\xd9\x0dv\xea\xd3J%\ +&s\xc8\xa9V\xcb\x8c\x89,\xb5\xf5\xf6*kR\x9b\ +\xef\xf7&:\xd6\x98\x90\xcd\x99\xdf\xb5\x99\xaa\xbf\xc6\x14\ +\xfb\x16\xed\xec\x16Y\xb9X|f\x9c6\x09\xe9\xd3\xc1\ +N\xc5\xa9\x8f\xe5\xed}\xdc\x1a\x89\x89J\xde\xd1c\xdd\ +\x0e6L\xd4\xd2X\xe4\xa6n\xa5\x87^\x7fi\xd1\xd0\ +\x93f\x1b\x8f)\x9bF?\xf3\x1e\xcb\xd6m\xf0j=\ +;\xf1\xfd\x22\x9f\xf3\x9d\x98wW\xe5\xa6F\xa4\x1et\ +z\xc2^xs\xc6\xee\xa8\xd7*1+\xb9\x83\x96x\ +^\xdc=E\x97`/\x09\xea\x1a\x91\xba\xdb\xc9\xb0\xd7\ +\xc4\xa7\x87\xe6\xde\xf4!B\xdd\xa3\x9b\xfc5\xd9=\xd1\ +\x7f\xfad\xe2\x87\xa3\xbaYD\xea\x0e\xa7K\x11\x93\x93\ +\x991w\x1d\x99l\xeb\xe8\xe8{\x8bMZ\x18\x0c\xef\ +\xdfTs\x94K\x81\xde%\xdd\x1a\xce\x1b]\x5c\x1f/\ +\xfep\xe6\xcd\xe7\x1d\x86\xb7\x08\xdb\x5c\x03k\x9f\x86\x86\ +>J\x97\x163f\x12\xedM\xf6M\xbc9\xcfu\xf4\ +\xbac\xb9\xf9jw\x0fu\xb0k\x15\xb9y%\xd7k\ +\x15\xc95y\x9bk\xd43\xa8]~#'7\xbb\xba\ +\xfa\xb3RL\x0e\xd5%\xec>\x9e\xe9\x14\xb6\x7f\xe8\x00\ +5\xcd\xa6K<\x0bR_v\x9c\xdf\x84\xf8\xb6\xca\x0c\ +\xd1\xa9z\xfb\xf4\xddj\xee\xbcy\xee\x9d\x82\xa7F\xb4\ +6h1\xf1\xa0\xd7\xd3\xd6\xca\xa6\xb33\x1d\x86\xf8G\ +\x0e\xfaoG\xd7u'y\xa6~\x8d\x82U~\xd6o\ +\xb0\xefb\xa7\x83\xec9~\x84y\xa6\xebc\xb3\x11o\ +\x7fj(\x99F'_\x18\xe9\xef3\xe8\xbfK\x9d\x02\ +-\x83.LRg\xaeqh\xb1\x86}\xda\xf2\xf01\ +U\x9dC\x9dZ\x87\xed\xd8z\xfc\xaf:y\x9a\xb5\xba\ +\xc5\x9e\x0b\x0fX?(\xebvN\xe6\x84\xfaa\xban\ +=V\xe6\x9b\x04\x8dJ\xd1?\x196g\x95\x9d\x86'\ ++ b\xf9\xf6\x7fB\x88\xd0\xda\x0ei\xb3\x88\x1f-\ +\xb7\x98E\xb8\xb7Z_\xc7\xa6\xe0\xaf\xba=\xbe\x9e\x9b\ +\xa7\xa6Y\xb7GB{\xfb@rJ\xeb!\x9a\xcf\x02\ +\x9f\xc4\xed\xc9\xee\xdbj\x7f\xefa\xad\xd6\xb8u\xde\x18\ +e\xb3*\xf4\xad\x7f\xed5u\x0c-\x19uF\x1e\xde\ +{\xf9\xafZ\xdd\xe6\x9c\xfe\xb8\xe7\x80\x01\xc7i\xebq\ +\xcd\xba\xa9\xbe5#\x93\xa2L\x98\xc3~\x04\xab\xf5\xef\ +\xe2{\xf0\xe1'\xcf\x0e\xb3\xda\xac1\xf2=8\xb8\xd7\ +\x01\xcf\xcc\xbf=F\x84\xb4\x9d\xd6d\x1a\xa7\xa3qG\ +\xb3^\xff\x8b2_\xaenYOoH\xab\xff\xd8\xc3\ +\xd3\xcek\xfd\xc8\x09j\x5c{\xe5\xc8\x7fG\x91\xb7v\ +\xf9\xb9w\x98Q\xf3\xad\xf1\xae\xda\x0c\xab!\xfe6\xb3\ +\xfe\xfb'h\xfd\xee\xa5k\xbe\xdb\xb4\xd9\xd6\xbe\xc1\xe2\ +\x89\xbc\xe4SV7\xae\xbb\x04\x1d\xbco:\xcd,:\ +\xe4Y\x5c\xa3f\xe4\xf5\x87FF/\x8f\xde7\xf9\xa6\ +l\xea\xdab\xd1\xe1N'?\x0e\x1e0\x1aM*\xbc\ +\x81ehp-\x870\xfb\x1e\xc7\xef\x1d\x1b\xf6\x83\xe8\ +t\xb2^L\xc4O\x02mL\x1f\xde|\xf5\x03\x99\xc7\ +\xef\xf4\xdb\xb58\xd5\xd9+\xbb\xc96\xa7\x86\xb7\x08\xb6\ +\xdb\xc8\xef\xdd\xef15^Yu\x1d\xb3\xba\xe0\xaf\xbe\ +W>\xc7{\xfa5m\xda2M\xa7\xaf\x83\x81\xb6e\ +\xe8s\xab\xaeh\xca\xf7\xbfq\xe0\x95\xdb\xfd\xdc/\xdb\ +\x5c;qI\xe3\xc3\xbd\xb3\xa9*\xc1\x06\x11\xee\xd6\xbd\ +\xe6M\x0aL\xb0]\x19\xa2l\xf1\xcaH\xf9\xf4\x89}\ +\xbc)\xceY\xbes-\xdb\x1d_\xfe\xf7\xb8\xd1\x19\xab\ +\xcc~6\x0aNd7\x8b{\xdd\x94\xa2\x9e\x0e\xedg\ +\xac\x08\xb2\x0bZ\xec=={\xf3\xf1.7n[\xfc\ +\x9b\xff9X\xab\xb3V\xd3%!yY\xaa\x8e\xa7\xd7\ +\xd9\x85.o\xa8\xc14s&'9\x10G\xdcoG\ +[\xae\x9e\xdfy~\xcc\xe6\xec\xab\xb1\x8d\xf6\x853>\ +5\xe9\xfbL\xdfc\x96k\xfa\xf1\x05\xfaA\xb9\xb3\x03\ +l.N\x1a\xf7i\xb9\xde\x8f\x9c\xa8@\xe3\xf3\x7f\xad\ +\xde\xec\xfcQ\xab\xbfU\xca\xeb)\xcec\x97[9\x9f\ +\xbe\xb3:\xf0\xc5\x05\xe7F\xa6\xae6\xdb\xdd\x1a\xe6|\ +T\xf3\xe7\x8e\x18}tp\xc8\xf3\x13\xbcK!\xa6\xbe\ +z{\xfc\xf7\xcc\xb5\xb6W\xff\xfa\xba\xa9\xce\xc8v^\ +\x1a\xc1\x13[\x12\x1f\xdcG\xb6\x8cuxD\x0e\xf5\xaf\ +\xf9w\x17B\xbbkD\xf4\x8e{_\x1a\xa1\xa7\x99\xcf\ +\xed\xde\xd2L\xe7\x8d\x8fr\xea)\xd5\xa4\x03\x9dvm\ +\x9e\xac\xaeV\xeby\xf8\xca;\xc3\xfdk\xeen\x5c'\ +\xcfje\x9ef3\xb3\xbeK\x0a2\x16>\xccf\xdc\ +A+\xea\xbc\x5c\xbe\ +\xd9jaV\xcd!l\x8d\x16\xf5\xaf\x9f\xef\x9c\xb4Y\ +\xc3\xd5\x8bXZC\xb9_\x1f\xffu\x91\x99fJ3\ +\xee\x9aeg\xcdoyF9\xbf\xb5+\xb3\xbb\xf9\xd4\ +w\xf3\x8d<\xb7\xec\x1d\x96\xd2\xd6,l\xddn\xcf\x19\ +\xdd{\xdcF\x13\xeb\xfb\xe8\xf8\x8a\xa5\x89^\x06\x015\ +\x0e9\x04\xb48\xd3f\xe2\xf7\xb7\xedU\xee?\x8b\x9b\ +\xf2YoV\x0b?O\x1b\xc2J?|]K\xae\x17\ +\x22L+^J\xd3\x97[\x98\xf1f\x84\xe7\xdd\xe0!\ +\xfa\x08\x95\xce\x0fI\x8e\xf2\xd8+\xbeQ\xddN\xe7f\ +\xdc\xfc\xd0\x95\x810\xbc\xcf\xc5iV\x03t0\xc3\xb2\ +\xe7\x8d\x88\xaes\xd9|-1\xe95\xd9\xc3\xf1\xda\xbe\ +!^\xc6\xf5L\xb7\x0e\xfb\xc1Lx\xab>\xad\x19\xb1\ +z\xcc\x8d7\x13\x8fN\x9e\xadN\x10\x06\xef\xebz\xef\ +\xeab\xfc\xc3E\xd3\xfb\x1fB\xfb\xfd\xfc[Sv\xf5\ +\xe9\xe5\xb9\xae\xbd\xfe\x93\x8d\xa63\xd6\xb6\xab\x81\x84B\ +o5\xefYw.\x05\xd5\xf7\xfcR;\xed~\xe3\xf1\ +\xddf\xd5b\x8cT\x0e\xbe\xc23\x09\x7fg\xc6\x89_\ +z\xd3\xb5\xd1\xeb[\x91\xdfW2Vg\x0e\xa8y\xfb\ +\xac\xca\xde\x17\xdf\xb4\xfdwz~Q\x22|:\xde\x7f\ +lo\xa9\xc1x\xaa\x14\xfc\x82\xe7p\xce.o\xb3\xbd\ +\xb3\xcb\x93u3\xd9*6J-k|2\xd6]\xfa\ +/a\xecN.\x1f\x17\xf8wp\xc7\xfb\xff\xf9u\xab\ +\xd1!\xc0\x0b\xfd\xc2\x95\xec1k\xbe\xcb\xacU\x17\x06\ +4\xf0\x990ud\xfc\xd2\x85\x87\x16\xddn\xb2f\xc8\ +\x9e#\xab\x17\xed\xac\xdd\xa1`JO\xf4%\xdb\x8b\xb6\ +\xea\xc1\xef<\xc3\x13\x0f=\xb9\xb4\xc2*\x81u\x92S\ ++\xe8f\x8d\xcb\xe3\xc7|\xfd/\xd6z\x9b\xab\xf1\x1b\ +\xc4/B\xef.\x9a8\xf7\xc6\xf4\xae\xd3\xea\x13\xab-\ +\xdd\x16\xbf\xd5\xb1oq1g\x14a\xfe\xbc\xd1\x9c\xd0\ +\xae\x7f\xbf\x0e\xb28jm\x9c\x10\x10lK\xae\xecR\ +\xcf@\x97}{\xf0\xb2s\x93o<\xd8\xfa!mn\ +;O\x0e{\xdf\x12\xf8\xde\x95\x99]\xcf\x91\x1a\x8bf\ +\xec\xbdz%\xb5\xdd\xcdv\x8b\xae\x9d\xfa:\xfa\xc8?\ +\xf1\x81\x1f\x1c\xbe\x7fIdwj\x93\xb0&s\xc6j\ +\x8d\x9c[\xe6?\xe6Y\xbc\xecg\xb0\xf5^\xee\xe1M\ +\xc6\xeb{\xc7\xe7\xd5\x1d\xea\xb5\x98a|\x99\x5c\x8e\xb8\ +\xa1f-\xbf5\xdb\xf4]vG\x1e\xfb\xf7S\xe8\xb3\ +\xb8\xee\x9b&\xf6\xab\x93s+.\xe3\xa2\xe3\x08\xe7\x95\ +\x93\xdak\xa5\xc5\xbe\xf81\xe7DP\xc1\x10\xff\xb6u\ +\xaf]V7W\xbb\xc1&U\xdf\xf8\xd6\xf4L\xf0\xe7\ +\x9dq\xbd\x92ek?2,`r\xaf\x03!\xdf8\ +m\xda$\xf4\xe8\x12\x9e\xfc(~\x22\xc3\xdd\xc4q\x80\ +\xf2g\xfbG{\xd4\x8e|\xf9rs\xe5?\xef\x92z\ +\xbfWWIq.\xb8\x98\xcd\x1e\xf3b\xf9\x5c\x8e\xc1\ +\xc2\xa4]&\x88\xb1\xbal\xff\xa7_A\xce\xd8\xd6\xd3\ +\xdb\xbdq\xaa{\xb3\xe7\xa5\xf8\x09\xcf\xb2\x08\xef\x1b\xa4\ +\x09\xf7\xb8\xdf\xc2\x84\x8bw\x1b\x11\xe3\xe6\xf5\x0a_\x82\ +\xb8\xbd\xb1Cn\xdd\x17\xd1v_\x9d^~C3\xfa\ +\xd9\x22\xf29\xa7\x11s`\x01\xc9\xcc7\xab\xdd\xdev\ +\xf1No\xc4k_|\x19\x13\xfb62z\xc4\xd5H\ +\xe7\xad\xc7\xbb\xe4\x0c>n\xd3%q\xee\x12\x02\x11\xff\ +G\x7f\xc2\x93\xb5\xfe\xd0?K'\xbb\xd6\xcb\x8a\xeb\xca\ +hp\xa3\xc9\x89\xc0\x93\x13\xfcgX\xfd\x9c\x14\xbcO\ +c\xd9\xd1/J\xb7s\xd2:\xb6I\xf0\xed`\xb2\xea\ +\xd1\xa5\xce\x8e\x8936\xfb-\x1e?\xba\xa3\xb3e\xff\ +\x96\x8f\x1e\xce\x083\xde\xf8x\xf1(\x8b\xb1\x8dv\xff\ +Pz\xdb\x89g\xb4\x94\x17t$\xd1\x8c0Ug\xb8\ +\xa4n\x9d\xa2M.V~\xbd\xf3\xe3\x88+O\xf5\x92\ +,V|\xeaY\xef\xd0\x95u\xaf\x1a9,\x8b{2\ +H;\x90\xe4\x05mp\xb0\x5c\xb0\x22 \xb6\xdf\xc9\xc9\ +\x8d\xdf\xd7\xd2;\x15\xe5[sd\xab5i\xef}Y\ +#\xb6f\x9f|\xd52b\xb0\xc7v\xaf\x96#\xb6\x7f\ +\xabW?\xec$g\xb8\xd7+\xef\xd5\xadr\xc3\x1a\xf0\ +\x8e}\xb7\xf7J$\x9b~?\x14io\xd4\xd6v\x9c\ +Wo\xd7\xcdHrd\xae\x89\x8ey\xf1\xe3/\xf7\xb7\ +u\x17\x05L\xd6{\xe6\xbbi\xb2A/\xb7\x99:5\ +\xd1~\x9fH\xca\x99\x17\xb39h\xae\x8a\x0f\xbb\xd3I\ +t\xa4\x1b\x10[4\x12\xee\x84\xbf\x1c7i\xed\xa0O\ +c~\x8e\xaa7\xb5M\x04\xbb\xd6\x04\xaf\x88\x87aM\ +\x9b.\x99GD\xce\xbb\xf2\x83\xb1\xd3z\xa3\xf9d2\ +Z\xff\xe6\xe0#]\xea\xf6\xcc5\xdcy8L\xb7\xd5\ +\xfe\x80\xdd\xd3\x82[&\x7f\x08\x09\x22L\x8fjZM\ +h\x8f\x94\xc6\xb6S\xb4j\x866\xfc\x1a\xd6\xe0\xad\xc6\ +\xf5wo|\xff\x193\xd8>\x7f]\x8fh\xed\xb3l\ +\x8fFJc\x16\xab\xb4\xd5%\xb6D\x1c\x9d\xc9c\xd4\ +\xf7l\xf2|Y\xd7\xa1G\x1b\xb0u\xb9\xf1K\xbd\xce\ +\xf6\x9e\xe3\xfdj\xf1\xa4\xda\x8c\x0b>J:\x0d\x88\xd5\ +K\xff\xd3arW\xf7\xf4=\xff\xa5\x85\xb6\xe5\x84\xb9\ +a>\x0d\xa632\xa6\x0e\x1f\xff\xc6\xf2\xafP\xfb\x80\ +\xe9\xce\xef7&\xf4\x18\x10\xc7\xaa\xd1\xe2\x8c\xf2\x93\xab\ +\xea\xe6\xc4\xc1\xf1\x96;\x8dr>\xbeY\xb89\xf8\x00\ ++9b\xf0=D`\xaa\xcc\x06\xad\xf7\x07$\xf9\xf6\ +\x9e\xd6\xc2\xcfq\xc6D\x87\x9d\x9d_\xea&Y\xf4o\ +\x15\xe9\xb6\xccs/c\x5c(\xa2\x7fD\x0c\xc3\x1b;\ +j\x1bu9\x1e\xd6y\xe0a\x9b+A\x93\xdf\x8f\x1e\ +\x18\xf9\x99\xd8\xf3\xb8FT\xb3\xbb\x7f\xeb1u\xf3\xb3\ +Ng\xcdo\xbb\x93az\xe5\x96\xff\xdd\x9d+b\x0d\ +j\xee\xbag\x8a\x04\xc4\xe3\xec6\x0b\xba\xc6}\xed\x91\ +\xd0\xd0B\xc5\x88x\xdc\xc0m\x06\xc3\xb0\xaf\xf7$'\ +\x86\xe9)\xa4\x02<\xfc\xc69\xf4\x8e\xd5\xd4\xa0\xcd\xc5\ +\xad\xe8\x17\xcd?\xd76\xce\xbd\xc8\xa9\x1f?\xb7\x0e\x91\ +Q\xc7\xc2Q\xdbo\x0as\x87\xf2F\xf4\xa1a\xd8\x14\ +\xe6\x8aE\x97Fh\x11\xdf\xda!\xfa=\xdbU\xe9\xd2\ +M\xa5\xc8\xa6.\x9ai?\x12\xc7\xef\x8b\xea\xb6\xda\xa7\ +\xf9\xfa\xcf\xeb\x0f\xfd\xe5\xe9d06\xcfe\xdd\x80\xf0\ +L\x86y\xd2\xc8\xb6\xfd\xaf\x9b$\xce\x0f=\xefS\x7f\ +\xac\xfa\xa5\xd5W\x1c\x13\x02\x88\xfd\x13\xbb\xe6\x9e\x9a\xef\ +\x16}w\xe1\xf0\xcf[&\xf7\x8e\xba\x97\xc9\x08_9\ +\xb3\xcd\xbe]\xa3\x07\x0e\x0b\xfd:\xfb\xdb\xf4\xd9\x86^\ +ji\x1b\x88avZ]w.\xe3\x8d\x1c\xd1jM\ +\x07\x93\xc0K:./V\xc1o\xb3I\xaf/\xef\x8f\ +\xe4\x18tk\xaa\xd9\xf3\xfc\x0bc\xe6\x05eS\xeb1\ +ow?\x1f}\xf1\xfa\xcf\xe8\xff\xa6\xcf\xd9\xb7\xb7\xd7\ +\x9b1'\xf7\xd7\xdc\xcb\xd6ws\x1d\xd5rWd\xc2\ +\xe9\x88\xe9\xc4X5\xe6$\xd2a\xf7s\x87\xd6n]\ +{^[AL^\xa0\xd4G\x97\x18\xba\xa5\xdf\xe6\xc3\ +:\xdc\xa9['\xb57u9\x8a\xfe\xc84\xeb\xf6\xb3\ +m{\x87]\xf2\xeb\xef\xd6\xd8\x8b\xf73\xf9\xe5\xfd\xd7\ +\x0c\xfd7u&\xae\xda6\xad\xf3\xba\xe6\x91J\xe3\xe6\ +\x1b\xeee\x98w\xadG\x5c\x1a\xbc.d\xdc\xe2S\xf5\ +\x88\xc9\xec9\xad\x1e\xb1<\xbb\xf4\xd5%\xea\x1b+\x9b\ +\x12FV\x9b\xcc\x88\x16\xadm\xfdf\x84\x12\xfa\x19F\ +\xaa\xcc\xe7_l7\x84o\x8f\xbcOx\x1f\x8dR;\ +9\xacc\x1d\xa2\x85\xc6<\xe2\xdd(\x97n1\x83\x08\ +\xe5\xe0\xd5}g\xae7\xad=\x87\xd0\x5c\xab\xa5\xcc\xec\ +3\xf6fH\xaf\xa4\x07\xe8OG|\xec\xbfo\xc8\xdc\ +yZ\x17\xedj\xeeX\xdb\x8bh\xd5\xac\xc6\xf8\xc5\xbd\ +\xdb\xd6\xdf\xd0b\xd2\xbeG{\xfbr-\x0e\x1e9\x90\ +k\xbdr\xe2\xe0Y\x0e\xfa\xcf\xd0\x147v$n\x0e\ +S\xc9\xd0KRz|`\xba\xcb\x85\x88\xf3{g\xe5\ +\xee\x1b\xa7\xefy\xbd\x9d\xf3\x05e\xfd\xc0v7:E\ +\xb3\x9e\x8fq\x19\xd6\xf7\x90\xe7\x93!\xad\xd0\xb4/>\ +\xef\xb9~sN\x0b7\x8d\xe17C'\xadm\xf3\x00\ +=\xc3\xe5~\x92\x92G\xffK\xad\xcf?i\xa8\x1f\x93\ +Nh\xf6\xd66\xb4a\xde`\xd4\xde[{\xeed6\ +\xc9|\xa4L\xec\x1d\xf9\x15\xe9\x19?}S\xd6\x0e\xd4\ +{\xbaA\xd5\xd0B=!\x9f\xd8\x9b\xb8\xf3\xe4\xa4\xff\ +\xc6\x1a_\xad\xad\xbaanGb\xf2y\x82\xa1J\xd4\ +\xbf:_\xc9\xdc\xdbw\xcb\x1eUu\xed$\x8b\xbf\xd7\ +[LVE\xbff\xbe\xbb\xa7\xc9L:\x1c\xceza\ +g\xb82\xf9\xf5Q\x1de\xd3\xd6\xfb\xf5B\xe3'\xaa\ +\xb9G\xdf5@\x8a\xe1)W^W\xdb~D\xab\x06\ +=\xb5-\xbf\x9f\xd5g\xec\xbf\xe7\x19A\xe8\x07\x13\x84\ +~@g\x0e:&?}S\xdb!-\xb0%\xd7\x9a\ +\xa1\xff>\xbe\xc3\x16\x06\x11\xb3\xc5\xdcpO\x96\xaf\xf5\ +h\xe6.\xd5\xf53\x1e\xaa\xce~}\xf4\x84\xf5\xb6\xbd\ +\xc8\xea\x7f\xd6\xc1r\xe7]\xde\xbbq\xc7\xb3Z\xa7\xd5\ +\x09[\xea5=^M\x8bX\xbb\xbfy\xf0\x14f_\ +\xa4M\xec\xe8\xb7\xbb/\xf7x\xd2\xae\xf3\x0dU\x99c\ +\xd8!s\xda\x07O\xbf\xc7\x1c\xcb\xd1~\xd9\xc1r\xf8\ +\xec\xad\x1aF\x84M\x84\xde\x8ba\x0e\xed\x17\xd6\xe8`\ +\x93oc\x9a\x95br\xe3\xb6\xd7\xb3\x96\xc1[\xb4\x08\ +\x7f\x9b6\x06\xfd\x1a!]jo\xd6|+\xf7a\x1e\ +\xb5\xba\xc4\x06\x04\xd9\xa8\xdf\xd0Q5e\xfc\xd4X\x8c\ +\x14\xef\x91\x17zv\x5ct\xab\xf7\xb9m\xee;\xf5\xbf\ +\xdc\xef\xb0\x8d1\xcet\x8b\xc6\xe2e\x1e\xbb\x947\xb5\ +\xf1\xb7\xe9\xa5\x7f\x97\xdct\xe9\xb9\x03\x93\x91v\x0a1\ +\xc0\xb0\xb5\xef5k\xaan\x18\xf1\xe9\xda'\xfd\xfaK\ +\xea\xeb\xe5)3\x1f\x05-\x1b\xd1\xf8L\x07U\xd3\xbf\ +\xfe\xeewy\xa3\xa5I\xe8\xdd\xbdd\x5c\xaf\xcf\xb9\x87\ +w;\x99\xac\xf0\xf3\xbfk\xd0\xef\xd6\xa4Z\xca\xa7\x1f\ +\x7f\x9a0e\xbbZ\xf7\xb9\x03-\xe2FO5\x08\xe8\ +\xban\xd0\xf6\x7f]\x86\xabX\xb6u1\x22F\xdc\xce\ +\x1d\xb5K#\xb9`d\xff\xd6w\x1f\x8c\xe9\x1a\xe1\xa0\ +\xd9\x92\xbb\xa4\xcf\x12\xbb\xe5HC\xdc\xbcC\x8bh=\ +\xea\xc5r\xc3.\x8b\xe69\xab\x0d\xeb1\xef\xe4\x7f\xde\ +\xbe\x83\x8fv9\x18\x96\xdf\xb1\xf5\xdd\xa1\xe8\xdbO\x8e\ +\xd8\x9e\x1fb=4\xe4\xb9\xadMv\xdb\x81\xa7k#\ +\xbd;\xc5\xb9\xd9\xbeNC\xbe\xe4em\xb5\xf9+\xe7\ +!\xd1\xf6\xeb\x97eC\x8ef-\xa8\x994\xf6\xe6\xd2\ +\x7fz\x06\xb9G\xa6\x04\xc5\xa3/\x0e[h\xfd\xc4G\ +\x89\xc8\xbe\x1bc8>\xfd\x83ql\x0a\xdaJ\xc3\x09\ +\x17\xff&<\xf2\x07\x1f>\x1f\xd7m\xc5\x93\xcfz\xe6\ +\xc4\xa4\x83/\xbe\x1d\x1ci\xfdU\xfd\xe5{\x86\x85\x0f\ +\x19\x96\xbd\xda\xdd\xd0$\xe6D\x80I0\xf1.\xea\xc1\ +\xb2!\x17\xcf\x9d`\xaei\x1e\x13\xbfJi\xdel\x9f\ +v]?\xccn\xdb/0\xf6\x08\xfc\xb3\xc7\x13\xaf\x8b\ +\xbe\x11\x83\xf3L\x8dbB\xcey\x0e\xed\xeb;7\xdc\ +6:bl\xae\xcb~\xf4D\xd7\xdc\xce\xa1\x84\x07R\ +\xecn\xed\xff\xd8\x93\xf1a\xc7\xbd\xa1S\xf7\xac\x8bc\ +\x9cE[x*\xbaf\xbb\x8f\x19~\xdd\xb2}'\xcc\ +\x1b\xf2\x06\xcd\xe0\xe7\x87\x10\xdf'\xf6\xb7\x91\x98\xe9\xe3\ +\xf7%\xe5\xc3\xd0\xed\x8e\x0c=dCL\xb0\xf4\xbe\x1a\ +\xfeiB\xa3\x03\x99K.\xeb{\xa9(\x9f\x1e\x81\x98\ +\xdd\x88\xd1\xea\x8d\xda_ne\xb9\xa4\xaes\x96\xde\x83\ +\xcc\xa6\xcd\xe3C\x92?\x0c\xcc\xf6\xdf\x8e\x1e\x14i\xce\ +\xb9P\xb7u\xb3\x86\x97\x06-m\xa3\x17rr|\xbe\ +A\xac\xb6\x9d\xde\x9e+\xcdV=\x98\xb0\xe8B]\xce\ +\xdfs\x89\xc8\xc7\xed\x86\x9b %\x1d\xc9\x01\xad\x82\xb3\ +\xa3\xf3kOz\xec\x917\xe6h<\xd2W\x8cx\x07\ +V\x9e\xe7\xee\xe8B,98,%\x06\x09\xfb\x88\x87\ +\x1e#\x8cb\x06.\xf3\xa8\xbb\x93\xccWC\x9b>\xde\ +\x7f)\xd2\x9f\xe3\xaa*\xf7\xcb\xfa\xe1\xd0\xc3\xfar-\xa4T\x0f\ +\x1a0z\xf7\xb2\xb7\xa9\x8f~\xe4\x8e\x5c\xe3\xfa\xec>\ +\xb1(\x1c)acN\xa5\xb5\xbf\xa1\xe2r\xd4\xd9\xb4\ +\xad\x0e\xcb\xa9\xee\xf0\xc3#\xde\xbal\xf7\xf9Z\xc0\xdd\ +\xf2\xa9\xf1+\xa5+}9#\xdf\xbb\xdf\xff\xef\xd4R\ +\x8dh\xff\x94\x95\x0b\x88#\xdf\x9e\x0c\x9a\x9e?x\xc0\ +\xa4 D\xa8\xedUagG\xcd\xadS\xfb\x7f5>\ +-\xb4\xb6\x7f\xfaj\xa7\x9d\xe1\x1c\xe2\xc8c\x0b\xe5A\ +n\x93yK5N\x06\xbe\xbbP_\x95\x196%j\ +\xcd\x0a\xa4\xf7oe\xaa\xdf\x0e\xccP\xaeC\x8cDS\ +\xae\xc3\xd8\x969 ^\x0d\xd91#\xfc\xf5-\x0f \ +\xec$%7|TC\xe7qK\x17\x13\x95;\xeaL\ +r\xc4\xe1\xd0]\xce)\xc4\xc0\xe0\x9e}&\xe43\x02\ +_\xdcP7\xef2ou\xb0E\xe6#K\x9b\xe4\xd8\ +\x19W'\xf1\xfa\xb4$\x1a\xb9\xa9\xc7-\x18R\x13\x1d\ +\xb6\xe1\x8d\xb7\x8c\x08_\xb0<\xe2\xca\x89\x84\xab\xe6\x8c\ +\x18-\xa6\x17\x9a\xfc\xd3.Mw\xd8>D\xa6'R\ +\x05\x1d\x8f>\xb4\xf8y\xf6]G\x15o\x15\xf3\x80\xb0\ +\xfc\xd1H\xa1\x8b\xab\xb3Q\x09\x99M+rr\x5cg\ +\xe9\x9e\xfb\x97 fw:\xec\xf82a\xe0\xc2e\xc9\ +c\x89\xe4\x08\xf7\x8c\x1d\xe6\xc1\xdbR;\xab\xe8\xde\xbc\ +\xe8t\xfc\xc1\x85\x1e3\x03\xfc\xaf\xd6\xbe\xe8b\xfb\xe8\ +\xda\xccN\xb9_\xf4c6\xd9\x5c\xbc\xf3@\xab\xb1\xd5\ +\x93\x9c\xb9\x96\xd7\xfc\xefn\xbb\xe1;q\xd6\x15\x8ei\ +\x7f\xb3\x85D\xcd\xe3G\x9e[\x052G\xfb,M~\ +\xdf\xc6\xe1\x8c\xca\xd7\x1e[\x18\xc17\x89\x01\xce\x83\x07\ +\xaeP\xd35\xfc\xd9*\xd4\xd4\xb5\xc1\xd8\x94k\xc3\xee\ +^\xb9\xa1rzo\xbakw\xa4\xe09X?gn\ +GV\xb2\xf1\xfa\xe3Q>\x8f7\xa5\x1f\xe7\xe8\xeb\x13\ +\xf5j\x18\x22\xf6\xa1\xd2c\x9bF\x80\xf5\x8c\x8e\xbd\x9f\ +u\x9c\xbf\x02-\x7fj\xf2\x97y\xfb#G\xdc\xf9\xaa\ +E4\xd4PR\x0a\x9e\xf83b\xdf\x82\x05+\x9a\xba\ +\xda\x05\xecF6\xf4q\xafnGf<\x89\xe3|\xee\ +\x01\x8ap{\x87\xc5c\xd5\xc9q:\xc4\x8d\x08\xf7s\ +\xb9\x8b?+\xa9\xde\xb3\x9f\x12\x95k\x85\x9e\xd3)\xe0\ +K\xf4\xfaUwn\xab\x87\xab\xc4\xac\xf9Y\x7f\xc2b\ +\xc4\xff\x0f\x8fU\x03=\xf7\xef\x8fK\x8f7\xdf\xf7\xe9\ +^\xe0\xc2\xe3\xb3f\x84~tr\xb8\xf2\xa0\xf7!\x86\ +\x97z\xda,\x820E[\xb4\x860\x8bpw\xcb\xd8\ +\xc4\xd0C\xa2\xa1K\xf7\xc8\xef\xef\xbblB{\xfci\ +\xfd\xa1\xd8\xcfw\xbb?m\x9f\xbe\xdbA\xdb\xe1q\xbc\ +uk_\x15W%\xcd\x03\x9d\xda\xb9\x1c~\xf1\xcd\xa8\ +\xe7\xd5\xcf\xd9>\x13W\xd7\xf3\xb7\x9e\x911\x17)\xd9\ +G\xfe\xfdt\x1f\xbd!):j\xe1\xa5<5\xf7\xe9\ +\x84\x1da\xef\xd2\xa9\xdd\x9b6Zis\xdc\xc8\xd3_\ +\xf7|2<\xf4\xe2[V\xcbg\xb7\xee\xe4\x05\xef~\ +\xcf\xbbY?u\x9a\x11o\xdcuu\xc2\xbbi\x8c\xcd\ +\xbeq\x0d\xd0\xcbu\xd60/\x9e\xf1\x8ex\xd9q~\ +\xcf\xbc\xc5\xbbs\x8f\x91\xa6\xf9\x9d\x08\x82C\xc4\x9c\xb7\ +\xd9~\xb1\x1fb0G\x8e4\xf5\x9d\xfd\xbe\x8b#\xb2\x01\xbe\xcf\ +\x9a\x9f>\xb7\xddS\xfb\xc0n\xf6m\xb3C\xac;2\ +;Yu\x0fvq6\xfdQ\xab\x07\xd2\x8dW[]\ +\xf8\xdc\xb56\x9a\xe5\x07\x86\xdb\xd55\xd3b\x93\x0f\xe8\ +q{\x1cLV\xc2\xeb_H,t\x1b9\xfa\xad\xba\ +\xea\x86\xfc\x90\xfe{T785\x8b\x1ap/om\ +_\xe7\x11\x81\x88\x16\xde\x98\xacI\x8f0^t\xd0\xc5\ +\xec\xfekD1\x09\x0fM.G\xaa\x10>mB\x9a\ +\xfc\xdc\x7f \xdby\x87\xdf\xec\xf8\xcb\x0dk^~7\ +\xa2\xe0\xca'\x87\xb6\x1f\x95\x08c\xada\xdf\x94\xf4\xea\ +\xf0\x9a\x8c\xe8\xab>.\xa9\xb7\xe6\xbe\x03\x93/\x87\x14\ +\xa8\x04+[\x85\x8ckqaD\x7f\xcf\x90\xf9\x93\xeb\ +0]l\x87\xd6^\xa6\xa2\xd9\xf3\xcd\x9au\xba\x9d<\ +\x8f\x0d\x8f5~W[\xdf\xfaXC\x1f\xff\x19\xaf\xee\ +2\x82\xa7\x10\xfbZ&\xaf\xcfU\xcb9\xaa\xd2\xf6A\ +\x13F\x8bGm{g\xcc\xf68\xf6/s\xc0<\xec\ +\x0c\x9c\xb0\xca\xcc\xcb_\x85\xb0Z\xa5\xbfO\xf9\xe0\xcc\ +\xec\xa9#\xbc:\x12\xc4\xed\xb6\xdc\x14'\x8f\xb3\xc7\x12\ +\x17\x8df\x9c6\x19e5\x92\xad[\x7f\xfcp\xdf\x1d\ +zy\xef\xc7\x8e6#\xe08L%\xec\x22R\x0f9\ +e4\x9a\x12\xa7K^\x8a\xef\x1c\xf9Z\xc5hf\x8d\ +(U\xcd\x8b-\xb6\xf4\x0aHX\xa5\xc2\xbc\xeb\xa5\x19\ +\xa04h\xc9\xed\xc9\xb7\xdfL\xed\x98\x98\xa8\xa4\xd5k\ +\xdeR\x13%\xadC\xf6\xb9\x83\x9auGH\xcb\xbb~\ +*\xe8\xdd\xb0\xc6\xc1V\x7f\xf5\x99\xb8w\xd8\x13%\xed\ +\x13G\x97\xb6%\xb2\xdc\xb7\xeah\xe8_\x9f];\xf8\ +\xe1\x92C\x05S\x22\xd2\xea\xed[\x17\xed\xc8RvJ\ +Zu\xed\x8b\xbdC\xf3\x89\xb7\x9c\xcc\xb6\xaf\x19\xb5\xb1\ +\xb9\xe9\x7f\x87w\xae;\xb9\xf4\xc8\xe2\xf5\x83\xae>\xe8\ +\xdd\xcc\xdc\xf5\xb9\x9a\x93\xc7\x1c\x8d,\xff\x05\xb5'\xf6\ +\xd8>{\xfb\x9a\x995\x062\x1f\x1bjd\xf5Z\xa0\ +\xfdz\xecv\xf3\x9a\x9a\xff\xde\xcfz\xa21M\x93 \ +\xd0\xef\xff;4\xc9>\xf8,Ah)\x0djs\xed\ +r\x86r#U\x22XS\x7f\xc9^w\x17=oc\ +\x82\xb9\xa1\xa6\xd5\x8e\xbd\x1e\xdd\x1b{]\xe10\xeb2\ +\x88o\x17j\x06\xd5\x98Z\xa7~\xb2\x81J\xcc\xc8\xae\ +\x17\xd8\x86\xd7\x92//\xb2\x8b\xbd\x9f\x10sCyP\ +|\x07\x22\xef\xd3\xe5\xc1\xfd\xdexg{\xdb\xda\xfd\x8c\ +K \x96\xadw\x1cT\xaf\x8b\x8aWF\xe7w\xf9g\ +\xeb\xf6\xad\xf7\xc9\x09{\xbcG\x13\xc9\xb7\x82\x8c\xf7\xf5\ +\x9a\x10~*a\xcfG\xee}\xbf\x99c\x10Q\x9e\xee\ +z\xeb\xadZL\xbf\xdb\x81Zk_+\x8dZ\xe7q\ +\xa1Y\x1c\xc3\xfc\x87\xb3\x97\x9d\xb2W7\xd3o\x7f\xfb\ +\xa2\xfd\x8d\x1e\xd6\xc8\xf4(A\x80\xdf\xbc\xb1\xcf\xdb\xe0\ +&?\xafk\xfe\xfb\xa3\x9dO\x00\xef\xbaM\xff\x83\xb5\ +\x01\x1d\x16Z\xc1\xc6\xb6u\x9a\xf8=\xfd\x1f\xb3K\xef\ +\xa1y\xb9\xe7\xfe#\xb4\xb2,\xefE\xc5\xe87\xef9\ +\xa3\xf6\x87\xfe'\xa3\x875v;\x12\xc1 \x08\x1c\xc3\ +\xe8bT\xf3\xf1\xda\x09\xe1c\xde\xec\xee\xe1oqx\ +\xf2A\x0d\xc2\xb1Y\x8d\xc6\xa7U\xfa\x1f\xb6Y\xebd\ +8m\xeb\x94t=\x8f\xe9\xe9uw\xd7b\x0e`k\ +\xa1\xa7\x987\xbf\x18\xdf\xce\xe7<\xef\xa0M\xffK\xf1\ +\x8f\x09\xa3\x1f7T\x1b\x13\x1bTn\xd5\x1e\x16\xfa?\ +\xa6\xde\xe9\xcf\xc9\x87\xdd\xd5'\xb6\x1f\xaaf\xda\xdc\xae\ +\xff\xab\xa9\xe1\xf5\xda\xfd\xa4\xc2\x16>\xf3\x03L\x86u\ +\x8c\xe9\xb2s\xa9\xd2\xe9\xbd_\xfe\xa7\xa5j\xa5=\xbd\ +\xc7\x193\xffQ\xdf\x16\xb5[\xf4\xbc\xe0\xdfE\x17\xd2\ +:\x8cl\xfc\xb8\xc3\x95:\xcc\xb9\xebX\xa75\x82\x0d\ +\xd8W\xed\xcc\xdb\x8e[\xdcn\xd1\xd3\x95\x8d\x09N\xe3\ +\x0f\xdb\x03\xd4#\xb6\x5c\xdfa\xa0t\xba\xa1\xdd\x943\ +\x1a\xd35\xb3~\x9a\xf0j\xd4Q\xbd:\xac\xbfg\x0c\ +s\xca\xfb\x89\xe1\x86\xebG\xf8\x9b\xaf~\xa2F\xfc\xb5\ +\xf65\xa9\xdc\xc0\xa2\x87\x91\x96\xb6\xdfr\x08[\x0d\x1f\ +\x08\x1c\x11\ +\xe8\x15\xd5\xf2\xbe\ +\xeaA:\x8d\xbbr\xdb\x05\x02\xf8\x07\xbb\xa2Z\xcf\xaf\ +\xba\x10K\xe3Pb\xfc\x0b\xe0^\x8fUm\xdf\xff\x0e\ +\x10\xc2\x12\xf0\x11I\x88{\xf0+\xeeV\x80\xb9W\x83\ +|`7K\xc0W,\x01\xfe\xc1\xb7\x5c\xed\xd3\xfd}\ + \x85\xc6\xa9X\xfc\x0b\xe0\x1ebK~\x0a0\xe7j\ +\x90/\xf8\xb1\x04\xe2\x86\xa5\xe0\x1f\xe2\x8b\xd5\xb6\xde\xef\ +\x07\xf94nK\xe0_\x00\xf7\x90_\x10\xaa\x00s\xad\ +\x86\x8a\x81P\x96@\x0e\x89\x08\xfc\xcfb\xfdI\xb9\x1b\ +\xcbu\x11\xe8\x90,\xcb\xa64h\x93,\x0bm\xea\xff\ +\xad\xf5\x114'Y+\xf4\x05~O\xff\x9b\xa5\x0e\xf5\ +=\xf8~e\xaf\xa1|\x00\xb85\x13\x83\x7f\xc81\xbb\ +\xa5\x00s\xfc\x05\xf8Fx\x5c\xa1G&\xad\xed@&\ +o\xecI\xa6\xed\x9fHf\x9cYJf^\xb6#\xb3\ +o\xed\x22\xb3\xfd\x0f\x90\xb9\x0f\xce\x91\xb9\xa1\xee\xe8\xe7\ +Y2\xdbo\x1f\xfa\xbd3\x99yi\x1d\x99\xe1\xba\x88\ +L\xdd3\x86Lfv'\x93l\xdbS\xcf\x85\xe7\xc1\ +s\xad\xaa\x04=\xf8\xd0\xb8\x16>\xfb\x90g\xf8\x1b\xe6\ +\xeb\xe9\xd2\xe7U\x17\xe3+u\xe702\xf3\xe2\x1a2\ +\xe7\xbe+\xc9\xfe\x18JrS\xbe\x91\xbc\x9c4\x92\xc7\ +\xce#I\x1e\x8f,up\xb9\xe8\xefrI^v*\ +\xc9I\x8a!\xd9\xefC\xc8\x9c\xa0cd\xc69K2\ +e\xdb\x002i\xb5\x01\xcd\x17\x14\x9a\x16\x92i\x5c\x0b\ +\xe7\xf28*\xc0\xdc\xe4\x07p\xd6\x11\xafNZ\xd5\x86\ +L\xdd=\x86\xcc\xbe\xb9\x13\xe3\x8b\x9b\xc1Bx\xe4\x94\ +\x8e\xe7r\x0e^A>\xc9M\xfdN\xe6G\xdc&\xb3\ +\xbc\xec\xc9\x14\xa7Ad\xd2\xca\x96\x94\xacPL\x19\xe1\ +(\x84{\xb0\x0b\x1e*\xc0\xbc\xe4\x83wt\xfe\x92\xed\ +\xfe\x87\xce\xa5\x05\x99\xff\xea\x16\xc9\xcdL\x92+\xbeK\ +\x1d\x88G\x00O\xc9{r\x99L?f\x8ee\x0c\x9f\ +\xffT\xfa\xde\x14\xc1CV\xf1\x1cR\xb8o\x90\xa4\x00\ +\xf3\x92\x03\xde\xbb\x229\xbd\x96,\x88~\x86\xcee\xde\ +\xaf\xc3\xbb\x88\xc1\xcb\xcb$\xd9\xef\xee\x91\x19g-(\ +]\x01\xf8\x81b\xc8\x85$\x1a\xe7|\xfc\xc3\x9d\x93\xaa\ +{7\x03\x9d\xaf\xa45m\xd1>/#\xd9_\x9e\x91\ +$\x87]\xa9x\x17\x1e\xc3m9\xf6\xb3\xfc\x8e\x83\xf3\xfd\x1d\xf6C\ +\xffB\x1a\xc8c\x15\xd5\xdd`W>\x8eK\x07\xc0=\ +/+\xb9\xb2\xd1T\xa1\xe3\x17\xd3\x00\x9bUT\x8bD\ +q\xf1\x8f\xe4=\xf0\xfc\xdf\xf5\xdc\x0b\x8f\x82\xf8(2\ +u\xd7\xa8_A\x03\x8a\x8f\x7f\xb4\x07\xa0\xeb\xfdJy\ +\x0f\xf6\x04//\x8b\xe4e\xa7\x90\xdc\xac\x14\x1c\xeb\xe1\ +\xe5e\xffR\x9f\x12\xfbs\x18\x99\xb2\xb9_E\xd3\x80\ +b\xe3\x9f\xb6\xf1*T\xcf\xe7\xf1Hnf2\xf6\x19\ +\xe6>\xba@f\xdd\xd8\x86cy\xe0\xb7O\xdd3\x16\ +\x9f\xc3\xd4}\xe31\xff\xc9<\xbf\x92\xcc\xbe\xb9\x83\xcc\ +{z\x19\xd9\xefo\x10]\xa4U\xdc\xbc\xd0\xc8{q\ +\x9dLZ\xd7\xa9\x22\xe3\x06\x8a\x8b\x7f\x88\xd5\xaei\x87\ +\xed\xfb\x8a\x18\xbc\x9ct2?*\x98\xcc\xf2v\xc4\xbe\ +C\xbc\xcf\xd6-\x04\xf2;\x04r<,u\x8arC\ +\xe0\xe7\xca\x968\xb6\x04r\x1ar\x02\x80>y\xf9\xd9\ +\xf2\x9f$\x97Cf\xfb\xee\xa6rP*\xc6O\xa8\xb8\ +\xf8G\x00~=y\xfbv \x0e\x98\xfb\xd8\x83Ls\ +\xf9\x97L\xb25,\xc2o\xb9\xce\x98nal\x19~\ +&\xaf\xefL\xa6\x9fZ\x80\xfd\xfb 7\xe49xH\ +\xfe\x00\xef\xa9 9\xa0\x98\xf8G\xf8\x00\xbe\xcbI\x8e\ +\x93\xdf>\xb2\xf3\xc8\xfc\xf0\x9bd\xda\x81)\xe8\xfc\xb6\ +\x90o\x5c\x9e\x9fg\x80\xf8U\xfa\xc9\xffpN\x89<\ +\xf3\x0b\x0ab_\x92\xc9L\xe3\x8a\xa0\x01\xc5\xc3?\x9f\ +\xef?\xbf&\xb7\xfd\x03\xdd\x11\xf2\xb6\xe0\xb9\x15\x1a\x7f\ +\xa5\xe9 y\xc3\xff\x90\x1e\xe1D\xe5\x99\xc8e\xf0\xc8\ +\x9c\x80\x83\xb4\x1c\xf8\xcd\xf1\x8f\xce~\xc6\x99er\x8a\ +\xe3\xf1H\xf6\xbb\xfbX\xbeS\xb9\x01\xbf(\xd6\x06\xe7\ +t\x85\x1e\x99~\xc4\x8c,\xf8\x1a!\x87u v\x92\ +\xfe\x13\xd9A\xe3\xe5\xed#V,\xfc\x83,Ez\x15\ +\xd8>2\x0f\x1e\x97\xccE\xbac2\xb3\x07}\xe6+\ +\x87\x96S\xb7\x0f\x22\xf3\xdf\x06\xc9\xbe\x1e4\xf2\x9e]\ +!\x93V\xb5&\xe5\xc8\xbf\x14\x0c\xff:8o\x87\xe4\ +\x14\xc8\x8e\xfb\xb0KdR\xe5\xc5\xd5\x04h\xa0)\x99\ +\xb2\xb1'\xd6\x0de\x1d`o\xa6\x1d\xfaW\x9e\xf4\xac\ +8\xf8\xc7z\xb4\x11\x95\xbb#\xdb.\xe1s\xaf\x10\xb8\ +\x17\xa0k\xec\xc7x\x1f\x223\x0d\xe4=\xbbJ&\xd9\ +\xc8\x8d\x07(\x0e\xfe\x11MC\xfe=\xe4V\xcb2@\ +\xdec\x9e\xaf(\xb8\x17\xa0\x81\xd4\x1dCIN|\x94\ +L\xeb\xe3f%\x93\xa9{\xc7\xc9\x8b\x07(\x08\xfe\x91\ +\xce\xbf\xca\x00\xdbg\xb2\x0c\xd0\xf3\xb1\xaeWY\xf2^\ +\x02\x1aH?1\x0f\xdb\xf4\xb2\x8c\x9c\xc0\xc3X\xbf\xfc\ +m\xf0\x0fg\xc3y\x84L\xf6\x12\xd8\xf7`\xe3)X\ +\x8eu\x09:\x07\x1f#\xe4\xfc\x80\x9c\x92v@\x8c0\ +\xd9\xa1\xbb\xfeA\xef?\xbfR\xea\xd8:\xe4\ +\xd0\xa6\x1f\x9eYu\xce\xbe\x00\xdd\x83\xbe\x22m\x9c\x00\ +\xe2\x97i\x07\xa7\xca\xba\xee\xca\xc7\xbf\x95\x1e\x99\x13|\ +B\xaa=\x80\x0165\xbeW\xa5\xe8r_\x04\xfe\xc1\ +'\xc0\xf9\xf1I\xca\x95\xf3\xc8\xac\xab\x1b\xab6\xfe\xe9\ +\xfb\xd8`\xb3K\xb7\x05h\x0f\xaemV<[_R\ +\xb0\xd6\xc7\xb5\x05\xa4\x1d\xb9\x8f\xce\xcb\xaa\x03T2\xfe\ +up\x0d\x05\xb8G/\x15\xfa\x91\x1d\x9d\xb6gl\xd5\ +\xe3\xfd|(\xf4yIwG\x15\xe2\xcc\xf8n\xb1\xf4\ +\xbc\xafr\xf1\x0f\xf1\x91=cp~\x9d4\xa3 \xf6\ +\x15}w\xa6\x8a\xf1~>\x80\x0c\xd8jJr\xd3~\ +H\xb5~NR\xac\xac~\x80J\xc6\x7fSd\xc7.\ +\x90:\xd6\x0b\xb9a\xf8\xfe\x5cU\xd1\xfbK\xe0_\x17\ +\x9f_\xf6\xc7GR\xad\x1f\xf2\x16Sw\x0e\xaf\xc2\xf8\ +\xd7&3/\xae\x95Z\x07\x86\x5c\xcc*\xcb\xfb\xf9\xb0\ +\xb2\x05\x99\xfb\xf8\xa2T\xeb\xe7\xe5f\xcaj\xfbT.\ +\xfe-\xb41\x0e\xa5\x1a\xc8^\x84|\x5c\x9c\x93Y\xd9\ +8\x94\x05\x10\xee\xb2\xfd\xf7K\x87\x7fv.u\x87\xb8\ +*\xe3\xff\xf6\x1e\xe9\xd6\x9e\x9f\x8ds\xed\xaa\xfc\xf9G\ +\xf4\x0by\xaeR\xc5\x03\x10\xdf\xcc\xbch+\xcb\x19\xa8\ +\xba\xf8\xcf\xcd\x90w.D\xa5\xe1\x1fp(\x95\x0c\xc4\ +\xf8_\xfbg\xe2\x1f\xd9~`;T\xc2\x9d\xe9\x0a\xc0\ +\xbf\x94:\xd0\x9f\x8c\xff\xbcL\xaa\x96\xce\x9f~\xfe/\ +\xfd\xa1\xf8G6\xa3\x9cb\xa0\x95\x0bh\x0fp\xec[\ +\x9a\xc1- 3=VWi\xfcg]\xdf*\xe5\xda\ +e\xd6}\x14\x03\x90\xed\x0e\xf9<\xd2\x0c\xea\x0c,\xa8\ +\xba\xfa?\xf0\xbe\x0b\xab\xa4\xce\xf7\x85\x1a\xbdU\xd6\xf7\ +\x8fA\x17\xe7r\xe6=\xf7\x96\x0e\xff\x10\x03t\x99V\ +\x85\xf1\xdf\x14\xdf\xb3\x95\xf6\xce\x1c\xd4{\xabRq\x7f\ +a\xa0\xef\x0e\x16\xc4\xbd\x92j\xfd\x90/\x97\xe24\xb0\ +\xea\xfa\xff\xe8\x9cX\xf0cJ38?>\x90\xc9\xf6\ +\xdd\xaa\xb4\xff\x1f\xfc\xb7\xd2\xd6\xa6\xe5\xfc\xf8\x88k\x9d\ +V\xd9\xf8\x0f\xd0\xbf}W\x8cGi\x06\xf0\x8d\xb4#\ +U0\xf7\x83\x0fh\xdeR\xeb\xfeh\xc0\xbd\x22|\xa7\ +\xb1\xaa\xe2\x1f\xe4\xdfj\x032?\xc2O\xaa\xf5\xc3\xc8\ +\x09p\xa9|\x8f+5\xfes\xee\x80\xde#\ +3\xed+ \xfe\xab\xe8\x99\x96\x14 \xe7\xcdy8\xc9\ +aI\x97\xf3\x08\x03\xe7}\x1c1\xfb=\xee\xff\x08A\ +\xd2\xea\xb6\x94M\x83\xf7J[\xa06\xd3o@\x13r\ +\xaam#'\xde\xafx\xf8\xb7nN\xd5S\x8b\xbaG\ +\xe6\xdc=\x8a\xefHA\x0d>\xf0q`?\x1f\xbfO\ +\xdf\xf2\xaaK\x13YW\x99\xb2\xd54\x83\x9c\xf7\xeb[\ +\xe5\xc5#+\x06\xff\xfc\x1e{\xe5\xd1M\xf9w\x01\x04\ +s!9l\x1c\xe7\x87\xfb\xae\xe0\xeb\x85Zx\x90\xef\ +\x04\xf7\xc5\x92\x90\xee\xcf\xe2\xf7\xd7*F\x13\x95\x8fc\ +\xd1\xeb\xd3\xa1j\xd8\xcaX\x13\x8a\x9b\x1c\x87\xfb\xcc\xc9\ +)\xee!\x1f\xfc\x0b\xf6TD\x9f!\xa7\x15d\x1c\xf4\ +\xd9\x93\xf8\x9e:\xd8\xc3[L\xca\xac\xf1\x0b\xb9\xf2\xdc\ +\x8cD\xb2 \xe6\x05\xce\xff\x85\xfb\x1f\x10C\x80\xef&\ +\xadmO\xf7\xea\xe4\xd3\x84\x82\xd0\x03\xd4\xb0\xdd?\x09\ +\xfbke\x1d\xd0kP\x8e\xf6\xaet\xf8\x17\xec\x99\x8a\ +e\x9a!\xa2\xc9\x81\xb8njN\xd0Q\xaa\xa7bV\ +2\x99\x1f~\x0b\xcbs\x89\xf0 C,\x08\xbe\x03t\ +\x03\xef\x85\xde:\x99\x977\x90i.S\xc9d\xfb\x7f\ +\xe4U'Av\xdc#\x1e&\xeb\x80\x9er\x10/\x91\ +c\xceS\xf9\xf0O\xf3Y\xd0a\xe0\xbcA\x1fT\xa8\ +K\x07\xfd\xcd\xb8\xe9?J\xc85\xcc\xab\x1c{K\xc6\ +\xab\xd0\xb3\xe1,\xcbe \xbb\x0ah\x22\xf7\x89'\xd5\ +\x8f\xb32\xf4\x04\x9a\xe6\xa1\xde\x07\xe7\xe7g\xb9,+\ +\xe7\xee\x11y\xfb\xba$\xc4?\xe5\xa7\x87\xf3\x09\xf9:\ +\xf9o\xee\xa2\xf3\x16_\xe6\xfdu\x9c\xa3\x0b\xb5K\xcb\ +\xb4St\xb1,\x97w\xad\xdf\x9c{\xa7*\xe7\xfcC\ +/:[C2\xcb{\x93\xdcj@r\x12>P=\ +b\xe4\x9b\xef \x19\xfe\xe98\x1d\xf8\xdb\xcb;p\xfd\ +\xe2\xb2\xe6\x0c2\x04\xe2\xe0H\xa6\xcbm\x94\xea\x1f\xaf\ +\xa8\xfa\x9f\xd4:\xa1\x96\x0d\xd8x\xf2\xaa]\x0c\xcf\xc9\ +\xf4\xdcP\x11\xfa\x8c\xe4\xe7_\xcaX\x15\xf0\x8a2c\ +\x94\xfc\x1a\x10H\xaf\x93\xd7\xc0\xb91H\xee\x96\xe0=\ +\xd0\xf3\x9b\x9f3\x22\x97\xfe{\xba\x85\xf2\x18\xees\x03\ +\xbd\xcb\xb3n1\x0c\xe8\x0f%\xe3=O\x19\xf1\xdfL\ +j\xf9\x0cr\xa2\xa8\xd7\xa1\xf8g\x83]'\xed=X\ +Q\x03\xe7F\x80\xfeWl\xcf(9\x96\xed\xbb\x0b\xdb\ +\xd0)\xdb\xfac\xba.\x9fO\x81_\xfb\x9b\xd2\x7f\xe1\ +y\xa9\xce#q\x7fxN\xc2{\x99j\x18\x89\x5c\x07\ +\xaeiVa\xbd@\xca\x85\x7f\xa87\x01\xe7\xaa<\x03\ +\xdfQBzb\xa9:\x00Z\x1b\x9c\x1by\x0e|f\ +\x84u?~\xad\xa17\x81\xf8o\xb8\xc9_1O\x83\ +\x1c\x0c\xb0U!\x06\x09\xb1H\xfc\x1d\x0bm\xda\xffH\ +\x83\x05m\xdb\xa2gB\xdd\x15\xe8'\x0e}\xbe\xe1=\ +\xb2\xd4\xae)m\x80\x0e\x9by\xdeZ\x0e\ +;\x90\xcc]\x87i#\xd3s=\xf6K\xe6\xdc;\x89\ +{\x89s\xbeGQ5\xfcd\x88\xdf\x949\xd0\x9c\xa0\ +N\x1c\x0b\xf8\x93\x22\xe0\x9f\xaf\xa3\x87]*\xf7R\xd8\ +Q\xc1\xe2u\x00\x9a\xae\xe4a\x1f\xf3\x07\xe8K\xd0\xf7\ +\xbd\x04\xcf\x11\x87\x7fq\x83\xcb\xa5r\xb3*\x12\xcfb\ +F\xde3o\xda\xc7\xaf@\xfd\x7f@\x07@<\xaf\xbc\ +2\xaeT\x1d\x00\xd7\x80\x18+sM\xccb\xefCg\ +\x19t\xf0\x12\xef+/\xfe+i@\xadh\xa8\x11\xa8\ +p\xfd\xbf@\x07\xd87\xa1\xdc}\x8f(\x1d`\xb1h\ +\x1d\x00\xc9V\x88\xf3\xc8\xb3\xb7\x16\xe4\x92%o\xe8\x22\ +9\xffW\xa0\x01\xb8\x87z\xe1\x0a\xd9\xff\x0fx5\xda\ +Wiz\x1a`\xdf\x95\x98gJ{\xffE\xdc\x00\x9d\ +.I\x94\xdcTp\xfc\xffb\xdc\x97\x1f\xff\x00\xd6\xcd\ +\xc9\xdcP\xb7\xb2\x17\x83\xe4&\xf4\xea\x84:\xa7\xb9\x0f\ +\xceQ\xf9\x0a\xd6B\xbeK:\x1e\xce\xd7\xc7\xe55\xb2\ +o\xee\x14\xcdk\x14\x15\xffh\xaf\xf2\x9ezUF\xdd\ +\xf2\xf2\xe3\x1f\xe7\xac\x8b\xb8\xaf\x8ct\x02\x90\xe1\x18\xdf\ +\x88>\xe0o ?\x11\xd7g\x02\xbc\x8b\x8aY@\x1e\ +\x9cC7\x92\x93\xf8En[\x09w\xe22N\x89\xb9\ +\x13\xa7\x80\xf8\x87\x5c\x9el\xbf\xfd\xb8\x1eh%\xdce\ +\xe3\xe3_\xf2\xfe\xcf\xa0\xaf!\xbc\x82_\x1b|\x01\x90\ +\xc3\x086\x01\xe8\x85i{\xc7\xd1\xf8n.YL^\ +J\x9fBi\x03lq\xb1\xf1q\x05\xc3?\xd0}\x86\ +\x9b\x15\x95\xc7P9\xb1j~\xff\xe7r\xf4\x7f\xa7x\ +v\xc6\xd9e\xb8\xff!\xaeA[\x98\x87Q\xce\x1c\x0c\ +\xa8}\xe2e'\xd7=e\x7fyJ\x9f%\xd1\xb6\xa6\ +\x22\xe0\x1f\xfc\x9cy\xe17i\x1b\xa5R\xf3\x98\xf8\xfd\ +\xdfM\x10\xb0$\xff\x9e\xaeP\x8e\x85\xf0\xfcK\xcb\xd5\ +\x16\xf4\xc7\xe9\xe08=\xc8>\x90\x1b\x98\xa7\xc8\xe8\x03\ +\x86\xfb \xb8\x8f\xa7\xa8y+\x00\xfe\xc1w\x04>\xa5\ +\xc2\xde\x93\x95\x83w>\xb0h\xdc\xb7D\x10-\x97g\ +\xae\xd0\xc7>R\xf0\xb3\xe2\x5cNA\x80\xdfa\xff\xaa\ +@Lv9\xe5W\x82\xbb\x1c\x10\x03\x82z\x98\xe0K\ +\x07?\x1c\xc4\xb8\xf7 \x0a\xd2\x0eL\xa2{6T\ +\xfa\x1e\xfc\xa9\x90G\xe3\xb8\xf0\xec\x0b\xe1\xbf1\x82P\ +\xe9\x9f\xaf+$C\x85Aa\xed\xa1?\x05Bi\x1c\ +\x17\xc3\xbf\x10\x0d,@\x90\xaf\x00s\xad\x06\xf9B>\ +\x8d\xdb\x12\xb8\x17\xc2?\xd8\x05~\x0a0\xdfj\x90/\ +\xf8\xb1\x04l>QC\x80\x06\x86#HQ\x809W\ +\x83| \x85\xc6\xa9X\xdc\x0b\xe1\x1f\xfc\xc2\xbb\x15`\ +\xde\xd5 \x1f\xd8\xcd*\xf2\xf5\x8b\xc5\xbf\x10\x0d\xe8!\ +\x08Q\x80\xb9W\x83l\x10B\xe3\xb2L\xdc\x8b\xa0\x81\ +\x81\x08\xbe)\xc0\x1a\xaaA:\x88E0\xa0<\xb8\x17\ +\xc2?\x83E\xe9\x8c\xe9\x0a\xb0\x96j(\x1f\x00\xce\xe6\ +\xd38,\x17\xfe\x85h@\x15\x81\x03\xab\xda&\xacJ\ +\x90O\xe3LU\x1a\xdc\x8b\xa0\x81:\x08vT\xd3@\ +\x95\x80|\x1aWud\xc1}5\x0dTI\x90+\xee\ +\xc5\xd0\x80\x03\xabZ\x1fPDH\xa7q#W\xdc\x8b\ +\xa0\x01\x90)\xa0W\xc4*\xc0\x9a\xab\x81\x82X\x1a'\ +2\xc9\xfbr\xd0\x00\xe8\x94`WT\xfb\x07*\x1fB\ +h\x5cH\xa5\xe7\xcb@\x03|\x1f\x11\xf8\x96\xaa}\xc5\ +\xbf\x1eR\xe8\xbd\xd7\x13\xc4\xc9\xaf\x18B4\x00~E\ +\xf0-C|\xa1Z7\xacx\xc8\xa7\xf7z8K\xc0\ +\xa7\xfb\xabp_\x0a\x1d@l\x09|E\x10c\x962\ +\x87\xa4\x1aJ\x81\x13\xd5\xa3zT\x8f\ +\xeaQ=D\x0ef\xf1\x8fe\xf2\xd3\xe0\xe2\x9fK\xf0\ +\xe7\x98\xe2\x9f\xd5\x84?\x0b\xcb\x83\xb2\xe4\x87\xb0\xbc\x11\ +\x96G\xc2\xf2\xaa\x84<+6A\xe5\x92\xf2PX^\ +\x0a\xcbSay+,\x8fK\xc8kay^\x5c\xde\ +\xb7E?L\x09j\xdf\x19\x84>\xf5{\xf4\x8b\x96\x9d\ +(\x90t\x88\xd0[\xfeB\xd0\x17\xc1r\x04G\x11\xdc\ +D\x10TIp\x93\x9e\xc3rzN\x7f\xc9K\xc7\x12\ +z\x0e\xf8\xe0\x0d\x11\xaccQ~y\xb0\xdd9\xac\xca\ +\xb7)\xf9\xc0\xa1\xe7\x14B\xcf\xd1\x90%\x107(\xef\ +>\x08\xad]\x1f\x81\x13\x828\x05X\xa7\xa4\x10G\xcf\ +Y\xbf\xbc{ \xf0\xf7J\x08& x\xa5\x00\xeb\x91\ +\x16^\xd1kP\x92d\x0f\x04\xd6^\x13\x81-\x824\ +\x05X\x83\xac\x90F\xaf\xa5fi{ \xb4\xf6-,\ +E\xe8\x9d)?`\xd3k\x12\xb9\x07\xac\xe24o\xfb\ +\x9b\xad]p\x0flY\x22\xce\x82\xc0\xfa\xe1\xac\xfc\x0e\ +4/\x0e\xd2\xe85\x16\xae\x9fU\x9c\xcfWe^'\ +)\xbcb\x09\xc9\x05\x16%+\x9d*\x7fn\xbf\xec\xfe\ +\xbc\x13\xab\xb8~\x00\xfa\xc2/\x94\xef\xbaE\xb58\xa0\ +\xee\x06\xd4\xd2Z\xd9\x92\xaa1\x08}\x9c\xa0\xd6,\xfc\ +]\xc5\xf5\xfc\x8c\xa3\xd7\xcc_\xff\xfa_\xb2n\xbav\ +\x14\xf4\xd6\x84\x9a\x0a\x19\xee\xd6dN\xe0!\x5c\xdb<\ +?*\x88d\x7fzL\xb2\xdf?\xc0\xfdgr\x1f\x9c\ +\xc5u\x0f\xd3\x0eM\xc7\xf5\x94q\xcf;\x5c\x0bIn\ +{\xb1\x8eU\xa4\xcfWl\xbe\x0f\xae\x97\xa5\x87k\xf8\ +f\xdd\xd8\x86\xd7I\xf5Z)\xbb\xe7\x04\xd4\x97\x80\xbe\ +O\xb9\x0f\xddp\x0d\x02\xa8\xf5&\xa7}\x08a\x15\xd9\ +2\x15\x14\xcf\xd4\xa5\xeb'\x9a\xe2Z\x87\xd03C\x96\ +\x01\xf56\xf2\xdf\x06\xe1\x9aET}a\x99\xee\xdd'\ +\xb3\x8a\xec8\xf9\xdb2\xb8\xdec\x1b\x5coI\x9e\xb5\ +\xc0\xf1>\xe4e\xe3\x9a\xb0P\xb3I\x86\xda\x03\x1cV\ +\x91\x0d+\xdf\xb5\xa39A\x9d\xcb\xdcG\xe7\xe5\xda\x03\ +Cx@\xbd2\x5c\x1bSz\xfe\xc8\xb7\xdf\xe5\x8aw\ +\xa8\x87\x0a\xfc\xecW\x0c\xa8\xef\x96q\xd6B\xda\xf9\xf2\ +}\x17r[{\xf2\xa6^\xb8N\xe4\xaf\x1c\xd0\x97\x19\ +j\x9bIA\x07Ar[?\xd4\xe1\x5c\xdb\x11\xf7\x87\ +\x96xp\x0aHNR,\xc9~w\x9f\xcc}|\x91\ +\xcc\x09>\x81\xe5\x1e\xd4\xe0+\xf8\x16I\xd5\x84\x95t\ +\x0f\xd2~\x90i.\xff\x96\x97\x1f\xc8o\xfdH\x87\xc9\ +\xf6\xdf\x8ff\x22\x81L\xcb\xcd\xc4\xb5\xc2q\xdf{D\ +/T\xefJ\xbd\x22\xfc!\xfd\x07\xeaiA_\x1e\xa8\ +/\x0c\xfd\x02%\x19\x05q\x11\xf8y\xe5\x90\x0b\xf2Y\ +?\xd4\xfc?4C\xa2\x1eB\xec\xcfad\xfa\xd1Y\ +t\x8fF\xedR\xeb\xba\xf2u?\xa8?\x0c\xb4\xc1\xcb\ +\xcb,\xf3\xf9\xb9\x0f\xce\x88\xaf\x15^\x11\xeb\x07\xba\xb7\ +m\x8f\xf8\xdd\xbd\xd2'\xc6\xe5\x90\xb9\xa1\xeeT\xff\xca\ +\xf2\xea/\xb0G\xd6\xfa\xb8>\x7fY:\x04\x9c\x19\xd0\ +\x19%\xac\x9b)\xfb\xfa\xa1\xcf\xe7\x99\xa5\xa5\xcb9\x1e\ +\x17\xf7m\xa6\xfa\xc6K+\xaf)]*\xfd\xc8,\x5c\ +\x1b\xb8\xb4\x01\xfc\x03j\xc2J\xb0\xc72\xae_\x17\xeb\ +8e\xf5\xba\xc9{\xe9C\xd7\x11\x95C\x9d$\xf4\x8c\ +\x0c\xb7\xe5X\x17\x14\xbb\xdd9\x19\xa2\xfb\xe4\xca{\xfd\ +\xe8\xf9\xd03\x13\xd7\xdd\x1538I1d\x8a\xd3 \ +\x89\xeb\xb8JD\x07\xe8|C\xff\xa1\xd2\x06\xeeMU\ +\xb6<\x94y\xfdY\xd7\xb7\x95:\x8fl\x9f\xed\xf2\xaf\ +\x17\x8cd\x5c\xca\xb6\x81\xb8&\x9f\xb8\x015\x19\xa9^\ +\x83\xa5\xee\x81l\xebG6\x08\xf4\xb7\x12\x8f\xfbXJ\ +G\xaf\xa0z\xc9\xb9\xf7O\x8b}7\xf0A\xa83_\ +\x06\xddI\xbf~\xdc?\xfd\x1fl\x9b\x8a\x1b\xd0C\x07\ +\xfb6*`\xed\xb0.\xb0\x87\xc1>\x16\xbd\x01<2\ +\xf3\xbc\x8d\x98^\xe9rX?\x9c\xfd\x1dC\xb1\xee)\ +nd^\xb2-\xeb\xfd\xd2\x03\xe8\xda\xcc\x1e\xa5\xda\x96\ +\xa0\x8f\x95\xa1\x0f\xca\xb4~\xa8\x09.n\xff\xa1\xb64\ +\xfc\xbb\xfc\xf8\x9e0P\xb2\xa74[\x03\xf7M\x11\xec\ +\xcb!\xd7\xf5kc=\x0etx\x91\xeb\xcfJ!S\ +\x9d\x87W\xe0\xfa\x9ba=\x19\xf4hq#\xef\xc5\xf5\ +\xb2\xce_\x85\xad\x9f\x9b\x99\x8c\xce\xc7\x90\x8a\xad\x8d\x88\ +t\xc2\xbc\x17\xe2{,C\xbf\x95B_jE\xac\xff\ +\xf0L\x5c'_$\xfe\xa1o\xc7\xde\xf1\x15\x8b\x7f\x90\ +?o\xee\x8a_\xffS/\xcaoZ!\xeboJ\xf5\ +\xb8\xcd\x11c\xa3\x22}\x1f\xec\xbb\x8a\xe3\x7f\xba\xd8F\ +\x04;Y\xdc\xc8\x09:^q\xf2\x1f\xfc<\x9bz\x97\ +j\x8fH\xa8\x83I\x07\xb0\xff\xce#K\xd5=\xb3\xae\ +0+N\xfe[Q}G\xf2\x9e_\xc32\x08\xf4\x80\ +b\x80~\x07\xf4\x87m\x9e\x8a\x88\xeb\x80\xee\xe9\xed(\ +v\xed\xd0\xeb\x00\xf7\xe7\xac\xb0\xf5S\x00>\x9fd\xfb\ +\xae\xb8\xdfw\x09\x80~\xbd\xc2\xfdA\xe5D\xfb\xa0\xdb\ +\x16D?\x13\xbb~\xd0=\x93\xcb\xf6\x85\xc8\xbc\xfeB\ +?\x85E\x13\x04\xda\xc5\xfb\x06T\x18\xed\xeb\xe0\x1e\x06\ +\xa5\xf5:\xc1\xb2\xafl?\x88\xe4\xeb\x17\xd7\x0b\x01\xf8\ +\xc0\xd6\xfe\xd8\x07\x0b1-\x0a\xe7\xcd\x8b\xc7\xf7\xe4\xb9\ +\x0fp\xee\xb7\x0fA\xe7+Z\xec\xdaa_\xa0\x7f\x87\ +L\xf6\xefr\x81\x9a\xc7\xd0\xcbl\xa31\xd5\x83P\xf8\ +,\xc39\xbc\x016 \x0f\xf3\xa2\x82\xf8\xb7\xd8\xde\xcf\ +\xbe\xe5L\xa6\x9f\xfc\x0f\xdbi\xb8o\xcc\x0a=\xd9\xf7\ +\x01\xbd\x0bb\x81\xf9Q\xc1\xe2\xd7N\x92\xb8\x973\xee\ +\xe1(\xad\xfd\x8b\xe8\x06\xde\x03\xfa+\xac\x0d\xe2\x91`\ +\xc7C?\xd0\x12{\x8a\xf4+\xe8\x0d&\xfa\x10\xb2q\ +\x0f\x9d\x82\x98\x97d.\xb2\x85Rw\x8d\x94\xde\x16D\ +\xef\x05yS\x9a\xbd\x89\x07\x8fKf]\xb6\x93T\xef\ +*\xbe\xfe\xe5T\xads\xe8A\xc5\xf9\xf9\x19\xd7\xbb/\ +~\xa6\x84\xf4)>\x1f\xfa\xfa\xba\xf49a\x9a\xe4 \ +}qv\xd1\xfeI\x14\xd7\xa6i\x10\xf1P\x88\xf3\x14\ +\xc4\xbc(\xf35\x10[\x95\xac\x17\x97(\xfc\xeb\xe2\x18\ +<\xf4\xdf\x12\x89\xce\x9f\x9f\xa8X4\xff\xd9\xd07\x16\ +l\xc0\xcc\xa4\xb2\x97\x9f\xfa\x9d\xea\x93gI\xf5\x81K\ +;0\x19=\xab;\x89\xed\x93\x12\xfd8\xb4\xa9\xbfC\ +sI\xdb7\x1e\xc7~%\xf1-\xc3\xf9\x93@\xe6\x95\ +N\xff\x88>\xb3\xfd\x0f\x88~~\xbe\x90M\x07}\xee\ +O/.\xd97Y\xc4\xc0=\x12\xd6\xb4\xa3\xe2\xa2X\ +v=\xa7\xe2\xda\xa1\xe7\xc9L\xcf\xf5d\xfa1s\xbc\ +'\xe0\xbb\x05^\x0a=\xa7\xa0\xf7\x94$\xeb\xa66\x98\ +C\xf9\x9aJ\xd7w\xcb^?\xf8\x15\x8e\xcfE\xc8\x16\ +\xad\xd7g]\xdfZ\xb4~\xd8+4OI\x06\xf8\xeb\ +\x8a\xceL\xc7\x92\xfd\xe6\x11\xaf\xa0\xfaI\xe4\x89\xec\x03\ +X\xfa\xe0\xe1\xe7S\xfe\xe5r\xf1X\x91\xf8\x87~s\ +\xdc\x94\xaf\x22\xdfD\xf1\x80\x16\xd4\xd9\x04\xfb\x03\xf1F\ +IF\xa6\xe7\xba\xc23\x8f\xf1\x1f\x17Q\xf6\x97$Z\ +:\x0f\xd3Pao\xb2\xf2\xf1U\x91\xfc\x1fb3\x90\ +g\x80\x07\xf4AK\xfe\x8a}\xdc\xd9\xb7\xf7`\x1a\xc5\ +:\x1d\xf8_\x90\x8e\x07\xfd<\xcb\x9cb^6\xf5=\ +9\xaf\x1f|/9\x81\x87e\xf1\xad\xc3\xdaK\xc6\xbf\ +\xd1\x1c3\xce-\xc7\xb178\x0b)\x8e\xbd\xe9x\x15\ +_\xa7\xd1\xa5}\xdf\xa3%\xeas\xcda\xc5\x90\xc9L\ +\xe3B]H\x1e\xeb\x87\x98`\xe6\xf9\x95\xb2\xf6\x99\xe6\ +\xe7\xcf\x8b\xf87=\x81~Zz\x98\xbe`\xde\x18`\ +\xbf\xd1\xdf\x80\xae\x07}\xb4\xa8\x9e\x8d\x8f\xd0\x99\x89\x17\ +\xe9\x0f\x83\xd8\x18\x15\xe3l&\xf3\xfa\x81\xc7C\x1c-\ +\xc5i \x8ds\x99t*\xfe\xdd\x01\xf1\xf9/\xb4\x9d\ +\x9b\xff\xee\x1e\x96\xf3\x05_#\xb1\xcf\x89\xeaw\xa4S\ +\xc8\x0b\x81\xf7\x00\xdf\x009\x9duu\x13\x99\x17\xe6\x89\ +\xf5\x1e\xf0\x8f\xe6\xdcq)\xc2\x11\x7f\xfd\xa5\xd8\xed%\ +\x06\xe2\xed`\xcf@l\x1cb\xc2\x98\xff\xc8\xeeS\xe7\ +\xe7\xbf\x94\x9e\xff\x04\xeb\xdf6\xa0\x98\x8c\xe7$~\xc6\ +g\xbfd\xbfW\xc1>\x8e\x14\xcd@\xec\x87\xa2\xfd\xa2\ +\xbf\x85\xb3\x94\x89t\xb4b=\xfa\xd09\xc2\xfc?7\ +\x13\xef\x19\xce\x0b@2\x13\xd6\x0c\xba<\xee\x01Z\x98\ +\x03'\xd3\xba\xf9\xc0\xcf\x7f*=\xff\x8d\xbf\xfe\x0cV\ +\xd1\xfaA\x0f*\xb1~Q\xdf\xd5\x15\xdf/\x12~\x07\ +v\x05\xf4\xa8\xdbj\x8ay\x09\xf0\xc8\xd4\xbd\xe3\xb1N\ +\x05{\x86\xf5\x05\xf8[\xbeM)\x9fu\xf3\x81\x9f\xff\ +Vz\xfe#\xce_3\xc1k\xc68\xca\xcd\xc0zh\ +\xf2\x06\x89\xec\x8b\xb2Ad\x8f6\x1dy\x9c\xed\xb2`\ +=K\xc2\xfcW\xe0]\x80\x93\xd4]\xa30\xe0x\xe6\ +J\x89s\x0c\x14\x11\x84\xf3_\xcb\xce\x7f\xb6\x14\xc0\x91\ +\xe2\xf6?\x95\x14\x84\xf3\x9f\xff\xd8\xfc\xf7?\xfd\xfeC\ +\xf5\xfd\x97\xea\xfbO\xd5\xf7\xdf\xc4\xde\x05\xfb\xa3\xee?\ +\x8a\xd8\x83?\xee\xfe\xab\x98=\xf8\xa3\xee?\x97\xb2\x0f\ +\xbf\xdd\xfdw\xba\xcc\x00I\xd7?\xe0\xd7\xd9\xe1\xd7\xd3\ +\xe1\xd7\xd1\xe1\xd7\xcb\xe1\xd7\xc5\xe1\xd7k\xa8\xae{S\ +\xc9\x83I\xfd(\xc4G0\xf5\x93_\x1f\x83_\x07\x83\ +_\xff\x82_\xe7\xc2\x84\x8fw\xa8\x13a\x84\xc0\x9c\x10\ +\xa8\x13\xd1\xaa\xec:\x11\x22\xce\x85\x06\x82A\x086!\ +\xb8\x8e\xe0\x19\x82\x08\x16\xc5\x83\xe5\x09\x11\xf4\xb3\xaf\xd3\ +\xef\x1aD\xbf[b\xba\x171os\x04\x81\x08R\x7f\ +\x01\xaf\x12\x86T\xfa\xdd\xe6\xc2\xeb\x90`\xee=\x10\xf8\ +\x22(\xa8\x84y\x0bC\x01=\x97\x1e\xe2\xd6 4\xf7\ +\xc9\x08b\x14`\xde\xc2\x10C\xcf\xad\xd8\x1aD\xcc\x9d\ +\xa5\x00s\x15\x07,\xe15\xb0\x8a\xd3\x8c\x22\xee\xbb(\ +<\x14\xa3%\x16u>|\x15`n\x92\x82/\xab\xf8\ +\x99\x863.\xdf\xb3Z\xccw\xa6#\xe4O\xd3&%\ +\x8b3\x8b\x85\x02z\xce\xfc\xbd\x0f\x94\xcf\xbc\xe9\xb8\xf6\ +\x0a}\x9cW\x011\xdb\xac+\x0ed\xce\x9dC\xd8\xdf\ +\x9bs\xff\x14\x99\xed\xbb\x1b\xdf\x8f\x06\x7f[a|G\ +:\xbfS \xabH6\xc9\xce\xdf\xd1\xbc!\xef\x16\xe6\ +\x9c\x1bv\x11\xc7vx\x05yb\x82\x15<\x92\x97\x93\ +F\xb2\xbf<%\xb3|\x9c\xb0\x1fU\x8a\xbc\xa0TV\ +\x91\x5c\x95m\xcf\xd1{S\xf7\x8c\xc1\xf1\x8b\xd2\xee\x8f\ +\x88\x1b\xe0\x93\x87|\x10|\xaf\xa0|y\xad|\x9d@\ +J\x1a\xa7b\xad\x10'\x87{~\xb2\x0d\x1e\xce\x9f\x01\ +\x7fn9\xe8\x89\xaf\xcfH5w\xf0\x1fC.\xbaX\ +:\x91b@\xdc5m\xffDI\xd7\xc0\xd7\xc5\xca?\ +\x7f\xeb\xe68\x97\xa9\xb4\x5c3\xbc\xab\xec\x5c\x9c\xe3\xc1\ +\xcf}\xc4\xf7\x02\xca\x88\xe3\xc3\xdf\xe2\x9c\xa0\xb2i\x89\ +\xafG\x96s\xefu\xf0\xbdX\x88?\x8b\x9f\xc3'\x1c\ +\xe7\x85\xbc\x0b\xb8\xab\x02yS\x90\xe3\x02\xb9\xdbp\xcf\ +\x0a\x9f\x95R\xee\xa1\xb2?=\xa2cA\xa5\xae\xe1U\ +\xb9\xe7O\xe7!\x88\x8b\xaf\xc3\x9ar\x82\x8e\x15\xdd\xf7\ +,\x96w(\xc0\xff\xd1\xb9I?2\x13\xf1\xa0'b\ +\xd7\x00\xeb/\xe3\x9e@\xf9\xe7\x8f\x00\xf8\xb9\xc8\xb9g\ +\xa7\x91\x99\x1e\xab\x0b\xf3\x08\xca|\x16\xce{\xeb\x8ep\ +\xe1#\xf2y\x90c\x06\xb9\x87\xa5\xc4\x06\xcb7\x7f\xfa\ +\x0e;'9\xae\xe4\xdc\x0b\xf2\xa9<8\xfc\xb7\xe5\xe0\ +\xe3\xb0\x06\xfb\x7fH\xf6\x87\x07\xa2q\x10|B~\xfb\ +\x8f\xde\x95yi\x9d\xc8\xf7@^(\x8e\xc5K\xa3\x13\ +\xc0\x9d\x97}\x13D\xe6{Q\xb9a\xdd\xc5\xe1\xb3|\ +\xf3\xb7n!\xf2^\x07\xc4\x1f\x0b\xf3H\xca;w>\ +\xac\xd0\xc3:F\x89\x81\xf0\x0a\xb9\x1eb\x9e-\xf9\xfc\ +!/\x1f\xf1\x10\xce\x8f\x92\xe7\x16\xee\xb9\x83\xee S\ +\x5c\x13\xdf\xb5\x9e^\x227\x11\x06\xe8L2\xcf\x1fr\ +\xfa\xb6\x0f\x11y\xaf+\xdbo\xbf\xec\xf1w|o\xae\ +\x1b\xce\xaf,A\x9bO/\x8b\xcb_+\xc7\xfc)\x1a\ +\x15\xa5\xdfd^(\xf3>\x9c\x04@\xe7H\x22\xbe/\ +< G\x0a\xe7\xe3\xc8:\xff\xfd\x93\xf0\xfd\xb1\xe2\xc4\ +\xcf\xa5j<\xc8\xe1>\x09\xce\xef\x12q_\x9d\xfd\xee\ +\x1e\x95\xe7$\xe3\xfc!\x87\x81\x97\x97Ur\xff/\xad\ +\x95\xcf\xfe\xafi\x87u\xea\x12\xfb\x1f\x19 \xfb\xfe\x8b\ +\xc8w\xe1\x8f\x9c{'e\xcfy\xc0\xf9D\xbdpN\ +\x9d\xf0\xa0\xee\xeb\x89\xfc^\xb9\xf8\x0f\xe4W\xe6G\xde\ +\xc1\xb6\x09\xbe\x9b\x02\x80\xfe\x1f\xf2\xb4\x81ve\xe3?\ +\xdad\xfa\xa9\x85\x22\xef\xfbg]\xdb\x22;\xff\x01\x80\ +\xdc\xa1\xf5\x9d\xa9;$p\xb7\x04\xc9M\x0c\x90s\x22\ +\xd3}\x12]L\xdf\x90\x9f/\xf8\x1e\xce\x19\x17:c\xf9\xaf\ +\xfdp\xce\x1b\xf0\xe5\xack\x9b\xb1\x1e\x09:2\xce\xb3\ +-_\x9e1}_x\x98H\xb9\x08#\xfb\xe6\x8e\xd2\ +t\xc1b\xf3\x07\x19\x0awKA\x87\x843S\x10\x1f\ +\x85u\xe1bg\x0c\xe9o\xdc\xb4\x84\xe28F\xf6\x17\ +\xd8(\xec\xcfO0\x0d\x17\xe1\xa2\x14|\xd0~\x07\xb8\ +\xeb\x07w\xeaE\x0d\x90e8?M\x92\xf9\xc3>\xec\ +\x1c^\xe2\xee~A\xec+:\x87W\x97\xbe\xdb=\xab\ +T{\x11\xdf\xed\xb6\xd0\xc6x\xc2:\x85\xa0\xbfG \ +O\x15\xceM\xd6\xd5\x8d\xe2k\x05p9d\xe6\xe5\x0d\ +\x92\xdb/X\xbf)\x99\xf7\x0by\x99p\x1f\x8f\xff~\ +\xe0\x05\xe2\x06\xe8q\x90\xef\x96\xb8L\x0b\xd9XVX\ +nfyo\xc2k\x06\xff\x04\xc8o\xb8;\x095\xd3\ +\x00\xb7\xa5\xd9\x91\x14O+3\xef\xbb8\xfd\x8b\xb9\xb3\ +\x02\xb5\xca\xf0\xbe\xc1\xbf?\xbd\x22\xf6\x9d\x9c\x84\xf7\xd4\ +\xfd\x1at\x9e!\x8f\xb1p]H\x87\x84\xfcE|>\ +%\xb9\x13\xf0>\xa4(/\xb8\xf4\xf3\xc3\x8f{\x14\x9e\ +%\xb0A\x0a\xdf\x8b\xecS\xc8\xa3\xc4\xfa\x0d\xdc/X\ +\xdbQ,\xad\xc2\x80\x9cu\x9c\xbf\x86\xd6\x0a\xb8\x97f\ +\xe4\xbf\xbd[\x16\xcd\x0b\x02?fC\xcf_\x07\xf3\x82\ +\xdcG\x1eh\x1d\x0e\xf8lQw\xab\x9a\xe19\xe1\xda\ +\x16\xe9?\xc5\xbe;\xdboo!\x8d\x97w\xfe\xa0\x97\ +@-\x1a\xea~\x94\xc4\xfe\x1f~\xbc\xa9\xe8w\xc0\xff\ +VPwW\xc0\xee\x815\xa4\xee\x1d\x87\xcf\x1b\xf8+\ +3\x5c\x17\x93\xd9\xc8\xae\xc69\xae\x90;)\xe0C\x00\ +\x1e\xce\xbf\xf3\x92\xe9eW\xcal\x05\xe6\xcd\xce\xc3\xf4\ +\x82m\x94\x95\xe5\xce\xff\xe5\xc7\xcaD\xf27\xb8\xc3\x01\ +\xf2\x0f\xe8\x16\xdf\x99\xb5\xd0.\xba\xbb\x079\xaahM\ +\xb0\xb6\xcc\x0b\xab\xf0\x9a\x0a\xf1\x0e\xf2\xe8\xf4b|\x07\ +\x18\xe7}\x0b\x9eSn\x01>\xe7@\x97\xc0\xa3\xa1f\ +a\x92\xad\xa1\xb4\xf6\x03?\xceW\xd2\x7f\x8b\xe6\x01\xbc\ +\x9f?\xc0?YB\xc7,\xbc\xcbW\xb2\xde\x14\xe8\x03\ +8o\xfb\xe0T\x5c_\x09x\x00\x9c#\xb8\xbb\x07\xbe\ +g|\x9f\x91\x9fw,\x9d\xee\xc7\xf7\xdf\x8a\xf6\x9f\xa3\ +\xf9\x00\x0f)\x88\x0dG\xf0\x12\xdf;(\xf7\x1e\x15\xf3\ +\xf7\x0b\x82\x5cj\xa7\xf2\xfd\xe7b\xe3\x17Pk\x09p\ +\x8bA\xb4\xfdPY \x18\xbf(=~T\x91\xf7u\ +\xa5\x07\xe1\xf8\x11?~W\x15zt\x8b\x8a\xdfU\xd9\ +\xf8iU\x8f_\xff\x0e\xf9\x03\xbfC\xfeFU\xcd\x9f\ +\xc1yD\x1a\x04\x11\x03?\xd5\x08\x22\x18~*\xd3\xf9\ +G\xd5Y`2\x0f&\xfc\x87Q\xb4\xaf1\xf0S\xad\ +h\xdf!OK\x9f\xa0z\xfa\x14\xe6ii\x8a\xce\xd3\ +\x12\xc2e\x0d\x16\x95\x1b\xb8\x07\xc1c\x16\xc5\x1f\xbeJ\ +\x091\xf43\xf6\xd0\xcf\xac!L3B\xefn\x8e\xe0\ +\x10\x82\xc4\x0a8w\x89\xf4\xb3\x9b\x8b8K\x00\xffC\ +\x10V\x01\xef\x15\x860\xfa]\xc2\xeb\xfe\x15\xef\x16\x9c\ +\x03\x7f\x1fj\xd0\xfbR\xceg\xe8\x8a\xd6!%\xb73\ +\x0e\xb1\x8ahMr|\xd3\xbayaM|d\x1b\x81\ +\x9f\x18|;\xe9\xc7\xe6P9\x13\xe0\xd3-\xbbfj\ +\x22\xab\x88\xce%{7\xc4\xe4\xd6\x1b\xe1\xbc\x02\x5c\x93\ +\x1ej\x09\x09\xda\xedP\xbf\x22-\x01\xc7\xa2\xc0\xee\xc2\ +\xbam\xe9\xfb\xc1?c\x12\xac[\x07\xfb9\xe0\xbd\x12\ +\xd5\xc1G\xf6\x19\xd8\xcbP\xf3\xae\x949\xf0\xcfw\x99\ +\xefN?6\xbb\x14\x9f\x10W\xec\x9c\xa0\xde\x5c)v\ +?\x9f\xb7\x94\xb2\xe7:\x18\xcf\x90K\x22\xbc>\xf0)\ +\x80\x9f\x11h\x00\xd7@\xf0\xdfO\xf91\x84\xfc?P\ +\x03\x04|\x1c\x22\xf4\xf5\xaf\xa5\xbf_\x17\xfb\x8a\xc0\xaf\ +)8\xc0\x7f\x07\xef\xa4\xfch\xc5\xed'\xb0\xff\xc07\ +Y\xbc\xce\x17\x8f\xcc\xba\xbe\xa5\xfc\xef\x07\xbf\xde\x89y\ +\xc5\xfcz\xb0\x0f\xa9\xb8N\x9a\xb6h\xfa\xa6\xf7\x19h\ +T\xf0n5\xcc9ycOa<\x94\xfe~\xf0k\ +\x85y\x0a\xe0\x99C\xc7d\xca\xb07\xe9\xfc\x1b\xa8g\ +U\xb4\x05<\xea\xfey\xf1\xef\x8a\x7f?\xf8\x0d\xd1^\ +\x82?\x80?\x0a\xbeGIZ\xa7\x86\x8a\x8b\xb9\xfc[\ +\xcc\xf7-\x22^\x22\xfe\xfdP3\xd5i`\xb1\xb8N\ +\xde\x93\xcbd\x19y\x0f\xc5\xe7\x0f~\xcf\x9f\x9f\x04\xce\ +\xc2\x03:\x9e+\xc9\xfb\x9bb\xba\x17\xac\x17\x00\xfeM\ +\xc9\xf9\xab.\xb6\xaf\xe1Ny\xe1\xfe\xc5\xbc\xc0|S\ +\x80nJ\x7f\xff\xf6!\xc5\xea\xa5\xe5>\xf6\x90\xf0\xdd\ +\xf4\xfa\x11\xaf\x14\xc4\x1f\xc4@\x85\xe2I\xa5\xe3\x1f\xed\ +\x1f\xf0\x0f\xa0y\xc8\xad\xc0\xb9\x08\x92\xc6\xa3p\xbd\xbd\ +1\xc5\xf6/\xef\xd9\x15\xe18\x84\x88\xf7\x0b\xd8\xf5\xe8\ +o\x81\x06!\xbf\x07\xe7\xf9@\xcc\x03\x7f_2\xbb_\ +\xd0\xcf\x04CD=\xa4\xa2\xf7\xf3{\xbd ~S\x18\ +[\xa1c\xeb\xe0\x7f\x84\xfc\x0c\x88\xf3\xe2{\xde6\xad\ +K\xc5;U\x93eN1\xdc\x81\x5c\x02?\xa9\xc8\xf3\ +\x0fy~;\x86\xe2\xde2 _\xf2^\x5c\xa3\xe8\xd4\ +B\x9b\xaa\x1b\xc6?\xc2\xe8,A\x1c2\xeb\x86\x13Y\ +8g\xe1\x18\x02:\xf7\xe0\x8b\x17\x96\x15\x98vK\xc6\ +\x80\xa8\xf7C]\x1c\xc4\xbf\x0b\xe7\x9a\xfe\x83\x92\xe3\xe8\ +\x99\xa2r/ 7\x12\xe2\x18\x10\xa7\x009\x0b\xf1\x11\ +\xf0\xbd\xc13\xa0\x86\xa5p\xfe\x16\xf8\xcaD\xf0>\x81\ +\xf5\xf3\xcf:\x95\xc3\x00\xfc\x16\xea\xeb\x80\xfc\xc6\xf2V\ +`\x80\xcf\x1e\xeaxB\x0c\x0c\xd7\x92E<\x11\xf2\x93\ +\xb0\xbfVD\xdc\x01\xf2\xc5\xc0\x17*&g\x8d\xd6\x91\ +\xa9\xda\x80\xb9\xf7]1\xae3\x5c\x17b\xff6\xe4\xba\ +B\x1cVp\x80\x9f\x1bbU\xc9\x1b\x8c\x8a\xf1\x16Q\ +\x03\xea2\xc1\xbe\x94\xc23\xf8\xfa9\xf5\xd9\xba\x05\xf6\ +E\x83\xdf\x16x5\xd0>\xaeOq}+>;\xd0\ +?\x04\xe2\xc4\x98F\x817\x8b\x88\xcd\x81\xdf\x19jU\ +\x80\x5c\x869\x96\x91\xab\xc7\xb7\x0d\x8ax.Z\x1b\xd4\ +\x82\x85\xb8\x1b\xce\x0f\x82\x18(\x9d\xfb\x0by\xa4\xb8\x9e\ +\x14\xf0`\xc8SC4\x9e\xed\xbb\x0b\xe7\xffB\xedJ\ +\x1cs<<\x93\xf2\xc1JVG\x8eo\x97$\x16\xf1\ +\xac\xce\xb8n\x0d\xc8p*\x8fX\xe0\x19\xc2>?\xc1\ +\xfb\xed\x82y\xd5\x92\xf9\x05\xf9\xfagI\xfd[\xfa\xdc\ +\xe5\xf2\x00_\xff\xe6\xdb\x1fO*\xf8}\x82 h\x7f\ +(\x82\xfdUi\xf6ge\xdb\xdf\xccJ\xf4\xc6\xc0\xbb\ +\xc1O\xa1IP\xbe\x8aB?E\x8d\x92~\x0az\xce\ +\x0d\x100\x11\xbcG\x90\x8e C\x0c\xa4\xd3\x7f\xc3\xa4\ +\xbf\xc3\xff\xee9\x04\xdc\x12\xb8\x11\x7fv\xb9\xf4w\xf8\ +\xef\xe5\x96\xf8\x1e\xe4C >\x0du\xe7 \xa6\x07\xf9\ +'B|\x82+0\xe7b\xdf\x858\x19\xd4\xbc\x05\xdd\ +\x06\xc7\x82\xe9\xdeeX\xbe\xad-\xc6{\xf8\xeb-\xe4\ +C\xa0\x8f\xf0\xfbH\x80L\x86\x5c\xd7\xfc\x08\xbf\xc2\xba\ +3\xa0\xeb\x0a\xd4\x92\xe5\xefU\xe1\xf7!\xf7\x0f\x06<\ +\x03\xea\xc9\xe0\xba\xd7\xd6\xcd\xb1\xbc\x80\xb8\x92\x90\xbe\x9e\ +Q\xf8\xfd\xc2zDo\xb0n\x8c\xf3\xe2-\x04\xe2g\ +\x88_S\xb9\x08\xf9T~\x1b\x15\xdf\x11\xf8>U\x1f\ +\x06x<\xe4d\x95\xc8o\xc49\x08]q\xfdH\x88\ +\x9b'Q9*E\xdf\x07\x1b\x0b\xed7\xd4\xc3\xc3}\ +;\x84\xeb\xe5\xf2\xeb\xb1!\x1d\x02rQh\x1eN}\ +\x1fz\x06B\xdcxe\x0b\xec+H?\xb5\x80\xea\xaf\ +\x22X\xbb\x13\xfd?U\x9f\x93\xc4u\x04i}\x1f\x7f\ +\x1f\xf0\x022\x15\xea{\x81M\x08\xf8\x02\x9d\x18\xea\x99\ +\xa6\xee\x1c\x86\xf5\xf5\xec\xdb{q\xcc\x98\x9b\x9e\x88\xf5\ +OZ\xfee\xa0\x9f\xe9`\x93\xc1\x9cqn\x08]\x03\ +\x12\xf2xA\xbe\xc2w\xf89p \xfb\xa0G\x8a\x00\ +\xbdP\xf4\x8c\xf0\x93q\xde\x1a\xe7\x99C>\x04\xe8f\ +\xd0O\x03\xe4 \xc4z\xa1\x87\x16\xc4[\xa9\xbb\x0a\xc5\ +\xe8\x98:\x0b\xcbu\xb9)\xdb\x07Sz\xa0`\xcc\x16\ +\xeau\x01\x9e\xf8=\xe2\x8a\xcb\x1e>\xfdR\xe7g\xb9\ +\x0e\xb7\x1c\xb2I\xf0\xfc\xc8t~e\x1d\xff\x07\x9d\xab\ +\xf3\x85\ \x00\x00_\x84\ I\ I*\x00\x08\x00\x00\x00\x17\x00\xfe\x00\x04\x00\x01\x00\x00\ @@ -26805,336 +26919,336 @@ qt_resource_struct = b"\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x01\x00\x00\x00+\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x06\x0c\x00\x01\x00\x00\x00\x01\x00\x00\xde\xc4\ -\x00\x00\x01y\x1f\xa4\xb3Q\ -\x00\x00\x16\x86\x00\x00\x00\x00\x00\x01\x00\x05\x97+\ +\x00\x00\x01y\xd2\xb2\xf5B\ +\x00\x00\x16\x86\x00\x00\x00\x00\x00\x01\x00\x05\x9eC\ \x00\x00\x01x\xc7F\xed\xa9\ -\x00\x00\x16:\x00\x00\x00\x00\x00\x01\x00\x05\x95\x7f\ +\x00\x00\x16:\x00\x00\x00\x00\x00\x01\x00\x05\x9c\x97\ \x00\x00\x01x\xc7F\xf0\x97\ -\x00\x00\x16`\x00\x00\x00\x00\x00\x01\x00\x05\x96S\ +\x00\x00\x16`\x00\x00\x00\x00\x00\x01\x00\x05\x9dk\ \x00\x00\x01x\xc7F\xeb4\ -\x00\x00\x16\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x94\x8c\ +\x00\x00\x16\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x9b\xa4\ \x00\x00\x01x\xc7F\xf0\x87\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x1f\x00\x00\x001\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x13\xb4\x00\x00\x00\x00\x00\x01\x00\x05\x80\xf9\ +\x00\x00\x13\xb4\x00\x00\x00\x00\x00\x01\x00\x05\x88\x11\ \x00\x00\x01x\xc7D\x85\xca\ -\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\x83\x8d\ +\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\x8a\xa5\ \x00\x00\x01x\xc7D\x85\xcc\ -\x00\x00\x15v\x00\x00\x00\x00\x00\x01\x00\x05\x90T\ +\x00\x00\x15v\x00\x00\x00\x00\x00\x01\x00\x05\x97l\ \x00\x00\x01x\xc7D\x85\xc3\ -\x00\x00\x12\xf0\x00\x00\x00\x00\x00\x01\x00\x05z\x87\ +\x00\x00\x12\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x81\x9f\ \x00\x00\x01x\xc7D\x83\xc5\ -\x00\x00\x12|\x00\x00\x00\x00\x00\x01\x00\x05w\x97\ +\x00\x00\x12|\x00\x00\x00\x00\x00\x01\x00\x05~\xaf\ \x00\x00\x01x\xc7D\x85\xbb\ -\x00\x00\x152\x00\x00\x00\x00\x00\x01\x00\x05\x8d\xc0\ +\x00\x00\x152\x00\x00\x00\x00\x00\x01\x00\x05\x94\xd8\ \x00\x00\x01x\xc7D\x85e\ -\x00\x00\x12\x9e\x00\x00\x00\x00\x00\x01\x00\x05x\xe1\ +\x00\x00\x12\x9e\x00\x00\x00\x00\x00\x01\x00\x05\x7f\xf9\ \x00\x00\x01x\xc7D\x85\xd0\ -\x00\x00\x14\xf6\x00\x00\x00\x00\x00\x01\x00\x05\x8b,\ +\x00\x00\x14\xf6\x00\x00\x00\x00\x00\x01\x00\x05\x92D\ \x00\x00\x01x\xc7D\x84\xb4\ -\x00\x00\x11\xce\x00\x01\x00\x00\x00\x01\x00\x05s\x95\ +\x00\x00\x11\xce\x00\x01\x00\x00\x00\x01\x00\x05z\xad\ \x00\x00\x01x\xc7D\x83\xca\ -\x00\x00\x12(\x00\x00\x00\x00\x00\x01\x00\x05u\xe9\ +\x00\x00\x12(\x00\x00\x00\x00\x00\x01\x00\x05}\x01\ \x00\x00\x01x\xc7D\x85\xd2\ -\x00\x00\x12\x0c\x00\x01\x00\x00\x00\x01\x00\x05u>\ +\x00\x00\x12\x0c\x00\x01\x00\x00\x00\x01\x00\x05|V\ \x00\x00\x01x\xc7D\x84\x12\ -\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05s\xf4\ +\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05{\x0c\ \x00\x00\x01x\xc7D\x84\x12\ -\x00\x00\x12\xc8\x00\x01\x00\x00\x00\x01\x00\x05z+\ +\x00\x00\x12\xc8\x00\x01\x00\x00\x00\x01\x00\x05\x81C\ \x00\x00\x01x\xc7D\x83\xcf\ -\x00\x00\x13\x12\x00\x00\x00\x00\x00\x01\x00\x05{\xd1\ +\x00\x00\x13\x12\x00\x00\x00\x00\x00\x01\x00\x05\x82\xe9\ \x00\x00\x01x\xc7D\x85\xc7\ -\x00\x00\x13\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x82C\ +\x00\x00\x13\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x89[\ \x00\x00\x01x\xc7D\x85\xc8\ -\x00\x00\x13\x90\x00\x00\x00\x00\x00\x01\x00\x05\x7f\xaf\ +\x00\x00\x13\x90\x00\x00\x00\x00\x00\x01\x00\x05\x86\xc7\ \x00\x00\x01x\xc7D\x85d\ -\x00\x00\x13B\x00\x00\x00\x00\x00\x01\x00\x05}\x1b\ +\x00\x00\x13B\x00\x00\x00\x00\x00\x01\x00\x05\x843\ \x00\x00\x01x\xc7D\x85\xce\ -\x00\x00\x12\x5c\x00\x01\x00\x00\x00\x01\x00\x05w3\ +\x00\x00\x12\x5c\x00\x01\x00\x00\x00\x01\x00\x05~K\ \x00\x00\x01x\xc7D\x85-\ -\x00\x00\x15\xc4\x00\x01\x00\x00\x00\x01\x00\x05\x92\xe8\ +\x00\x00\x15\xc4\x00\x01\x00\x00\x00\x01\x00\x05\x9a\x00\ \x00\x00\x01x\xc7D\x83\xcb\ -\x00\x00\x14.\x00\x01\x00\x00\x00\x01\x00\x05\x84\xd7\ +\x00\x00\x14.\x00\x01\x00\x00\x00\x01\x00\x05\x8b\xef\ \x00\x00\x01x\xc7D\x85\xb9\ -\x00\x00\x14Z\x00\x01\x00\x00\x00\x01\x00\x05\x85\xa4\ +\x00\x00\x14Z\x00\x01\x00\x00\x00\x01\x00\x05\x8c\xbc\ \x00\x00\x01x\xc7D\x83\xcc\ -\x00\x00\x13h\x00\x00\x00\x00\x00\x01\x00\x05~e\ +\x00\x00\x13h\x00\x00\x00\x00\x00\x01\x00\x05\x85}\ \x00\x00\x01x\xc7D\x85d\ -\x00\x00\x14x\x00\x00\x00\x00\x00\x01\x00\x05\x86\x04\ +\x00\x00\x14x\x00\x00\x00\x00\x00\x01\x00\x05\x8d\x1c\ \x00\x00\x01x\xc7D\x85\xb6\ -\x00\x00\x15\x94\x00\x00\x00\x00\x00\x01\x00\x05\x91\x9e\ +\x00\x00\x15\x94\x00\x00\x00\x00\x00\x01\x00\x05\x98\xb6\ \x00\x00\x01x\xc7D\x85\xd4\ -\x00\x00\x14\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x87N\ +\x00\x00\x14\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x8ef\ \x00\x00\x01x\xc7D\x84\x0b\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\x88\x98\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\x8f\xb0\ \x00\x00\x01x\xc7D\x84\x0d\ -\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\x89\xe2\ +\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\x90\xfa\ \x00\x00\x01x\xc7D\x84\x0e\ -\x00\x00\x15\x14\x00\x00\x00\x00\x00\x01\x00\x05\x8cv\ +\x00\x00\x15\x14\x00\x00\x00\x00\x00\x01\x00\x05\x93\x8e\ \x00\x00\x01x\xc7D\x84\x0e\ -\x00\x00\x15X\x00\x00\x00\x00\x00\x01\x00\x05\x8f\x0a\ +\x00\x00\x15X\x00\x00\x00\x00\x00\x01\x00\x05\x96\x22\ \x00\x00\x01x\xc7D\x84\x0f\ -\x00\x00\x11\xba\x00\x01\x00\x00\x00\x01\x00\x05s6\ +\x00\x00\x11\xba\x00\x01\x00\x00\x00\x01\x00\x05zN\ \x00\x00\x01x\xc7D\x84\xb5\ -\x00\x00\x15\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x93B\ +\x00\x00\x15\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x9aZ\ \x00\x00\x01x\xc7D\x85\xc5\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x08\x00\x00\x00Q\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00$\x98\x00\x00\x00\x00\x00\x01\x00\x06+\x0e\ +\x00\x00$\x98\x00\x00\x00\x00\x00\x01\x00\x062&\ \x00\x00\x01y+\x8f\x94\x0d\ -\x00\x00#\xa8\x00\x00\x00\x00\x00\x01\x00\x06\x16\xea\ +\x00\x00#\xa8\x00\x00\x00\x00\x00\x01\x00\x06\x1e\x02\ \x00\x00\x01y+\x8f\x94\x0b\ -\x00\x00$\x0a\x00\x00\x00\x00\x00\x01\x00\x06\x1e\xba\ +\x00\x00$\x0a\x00\x00\x00\x00\x00\x01\x00\x06%\xd2\ \x00\x00\x01y+\x8f\x94\x0b\ -\x00\x00$p\x00\x00\x00\x00\x00\x01\x00\x06&\x8c\ +\x00\x00$p\x00\x00\x00\x00\x00\x01\x00\x06-\xa4\ \x00\x00\x01x\xc7F\xf7d\ -\x00\x00#\xe6\x00\x00\x00\x00\x00\x01\x00\x06\x1a8\ +\x00\x00#\xe6\x00\x00\x00\x00\x00\x01\x00\x06!P\ \x00\x00\x01x\xc7F\xf6\xff\ -\x00\x00#D\x00\x00\x00\x00\x00\x01\x00\x06\x0f\x1e\ +\x00\x00#D\x00\x00\x00\x00\x00\x01\x00\x06\x166\ \x00\x00\x01x\xc7F\xf6\xed\ -\x00\x00$J\x00\x00\x00\x00\x00\x01\x00\x06\x22\x0a\ +\x00\x00$J\x00\x00\x00\x00\x00\x01\x00\x06)\x22\ \x00\x00\x01x\xc7F\xf6\xf0\ -\x00\x00#j\x00\x00\x00\x00\x00\x01\x00\x06\x13\xa0\ +\x00\x00#j\x00\x00\x00\x00\x00\x01\x00\x06\x1a\xb8\ \x00\x00\x01y+\x8f\x94\x0c\ \x00\x00\x16\xba\x00\x02\x00\x00\x009\x00\x00\x00Z\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00 :\x00\x00\x00\x00\x00\x01\x00\x05\xf1\xf4\ +\x00\x00 :\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x0c\ \x00\x00\x01x\xc7F\xf0|\ -\x00\x00#\x0e\x00\x00\x00\x00\x00\x01\x00\x06\x0a1\ +\x00\x00#\x0e\x00\x00\x00\x00\x00\x01\x00\x06\x11I\ \x00\x00\x01x\xc7F\xed\xa1\ -\x00\x00\x1e\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xe2\xb6\ +\x00\x00\x1e\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xe9\xce\ \x00\x00\x01x\xc7F\xef\xf0\ -\x00\x00\x19\x08\x00\x00\x00\x00\x00\x01\x00\x05\xaf\x1d\ +\x00\x00\x19\x08\x00\x00\x00\x00\x00\x01\x00\x05\xb65\ \x00\x00\x01x\xc7F\xf0\xa1\ -\x00\x00!\xbc\x00\x00\x00\x00\x00\x01\x00\x05\xff\xfd\ +\x00\x00!\xbc\x00\x00\x00\x00\x00\x01\x00\x06\x07\x15\ \x00\x00\x01x\xc7F\xf0W\ -\x00\x00\x17@\x00\x00\x00\x00\x00\x01\x00\x05\x9d\xdd\ +\x00\x00\x17@\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xf5\ \x00\x00\x01x\xc7F\xed\xd6\ -\x00\x00\x19\xae\x00\x00\x00\x00\x00\x01\x00\x05\xb2\xec\ +\x00\x00\x19\xae\x00\x00\x00\x00\x00\x01\x00\x05\xba\x04\ \x00\x00\x01x\xc7F\xe9{\ -\x00\x00\x22b\x00\x00\x00\x00\x00\x01\x00\x06\x04c\ +\x00\x00\x22b\x00\x00\x00\x00\x00\x01\x00\x06\x0b{\ \x00\x00\x01x\xc7F\xef\xab\ -\x00\x00\x1b\xde\x00\x00\x00\x00\x00\x01\x00\x05\xc5\xad\ +\x00\x00\x1b\xde\x00\x00\x00\x00\x00\x01\x00\x05\xcc\xc5\ \x00\x00\x01x\xc7F\xcb~\ -\x00\x00\x18,\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x22\ +\x00\x00\x18,\x00\x00\x00\x00\x00\x01\x00\x05\xac:\ \x00\x00\x01x\xc7F\xf0\x90\ -\x00\x00\x1a\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xba\xbf\ +\x00\x00\x1a\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xc1\xd7\ \x00\x00\x01x\xc7F\xe9@\ -\x00\x00\x1e<\x00\x00\x00\x00\x00\x01\x00\x05\xdcu\ +\x00\x00\x1e<\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x8d\ \x00\x00\x01x\xc7F\xf0=\ -\x00\x00 \xe0\x00\x00\x00\x00\x00\x01\x00\x05\xf6-\ +\x00\x00 \xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfdE\ \x00\x00\x01x\xc7F\xf0\x03\ -\x00\x00\x1c\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd0B\ +\x00\x00\x1c\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd7Z\ \x00\x00\x01x\xc7F\xed\xd8\ -\x00\x00\x1f\x8e\x00\x00\x00\x00\x00\x01\x00\x05\xec\xea\ +\x00\x00\x1f\x8e\x00\x00\x00\x00\x00\x01\x00\x05\xf4\x02\ \x00\x00\x01x\xc7F\xed\xda\ -\x00\x00\x1cT\x00\x00\x00\x00\x00\x01\x00\x05\xcb\x82\ +\x00\x00\x1cT\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x9a\ \x00\x00\x01x\xc7F\xcc\x09\ -\x00\x00\x18b\x00\x00\x00\x00\x00\x01\x00\x05\xa6\x14\ +\x00\x00\x18b\x00\x00\x00\x00\x00\x01\x00\x05\xad,\ \x00\x00\x01x\xc7F\xe9f\ -\x00\x00\x1e\xe8\x00\x00\x00\x00\x00\x01\x00\x05\xe4c\ +\x00\x00\x1e\xe8\x00\x00\x00\x00\x00\x01\x00\x05\xeb{\ \x00\x00\x01x\xc7F\xe9\x86\ -\x00\x00\x1b\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xbc\x0d\ +\x00\x00\x1b\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xc3%\ \x00\x00\x01x\xc7F\xcb\xfe\ -\x00\x00\x1d\x96\x00\x00\x00\x00\x00\x01\x00\x05\xd7\x13\ +\x00\x00\x1d\x96\x00\x00\x00\x00\x00\x01\x00\x05\xde+\ \x00\x00\x01x\xc7F\xe9\x82\ -\x00\x00!\x16\x00\x00\x00\x00\x00\x01\x00\x05\xf7\xeb\ +\x00\x00!\x16\x00\x00\x00\x00\x00\x01\x00\x05\xff\x03\ \x00\x00\x01x\xc7F\xe7N\ -\x00\x00\x17v\x00\x00\x00\x00\x00\x01\x00\x05\x9f\xff\ +\x00\x00\x17v\x00\x00\x00\x00\x00\x01\x00\x05\xa7\x17\ \x00\x00\x01x\xc7F\xe97\ -\x00\x00\x1f\xc4\x00\x00\x00\x00\x00\x01\x00\x05\xee\xfa\ +\x00\x00\x1f\xc4\x00\x00\x00\x00\x00\x01\x00\x05\xf6\x12\ \x00\x00\x01x\xc7F\xe7\xa5\ -\x00\x00\x22\x98\x00\x00\x00\x00\x00\x01\x00\x06\x06c\ +\x00\x00\x22\x98\x00\x00\x00\x00\x00\x01\x00\x06\x0d{\ \x00\x00\x01x\xc7F\xe6\xb8\ -\x00\x00\x19\xe4\x00\x00\x00\x00\x00\x01\x00\x05\xb4\x0b\ +\x00\x00\x19\xe4\x00\x00\x00\x00\x00\x01\x00\x05\xbb#\ \x00\x00\x01x\xc7F\xef\xeb\ -\x00\x00\x1d\x16\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xe3\ +\x00\x00\x1d\x16\x00\x00\x00\x00\x00\x01\x00\x05\xda\xfb\ \x00\x00\x01x\xc7F\xef\xe2\ -\x00\x00\x1bn\x00\x00\x00\x00\x00\x01\x00\x05\xc1)\ +\x00\x00\x1bn\x00\x00\x00\x00\x00\x01\x00\x05\xc8A\ \x00\x00\x01x\xc7F\xef\x93\ -\x00\x00\x1af\x00\x00\x00\x00\x00\x01\x00\x05\xb7h\ +\x00\x00\x1af\x00\x00\x00\x00\x00\x01\x00\x05\xbe\x80\ \x00\x00\x01x\xc7F\xef\xee\ -\x00\x00\x19>\x00\x00\x00\x00\x00\x01\x00\x05\xaf\xef\ +\x00\x00\x19>\x00\x00\x00\x00\x00\x01\x00\x05\xb7\x07\ \x00\x00\x01x\xc7F\xf00\ -\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xf2\ +\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x05\xaa\x0a\ \x00\x00\x01x\xc7F\xed\xd3\ -\x00\x00\x16\xd0\x00\x00\x00\x00\x00\x01\x00\x05\x9b\x19\ +\x00\x00\x16\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xa21\ \x00\x00\x01x\xc7F\xef\xe6\ -\x00\x00!\xf2\x00\x00\x00\x00\x00\x01\x00\x06\x01P\ +\x00\x00!\xf2\x00\x00\x00\x00\x00\x01\x00\x06\x08h\ \x00\x00\x01x\xc7F\xf0f\ -\x00\x00 p\x00\x00\x00\x00\x00\x01\x00\x05\xf2\xfb\ +\x00\x00 p\x00\x00\x00\x00\x00\x01\x00\x05\xfa\x13\ \x00\x00\x01x\xc7F\xf0k\ -\x00\x00\x1f\x1e\x00\x00\x00\x00\x00\x01\x00\x05\xe5k\ +\x00\x00\x1f\x1e\x00\x00\x00\x00\x00\x01\x00\x05\xec\x83\ \x00\x00\x01x\xc7F\xed\xc2\ -\x00\x00\x1d\xcc\x00\x00\x00\x00\x00\x01\x00\x05\xd8!\ +\x00\x00\x1d\xcc\x00\x00\x00\x00\x00\x01\x00\x05\xdf9\ \x00\x00\x01x\xc7F\xf0\x0e\ -\x00\x00\x1a&\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xde\ +\x00\x00\x1a&\x00\x00\x00\x00\x00\x01\x00\x05\xbc\xf6\ \x00\x00\x01x\xc7F\xf0%\ -\x00\x00\x18\x98\x00\x00\x00\x00\x00\x01\x00\x05\xa7M\ +\x00\x00\x18\x98\x00\x00\x00\x00\x00\x01\x00\x05\xaee\ \x00\x00\x01x\xc7F\xf0I\ -\x00\x00\x17\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa1s\ +\x00\x00\x17\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa8\x8b\ \x00\x00\x01x\xc7F\xf0,\ -\x00\x00\x22\xce\x00\x00\x00\x00\x00\x01\x00\x06\x08\x1f\ +\x00\x00\x22\xce\x00\x00\x00\x00\x00\x01\x00\x06\x0f7\ \x00\x00\x01x\xc7F\xed\xdd\ -\x00\x00!|\x00\x00\x00\x00\x00\x01\x00\x05\xfe\x91\ +\x00\x00!|\x00\x00\x00\x00\x00\x01\x00\x06\x05\xa9\ \x00\x00\x01x\xc7F\xf04\ -\x00\x00\x1f\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xf0{\ +\x00\x00\x1f\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xf7\x93\ \x00\x00\x01x\xc7F\xf0*\ -\x00\x00\x1er\x00\x00\x00\x00\x00\x01\x00\x05\xdd\xd6\ +\x00\x00\x1er\x00\x00\x00\x00\x00\x01\x00\x05\xe4\xee\ \x00\x00\x01x\xc7F\xed\xc6\ -\x00\x00\x1dV\x00\x00\x00\x00\x00\x01\x00\x05\xd5\xb7\ +\x00\x00\x1dV\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xcf\ \x00\x00\x01x\xc7F\xf0@\ -\x00\x00\x1c\x14\x00\x00\x00\x00\x00\x01\x00\x05\xca)\ +\x00\x00\x1c\x14\x00\x00\x00\x00\x00\x01\x00\x05\xd1A\ \x00\x00\x01x\xc7F\xf0K\ -\x00\x00\x1bB\x00\x00\x00\x00\x00\x01\x00\x05\xbe&\ +\x00\x00\x1bB\x00\x00\x00\x00\x00\x01\x00\x05\xc5>\ \x00\x00\x01x\xc7F\xed\xcb\ -\x00\x00\x1e\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xd9\xa3\ +\x00\x00\x1e\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xe0\xbb\ \x00\x00\x01x\xc7F\xed\xc8\ -\x00\x00 \xb0\x00\x00\x00\x00\x00\x01\x00\x05\xf4(\ +\x00\x00 \xb0\x00\x00\x00\x00\x00\x01\x00\x05\xfb@\ \x00\x00\x01x\xc7F\xef\xa6\ -\x00\x00\x1c\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x7f\ +\x00\x00\x1c\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xd4\x97\ \x00\x00\x01x\xc7F\xed\xce\ -\x00\x00\x17\x10\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xd8\ +\x00\x00\x17\x10\x00\x00\x00\x00\x00\x01\x00\x05\xa3\xf0\ \x00\x00\x01x\xc7F\xf0\x80\ -\x00\x00\x1f^\x00\x00\x00\x00\x00\x01\x00\x05\xeb\x8c\ +\x00\x00\x1f^\x00\x00\x00\x00\x00\x01\x00\x05\xf2\xa4\ \x00\x00\x01x\xc7F\xf0:\ -\x00\x00\x19~\x00\x00\x00\x00\x00\x01\x00\x05\xb1p\ +\x00\x00\x19~\x00\x00\x00\x00\x00\x01\x00\x05\xb8\x88\ \x00\x00\x01x\xc7F\xf0(\ -\x00\x00\x222\x00\x00\x00\x00\x00\x01\x00\x06\x02\x83\ +\x00\x00\x222\x00\x00\x00\x00\x00\x01\x00\x06\x09\x9b\ \x00\x00\x01x\xc7F\xef\xdd\ -\x00\x00\x1b\xae\x00\x00\x00\x00\x00\x01\x00\x05\xc3\x18\ +\x00\x00\x1b\xae\x00\x00\x00\x00\x00\x01\x00\x05\xca0\ \x00\x00\x01x\xc7F\xed\xd1\ -\x00\x00!L\x00\x00\x00\x00\x00\x01\x00\x05\xf9\xa7\ +\x00\x00!L\x00\x00\x00\x00\x00\x01\x00\x06\x00\xbf\ \x00\x00\x01y+\x8f\x93{\ -\x00\x00\x1a\xa6\x00\x00\x00\x00\x00\x01\x00\x05\xb92\ +\x00\x00\x1a\xa6\x00\x00\x00\x00\x00\x01\x00\x05\xc0J\ \x00\x00\x01x\xc7F\xf0\x0c\ -\x00\x00\x18\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xa5\ +\x00\x00\x18\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xaf\xbd\ \x00\x00\x01x\xc7F\xed\xa4\ -\x00\x00\x1c\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x83\ +\x00\x00\x1c\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xd9\x9b\ \x00\x00\x01x\xc7F\xe9<\ -\x00\x00\x0e\xba\x00\x00\x00\x00\x00\x01\x00\x04(&\ +\x00\x00\x0e\xba\x00\x00\x00\x00\x00\x01\x00\x04/>\ \x00\x00\x01y+\x8f\x94\x12\ -\x00\x00\x0d\x0a\x00\x00\x00\x00\x00\x01\x00\x03\x91\x87\ +\x00\x00\x0d\x0a\x00\x00\x00\x00\x00\x01\x00\x03\x98\x9f\ \x00\x00\x01y+\x8f\x93\xdc\ -\x00\x00\x0e\x80\x00\x00\x00\x00\x00\x01\x00\x04#\x0e\ +\x00\x00\x0e\x80\x00\x00\x00\x00\x00\x01\x00\x04*&\ \x00\x00\x01y+\x8f\x94\x08\ -\x00\x00\x0d~\x00\x00\x00\x00\x00\x01\x00\x03\xf4\x82\ +\x00\x00\x0d~\x00\x00\x00\x00\x00\x01\x00\x03\xfb\x9a\ \x00\x00\x01y+\x8f\x93\xcd\ -\x00\x00\x09\xf6\x00\x00\x00\x00\x00\x01\x00\x032\x17\ +\x00\x00\x09\xf6\x00\x00\x00\x00\x00\x01\x00\x039/\ \x00\x00\x01y+\x8f\x94\x10\ -\x00\x00\x09f\x00\x01\x00\x00\x00\x01\x00\x02i(\ +\x00\x00\x09f\x00\x01\x00\x00\x00\x01\x00\x02p@\ \x00\x00\x01x\xc7F\xf5\xfe\ -\x00\x00\x07\x12\x00\x00\x00\x00\x00\x01\x00\x01\x87\xf3\ +\x00\x00\x07\x12\x00\x00\x00\x00\x00\x01\x00\x01\x8f\x0b\ \x00\x00\x01y+\x8f\x93\xce\ -\x00\x00\x08:\x00\x00\x00\x00\x00\x01\x00\x01\xa3\x1b\ +\x00\x00\x08:\x00\x00\x00\x00\x00\x01\x00\x01\xaa3\ \x00\x00\x01y+\x8f\x94\x0a\ -\x00\x00\x0f$\x00\x00\x00\x00\x00\x01\x00\x04jD\ +\x00\x00\x0f$\x00\x00\x00\x00\x00\x01\x00\x04q\x5c\ \x00\x00\x01x\xc7F\xf6G\ -\x00\x00\x0e\x10\x00\x00\x00\x00\x00\x01\x00\x04\x15\xa7\ +\x00\x00\x0e\x10\x00\x00\x00\x00\x00\x01\x00\x04\x1c\xbf\ \x00\x00\x01y+\x8f\x93\xe3\ -\x00\x00\x08\xd2\x00\x00\x00\x00\x00\x01\x00\x02^f\ +\x00\x00\x08\xd2\x00\x00\x00\x00\x00\x01\x00\x02e~\ \x00\x00\x01y+\x8f\x93\xd6\ -\x00\x00\x0eL\x00\x00\x00\x00\x00\x01\x00\x04\x1d\xe2\ +\x00\x00\x0eL\x00\x00\x00\x00\x00\x01\x00\x04$\xfa\ \x00\x00\x01y+\x8f\x94\x09\ -\x00\x00\x09*\x00\x00\x00\x00\x00\x01\x00\x02f\x89\ +\x00\x00\x09*\x00\x00\x00\x00\x00\x01\x00\x02m\xa1\ \x00\x00\x01y+\x8f\x94\x07\ -\x00\x00\x0c \x00\x00\x00\x00\x00\x01\x00\x03u!\ +\x00\x00\x0c \x00\x00\x00\x00\x00\x01\x00\x03|9\ \x00\x00\x01y+\x8f\x93\xd5\ -\x00\x00\x07z\x00\x00\x00\x00\x00\x01\x00\x01\x96/\ +\x00\x00\x07z\x00\x00\x00\x00\x00\x01\x00\x01\x9dG\ \x00\x00\x01y+\x8f\x93\xdf\ -\x00\x00\x08x\x00\x00\x00\x00\x00\x01\x00\x01\xbe\xd0\ +\x00\x00\x08x\x00\x00\x00\x00\x00\x01\x00\x01\xc5\xe8\ \x00\x00\x01x\xc7F\xf6*\ -\x00\x00\x0f\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x87Q\ +\x00\x00\x0f\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x8ei\ \x00\x00\x01y+\x8f\x94\x12\ -\x00\x00\x08T\x00\x00\x00\x00\x00\x01\x00\x01\xa9(\ +\x00\x00\x08T\x00\x00\x00\x00\x00\x01\x00\x01\xb0@\ \x00\x00\x01x\xc7F\xf61\ -\x00\x00\x0cb\x00\x00\x00\x00\x00\x01\x00\x03\x81c\ +\x00\x00\x0cb\x00\x00\x00\x00\x00\x01\x00\x03\x88{\ \x00\x00\x01y+\x8f\x93\xda\ -\x00\x00\x07B\x00\x00\x00\x00\x00\x01\x00\x01\x8c\x97\ +\x00\x00\x07B\x00\x00\x00\x00\x00\x01\x00\x01\x93\xaf\ \x00\x00\x01y+\x8f\x94\x11\ -\x00\x00\x08\xf8\x00\x00\x00\x00\x00\x01\x00\x02b\x14\ +\x00\x00\x08\xf8\x00\x00\x00\x00\x00\x01\x00\x02i,\ \x00\x00\x01y+\x8f\x93\xce\ -\x00\x00\x0a\xc0\x00\x00\x00\x00\x00\x01\x00\x03N\xe6\ +\x00\x00\x0a\xc0\x00\x00\x00\x00\x00\x01\x00\x03U\xfe\ \x00\x00\x01y+\x8f\x93\xd8\ -\x00\x00\x09\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x12$\ +\x00\x00\x09\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x19<\ \x00\x00\x01x\xc7F\xf5\xf8\ -\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x01|\xed\ +\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x01\x84\x05\ \x00\x00\x01y+\x8f\x93\xcc\ -\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x03R\x98\ +\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x03Y\xb0\ \x00\x00\x01y+\x8f\x93\xdb\ -\x00\x00\x0d\x98\x00\x00\x00\x00\x00\x01\x00\x03\xf8 \ +\x00\x00\x0d\x98\x00\x00\x00\x00\x00\x01\x00\x03\xff8\ \x00\x00\x01y+\x8f\x93\xdc\ -\x00\x00\x0c<\x00\x00\x00\x00\x00\x01\x00\x03|B\ +\x00\x00\x0c<\x00\x00\x00\x00\x00\x01\x00\x03\x83Z\ \x00\x00\x01y+\x8f\x93\xde\ -\x00\x00\x0bZ\x00\x00\x00\x00\x00\x01\x00\x03W)\ +\x00\x00\x0bZ\x00\x00\x00\x00\x00\x01\x00\x03^A\ \x00\x00\x01y+\x8f\x93\xd8\ -\x00\x00\x0a\x94\x00\x00\x00\x00\x00\x01\x00\x039\x16\ +\x00\x00\x0a\x94\x00\x00\x00\x00\x00\x01\x00\x03@.\ \x00\x00\x01x\xc7F\xf60\ -\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04`\xcc\ +\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04g\xe4\ \x00\x00\x01y+\x8f\x94\x13\ -\x00\x00\x0fN\x00\x00\x00\x00\x00\x01\x00\x04\x7f\x0c\ +\x00\x00\x0fN\x00\x00\x00\x00\x00\x01\x00\x04\x86$\ \x00\x00\x01y+\x8f\x93\xe2\ -\x00\x00\x0dD\x00\x00\x00\x00\x00\x01\x00\x03\x95\x12\ +\x00\x00\x0dD\x00\x00\x00\x00\x00\x01\x00\x03\x9c*\ \x00\x00\x01x\xc7F\xf6\x18\ -\x00\x00\x0a0\x00\x00\x00\x00\x00\x01\x00\x034\xb6\ +\x00\x00\x0a0\x00\x00\x00\x00\x00\x01\x00\x03;\xce\ \x00\x00\x01y+\x8f\x93\xd9\ -\x00\x00\x060\x00\x00\x00\x00\x00\x01\x00\x01\x1a\xc3\ +\x00\x00\x060\x00\x00\x00\x00\x00\x01\x00\x01!\xdb\ \x00\x00\x01x\xc7F\xf6\x1b\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03p\xae\ +\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03w\xc6\ \x00\x00\x01y+\x8f\x93\xd7\ -\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x01\x84\xba\ +\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x01\x8b\xd2\ \x00\x00\x01y+\x8f\x93\xfd\ -\x00\x00\x07\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x9bR\ +\x00\x00\x07\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xa2j\ \x00\x00\x01y+\x8f\x94\x0f\ -\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\x90\xf8\ +\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\x98\x10\ \x00\x00\x01y+\x8f\x93\xcb\ -\x00\x00\x0e\xd0\x00\x01\x00\x00\x00\x01\x00\x04/\x99\ +\x00\x00\x0e\xd0\x00\x01\x00\x00\x00\x01\x00\x046\xb1\ \x00\x00\x01x\xc7F\xf5\xf3\ -\x00\x00\x06^\x00\x00\x00\x00\x00\x01\x00\x01zK\ +\x00\x00\x06^\x00\x00\x00\x00\x00\x01\x00\x01\x81c\ \x00\x00\x01y+\x8f\x94\x06\ -\x00\x00\x09\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x0c\xa3\ +\x00\x00\x09\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x13\xbb\ \x00\x00\x01y+\x8f\x93\xd0\ -\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x93\xb2\ +\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x9a\xca\ \x00\x00\x01x\xc7F\xf5\xf0\ -\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\x8c\x97\ +\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\x93\xaf\ \x00\x00\x01x\xc7F\xf6$\ -\x00\x00\x0d\xe4\x00\x01\x00\x00\x00\x01\x00\x03\xfb\xab\ +\x00\x00\x0d\xe4\x00\x01\x00\x00\x00\x01\x00\x04\x02\xc3\ \x00\x00\x01x\xc7F\xf6\x04\ -\x00\x00\x0c\xec\x00\x00\x00\x00\x00\x01\x00\x03\x8a\xa7\ +\x00\x00\x0c\xec\x00\x00\x00\x00\x00\x01\x00\x03\x91\xbf\ \x00\x00\x01y+\x8f\x94\x0e\ -\x00\x00\x0b\xaa\x00\x00\x00\x00\x00\x01\x00\x03[\xa0\ +\x00\x00\x0b\xaa\x00\x00\x00\x00\x00\x01\x00\x03b\xb8\ \x00\x00\x01x\xc7F\xf6L\ -\x00\x00\x06\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x7f\x95\ +\x00\x00\x06\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x86\xad\ \x00\x00\x01y+\x8f\x93\xe0\ -\x00\x00\x0c\xb0\x00\x00\x00\x00\x00\x01\x00\x03\x86\x07\ +\x00\x00\x0c\xb0\x00\x00\x00\x00\x00\x01\x00\x03\x8d\x1f\ \x00\x00\x01y+\x8f\x93\xdd\ -\x00\x00\x07\xea\x00\x00\x00\x00\x00\x01\x00\x01\x9d\xf4\ +\x00\x00\x07\xea\x00\x00\x00\x00\x00\x01\x00\x01\xa5\x0c\ \x00\x00\x01y+\x8f\x93\xe1\ -\x00\x00\x08\xb4\x00\x00\x00\x00\x00\x01\x00\x02H\xcc\ +\x00\x00\x08\xb4\x00\x00\x00\x00\x00\x01\x00\x02O\xe4\ \x00\x00\x01x\xc7F\xf6D\ -\x00\x00\x11\x06\x00\x00\x00\x00\x00\x01\x00\x05@\xf3\ +\x00\x00\x11\x06\x00\x00\x00\x00\x00\x01\x00\x05H\x0b\ \x00\x00\x01y+\x8f\x93\xd4\ -\x00\x00\x11\x88\x00\x00\x00\x00\x00\x01\x00\x05k\xae\ +\x00\x00\x11\x88\x00\x00\x00\x00\x00\x01\x00\x05r\xc6\ \x00\x00\x01y+\x8f\x93\xd1\ -\x00\x00\x11b\x00\x00\x00\x00\x00\x01\x00\x05Yl\ +\x00\x00\x11b\x00\x00\x00\x00\x00\x01\x00\x05`\x84\ \x00\x00\x01y+\x8f\x93\xcf\ -\x00\x00\x11\xa0\x00\x00\x00\x00\x00\x01\x00\x05o\xc9\ +\x00\x00\x11\xa0\x00\x00\x00\x00\x00\x01\x00\x05v\xe1\ \x00\x00\x01y+\x8f\x93\xd2\ -\x00\x00\x10\x94\x00\x00\x00\x00\x00\x01\x00\x059\xf7\ +\x00\x00\x10\x94\x00\x00\x00\x00\x00\x01\x00\x05A\x0f\ \x00\x00\x01x\xc7F\xf8/\ -\x00\x00\x106\x00\x00\x00\x00\x00\x01\x00\x05!\xc2\ +\x00\x00\x106\x00\x00\x00\x00\x00\x01\x00\x05(\xda\ \x00\x00\x01x\xc7F\xf8,\ -\x00\x00\x11 \x00\x00\x00\x00\x00\x01\x00\x05F\xfd\ +\x00\x00\x11 \x00\x00\x00\x00\x00\x01\x00\x05N\x15\ \x00\x00\x01x\xc7F\xf83\ -\x00\x00\x10\xdc\x00\x00\x00\x00\x00\x01\x00\x05@%\ +\x00\x00\x10\xdc\x00\x00\x00\x00\x00\x01\x00\x05G=\ \x00\x00\x01x\xc7F\xf8(\ -\x00\x00\x10`\x00\x00\x00\x00\x00\x01\x00\x05\x22O\ +\x00\x00\x10`\x00\x00\x00\x00\x00\x01\x00\x05)g\ \x00\x00\x01y+\x8f\x93\xd3\ -\x00\x00\x11J\x00\x00\x00\x00\x00\x01\x00\x05G\xa1\ +\x00\x00\x11J\x00\x00\x00\x00\x00\x01\x00\x05N\xb9\ \x00\x00\x01y+\x8f\x93\xca\ -\x00\x00\x10z\x00\x00\x00\x00\x00\x01\x00\x0509\ +\x00\x00\x10z\x00\x00\x00\x00\x00\x01\x00\x057Q\ \x00\x00\x01y+\x8f\x93\xc9\ -\x00\x00\x10\xbe\x00\x00\x00\x00\x00\x01\x00\x05:\xa6\ +\x00\x00\x10\xbe\x00\x00\x00\x00\x00\x01\x00\x05A\xbe\ \x00\x00\x01y+\x8f\x93\xe3\ " diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py index b0c3c9c1b3..8152d2addc 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py @@ -12,10 +12,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import boto3 from botocore.paginate import (PageIterator, Paginator) from botocore.client import BaseClient -from botocore.exceptions import ClientError +from botocore.exceptions import (ClientError, ConfigNotFound, NoCredentialsError, ProfileNotFound) from typing import Dict, List -from model import (constants, error_messages) +from model import error_messages from model.basic_resource_attributes import (BasicResourceAttributes, BasicResourceAttributesBuilder) """ @@ -65,8 +65,11 @@ def _initialize_boto3_aws_client(service: str, region: str = "") -> BaseClient: def setup_default_session(profile: str) -> None: - global default_session - default_session = boto3.session.Session(profile_name=profile) + try: + global default_session + default_session = boto3.session.Session(profile_name=profile) + except (ConfigNotFound, ProfileNotFound) as error: + raise RuntimeError(error) def get_default_account_id() -> str: @@ -76,6 +79,8 @@ def get_default_account_id() -> str: except ClientError as error: raise RuntimeError(error_messages.AWS_SERVICE_REQUEST_CLIENT_ERROR_MESSAGE.format( "get_caller_identity", error.response['Error']['Code'], error.response['Error']['Message'])) + except NoCredentialsError as error: + raise RuntimeError(error) def get_default_region() -> str: From 5d7aae9bd899e838e183a7b09a530e18261c4acb Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:12:54 -0700 Subject: [PATCH 55/65] SPEC-2513 Fixes to enable w4459 (#1107) * fixing w4459 * Fixes for nounity * putting OLD_APARAM_USER in a common place to avoid duplicated declarations --- .../CryCommon/Maestro/Types/AnimParamType.h | 1 + Code/Framework/AzCore/Tests/AZStd/String.cpp | 16 ++++++------- .../AzCore/Tests/Math/Matrix4x4Tests.cpp | 10 ++++---- Code/Framework/AzCore/Tests/Math/ObbTests.cpp | 24 +++++++++---------- Code/Sandbox/Editor/QtViewPaneManager.cpp | 6 ++--- .../TrackView/SequenceBatchRenderDialog.cpp | 16 ++++++------- .../DiskLightFeatureProcessorInterface.h | 2 -- .../Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp | 10 ++++---- .../Source/RPI.Reflect/Shader/ShaderAsset.cpp | 5 +--- .../RPI.Reflect/Shader/ShaderVariantAsset.cpp | 5 +--- .../StaticLib/GraphCanvas/Styling/Parser.cpp | 6 ++--- .../Animation/Controls/UiTimelineCtrl.cpp | 4 ---- .../Code/Source/Animation/AnimNode.cpp | 1 - .../Code/Source/Cinematics/AnimNode.cpp | 1 - .../Code/Source/Cinematics/AnimPostFXNode.cpp | 1 - .../MicrophoneSystemComponent_Windows.cpp | 16 ++++++------- .../Source/Optimization/Constants.h | 4 ++-- .../Source/Optimization/LineSearch.cpp | 8 +++---- .../Tests/OptimizationTest.cpp | 10 ++++---- .../Benchmarks/PhysXBenchmarksUtilities.h | 6 ++--- .../Common/MSVC/Configurations_msvc.cmake | 1 - 21 files changed, 68 insertions(+), 85 deletions(-) diff --git a/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h b/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h index f7caf2db7f..4614b1638d 100644 --- a/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h +++ b/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h @@ -137,5 +137,6 @@ enum class AnimParamType Invalid = static_cast(0xFFFFFFFF) }; +static const int OLD_APARAM_USER = 100; #endif // CRYINCLUDE_CRYCOMMON_MAESTRO_TYPES_ANIMPARAMTYPE_H diff --git a/Code/Framework/AzCore/Tests/AZStd/String.cpp b/Code/Framework/AzCore/Tests/AZStd/String.cpp index 5b8c176f02..a48725309a 100644 --- a/Code/Framework/AzCore/Tests/AZStd/String.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/String.cpp @@ -1914,7 +1914,7 @@ namespace UnitTest TEST_F(String, StringView_CompareIsConstexpr) { using TypeParam = char; - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { return "HelloWorld"; }; @@ -1922,7 +1922,7 @@ namespace UnitTest { return "HelloPearl"; }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2(); constexpr basic_string_view lhsView(compileTimeString1); constexpr basic_string_view rhsView(compileTimeString2); @@ -1937,11 +1937,11 @@ namespace UnitTest TEST_F(String, StringView_CompareOperatorsAreConstexpr) { using TypeParam = char; - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto TestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { return "HelloWorld"; }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = TestMakeCompileTimeString1(); constexpr basic_string_view compareView(compileTimeString1); static_assert(compareView == "HelloWorld", "string_view operator== comparison has failed"); static_assert(compareView != "MadWorld", "string_view operator!= comparison has failed"); @@ -1955,7 +1955,7 @@ namespace UnitTest { auto swap_test_func = []() constexpr -> basic_string_view { - constexpr auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + constexpr auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { if constexpr (AZStd::is_same_v) { @@ -1977,7 +1977,7 @@ namespace UnitTest return L"InuWorld"; } }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2(); basic_string_view lhsView(compileTimeString1); basic_string_view rhsView(compileTimeString2); @@ -2001,7 +2001,7 @@ namespace UnitTest TYPED_TEST(BasicStringViewConstexprFixture, HashString_FunctionIsConstexpr) { - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { if constexpr (AZStd::is_same_v) { @@ -2012,7 +2012,7 @@ namespace UnitTest return L"HelloWorld"; } }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr basic_string_view hashView(compileTimeString1); constexpr size_t compileHash = AZStd::hash>{}(hashView); static_assert(compileHash != 0, "Hash of \"HelloWorld\" should not be 0"); diff --git a/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp b/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp index a9baa3ddea..8c4fac86c2 100644 --- a/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp +++ b/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp @@ -59,7 +59,7 @@ namespace UnitTest TEST(MATH_Matrix4x4, TestCreateFrom) { - float testFloats[] = + float thisTestFloats[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, @@ -67,20 +67,20 @@ namespace UnitTest 13.0f, 14.0f, 15.0f, 16.0f }; float testFloatMtx[16]; - Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(testFloats); + Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(thisTestFloats); AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 2.0f, 3.0f, 4.0f)); AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(5.0f, 6.0f, 7.0f, 8.0f)); AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(9.0f, 10.0f, 11.0f, 12.0f)); AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(13.0f, 14.0f, 15.0f, 16.0f)); m1.StoreToRowMajorFloat16(testFloatMtx); - AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0); - m1 = Matrix4x4::CreateFromColumnMajorFloat16(testFloats); + AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0); + m1 = Matrix4x4::CreateFromColumnMajorFloat16(thisTestFloats); AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 5.0f, 9.0f, 13.0f)); AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(2.0f, 6.0f, 10.0f, 14.0f)); AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(3.0f, 7.0f, 11.0f, 15.0f)); AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(4.0f, 8.0f, 12.0f, 16.0f)); m1.StoreToColumnMajorFloat16(testFloatMtx); - AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0); + AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0); } TEST(MATH_Matrix4x4, TestCreateFromMatrix3x4) diff --git a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp index de267b4265..5eb9057761 100644 --- a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp @@ -119,10 +119,10 @@ namespace UnitTest TEST(MATH_Obb, Contains) { - const Vector3 position(1.0f, 2.0f, 3.0f); - const Quaternion rotation = Quaternion::CreateRotationZ(DegToRad(30.0f)); - const Vector3 halfLengths(2.0f, 1.0f, 2.5f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(1.0f, 2.0f, 3.0f); + const Quaternion testRotation = Quaternion::CreateRotationZ(DegToRad(30.0f)); + const Vector3 testHalfLengths(2.0f, 1.0f, 2.5f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); // test some pairs of points which should be just either side of the Obb boundary EXPECT_TRUE(obb.Contains(Vector3(1.35f, 3.35f, 3.5f))); EXPECT_FALSE(obb.Contains(Vector3(1.35f, 3.4f, 3.5f))); @@ -134,10 +134,10 @@ namespace UnitTest TEST(MATH_Obb, GetDistance) { - const Vector3 position(5.0f, 3.0f, 2.0f); - const Quaternion rotation = Quaternion::CreateRotationX(DegToRad(60.0f)); - const Vector3 halfLengths(0.5f, 2.0f, 1.5f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(5.0f, 3.0f, 2.0f); + const Quaternion testRotation = Quaternion::CreateRotationX(DegToRad(60.0f)); + const Vector3 testHalfLengths(0.5f, 2.0f, 1.5f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); EXPECT_NEAR(obb.GetDistance(Vector3(5.3f, 3.2f, 1.8f)), 0.0f, 1e-3f); EXPECT_NEAR(obb.GetDistance(Vector3(5.1f, 1.1f, 3.7f)), 0.9955f, 1e-3f); EXPECT_NEAR(obb.GetDistance(Vector3(4.7f, 4.5f, 4.2f)), 0.6553f, 1e-3f); @@ -146,10 +146,10 @@ namespace UnitTest TEST(MATH_Obb, GetDistanceSq) { - const Vector3 position(1.0f, 4.0f, 3.0f); - const Quaternion rotation = Quaternion::CreateRotationY(DegToRad(45.0f)); - const Vector3 halfLengths(1.5f, 3.0f, 1.0f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(1.0f, 4.0f, 3.0f); + const Quaternion testRotation = Quaternion::CreateRotationY(DegToRad(45.0f)); + const Vector3 testHalfLengths(1.5f, 3.0f, 1.0f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); EXPECT_NEAR(obb.GetDistanceSq(Vector3(1.1f, 4.3f, 2.7f)), 0.0f, 1e-3f); EXPECT_NEAR(obb.GetDistanceSq(Vector3(-0.7f, 3.5f, 2.0f)), 0.8266f, 1e-3f); EXPECT_NEAR(obb.GetDistanceSq(Vector3(2.4f, 0.5f, 1.5f)), 0.5532f, 1e-3f); diff --git a/Code/Sandbox/Editor/QtViewPaneManager.cpp b/Code/Sandbox/Editor/QtViewPaneManager.cpp index b242f3d914..3949f0516e 100644 --- a/Code/Sandbox/Editor/QtViewPaneManager.cpp +++ b/Code/Sandbox/Editor/QtViewPaneManager.cpp @@ -121,7 +121,7 @@ protected: }; #endif -Q_GLOBAL_STATIC(QtViewPaneManager, s_instance) +Q_GLOBAL_STATIC(QtViewPaneManager, s_viewPaneManagerInstance) QWidget* QtViewPane::CreateWidget() @@ -611,12 +611,12 @@ void QtViewPaneManager::UnregisterPane(const QString& name) QtViewPaneManager* QtViewPaneManager::instance() { - return s_instance(); + return s_viewPaneManagerInstance(); } bool QtViewPaneManager::exists() { - return s_instance.exists(); + return s_viewPaneManagerInstance.exists(); } void QtViewPaneManager::SetMainWindow(AzQtComponents::DockMainWindow* mainWindow, QSettings* settings, const QByteArray& lastMainWindowState) diff --git a/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp b/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp index 69322c481c..0abe3ced75 100644 --- a/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp +++ b/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp @@ -59,7 +59,7 @@ namespace { int fps; const char* fpsDesc; - } fps[] = { + } fpsOptions[] = { {24, "Film(24)"}, {25, "PAL(25)"}, {30, "NTSC(30)"}, {48, "Show(48)"}, {50, "PAL Field(50)"}, {60, "NTSC Field(60)"} }; @@ -213,9 +213,9 @@ void CSequenceBatchRenderDialog::OnInitDialog() m_ui->m_resolutionCombo->setCurrentIndex(0); // Fill the FPS combo box. - for (int i = 0; i < AZStd::size(fps); ++i) + for (int i = 0; i < AZStd::size(fpsOptions); ++i) { - m_ui->m_fpsCombo->addItem(fps[i].fpsDesc); + m_ui->m_fpsCombo->addItem(fpsOptions[i].fpsDesc); } m_ui->m_fpsCombo->setCurrentIndex(0); @@ -306,9 +306,9 @@ void CSequenceBatchRenderDialog::OnRenderItemSelChange() m_ui->m_destinationEdit->setText(item.folder); // fps bool bFound = false; - for (int i = 0; i < arraysize(fps); ++i) + for (int i = 0; i < arraysize(fpsOptions); ++i) { - if (item.fps == fps[i].fps) + if (item.fps == fpsOptions[i].fps) { m_ui->m_fpsCombo->setCurrentIndex(i); bFound = true; @@ -621,7 +621,7 @@ void CSequenceBatchRenderDialog::OnFPSEditChange() void CSequenceBatchRenderDialog::OnFPSChange(int itemIndex) { - m_customFPS = fps[itemIndex].fps; + m_customFPS = fpsOptions[itemIndex].fps; CheckForEnableUpdateButton(); } @@ -1543,13 +1543,13 @@ bool CSequenceBatchRenderDialog::SetUpNewRenderItem(SRenderItem& item) item.frameRange = Range(m_ui->m_startFrame->value() / m_fpsForTimeToFrameConversion, m_ui->m_endFrame->value() / m_fpsForTimeToFrameConversion); // fps - if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fps[m_ui->m_fpsCombo->currentIndex()].fpsDesc) + if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fpsOptions[m_ui->m_fpsCombo->currentIndex()].fpsDesc) { item.fps = m_customFPS; } else { - item.fps = fps[m_ui->m_fpsCombo->currentIndex()].fps; + item.fps = fpsOptions[m_ui->m_fpsCombo->currentIndex()].fps; } // prefix item.prefix = m_ui->BATCH_RENDER_FILE_PREFIX->text(); diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h index 50c0aa2455..ce911fecf7 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h @@ -46,8 +46,6 @@ namespace AZ uint16_t m_padding; // Explicit padding. }; - static constexpr size_t size = sizeof(DiskLightData); - //! DiskLightFeatureProcessorInterface provides an interface to acquire, release, and update a disk light. This is necessary for code outside of //! the Atom features gem to communicate with the DiskLightFeatureProcessor. class DiskLightFeatureProcessorInterface diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp index 5bebc92125..88d103d282 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp @@ -45,7 +45,7 @@ namespace AZ Fence* fenceToSignal) { AZStd::vector vkCommandBuffers; - AZStd::vector vkWaitSemaphores; + AZStd::vector vkWaitSemaphoreVector; // vulkan.h has a #define called vkWaitSemaphores, so we name this differently AZStd::vector vkWaitPipelineStages; AZStd::vector vkSignalSemaphores; VkSubmitInfo submitInfo; @@ -65,11 +65,11 @@ namespace AZ return item->GetNativeSemaphore(); }); vkWaitPipelineStages.reserve(waitSemaphoresInfo.size()); - vkWaitSemaphores.reserve(waitSemaphoresInfo.size()); + vkWaitSemaphoreVector.reserve(waitSemaphoresInfo.size()); AZStd::for_each(waitSemaphoresInfo.begin(), waitSemaphoresInfo.end(), [&](auto& item) { vkWaitPipelineStages.push_back(item.first); - vkWaitSemaphores.push_back(item.second->GetNativeSemaphore()); + vkWaitSemaphoreVector.push_back(item.second->GetNativeSemaphore()); // Wait until the wait semaphores has been submitted for signaling. item.second->WaitEvent(); }); @@ -77,8 +77,8 @@ namespace AZ submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext = nullptr; - submitInfo.waitSemaphoreCount = static_cast(vkWaitSemaphores.size()); - submitInfo.pWaitSemaphores = vkWaitSemaphores.empty() ? nullptr : vkWaitSemaphores.data(); + submitInfo.waitSemaphoreCount = static_cast(vkWaitSemaphoreVector.size()); + submitInfo.pWaitSemaphores = vkWaitSemaphoreVector.empty() ? nullptr : vkWaitSemaphoreVector.data(); submitInfo.pWaitDstStageMask = vkWaitPipelineStages.empty() ? nullptr : vkWaitPipelineStages.data(); submitInfo.commandBufferCount = static_cast(vkCommandBuffers.size()); submitInfo.pCommandBuffers = vkCommandBuffers.empty() ? nullptr : vkCommandBuffers.data(); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index 0a59772d6c..cf655d43f7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -10,6 +10,7 @@ * */ #include +#include #include #include @@ -34,10 +35,6 @@ namespace AZ uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType) { - static constexpr uint32_t RhiIndexBitPosition = 30; - static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; - static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; - static constexpr uint32_t SubProductTypeBitPosition = 0; static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp index 7864768351..5bcdcbb569 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp @@ -10,6 +10,7 @@ * */ #include +#include #include #include @@ -24,10 +25,6 @@ namespace AZ uint32_t ShaderVariantAsset::MakeAssetProductSubId( uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) { - static constexpr uint32_t RhiIndexBitPosition = 30; - static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; - static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; - static constexpr uint32_t SubProductTypeBitPosition = 17; static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp index 319996949b..db07c3ca8e 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp @@ -1072,8 +1072,7 @@ namespace GraphCanvas if (!id.empty()) { - Selector selector = Selector::Get(id); - result.emplace_back(selector); + result.emplace_back(Selector::Get(id)); continue; } @@ -1111,8 +1110,7 @@ namespace GraphCanvas { bits.emplace_back(stateSelector); } - Selector selector = aznew CompoundSelector(std::move(bits)); - nestedSelectors.emplace_back(selector); + nestedSelectors.emplace_back(aznew CompoundSelector(std::move(bits))); } } diff --git a/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp b/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp index 9f7fae6041..0b18539c54 100644 --- a/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp +++ b/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp @@ -23,10 +23,6 @@ #include #include -static const QColor timeMarkerCol = QColor(255, 0, 255); -static const QColor textCol = QColor(0, 0, 0); -static const QColor ltgrayCol = QColor(110, 110, 110); - QColor InterpolateColor(const QColor& c1, const QColor& c2, float fraction) { const int r = (c2.red() - c1.red()) * fraction + c1.red(); diff --git a/Gems/LyShine/Code/Source/Animation/AnimNode.cpp b/Gems/LyShine/Code/Source/Animation/AnimNode.cpp index 421e9a4fe3..369f080a15 100644 --- a/Gems/LyShine/Code/Source/Animation/AnimNode.cpp +++ b/Gems/LyShine/Code/Source/Animation/AnimNode.cpp @@ -48,7 +48,6 @@ static const EUiAnimCurveType DEFAULT_TRACK_TYPE = eUiAnimCurveType_BezierFloat; // Old serialization values that are no longer // defined in IUiAnimationSystem.h, but needed for conversion: -static const int OLD_APARAM_USER = 100; static const int OLD_ACURVE_GOTO = 21; static const int OLD_APARAM_PARTICLE_COUNT_SCALE = 95; static const int OLD_APARAM_PARTICLE_PULSE_PERIOD = 96; diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp index 7fea8097ed..8398ad47fd 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp @@ -60,7 +60,6 @@ static const EAnimCurveType DEFAULT_TRACK_TYPE = eAnimCurveType_BezierFloat; // Old serialization values that are no longer // defined in IMovieSystem.h, but needed for conversion: -static const int OLD_APARAM_USER = 100; static const int OLD_ACURVE_GOTO = 21; static const int OLD_APARAM_PARTICLE_COUNT_SCALE = 95; static const int OLD_APARAM_PARTICLE_PULSE_PERIOD = 96; diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp index 67b6ab5cdb..992527d2c4 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp @@ -281,7 +281,6 @@ void CAnimPostFXNode::SerializeAnims(XmlNodeRef& xmlNode, bool bLoading, bool bL paramType.Serialize(trackNode, true); // Don't use APARAM_USER because it could change in newer versions // CAnimNode::SerializeAnims will then take care of that - static const unsigned int OLD_APARAM_USER = 100; paramType = static_cast(static_cast(paramType.GetType()) + OLD_APARAM_USER); paramType.Serialize(trackNode, false); } diff --git a/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp b/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp index 8b3e3c2ce6..2eb840b253 100644 --- a/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp +++ b/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp @@ -51,11 +51,11 @@ namespace Audio // To avoid errors, we initialize COM here with the same model. CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); - const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); + const CLSID CLSID_MMDeviceEnumerator_UUID = __uuidof(MMDeviceEnumerator); + const IID IID_IMMDeviceEnumerator_UUID = __uuidof(IMMDeviceEnumerator); HRESULT hresult = CoCreateInstance( - CLSID_MMDeviceEnumerator, nullptr, - CLSCTX_ALL, IID_IMMDeviceEnumerator, + CLSID_MMDeviceEnumerator_UUID, nullptr, + CLSCTX_ALL, IID_IMMDeviceEnumerator_UUID, reinterpret_cast(&m_enumerator) ); @@ -133,8 +133,8 @@ namespace Audio AZ_Assert(m_device != nullptr, "Attempting to start a Microphone session while the device is uninitialized - Windows!\n"); // Get the IAudioClient from the device - const IID IID_IAudioClient = __uuidof(IAudioClient); - HRESULT hresult = m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast(&m_audioClient)); + const IID IID_IAudioClient_UUID = __uuidof(IAudioClient); + HRESULT hresult = m_device->Activate(IID_IAudioClient_UUID, CLSCTX_ALL, nullptr, reinterpret_cast(&m_audioClient)); if (FAILED(hresult)) { @@ -182,8 +182,8 @@ namespace Audio } // Get the IAudioCaptureClient - const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - hresult = m_audioClient->GetService(IID_IAudioCaptureClient, reinterpret_cast(&m_audioCaptureClient)); + const IID IID_IAudioCaptureClient_UUID = __uuidof(IAudioCaptureClient); + hresult = m_audioClient->GetService(IID_IAudioCaptureClient_UUID, reinterpret_cast(&m_audioCaptureClient)); if (FAILED(hresult)) { diff --git a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h index d480fb9cd0..feb626aa50 100644 --- a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h +++ b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h @@ -27,6 +27,6 @@ namespace NumericalMethods::Optimization const double epsilon = 1e-7; // values recommended in Nocedal and Wright for constants in the Wolfe conditions for satisfactory solution improvement - const double c1 = 1e-4; - const double c2 = 0.9; + const double WolfeConditionsC1 = 1e-4; + const double WolfeConditionsC2 = 0.9; } // namespace NumericalMethods::Optimization diff --git a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp index 1325638dd2..06152446d6 100644 --- a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp +++ b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp @@ -162,16 +162,16 @@ namespace NumericalMethods::Optimization { // if the value of f corresponding to alpha1 isn't sufficiently small compared to f at x0, // then the interval [alpha0 ... alpha1] must bracket a suitable point. - if ((f_alpha1 > f_x0 + c1 * alpha1 * df_x0) || (iteration > 0 && f_alpha1 > f_alpha0)) + if ((f_alpha1 > f_x0 + WolfeConditionsC1 * alpha1 * df_x0) || (iteration > 0 && f_alpha1 > f_alpha0)) { return SelectStepSizeFromInterval(alpha0, alpha1, f_alpha0, f_alpha1, df_alpha0, - f, x0, searchDirection, f_x0, df_x0, c1, c2); + f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); } // otherwise, if the derivative corresponding to alpha1 is large enough, alpha1 already // satisfies the Wolfe conditions and so return alpha1. double df_alpha1 = DirectionalDerivative(f, x0 + alpha1 * searchDirection, searchDirection); - if (fabs(df_alpha1) <= -c2 * df_x0) + if (fabs(df_alpha1) <= -WolfeConditionsC2 * df_x0) { LineSearchResult result; result.m_outcome = LineSearchOutcome::Success; @@ -184,7 +184,7 @@ namespace NumericalMethods::Optimization if (df_alpha1 >= 0.0) { return SelectStepSizeFromInterval(alpha1, alpha0, f_alpha1, f_alpha0, df_alpha1, - f, x0, searchDirection, f_x0, df_x0, c1, c2); + f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); } // haven't found an interval which is guaranteed to bracket a suitable point, diff --git a/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp b/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp index b02f13d8a4..92d7e0dcba 100644 --- a/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp +++ b/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp @@ -158,12 +158,12 @@ namespace NumericalMethods::Optimization double f_x0 = f_alpha0; double df_x0 = df_alpha0; LineSearchResult lineSearchResult = SelectStepSizeFromInterval(alpha0, alpha1, f_alpha0, f_alpha1, df_alpha0, - testFunctionRosenbrock, x0, searchDirection, f_x0, df_x0, c1, c2); + testFunctionRosenbrock, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); EXPECT_TRUE(lineSearchResult.m_outcome == LineSearchOutcome::Success); // check that the Wolfe conditions are satisfied by the returned step size - EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + c1 * df_x0 * lineSearchResult.m_stepSize); - EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -c2 * df_x0); + EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + WolfeConditionsC1 * df_x0 * lineSearchResult.m_stepSize); + EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -WolfeConditionsC2 * df_x0); } TEST(OptimizationTest, LineSearch_VariousSearchDirections_SatisfiesWolfeCondition) @@ -180,8 +180,8 @@ namespace NumericalMethods::Optimization EXPECT_TRUE(lineSearchResult.m_outcome == LineSearchOutcome::Success); // check that the Wolfe conditions are satisfied by the returned step size - EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + c1 * df_x0 * lineSearchResult.m_stepSize); - EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -c2 * df_x0); + EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + WolfeConditionsC1 * df_x0 * lineSearchResult.m_stepSize); + EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -WolfeConditionsC2 * df_x0); } } diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h index ab9bd58148..0971226644 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h @@ -112,9 +112,9 @@ namespace PhysX::Benchmarks for (double percentile : percentiles) { //ensure the percentile is between 0.0 and 1.0 - const double epsilon = 0.001; - AZ::ClampIfCloseMag(percentile, 0.0, epsilon); - AZ::ClampIfCloseMag(percentile, 1.0, epsilon); + const double testEpsilon = 0.001; + AZ::ClampIfCloseMag(percentile, 0.0, testEpsilon); + AZ::ClampIfCloseMag(percentile, 1.0, testEpsilon); size_t idx = aznumeric_cast(std::round(percentile * (values.size() - 1))); std::nth_element(values.begin(), values.begin() + idx, values.end()); diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index 24dffe56a6..f53b8aa769 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -74,7 +74,6 @@ ly_append_configurations_options( /wd4436 # the result of unary operator may be unaligned /wd4450 # declaration hides global declaration /wd4457 # declaration hides function parameter - /wd4459 # declaration hides global declaration # Enabling warnings that are disabled by default from /W4 # https://docs.microsoft.com/en-us/cpp/preprocessor/compiler-warnings-that-are-off-by-default?view=vs-2019 From 6681a5376844f9e3fcb0faebe6ffe6983c9bfa03 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Thu, 3 Jun 2021 14:13:44 -0400 Subject: [PATCH 56/65] Add the ability to remove tags. Updated some descriptions, and updated some log messages to include parameters. --- scripts/o3de/o3de/project_properties.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py index d8453a2c4f..7a9610a775 100644 --- a/scripts/o3de/o3de/project_properties.py +++ b/scripts/o3de/o3de/project_properties.py @@ -13,12 +13,13 @@ logging.basicConfig() def get_project_props(name: str = None, path: pathlib.Path = None) -> dict: proj_json = manifest.get_project_json_data(project_name=name, project_path=path) if not proj_json: - logger.error('Could not retrieve project.json file') + param = name if name else path + logger.error(f'Could not retrieve project.json file for {param}') return None return proj_json def edit_project_props(proj_path, proj_name, new_origin, new_display, - new_summary, new_icon, new_tag) -> int: + new_summary, new_icon, new_tag, remove_tag) -> int: proj_json = get_project_props(proj_name, proj_path) if not proj_json: @@ -36,6 +37,14 @@ def edit_project_props(proj_path, proj_name, new_origin, new_display, if 'user_tags' not in proj_json: proj_json['user_tags'] = [] proj_json['user_tags'].append(new_tag) + if remove_tag: + if 'user_tags' in proj_json: + if remove_tag in proj_json['user_tags']: + proj_json['user_tags'].remove(remove_tag) + else: + logger.warn(f'{remove_tag} not found in user_tags for removal.') + else: + logger.warn(f'user_tags property not found for removal of tag {remove_tag}.') manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json') return 0 @@ -47,7 +56,8 @@ def _edit_project_props(args: argparse) -> int: args.project_display, args.project_summary, args.project_icon, - args.project_tag) + args.project_tag, + args.remove_tag) def add_parser_args(parser): group = parser.add_mutually_exclusive_group(required=True) @@ -65,7 +75,9 @@ def add_parser_args(parser): group.add_argument('-pi', '--project-icon', type=str, required=False, help='Sets the path to the projects icon resource.') group.add_argument('-pt', '--project-tag', type=str, required=False, - help='Adds a tag to canonical user tags. These tags are intended for documentation and filtering.') + help='Adds a tag to user tags. These tags are intended for documentation and filtering.') + group.add_argument('-rt', '--remove-tag', type=str, required=False, + help='Removes a tag from user tags. These tags are intended for documentation and filtering.') parser.set_defaults(func=_edit_project_props) def add_args(subparsers) -> None: From 7bba5ed2fc6f91a66fd2fbaada65ac130d7b1c87 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Thu, 3 Jun 2021 14:43:13 -0400 Subject: [PATCH 57/65] Incorporated suggestion to use setdefault to handle missing tag property rather than explicitly checking for and creating it. --- scripts/o3de/o3de/project_properties.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py index 7a9610a775..63ff05a8da 100644 --- a/scripts/o3de/o3de/project_properties.py +++ b/scripts/o3de/o3de/project_properties.py @@ -34,9 +34,7 @@ def edit_project_props(proj_path, proj_name, new_origin, new_display, if new_icon: proj_json['icon_path'] = new_icon if new_tag: - if 'user_tags' not in proj_json: - proj_json['user_tags'] = [] - proj_json['user_tags'].append(new_tag) + proj_json.setdefault('user_tags', []).append(new_tag) if remove_tag: if 'user_tags' in proj_json: if remove_tag in proj_json['user_tags']: @@ -75,9 +73,9 @@ def add_parser_args(parser): group.add_argument('-pi', '--project-icon', type=str, required=False, help='Sets the path to the projects icon resource.') group.add_argument('-pt', '--project-tag', type=str, required=False, - help='Adds a tag to user tags. These tags are intended for documentation and filtering.') + help='Adds a tag to user_tags property. These tags are intended for documentation and filtering.') group.add_argument('-rt', '--remove-tag', type=str, required=False, - help='Removes a tag from user tags. These tags are intended for documentation and filtering.') + help='Removes a tag from the user_tags property.') parser.set_defaults(func=_edit_project_props) def add_args(subparsers) -> None: From e99a95d909948d51b02b6fefb3f393eace5b7aca Mon Sep 17 00:00:00 2001 From: mgwynn Date: Thu, 3 Jun 2021 15:02:18 -0400 Subject: [PATCH 58/65] Added copyright header for validation --- scripts/o3de/o3de/project_properties.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py index 63ff05a8da..69bd1b9406 100644 --- a/scripts/o3de/o3de/project_properties.py +++ b/scripts/o3de/o3de/project_properties.py @@ -1,3 +1,14 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + import argparse import json import os From dce87534c7d9a7cb1f686d3368998093cc08b2fa Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Thu, 3 Jun 2021 14:37:16 -0500 Subject: [PATCH 59/65] Removing the Pyside implementation of the project manager python scripts (#1112) * Removing the Pyside implementation of the project manager python scripts * Removing reference to the scripts/project_manager directory The Install_common.cmake script reference to the project_manager directory has been removed. --- cmake/Platform/Common/Install_common.cmake | 1 - scripts/CMakeLists.txt | 1 - scripts/project_manager/CMakeLists.txt | 22 - scripts/project_manager/__init__.py | 10 - scripts/project_manager/projects.py | 804 ------------------ scripts/project_manager/pyside.py | 61 -- scripts/project_manager/tests/__init__.py | 10 - .../project_manager/tests/test_projects.py | 255 ------ scripts/project_manager/tests/test_pyside.py | 54 -- .../ui/create_from_template.ui | 94 -- scripts/project_manager/ui/create_gem.ui | 94 -- scripts/project_manager/ui/create_project.ui | 94 -- .../project_manager/ui/manage_gem_targets.ui | 165 ---- .../project_manager/ui/project_manager.ico | 3 - scripts/project_manager/ui/project_manager.ui | 407 --------- 15 files changed, 2075 deletions(-) delete mode 100644 scripts/project_manager/CMakeLists.txt delete mode 100755 scripts/project_manager/__init__.py delete mode 100755 scripts/project_manager/projects.py delete mode 100755 scripts/project_manager/pyside.py delete mode 100755 scripts/project_manager/tests/__init__.py delete mode 100755 scripts/project_manager/tests/test_projects.py delete mode 100755 scripts/project_manager/tests/test_pyside.py delete mode 100644 scripts/project_manager/ui/create_from_template.ui delete mode 100644 scripts/project_manager/ui/create_gem.ui delete mode 100644 scripts/project_manager/ui/create_project.ui delete mode 100644 scripts/project_manager/ui/manage_gem_targets.ui delete mode 100644 scripts/project_manager/ui/project_manager.ico delete mode 100644 scripts/project_manager/ui/project_manager.ui diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 710a8b266f..11260ab018 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -443,7 +443,6 @@ function(ly_setup_others) install(DIRECTORY ${LY_ROOT_FOLDER}/scripts/bundler - ${LY_ROOT_FOLDER}/scripts/project_manager ${LY_ROOT_FOLDER}/scripts/o3de DESTINATION ./scripts COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index d3c9640665..6df0f4b77f 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -12,5 +12,4 @@ add_subdirectory(detect_file_changes) add_subdirectory(commit_validation) add_subdirectory(o3de) -add_subdirectory(project_manager) add_subdirectory(ctest) diff --git a/scripts/project_manager/CMakeLists.txt b/scripts/project_manager/CMakeLists.txt deleted file mode 100644 index 36c9ad0360..0000000000 --- a/scripts/project_manager/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -ly_download_associated_package(pyside2) - -ly_add_pytest( - NAME test_pyside - PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py -) - -ly_add_pytest( - NAME test_projects - PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py -) \ No newline at end of file diff --git a/scripts/project_manager/__init__.py b/scripts/project_manager/__init__.py deleted file mode 100755 index 4d5680a30d..0000000000 --- a/scripts/project_manager/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# diff --git a/scripts/project_manager/projects.py b/scripts/project_manager/projects.py deleted file mode 100755 index f9f40aa4bf..0000000000 --- a/scripts/project_manager/projects.py +++ /dev/null @@ -1,804 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -# PySide project and gem selector GUI - -import os -import pathlib -import sys -import argparse -import json -import logging -import subprocess -from logging.handlers import RotatingFileHandler -from typing import List -from pyside import add_pyside_environment, is_pyside_ready, uninstall_env - -engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) -sys.path.append(engine_path) -executable_path = '' - -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -from o3de import disable_gem, enable_gem, cmake, engine_template, manifest, register - -o3de_folder = manifest.get_o3de_folder() -o3de_logs_folder = manifest.get_o3de_logs_folder() -project_manager_log_file_path = o3de_logs_folder / "project_manager.log" -log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1) -formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s') -log_file_handler.setFormatter(formatter) -logger.addHandler(log_file_handler) - -logger.info("Starting Project Manager") - - -def initialize_pyside_from_parser(): - # Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up - # PySide - parser = argparse.ArgumentParser() - parser.add_argument('--executable-path', required=True, help='Path to Executable to launch with project') - parser.add_argument('--binaries-path', default=None, help='Path to QT Binaries necessary for PySide. If not' - ' provided executable_path folder is assumed') - parser.add_argument('--parent-pid', default=0, help='Process ID of launching process') - - args = parser.parse_args() - - logger.info(f"parent_pid is {args.parent_pid}") - global executable_path - executable_path = args.executable_path - binaries_path = args.binaries_path or os.path.dirname(executable_path) - - # Initialize PySide before imports below. This adds both PySide python modules to the python system interpreter - # path and adds the necessary paths to binaries for the DLLs to be found and load their dependencies - add_pyside_environment(binaries_path) - - -if not is_pyside_ready(): - initialize_pyside_from_parser() - -try: - from PySide2.QtWidgets import QApplication, QDialogButtonBox, QPushButton, QComboBox, QMessageBox, QFileDialog - from PySide2.QtWidgets import QListView, QLabel - from PySide2.QtUiTools import QUiLoader - from PySide2.QtCore import QFile, QObject, Qt, Signal, Slot - from PySide2.QtGui import QIcon, QStandardItemModel, QStandardItem -except ImportError as e: - logger.error(f"Failed to import PySide2 with error {e}") - exit(-1) - -logger.error(f"PySide2 imports successful") - - -class DialogLoggerSignaller(QObject): - send_to_dialog = Signal(str) - - def __init__(self, dialog_logger): - super(DialogLoggerSignaller, self).__init__() - - self.dialog_logger = dialog_logger - - -# Independent class to handle log forwarding. Logger and qt signals both use emit method. -# This class's job is to receive the logger record and then emit the formatted message through -# DialogLoggerSignaller which is what the ProjectDialog handler listens for -class DialogLogger(logging.Handler): - - def __init__(self, log_dialog, log_level=logging.INFO, forward_log_level=logging.WARNING, - message_box_log_level=logging.ERROR): - super(DialogLogger, self).__init__() - - self.log_dialog = log_dialog - self.log_level = log_level - self.forward_log_level = forward_log_level - self.message_box_log_level = message_box_log_level - self.log_records = [] - self.formatter = logging.Formatter('%(levelname)s : %(message)s') - self.setFormatter(self.formatter) - self.signaller = DialogLoggerSignaller(self) - - def emit(self, record): - self.log_records.append(record) - if record.levelno >= self.message_box_log_level: - QMessageBox.warning(None, record.levelname, record.message) - elif record.levelno >= self.forward_log_level: - self.signaller.send_to_dialog.emit(self.format(record)) - - -class ProjectManagerDialog(QObject): - """ - Main project manager dialog is responsible for displaying the project selection list and output pane - """ - - def __init__(self, parent=None): - super(ProjectManagerDialog, self).__init__(parent) - - self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve() - self.home_folder = manifest.get_home_folder() - - self.log_display = None - self.dialog_logger = DialogLogger(self) - logger.addHandler(self.dialog_logger) - logger.setLevel(logging.INFO) - - self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message) - self.mru_file_path = o3de_folder / 'mru.json' - - self.create_from_template_ui_file_path = self.ui_path / 'create_from_template.ui' - self.create_gem_ui_file_path = self.ui_path / 'create_gem.ui' - self.create_project_ui_file_path = self.ui_path / 'create_project.ui' - self.manage_project_gem_targets_ui_file_path = self.ui_path / 'manage_gem_targets.ui' - self.project_manager_icon_file_path = self.ui_path / 'project_manager.ico' - self.project_manager_ui_file_path = self.ui_path / 'project_manager.ui' - - self.project_manager_ui_file = QFile(self.project_manager_ui_file_path.as_posix()) - self.project_manager_ui_file.open(QFile.ReadOnly) - - loader = QUiLoader() - self.dialog = loader.load(self.project_manager_ui_file) - self.dialog.setWindowIcon(QIcon(self.project_manager_icon_file_path.as_posix())) - self.dialog.setFixedSize(self.dialog.size()) - - self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox') - self.refresh_project_list() - mru = self.get_mru_list() - if len(mru): - last_mru = pathlib.Path(mru[0]).resolve() - for this_slot in range(self.project_list_box.count()): - item_text = self.project_list_box.itemText(this_slot) - if last_mru.as_posix() in item_text: - self.project_list_box.setCurrentIndex(this_slot) - break - - self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton') - self.create_project_button.clicked.connect(self.create_project_handler) - self.create_gem_button = self.dialog.findChild(QPushButton, 'createGemButton') - self.create_gem_button.clicked.connect(self.create_gem_handler) - self.create_template_button = self.dialog.findChild(QPushButton, 'createTemplateButton') - self.create_template_button.clicked.connect(self.create_template_handler) - self.create_from_template_button = self.dialog.findChild(QPushButton, 'createFromTemplateButton') - self.create_from_template_button.clicked.connect(self.create_from_template_handler) - - self.add_project_button = self.dialog.findChild(QPushButton, 'addProjectButton') - self.add_project_button.clicked.connect(self.add_project_handler) - self.add_gem_button = self.dialog.findChild(QPushButton, 'addGemButton') - self.add_gem_button.clicked.connect(self.add_gem_handler) - self.add_template_button = self.dialog.findChild(QPushButton, 'addTemplateButton') - self.add_template_button.clicked.connect(self.add_template_handler) - self.add_restricted_button = self.dialog.findChild(QPushButton, 'addRestrictedButton') - self.add_restricted_button.clicked.connect(self.add_restricted_handler) - - self.remove_project_button = self.dialog.findChild(QPushButton, 'removeProjectButton') - self.remove_project_button.clicked.connect(self.remove_project_handler) - self.remove_gem_button = self.dialog.findChild(QPushButton, 'removeGemButton') - self.remove_gem_button.clicked.connect(self.remove_gem_handler) - self.remove_template_button = self.dialog.findChild(QPushButton, 'removeTemplateButton') - self.remove_template_button.clicked.connect(self.remove_template_handler) - self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton') - self.remove_restricted_button.clicked.connect(self.remove_restricted_handler) - - self.manage_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton') - self.manage_project_gem_targets_button.clicked.connect(self.manage_project_gem_targets_handler) - - self.log_display = self.dialog.findChild(QLabel, 'logDisplay') - - self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel') - self.ok_cancel_button.accepted.connect(self.accepted_handler) - - self.dialog.show() - - def refresh_project_list(self) -> None: - projects = manifest.get_all_projects() - self.project_list_box.clear() - for this_slot in range(len(projects)): - display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})' - self.project_list_box.addItem(display_name) - self.project_list_box.setItemData(self.project_list_box.count() - 1, projects[this_slot], - Qt.ToolTipRole) - - def accepted_handler(self) -> None: - """ - Override for handling "Ok" on main project dialog to first check whether the user has selected a project and - prompt them to if not. If a project is selected will attempt to open it. - :return: None - """ - if not self.project_list_box.currentText(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - self.launch_with_project_path(self.get_selected_project_path()) - - def get_launch_project(self) -> str: - return os.path.normpath(self.get_selected_project_path()) - - def get_executable_launch_params(self) -> list: - """ - Retrieve the necessary launch parameters to make the subprocess launch call with - this is the path - to the executable such as the Editor and the path to the selected project - :return: list of params - """ - launch_params = [executable_path, - f'-regset="/Amazon/AzCore/Bootstrap/project_path={self.get_launch_project()}"'] - return launch_params - - def launch_with_project_path(self, project_path: str) -> None: - """ - Launch the desired application given the selected project - :param project_path: Path to currently selected project - :return: None - """ - logger.info(f'Attempting to open {project_path}') - self.update_mru_list(project_path) - launch_params = self.get_executable_launch_params() - logger.info(f'Launching with params {launch_params}') - subprocess.run(launch_params, env=uninstall_env()) - - def get_selected_project_path(self) -> str: - if self.project_list_box.currentIndex() == -1: - logger.warning("No project selected") - return "" - return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole) - - def get_selected_project_name(self) -> str: - project_data = manifest.get_project_json_data(project_path=self.get_selected_project_path()) - return project_data['project_name'] - - def create_project_handler(self): - """ - Opens the Create Project pane. Retrieves a list of available templates for display - :return: None - """ - loader = QUiLoader() - self.create_project_file = QFile(self.create_project_ui_file_path.as_posix()) - - if not self.create_project_file: - logger.error(f'Failed to create project UI file at {self.create_project_file}') - return - - self.create_project_dialog = loader.load(self.create_project_file) - - if not self.create_project_dialog: - logger.error(f'Failed to load create project dialog file at {self.create_project_file}') - return - - self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler) - - self.create_project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates') - self.refresh_create_project_template_list() - - self.create_project_dialog.exec() - - def create_project_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current project. - Updates UI after completion. - :return: None - """ - - selected_item = self.create_project_template_list.selectionModel().currentIndex() - project_template_path = self.create_project_template_list.model().data(selected_item) - if not project_template_path: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", - manifest.get_o3de_projects_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - project_count = 0 - project_name = "MyNewProject" - while os.path.exists(os.path.join(engine_path, project_name)): - project_name = f"MyNewProject{project_count}" - project_count += 1 - folder_dialog.selectFile(project_name) - project_folder = None - if folder_dialog.exec(): - project_folder = folder_dialog.selectedFiles() - if project_folder: - if engine_template.create_project(project_path=project_folder[0], - template_path=project_template_path) == 0: - # Success - register.register(project_path=project_folder[0]) - self.refresh_project_list() - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Project {project_folder[0]} created.") - msg_box.exec() - return - - def create_gem_handler(self): - """ - Opens the Create Gem pane. Retrieves a list of available templates for display - :return: None - """ - loader = QUiLoader() - self.create_gem_file = QFile(self.create_gem_ui_file_path.as_posix()) - - if not self.create_gem_file: - logger.error(f'Failed to create gem UI file at {self.create_gem_file}') - return - - self.create_gem_dialog = loader.load(self.create_gem_file) - - if not self.create_gem_dialog: - logger.error(f'Failed to load create gem dialog file at {self.create_gem_file}') - return - - self.create_gem_ok_button = self.create_gem_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_gem_ok_button.accepted.connect(self.create_gem_accepted_handler) - - self.create_gem_template_list = self.create_gem_dialog.findChild(QListView, 'gemTemplates') - self.refresh_create_gem_template_list() - - self.create_gem_dialog.exec() - - def create_gem_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current gem. - Updates UI after completion. - :return: None - """ - selected_item = self.create_gem_template_list.selectionModel().currentIndex() - gem_template_path = self.create_gem_template_list.model().data(selected_item) - if not gem_template_path: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - manifest.get_o3de_gems_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - gem_count = 0 - gem_name = "MyNewGem" - while os.path.exists(os.path.join(engine_path, gem_name)): - gem_name = f"MyNewGem{gem_count}" - gem_count += 1 - folder_dialog.selectFile(gem_name) - gem_folder = None - if folder_dialog.exec(): - gem_folder = folder_dialog.selectedFiles() - if gem_folder: - if engine_template.create_gem(gem_path=gem_folder[0], - template_path=gem_template_path) == 0: - # Success - register.register(gem_path=gem_folder[0]) - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Gem {gem_folder[0]} created.") - msg_box.exec() - return - - def create_template_handler(self): - """ - Opens a foldr select dialog and lets the user select the source folder they want to make a template - out of, then opens a second folder select dialog to get where they want to put the template and it name - :return: None - """ - - source_folder = QFileDialog.getExistingDirectory(self.dialog, - "Select a Folder to make a template out of.", - manifest.get_o3de_folder().as_posix()) - if not source_folder: - return - - destination_template_folder_dialog = QFileDialog(self.dialog, - "Select where the template is to be created and named.", - manifest.get_o3de_templates_folder().as_posix()) - destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile) - destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - destination_folder = None - if destination_template_folder_dialog.exec(): - destination_folder = destination_template_folder_dialog.selectedFiles() - if not destination_folder: - return - - if engine_template.create_template(source_path=source_folder, - template_path=destination_folder[0]) == 0: - # Success - register.register(template_path=destination_folder[0]) - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Template {destination_folder[0]} created.") - msg_box.exec() - return - - def create_from_template_handler(self): - """ - Opens the Create from_template pane. Retrieves a list of available from_templates for display - :return: None - """ - loader = QUiLoader() - self.create_from_template_file = QFile(self.create_from_template_ui_file_path.as_posix()) - - if not self.create_from_template_file: - logger.error(f'Failed to create from_template UI file at {self.create_from_template_file}') - return - - self.create_from_template_dialog = loader.load(self.create_from_template_file) - - if not self.create_from_template_dialog: - logger.error(f'Failed to load create from_template dialog file at {self.create_from_template_file}') - return - - self.create_from_template_ok_button = self.create_from_template_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_from_template_ok_button.accepted.connect(self.create_from_template_accepted_handler) - - self.create_from_template_list = self.create_from_template_dialog.findChild(QListView, 'genericTemplates') - self.refresh_create_from_template_list() - - self.create_from_template_dialog.exec() - - def create_from_template_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current gem. - Updates UI after completion. - :return: None - """ - create_gem_item = self.get_selected_gem_template() - if not create_gem_item: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - manifest.get_o3de_gems_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - gem_count = 0 - gem_name = "MyNewGem" - while os.path.exists(os.path.join(engine_path, gem_name)): - gem_name = f"MyNewGem{gem_count}" - gem_count += 1 - folder_dialog.selectFile(gem_name) - gem_folder = None - if folder_dialog.exec(): - gem_folder = folder_dialog.selectedFiles() - if gem_folder: - if engine_template.create_gem(gem_folder[0], create_gem_item[1]) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"gem {os.path.basename(os.path.normpath(gem_folder[0]))} created." - " Build your\nnew gem before hitting OK to launch.") - msg_box.exec() - return - - def add_project_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid project. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - manifest.get_o3de_projects_folder().as_posix()) - if project_folder: - if register.register(project_path=project_folder) == 0: - # Success - self.refresh_project_list() - - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Project {project_folder}.") - msg_box.exec() - return - - def add_gem_handler(self): - """ - Open a file search dialog looking for a folder which contains a gem. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - manifest.get_o3de_gems_folder().as_posix()) - if gem_folder: - if register.register(gem_path=gem_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Gem {gem_folder}.") - msg_box.exec() - return - - def add_template_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - manifest.get_o3de_templates_folder().as_posix()) - if template_folder: - if register.register(template_path=template_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Template {template_folder}.") - msg_box.exec() - return - - def add_restricted_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - manifest.get_o3de_restricted_folder().as_posix()) - if restricted_folder: - if register.register(restricted_path=restricted_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Restricted {restricted_folder}.") - msg_box.exec() - return - - def remove_project_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid project. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - manifest.get_o3de_projects_folder().as_posix()) - if project_folder: - if register.register(project_path=project_folder, remove=True) == 0: - # Success - self.refresh_project_list() - - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Project {project_folder}.") - msg_box.exec() - return - - def remove_gem_handler(self): - """ - Open a file search dialog looking for a folder which contains a gem. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - manifest.get_o3de_gems_folder().as_posix()) - if gem_folder: - if register.register(gem_path=gem_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Gem {gem_folder}.") - msg_box.exec() - return - - def remove_template_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - manifest.get_o3de_templates_folder().as_posix()) - if template_folder: - if register.register(template_path=template_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Template {template_folder}.") - msg_box.exec() - return - - def remove_restricted_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - manifest.get_o3de_restricted_folder().as_posix()) - if restricted_folder: - if register.register(restricted_path=restricted_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Restricted {restricted_folder}.") - msg_box.exec() - return - - def manage_project_gem_targets_handler(self): - """ - Opens the Gem management pane. Waits for the load thread to complete if still running and displays all - active gems for the current project as well as all available gems which aren't currently active. - :return: None - """ - - if not self.get_selected_project_path(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - - loader = QUiLoader() - self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - - if not self.manage_project_gem_targets_file: - logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') - return - - self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - - if not self.manage_project_gem_targets_dialog: - logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') - return - - self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Gems for Project:" - f" {self.get_selected_project_name()}") - - self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') - self.add_gem_button.clicked.connect(self.add_project_gem_targets_handler) - - self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'availableGemTargetsList') - self.refresh_project_gem_targets_available_list() - - self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, - 'removeGemTargetsButton') - self.remove_project_gem_targets_button.clicked.connect(self.remove_project_gem_targets_handler) - - self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'enabledGemTargetsList') - self.refresh_project_gem_targets_enabled_list() - - self.manage_project_gem_targets_dialog.exec() - - - def manage_project_gem_targets_get_selected_available_gems(self) -> list: - selected_items = self.available_gem_targets_list.selectionModel().selectedRows() - return [(self.available_gem_targets_list.model().data(item)) for item in selected_items] - - def manage_project_gem_targets_get_selected_enabled_gems(self) -> list: - selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows() - return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items] - - def add_project_gem_targets_handler(self) -> None: - gem_paths = manifest.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): - for gem_path in gem_paths: - enable_gem.enable_gem_in_project(gem_path=gem_path, - project_path=self.get_selected_project_path()) - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - return - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - - def remove_project_gem_targets_handler(self): - gem_paths = manifest.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): - for gem_path in gem_paths: - disable_gem.disable_gem_in_project(gem_path=gem_path, - project_path=self.get_selected_project_path()) - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - return - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - - def refresh_project_gem_targets_enabled_list(self) -> None: - enabled_project_gem_targets_model = QStandardItemModel() - enabled_project_gems = cmake.get_project_gems(project_path=self.get_selected_project_path()) - for gem_target in sorted(enabled_project_gems): - model_item = QStandardItem(gem_target) - enabled_project_gem_targets_model.appendRow(model_item) - self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) - - - def refresh_project_gem_targets_available_list(self) -> None: - available_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = cmake.get_project_gems(project_path=self.get_selected_project_path()) - all_gem_targets = manifest.get_all_gems() - for gem_target in sorted(all_gem_targets): - if gem_target not in enabled_project_gem_targets: - model_item = QStandardItem(gem_target) - available_project_gem_targets_model.appendRow(model_item) - self.available_gem_targets_list.setModel(available_project_gem_targets_model) - - - def refresh_create_project_template_list(self) -> None: - self.create_project_template_model = QStandardItemModel() - for project_template_path in manifest.get_project_templates(): - model_item = QStandardItem(project_template_path) - self.create_project_template_model.appendRow(model_item) - self.create_project_template_list.setModel(self.create_project_template_model) - - def refresh_create_gem_template_list(self) -> None: - self.create_gem_template_model = QStandardItemModel() - for gem_template_path in manifest.get_gem_templates(): - model_item = QStandardItem(gem_template_path) - self.create_gem_template_model.appendRow(model_item) - self.create_gem_template_list.setModel(self.create_gem_template_model) - - def refresh_create_from_template_list(self) -> None: - self.create_from_template_model = QStandardItemModel() - for generic_template_path in manifest.get_generic_templates(): - model_item = QStandardItem(generic_template_path) - self.create_from_template_model.appendRow(model_item) - self.create_from_template_list.setModel(self.create_from_template_model) - - def update_mru_list(self, used_project: str) -> None: - """ - Promote a supplied project name to the "most recent" in a given MRU list. - :param used_project: path to project to promote - :param file_path: path to mru list file - :return: None - """ - used_project = os.path.normpath(used_project) - if not os.path.exists(os.path.dirname(self.mru_file_path)): - os.makedirs(os.path.dirname(self.mru_file_path), exist_ok=True) - mru_data = {} - try: - with open(self.mru_file_path, 'r') as mru_file: - mru_data = json.loads(mru_file.read()) - except FileNotFoundError: - pass - except json.JSONDecodeError: - pass - - recent_list = mru_data.get('Projects', []) - recent_list = [item for item in recent_list if item.get('Path') != used_project and - self.is_project_folder(item.get('Path'))] - - new_list = [{'Path': used_project}] - new_list.extend(recent_list) - - mru_data['Projects'] = new_list - try: - with open(self.mru_file_path, 'w') as mru_file: - mru_file.write(json.dumps(mru_data, indent=1)) - except PermissionError as e: - logger.warning(f"Failed to write {self.mru_file_path} with error {e}") - - def get_mru_list(self) -> List[str]: - """ - Retrieve the current MRU list. Does not perform validation that the projects still appear valid - :return: list of full path strings to project folders - """ - if not os.path.exists(os.path.dirname(self.mru_file_path)): - return [] - try: - with open(self.mru_file_path, 'r') as mru_file: - mru_data = json.loads(mru_file.read()) - except FileNotFoundError: - return [] - except json.JSONDecodeError: - logger.error(f'MRU list at {self.mru_file_path} is not valid JSON') - return [] - - recent_list = mru_data.get('Projects', []) - return [item.get('Path') for item in recent_list if item.get('Path') is not None] - - @Slot(str) - def handle_log_message(self, message: str) -> None: - """ - Signal handler for messages from the logger. Displays the most recent warning/error - :param message: formatted log message from DialogLoggerSignaller - :return: - """ - if not self.log_display: - return - self.log_display.setText(message) - self.log_display.setToolTip(message) - - -if __name__ == "__main__": - dialog_app = QApplication(sys.argv) - my_dialog = ProjectManagerDialog() - dialog_app.exec_() - sys.exit(0) diff --git a/scripts/project_manager/pyside.py b/scripts/project_manager/pyside.py deleted file mode 100755 index 80bf327f9b..0000000000 --- a/scripts/project_manager/pyside.py +++ /dev/null @@ -1,61 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import os -import logging - -from pathlib import Path - -logger = logging.getLogger() - -pyside_initialized = False -old_env = os.environ.copy() - -# Helper to extend OS PATH for pyside to locate our QT binaries based on our build folder -def add_pyside_environment(bin_path): - if is_pyside_ready(): - # No need to reinitialize currently - logger.info("Pyside environment already initialized") - return - global old_env - old_env = os.environ.copy() - binaries_path = Path(os.path.normpath(bin_path)) - platforms_path = binaries_path.joinpath("platforms") - logger.info(f'Adding binaries path {binaries_path}') - os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(platforms_path) - - path = os.environ['PATH'] - - new_path = os.pathsep.join([str(binaries_path), str(platforms_path), path]) - os.environ['PATH'] = new_path - - global pyside_initialized - pyside_initialized = True - - -def is_pyside_ready(): - return pyside_initialized - - -def is_configuration_valid(workspace): - return os.path.basename(workspace.paths.build_directory()) != "debug" - - -def uninstall_env(): - if not is_pyside_ready(): - logger.warning("Pyside not initialized") - return os.environ - - global old_env - if old_env.get("QT_QPA_PLATFORM_PLUGIN_PATH"): - old_env.pop("QT_QPA_PLATFORM_PLUGIN_PATH") - os.environ = old_env - return old_env diff --git a/scripts/project_manager/tests/__init__.py b/scripts/project_manager/tests/__init__.py deleted file mode 100755 index 4d5680a30d..0000000000 --- a/scripts/project_manager/tests/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# diff --git a/scripts/project_manager/tests/test_projects.py b/scripts/project_manager/tests/test_projects.py deleted file mode 100755 index d073cf6c7a..0000000000 --- a/scripts/project_manager/tests/test_projects.py +++ /dev/null @@ -1,255 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - - -import pytest -''' -import os -import sys -import tempfile -import logging -import pathlib -from unittest.mock import MagicMock - -logger = logging.getLogger() - -# Code lives one folder above -project_manager_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(project_manager_path) - -from pyside import add_pyside_environment, is_configuration_valid -from ly_test_tools import WINDOWS - -sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) -executable_path = '' -from cmake.Tools import registration -from cmake.Tools import engine_template - - -class ProjectHelper: - def __init__(self): - self._temp_directory = pathlib.Path(tempfile.TemporaryDirectory().name).resolve() - self._temp_directory.mkdir(parents=True, exist_ok=True) - - self.home_path = self._temp_directory - registration.override_home_folder = self.home_path - self.engine_path = registration.get_this_engine_path() - if registration.register(engine_path=self.engine_path): - assert True, f"Failed to register the engine." - - if registration.register_shipped_engine_o3de_objects(): - assert True, f"Failed to register shipped engine objects." - - self.projects_folder = registration.get_o3de_projects_folder() - if not self.projects_folder.is_dir(): - assert True - - self.application = None - self.dialog = None - - def create_empty_projects(self): - self.project_1_dir = self.projects_folder / "Project1" - if engine_template.create_project(project_manager_path=self.project_1_dir): - assert True, f"Failed to create Project1." - - self.project_2_dir = self.projects_folder / "Project2" - if engine_template.create_project(project_manager_path=self.project_2_dir): - assert True, f"Failed to create Project2." - - self.project_3_dir = self.projects_folder / "Project3" - if engine_template.create_project(project_manager_path=self.project_3_dir): - assert True, f"Failed to create Project3." - - self.invalid_project_dir = self.projects_folder / "InvalidProject" - self.invalid_project_dir.mkdir(parents=True, exist_ok=True) - - def setup_dialog_test(self, workspace): - add_pyside_environment(workspace.paths.build_directory()) - - if not is_configuration_valid(workspace): - # This is essentially skipif debug. Our debug tests use our profile version of python, but that means we'd - # need to use the profile version of PySide which works with the profile QT libs which aren't in the debug - # folder we've built. - return None - - from PySide2.QtWidgets import QApplication, QMessageBox - - if QApplication.instance(): - self.application = QApplication.instance() - else: - self.application = QApplication(sys.argv) - assert self.application - - from projects import ProjectManagerDialog - - try: - self.dialog = ProjectManagerDialog(settings_folder=self.home_path) - return self.dialog - except Exception as e: - logger.error(f'Failed to create ProjectManagerDialog with error {e}') - return None - - def create_project_from_template(self, project_name) -> bool: - """ - Uses the dialog to create a temporary project based on the first template found - :param project_name: Name of project to create. Will be created under temp_project_root - :return: True for Success, False for failure - """ - from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectManagerDialog - - QWidget.exec = MagicMock() - self.dialog.create_project_handler() - QWidget.exec.assert_called_once() - - assert len(self.dialog.project_templates), 'Failed to find any project templates' - ProjectManagerDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0]) - - QFileDialog.exec = MagicMock() - create_project_path = self.projects_folder / project_name - QFileDialog.selectedFiles = MagicMock(return_value=[create_project_path]) - self.dialog.create_project_accepted_handler() - if create_project_path.is_dir(): - assert True, f"Expected project creation folder not found at {create_project_path}" - - if QWidget.exec.call_count == 2: - assert True, "Message box confirming project creation failed to show" - - -@pytest.fixture -def project_helper(): - return ProjectHelper() - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_logger_handler(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - from PySide2.QtWidgets import QMessageBox - QMessageBox.warning = MagicMock() - logger.error(f'Testing logger') - QMessageBox.warning.assert_called_once() - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_mru_list(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - project_helper.create_empty_projects() - - from PySide2.QtWidgets import QMessageBox - - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}' - - QMessageBox.warning = MagicMock() - my_dialog.add_project(project_helper.invalid_project_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}' - QMessageBox.warning.assert_called_once() - - my_dialog.add_project(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}' - - my_dialog.add_project(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' - - my_dialog.update_mru_list(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' - - my_dialog.add_project(project_helper.project_2_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}' - - assert mru_list[0] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't first item" - assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item" - - my_dialog.update_mru_list(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list added wrong items {mru_list}' - assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" - assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - - my_dialog.add_project(project_helper.invalid_project_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}' - assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" - assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - - my_dialog.add_project(project_helper.project_3_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}' - assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item" - assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item" - assert mru_list[2] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't third item" - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_create_project(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - project_helper.create_project_from_template("TestCreateProject") - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_add_remove_gems(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - my_project_name = "TestAddRemoveGems" - - project_helper.create_project_from_template(project_manager_path=my_project_name) - my_project_path = project_helper.projects_folder / my_project_name - - from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectManagerDialog - - assert my_dialog.get_selected_project_path() == my_project_path, "TestAddRemoveGems project not selected" - QWidget.exec = MagicMock() - my_dialog.manage_gems_handler() - assert my_dialog.manage_gem_targets_dialog, "No gem management dialog created" - QWidget.exec.assert_called_once() - - if not len(my_dialog.all_gems_list): - assert True, 'Failed to find any gems' - - my_test_gem_path = my_dialog.all_gems_list[0] - gem_data = registration.get_gem_data(my_test_gem_path) - my_test_gem_selection = (my_test_gem_name, my_test_gem_path) - ProjectManagerDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection]) - - assert my_test_gem_name, "No Name set in test gem" - assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list' - - my_dialog.add_gems_handler() - assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list' - - ProjectManagerDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name]) - my_dialog.remove_gems_handler() - assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list' -''' - -def test_project_place_holder(): - pass \ No newline at end of file diff --git a/scripts/project_manager/tests/test_pyside.py b/scripts/project_manager/tests/test_pyside.py deleted file mode 100755 index c9be313f2b..0000000000 --- a/scripts/project_manager/tests/test_pyside.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import pytest -import sys -import os - -pyside_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(pyside_path) - -from pyside import add_pyside_environment, is_pyside_ready, is_configuration_valid - -from ly_test_tools import WINDOWS - -import logging -logger = logging.getLogger() - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_add_pyside_environment(workspace): - import_failed = False - try: - import PySide2 - except ImportError: - import_failed = True - - if not import_failed: - cur_path = sys.path - logger.warning(f"Expected to fail initial import but passed. Sys path was {cur_path}") - - assert is_pyside_ready() is False, "Expected pyside not to be initialized yet" - add_pyside_environment(workspace.paths.build_directory()) - assert is_pyside_ready() is True, "Expected pyside to be initialized yet" - - try: - import PySide2 - if not is_configuration_valid(workspace): - return - from PySide2.QtWidgets import QApplication - except ImportError as e: - assert False, f"Failed to import PySide2 with error {e}" - try: - from PySide2.QtWidgets import QApplication - except ImportError as e: - assert False, f"Failed to import QApplication from PySide2.QtWidgets with error {e}" - diff --git a/scripts/project_manager/ui/create_from_template.ui b/scripts/project_manager/ui/create_from_template.ui deleted file mode 100644 index b6a8740594..0000000000 --- a/scripts/project_manager/ui/create_from_template.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createFromTemplateDialog - - - - 0 - 0 - 467 - 288 - - - - Create From Template - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createFromTemplateDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createFromTemplateDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/create_gem.ui b/scripts/project_manager/ui/create_gem.ui deleted file mode 100644 index 5a5fd14a6d..0000000000 --- a/scripts/project_manager/ui/create_gem.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createGemDialog - - - - 0 - 0 - 467 - 288 - - - - Create Gem - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createGemDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createGemDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/create_project.ui b/scripts/project_manager/ui/create_project.ui deleted file mode 100644 index 67a13325b0..0000000000 --- a/scripts/project_manager/ui/create_project.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createProjectDialog - - - - 0 - 0 - 467 - 288 - - - - Create Project - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createProjectDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createProjectDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/manage_gem_targets.ui b/scripts/project_manager/ui/manage_gem_targets.ui deleted file mode 100644 index ea9e607d42..0000000000 --- a/scripts/project_manager/ui/manage_gem_targets.ui +++ /dev/null @@ -1,165 +0,0 @@ - - - manageGemTargetsDialog - - - - 0 - 0 - 702 - 297 - - - - Manage Gem Targets - - - - - 310 - 260 - 71 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - 440 - 50 - 250 - 221 - - - - - 0 - 0 - - - - QAbstractItemView::ExtendedSelection - - - - - - 10 - 50 - 250 - 221 - - - - QAbstractItemView::ExtendedSelection - - - - - - 440 - 30 - 251 - 16 - - - - Available Gem Targets - - - - - - 10 - 30 - 251 - 16 - - - - Enabled Gem Targets - - - - - - 264 - 130 - 171 - 23 - - - - Remove Gem Targets >> - - - - - - 264 - 100 - 171 - 23 - - - - << Add Gem Targets - - - - - - 10 - 5 - 400 - 21 - - - - Adding new Gem Targets may require rebuilding - - - - - - - close - accepted() - manageGemTargetsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - close - rejected() - manageGemTargetsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/project_manager.ico b/scripts/project_manager/ui/project_manager.ico deleted file mode 100644 index 597266946a..0000000000 --- a/scripts/project_manager/ui/project_manager.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:113be7ded1969e0d535722382b6d3730eed052a10430cf23804ae7f20414b999 -size 108278 diff --git a/scripts/project_manager/ui/project_manager.ui b/scripts/project_manager/ui/project_manager.ui deleted file mode 100644 index 6b3b5f758c..0000000000 --- a/scripts/project_manager/ui/project_manager.ui +++ /dev/null @@ -1,407 +0,0 @@ - - - Dialog - - - - 0 - 0 - 712 - 395 - - - - - 1 - 0 - - - - O3DE - - - Select and manage your projects for O3DE - - - - - 540 - 360 - 161 - 31 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 30 - 691 - 31 - - - - Current project to launch or manage gems for. - - - - - - 10 - 10 - 47 - 13 - - - - Project - - - - - - 270 - 220 - 431 - 141 - - - - QFrame::Panel - - - QFrame::Sunken - - - - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - 10 - 70 - 241 - 151 - - - - Create - - - - - 10 - 110 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create From Template - - - - - - 10 - 20 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Project - - - - - - 11 - 50 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Gem - - - - - - 11 - 80 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Template - - - - - - - 260 - 70 - 451 - 141 - - - - Registration - - - - - 10 - 80 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Template - - - - - - 10 - 20 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Project - - - - - - 10 - 50 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Gem - - - - - - 10 - 110 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Restricted - - - - - - 230 - 110 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Restricted - - - - - - 230 - 20 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Project - - - - - - 230 - 50 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Gem - - - - - - 230 - 80 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Template - - - - - - - 10 - 230 - 241 - 121 - - - - Manage Project - - - - - 10 - 80 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Server Gem Targets - - - - - - 10 - 50 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Tool Gem Targets - - - - - - 10 - 20 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Runtime Gem Targets - - - - - - - - okCancel - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - From 1b8810b9631a7cd42e141fa23f2cf6bbd1ca1ef0 Mon Sep 17 00:00:00 2001 From: Eric Phister <52085794+amzn-phist@users.noreply.github.com> Date: Thu, 3 Jun 2021 14:52:21 -0500 Subject: [PATCH 60/65] Remove Project namespace from project template (#1124) In the enabled_gems.cmake file, can remove the Project:: namespace on the project module because it's treated the same as a Gem module now. --- Templates/DefaultProject/Template/Code/enabled_gems.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Templates/DefaultProject/Template/Code/enabled_gems.cmake b/Templates/DefaultProject/Template/Code/enabled_gems.cmake index dfb7d93233..ec45be0743 100644 --- a/Templates/DefaultProject/Template/Code/enabled_gems.cmake +++ b/Templates/DefaultProject/Template/Code/enabled_gems.cmake @@ -10,7 +10,7 @@ # {END_LICENSE} set(ENABLED_GEMS - Project::${Name} + ${Name} Atom_AtomBridge Camera CameraFramework From 1245e0b327b3bbeb05ab4f181fd1403cbeaa908e Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Thu, 3 Jun 2021 15:02:03 -0500 Subject: [PATCH 61/65] Adding Tools and Builders alias to the DccScriptingInterface gem targets (#1087) * Adding a Tools and Builders variant to the DccScriptingInterface gem target to allow it to be used as a gem in the AtomTest project * Adding support to ly_create_alias to be able to specify an alias with no dependencies Updated the SettingsRegistry.cmake generation code to support generating a Gem target entry in the cmake_dependencies.*.setreg file when an interface library with no dependencies is parsed --- .../DccScriptingInterface/Code/CMakeLists.txt | 10 ++++++++ cmake/Gems.cmake | 10 ++++---- cmake/SettingsRegistry.cmake | 23 +++++++++++-------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt index 76cbf6546d..bad0d743c7 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt @@ -9,6 +9,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) + return() +endif() + ly_add_target( NAME DccScriptingInterface.Static STATIC NAMESPACE Gem @@ -38,3 +42,9 @@ ly_add_target( PRIVATE Gem::DccScriptingInterface.Static ) + +# Any 'tool' type applications should use Gem::DccScriptingInterface.Editor: +ly_create_alias(NAME DccScriptingInterface.Tools NAMESPACE Gem TARGETS Gem::DccScriptingInterface.Editor) +# Add an empty 'builders' alias to allow the DccScriptInterface root gem path to be added to the generated +# cmake_dependencies..assetprocessor.setreg to allow the asset scan folder for it to be added +ly_create_alias(NAME DccScriptingInterface.Builders NAMESPACE Gem) diff --git a/cmake/Gems.cmake b/cmake/Gems.cmake index d418d5dcd1..169210d991 100644 --- a/cmake/Gems.cmake +++ b/cmake/Gems.cmake @@ -29,9 +29,6 @@ function(ly_create_alias) message(FATAL_ERROR "Provide the namespace of the alias to create using the NAMESPACE keyword") endif() - if (NOT ly_create_alias_TARGETS) - message(FATAL_ERROR "Provide the name of the targets the alias be associated with, using the TARGETS keyword") - endif() if(TARGET ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}) message(FATAL_ERROR "Target already exists, cannot create an alias for it: ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}\n" @@ -78,8 +75,11 @@ function(ly_create_alias) list(APPEND final_targets ${de_aliased_target_name}) endforeach() - ly_parse_third_party_dependencies("${final_targets}") - ly_add_dependencies(${ly_create_alias_NAME} ${final_targets}) + # add_dependencies must be called with at least one dependent target + if(final_targets) + ly_parse_third_party_dependencies("${final_targets}") + ly_add_dependencies(${ly_create_alias_NAME} ${final_targets}) + endif() # now add the final alias: add_library(${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME} ALIAS ${ly_create_alias_NAME}) diff --git a/cmake/SettingsRegistry.cmake b/cmake/SettingsRegistry.cmake index 4d932601b4..d7ef91d284 100644 --- a/cmake/SettingsRegistry.cmake +++ b/cmake/SettingsRegistry.cmake @@ -25,14 +25,15 @@ set(gems_json_template [[ @target_gem_dependencies_names@ } } -}]] +} +]] ) -set(gem_module_template [[ - "@stripped_gem_target@": - { - "Modules":["$"], - "SourcePaths":["@gem_module_root_relative_to_engine_root@"] - }]] + string(APPEND gem_module_template +[=[ "@stripped_gem_target@":]=] "\n" +[=[ {]=] "\n" +[=[$<$,INTERFACE_LIBRARY>>: "Modules":["$"]]=] "$\n>" +[=[ "SourcePaths":["@gem_module_root_relative_to_engine_root@"]]=] "\n" +[=[ }]=] ) #!ly_get_gem_load_dependencies: Retrieves the list of "load" dependencies for a target @@ -161,10 +162,12 @@ function(ly_delayed_generate_settings_registry) message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist") endif() + get_property(has_manually_added_dependencies TARGET ${gem_target} PROPERTY MANUALLY_ADDED_DEPENDENCIES SET) get_target_property(target_type ${gem_target} TYPE) - if (target_type STREQUAL "INTERFACE_LIBRARY") - # don't use interface libraries here, we only want ones which produce actual binaries. - # we have still already recursed into their dependencies - they'll show up later. + if (target_type STREQUAL "INTERFACE_LIBRARY" AND has_manually_added_dependencies) + # don't use interface libraries here, we only want ones which produce actual binaries unless the target + # is empty. We have still already recursed into their dependencies - they'll show up later. + # When the target has no dependencies however we want to add the gem root path to the generated setreg continue() endif() From 0a9d6f5f0f54419b8bab035379b1f631c97a4a53 Mon Sep 17 00:00:00 2001 From: chcurran Date: Thu, 3 Jun 2021 13:13:22 -0700 Subject: [PATCH 62/65] Bug fixes and improvements brought over from demo work. * Generic Multi Function Call ability added to extensible nodes * Code gen improvements, including allowing for more manually codewritten extension of codegen facilities * CVAR to disable automatic update of deprecated node * Fixed variable sorting error that can apply to parser/runtime added variables * Made Edit/SerializeContext ClassBuilder public, as it was needlessly private * Fixed dangerous Datum::GetValueAddress(), it now checks for an empty storage AZSTd::any, as does Datum::Empty() --- .../AzCore/AzCore/Serialization/EditContext.h | 5 +- .../AzCore/Serialization/SerializeContext.h | 10 +- .../Code/Editor/Components/EditorGraph.cpp | 5 +- .../AutoGen/ScriptCanvasNodeable_Header.jinja | 40 +- .../AutoGen/ScriptCanvasNodeable_Source.jinja | 160 +- .../ScriptCanvas_Nodeable_Macros.jinja | 5 +- .../Code/Include/ScriptCanvas/Core/Core.h | 1 - .../Code/Include/ScriptCanvas/Core/Datum.cpp | 8 +- .../Code/Include/ScriptCanvas/Core/Datum.h | 2 +- .../Code/Include/ScriptCanvas/Core/Node.cpp | 11 +- .../Code/Include/ScriptCanvas/Core/Node.h | 2 + .../Include/ScriptCanvas/Core/NodeableNode.h | 2 + .../Code/Include/ScriptCanvas/Core/PureData.h | 2 + .../Grammar/AbstractCodeModel.cpp | 193 +- .../ScriptCanvas/Grammar/AbstractCodeModel.h | 2 + .../ScriptCanvas/Grammar/ParsingUtilities.cpp | 10 +- .../Include/ScriptCanvas/Grammar/Primitives.h | 28 + .../Grammar/PrimitivesExecution.cpp | 38 +- .../Grammar/PrimitivesExecution.h | 8 + .../Include/ScriptCanvas/Results/ErrorText.h | 6 + ...UnitTest_RunAllTransformNodes.scriptcanvas | 9124 +++++++---------- 21 files changed, 4416 insertions(+), 5246 deletions(-) diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContext.h b/Code/Framework/AzCore/AzCore/Serialization/EditContext.h index ed19130579..394a6f047c 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContext.h @@ -127,13 +127,14 @@ namespace AZ */ class EditContext { + public: /// @cond EXCLUDE_DOCS class ClassBuilder; class EnumBuilder; using ClassInfo = ClassBuilder; ///< @deprecated Use EditContext::ClassBuilder using EnumInfo = EnumBuilder; ///< @deprecated Use EditContext::EnumBuilder /// @endcond - public: + AZ_CLASS_ALLOCATOR(EditContext, SystemAllocator, 0); /** @@ -186,6 +187,7 @@ namespace AZ * look at the unit tests and example to see use cases. * */ + public: class ClassBuilder { friend EditContext; @@ -399,6 +401,7 @@ namespace AZ EnumBuilder* Value(const char* name, E value); }; + private: typedef AZStd::list ClassDataListType; typedef AZStd::unordered_map EnumDataMapType; diff --git a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h index 6806232337..279ce01039 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h @@ -101,6 +101,9 @@ namespace AZ class SerializeContext : public ReflectContext { + static const unsigned int VersionClassDeprecated = (unsigned int)-1; + + public: /// @cond EXCLUDE_DOCS friend class EditContext; class ClassBuilder; @@ -108,9 +111,6 @@ namespace AZ /// @endcond class EnumBuilder; - static const unsigned int VersionClassDeprecated = (unsigned int)-1; - - public: class ClassData; struct EnumerateInstanceCallContext; struct ClassElement; @@ -1131,6 +1131,7 @@ namespace AZ * ->Version(3,&MyVersionConverter) * ->Field("data",&MyStruct::m_data); */ + public: class ClassBuilder { friend class SerializeContext; @@ -1330,7 +1331,8 @@ namespace AZ AZStd::vector* m_currentAttributes = nullptr; }; - EditContext* m_editContext; ///< Pointer to optional edit context. + private: + EditContext* m_editContext; ///< Pointer to optional edit context. UuidToClassMap m_uuidMap; ///< Map for all class in this serialize context AZStd::unordered_multimap m_classNameToUuid; /// Map all class names to their uuid AZStd::unordered_multimap m_uuidGenericMap; ///< Uuid to ClassData map of reflected classes with GenericTypeInfo diff --git a/Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp b/Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp index 6f28cbfe19..616be11dd0 100644 --- a/Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp +++ b/Gems/ScriptCanvas/Code/Editor/Components/EditorGraph.cpp @@ -91,7 +91,8 @@ AZ_POP_DISABLE_WARNING #include #include -//// + AZ_CVAR(bool, g_disableDeprecatedNodeUpdates, false, {}, AZ::ConsoleFunctorFlags::Null, + "Disables automatic update attempts of deprecated nodes, so that graphs that require and update can be viewed in their original form"); namespace EditorGraphCpp { @@ -3642,7 +3643,7 @@ namespace ScriptCanvasEditor if (scriptCanvasNode) { - if (scriptCanvasNode->IsDeprecated()) + if (scriptCanvasNode->IsDeprecated() && !g_disableDeprecatedNodeUpdates) { ScriptCanvas::NodeConfiguration nodeConfig = scriptCanvasNode->GetReplacementNodeConfiguration(); if (nodeConfig.IsValid()) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja index 202fad5b60..313fd196b0 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Header.jinja @@ -22,6 +22,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #pragma once +#include +#include +#include +#include + #include #include @@ -43,6 +48,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. {% endif %} {% endif %} +{%- set attribute_Base = Class.attrib['Base'] %} +{% if not Class.attrib['Base'] is defined %} +{% set attribute_Base = "ScriptCanvas::Nodeable" %} +{% endif %} + {% if attribute_Namespace is defined %} namespace {{attribute_Namespace}} { @@ -66,6 +76,9 @@ namespace {{attribute_Namespace}} public: \ AZ_RTTI({{className}}, "{{nodeableClassName|createHashGuid}}"{% if Class.attrib['Base'] is defined %}, {{ Class.attrib['Base'] }}{% endif %}); \ static void Reflect(AZ::ReflectContext* reflection); \ + static void ExtendReflectionSerialize([[maybe_unused]] AZ::SerializeContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionSerialize'] is defined %};{% else %}{}{% endif %} \ + static void ExtendReflectionEdit([[maybe_unused]] AZ::EditContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionEdit'] is defined %};{% else %}{}{% endif %} \ + static void ExtendReflectionBehavior([[maybe_unused]] AZ::BehaviorContext::ClassBuilder<{{className}}>* builder){% if Class.attrib['ExtendReflectionBehavior'] is defined %};{% else %}{}{% endif %} \ static const char* GetDescription() { return "{{ macros.GetAttributeAsString(Class.attrib, 'Description') }}"; } \ ScriptCanvas::NodePropertyInterface* GetPropertyInterface(AZ::Crc32 propertyId) override; \ bool IsActive() const override { return false; } \ @@ -83,13 +96,33 @@ public: \ AZ_COMPONENT({{nodeableNodeName}}, {% if Class.attrib['NodeableUuid'] is defined %}"{{Class.attrib['NodeableUuid']}}"{% else %}"{{nodeableNodeName|createHashGuid}}"{% endif %}, ScriptCanvas::Nodes::NodeableNode); static void Reflect(AZ::ReflectContext* context); + + static void ExtendReflectionSerialize([[maybe_unused]] AZ::SerializeContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionSerialize'] is defined %};{% else %}{}{% endif %} + + static void ExtendReflectionEdit([[maybe_unused]] AZ::EditContext::ClassBuilder* builder){% if Class.attrib['ExtendReflectionEdit'] is defined %};{% else %}{}{% endif %} + + static void ExtendReflectionBehavior([[maybe_unused]] AZ::BehaviorContext::ClassBuilder<{{nodeableNodeName}}>* builder){% if Class.attrib['ExtendReflectionBehavior'] is defined %};{% else %}{}{% endif %} + void ConfigureSlots() override; + +{% if Class.attrib['ExtendConfigureSlots'] is defined %} + void ExtendConfigureSlots([[maybe_unused]] SlotExecution::Ins& ins, [[maybe_unused]] SlotExecution::Outs& latents); + +{% else %} + /* no slot configuration extension, Use Class attribute 'ExtendConfigureSlots' to extend them */ + +{% endif %} void ConfigureVisualExtensions() override; + size_t GenerateFingerprint() const override; -{% if Class.attrib['EntryPoint'] is defined and Class.attrib['EntryPoint'] == "true" %} + +{% if Class.attrib['EntryPoint'] is defined and Class.attrib['EntryPoint'] == "true" %} bool IsEntryPoint() const override { return true; } -{% endif %} + +{% endif %} {{nodeableNodeName}}(); + + {{Class.attrib['NodeDeclarations']}} }; } {% endif %} @@ -100,6 +133,5 @@ public: \ {{ macros.ReportErrors() }} - {% endfor %} -{% endfor %} +{% endfor %} \ No newline at end of file diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja index 71681ed73b..b977daf43b 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvasNodeable_Source.jinja @@ -19,15 +19,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include -#include -#include - #include #include #include #include - #include {% for xml in dataFiles %} @@ -47,13 +42,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. {%- set attribute_Category = Class.attrib['Category'] %} {%- set attribute_Uuid = Class.attrib['Uuid'] %} {%- set attribute_Icon = Class.attrib['Icon'] %} -{%- set attribute_Base = Class.attrib['Base'] %} {%- set attribute_GeneratePropertyFriend = Class.attrib['GeneratePropertyFriend'] %} {%- set attribute_Version = Class.attrib['Version'] %} {%- set attribute_VersionConverter = Class.attrib['VersionConverter'] %} {%- set attribute_EventHandler = Class.attrib['EventHandler'] %} {%- set attribute_Deprecated = Class.attrib['Deprecated'] %} + +{%- set attribute_Base = Class.attrib['Base'] %} +{% if not Class.attrib['Base'] is defined %} +{% set attribute_Base = "ScriptCanvas::Nodeable" %} +{% endif %} + {% set attribute_Namespace = undefined %} {%- if Class.attrib['Namespace'] is defined %} {% if Class.attrib['Namespace'] != "None" %} @@ -95,19 +95,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. {{CollectDisplayGroups('Output')}} {{CollectDisplayGroups('Parameter')}} -{# FOR DEBUGGING / DIAGNOSTIC - -// Standalone (No DisplayGroup) {{ global_standaloneTagMap }} -{% for key, value in global_standaloneTagMap.items() %} -// {{key}} : {{value}} -{% endfor %} - -// DisplayGrouped {{ global_displayGroupMap }} -{% for key, value in global_displayGroupMap.items() %} -// {{key}} : {{value}} -{% endfor %} -#} - {# ----------------------------------------------------------------------------------------- #} {% if attribute_Namespace is defined %} @@ -115,33 +102,6 @@ namespace {{attribute_Namespace}} { {% endif %} -{# Standard "In" function } -{{nodemacro.FunctionSignature(attribute_QualifiedName, Class)}} -{ -{%- for parameter in Class.findall('Parameter') -%} -{% if parameter.attrib['Input'] is defined and parameter.attrib['Input'] == "True" %} - -// this->{{parameter.attrib['Name']}} = arg{{loop.index0}}; - -{%- endif -%} -{% endfor %} - -{% set returnNames = [] %} -{% set returnTypes = [] %} -{%- for return in Class.findall('Parameter') -%} -{%- if return.attrib['Output'] is defined and return.attrib['Output'] == "True" -%} -{% if returnTypes.append(return.attrib['Type']) %}{% endif %} -{% if returnNames.append("this->" + return.attrib['Name']) %}{% endif %} -{%- endif -%} -{%- endfor -%} - -{% if returnNames|length() == 1 %} -return {{returnNames[0]}}; -{% elif returnNames|length() > 1 %} - return AZStd::tuple<{{returnTypes|join(", ")}}>({{returnNames|join(", ")}}); -{% endif %} -} -#} {%- set nodeableNodeName = attribute_Name + 'Node' %} {% set list_outputs = [] %} {% for output in Class.iter('Output') %} @@ -156,43 +116,81 @@ return {{returnNames[0]}}; {% for item in Class.iter('Output') %} {% if item.attrib['DisplayGroup'] is defined %}{% set displayGroup = item.attrib['DisplayGroup'] %}{% endif %} {% endfor %} +{% set branches = [] %} +{% for method in Class.findall('Input') %} +{% for branch in method.findall('Branch') %} +{% if branches.append(branch) %}{% endif %} +{% endfor %} +{% endfor %} {# ExecutionOuts #} // ExecutionOuts begin {{ nodemacro.ExecutionOutDefinitions(Class, attribute_QualifiedName)}} +{% if not Class.attrib['ExtendConfigureSlots'] is defined %} size_t {{attribute_QualifiedName}}::GetRequiredOutCount() const { return {{Class.findall('Output')|length + branches|length}}; }{% endif %} // ExecutionOuts end {# Reflect #} + +{% if Class.attrib['ExtendReflectionSerialize'] is defined %} +{% set ExtendReflectionSerialize = "defined" %} +{% set preSerialize = "serializeBuilder" %} +{% set postSerialize = ";" %} +{% else %} +{% set preSerialize = "" %} +{% set postSerialize = "" %} +{% endif %} + +{% if Class.attrib['ExtendReflectionEdit'] is defined %} +{% set ExtendReflectionEdit = "defined" %} +{% set preEdit = "editorBuilder" %} +{% set postEdit = ";" %} +{% else %} +{% set preEdit = "" %} +{% set postEdit = "" %} +{% endif %} + +{% if Class.attrib['ExtendReflectionBehavior'] is defined %} +{% set ExtendReflectionBehavior = "defined" %} +{% set preBehavior = "behaviorBuilder" %} +{% set postBehavior = ";" %} +{% else %} +{% set preBehavior = "" %} +{% set postBehavior = "" %} +{% endif %} + void {{attribute_QualifiedName}}::Reflect(AZ::ReflectContext* context) { using namespace ScriptCanvas; if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { - serializeContext->Class<{{ attribute_Name }}{% if attribute_Base is defined %}, {{ attribute_Base }}{% endif %}>() +{% if ExtendReflectionSerialize is defined %} auto {{preSerialize}} = {% else %} {% endif %}serializeContext->Class<{{ attribute_Name }}{% if attribute_Base is defined %}, {{ attribute_Base }}{% endif %}>(){{postSerialize}} {% if attribute_EventHandler is defined %} - ->EventHandler<{{ attribute_EventHandler }}>() + {{preSerialize}}->EventHandler<{{ attribute_EventHandler }}>(){{postSerialize}} {% endif %} {# Serialized Properties #} {% for Property in Class.iter('Property') %} {% set property_Name = Property.attrib['Name'] %} - ->Field("{{ property_Name }}", &{{ attribute_Name }}::{{ property_Name | replace(' ','') }}) + {{preSerialize}}->Field("{{ property_Name }}", &{{ attribute_Name }}::{{ property_Name | replace(' ','') }}){{postSerialize}} {% endfor %} ; +{% if ExtendReflectionSerialize is defined %} + ExtendReflectionSerialize(&{{preSerialize}}); +{% endif %} + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { - editContext->Class<{{ attribute_QualifiedName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - +{% if ExtendReflectionEdit is defined %} auto {{preEdit}} = {% else %} {% endif %}editContext->Class<{{ attribute_QualifiedName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}"){{postEdit}} + {{preEdit}}->ClassElement(AZ::Edit::ClassElements::EditorData, ""){{postEdit}} + {{preEdit}}->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly){{postEdit}} {% if attribute_Category is defined %} - ->Attribute(AZ::Edit::Attributes::Category, "{{ attribute_Category }}") + {{preEdit}}->Attribute(AZ::Edit::Attributes::Category, "{{ attribute_Category }}"){{postEdit}} {%- endif %} {% if attribute_Icon is defined %} - ->Attribute(AZ::Edit::Attributes::Icon, "{{ attribute_Icon }}") + {{preEdit}}->Attribute(AZ::Edit::Attributes::Icon, "{{ attribute_Icon }}"){{postEdit}} {%- endif %} {% if attribute_Deprecated is defined %} - ->Attribute(AZ::Edit::Attributes::Deprecated, "{{ attribute_Deprecated }}") + {{preEdit}}->Attribute(AZ::Edit::Attributes::Deprecated, "{{ attribute_Deprecated }}"){{postEdit}} {%- endif %} {% set uihandler = 'AZ::Edit::UIHandlers::Default' %} {% for item in Class.iter('Property') %} @@ -203,29 +201,34 @@ void {{attribute_QualifiedName}}::Reflect(AZ::ReflectContext* context) {% if item.attrib['Description'] is defined %} {% set description = item.attrib['Description'] %} {% endif %} - // {{ item.attrib['Name'] }} - ->DataElement({{ uihandler }}, &{{ attribute_Name }}::{{ item.attrib['Name'] }}, "{{ item.attrib['Name'] }}", "{{ description }}") + // {{ item.attrib['Name'] }} + {{preEdit}}->DataElement({{ uihandler }}, &{{ attribute_Name }}::{{ item.attrib['Name'] }}, "{{ item.attrib['Name'] }}", "{{ description }}"){{postEdit}} {% for EditAttribute in item.iter('EditAttribute') %} - ->Attribute({{ EditAttribute.attrib['Key'] }}, {{ EditAttribute.attrib['Value'] }}) + {{preEdit}}->Attribute({{ EditAttribute.attrib['Key'] }}, {{ EditAttribute.attrib['Value'] }}){{postEdit}} {% endfor %} {% endfor %} ; +{% if ExtendReflectionEdit is defined %} + ExtendReflectionEdit(&{{preEdit}}); +{% endif %} } } // Behavior Context Reflection if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { - behaviorContext->Class<{{ attribute_Name }}>("{{ attribute_Name }}") - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List) - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) +{% if ExtendReflectionBehavior is defined %} auto {{preBehavior}} = {% else %} {% endif %}behaviorContext->Class<{{ attribute_Name }}>("{{ attribute_Name }}"){{postBehavior}} + {{preBehavior}}->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List){{postBehavior}} + {{preBehavior}}->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common){{postBehavior}} {% for inputMethod in Class.iter('Input') %} {% set methodName = inputMethod.attrib['Name'] %} // {{ inputMethod.attrib['Name'] }} - ->Method(Grammar::ToIdentifier("{{ macros.SlotName(methodName) }}").c_str(), &{{ attribute_Name }}::{{ macros.CleanName(methodName) }}) + {{preBehavior}}->Method(Grammar::ToIdentifier("{{ macros.SlotName(methodName) }}").c_str(), &{{ attribute_Name }}::{{ macros.CleanName(methodName) }}){{postBehavior}} {% endfor %} - ; +{% if ExtendReflectionBehavior is defined %} + ExtendReflectionBehavior(&{{preBehavior}}); +{% endif %} } } @@ -253,27 +256,32 @@ Nodes::{{ nodeableNodeName }}::{{ nodeableNodeName }}() {# NodeableNode Reflection #} void Nodes::{{ nodeableNodeName }}::Reflect(AZ::ReflectContext* context) { - {{ attribute_QualifiedName }}::Reflect(context); // Serialization Context Reflection if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { - serializeContext->Class<{{ nodeableNodeName }}, NodeableNode>() + {%if ExtendReflectionSerialize is defined%}auto {{preSerialize}} = {%endif%}serializeContext->Class<{{ nodeableNodeName }}, NodeableNode>(){{postSerialize}} {% if attribute_Version is defined %} - ->Version({{ attribute_Version }}{% if attribute_VersionConverter is defined %}, &{{ attribute_VersionConverter }}{% endif %}) + {{preSerialize}}->Version({{ attribute_Version }}{% if attribute_VersionConverter is defined %}, &{{ attribute_VersionConverter }}{% endif %}){{postSerialize}} {% else %} - ->Version(0) + {{preSerialize}}->Version(0){{postSerialize}} +{% endif %} + ; +{% if ExtendReflectionSerialize is defined %} + ExtendReflectionSerialize(&{{preSerialize}}); {% endif %} - ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) { - editContext->Class<{{ nodeableNodeName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}") - ->ClassElement(AZ::Edit::ClassElements::EditorData, "") - ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) - ->Attribute(AZ::Edit::Attributes::AutoExpand, true) - ; + {% if ExtendReflectionEdit is defined %}auto {{preEdit}} = {%endif%}editContext->Class<{{ nodeableNodeName }}>("{{ attribute_PreferredClassName }}", "{{ attribute_Description }}"){{postEdit}} + {{preEdit}}->ClassElement(AZ::Edit::ClassElements::EditorData, ""){{postEdit}} + {{preEdit}}->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly){{postEdit}} + {{preEdit}}->Attribute(AZ::Edit::Attributes::AutoExpand, true){{postEdit}} + ; +{% if ExtendReflectionEdit is defined %} + ExtendReflectionEdit(&{{preEdit}}); +{% endif %} } } } @@ -324,6 +332,7 @@ void Nodes::{{ nodeableNodeName }}::ConfigureVisualExtensions() RegisterExtension(visualExtensions); } {% endfor %} + OnConfigureVisualExtensions(); } {# ConfigureSlots #} @@ -457,6 +466,9 @@ void Nodes::{{ nodeableNodeName }}::ConfigureSlots() {% endfor %} #} +{% if Class.attrib['ExtendConfigureSlots'] is defined %} + ExtendConfigureSlots(ins, outs); +{% endif %} // Generate the execution map m_slotExecutionMap = SlotExecution::Map(AZStd::move(ins), AZStd::move(outs)); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja index addd24f5d3..a7300791e8 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/AutoGen/ScriptCanvas_Nodeable_Macros.jinja @@ -349,7 +349,4 @@ void {{qualifiedName}}::Call{{CleanName(outName)}}({{ExecutionOutReturnDefinitio {%- for executionOut in Class.findall('Output') -%} {{ ExecutionOutDefinition(Class, qualifiedName, executionOut, loop.index0 + branches|length) }} {%- endfor %} - -size_t {{qualifiedName}}::GetRequiredOutCount() const { return {{Class.findall('Output')|length + branches|length}}; } - -{% endmacro %} +{% endmacro %} \ No newline at end of file diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h index 5b1e5eee76..8f3cefa138 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Core.h @@ -250,7 +250,6 @@ namespace ScriptCanvas }; using ScriptCanvasSettingsRequestBus = AZ::EBus; - } namespace AZStd diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp index 30e55cee49..64c9f2497d 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.cpp @@ -1476,9 +1476,11 @@ namespace ScriptCanvas const void* Datum::GetValueAddress() const { - return m_type.GetType() != Data::eType::BehaviorContextObject - ? AZStd::any_cast(&m_storage) - : (*AZStd::any_cast(&m_storage))->Get(); + return !m_storage.empty() + ? m_type.GetType() != Data::eType::BehaviorContextObject + ? AZStd::any_cast(&m_storage) + : (*AZStd::any_cast(&m_storage))->Get() + : nullptr; } bool Datum::Initialize(const Data::Type& type, eOriginality originality, const void* source, const AZ::Uuid& sourceTypeID) diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h index 781dcb9ed8..be6f3ee37e 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Datum.h @@ -389,7 +389,7 @@ namespace ScriptCanvas bool Datum::Empty() const { - return GetValueAddress() == nullptr; + return m_storage.empty() || GetValueAddress() == nullptr; } template diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp index 4a936c8824..98d5e6b121 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.cpp @@ -3577,12 +3577,12 @@ namespace ScriptCanvas } if (targetSlotType == CombinedSlotType::DataOut - && executionSlot.GetType() == CombinedSlotType::ExecutionIn - && executionInCount > 1) + && executionSlot.GetType() == CombinedSlotType::ExecutionIn + && executionInCount > 1) { if (!executionChildSlot || executionChildSlot->GetType() != CombinedSlotType::ExecutionOut) { - return AZ::Failure(AZStd::string("Data out by ExcutionIn must have child out slot")); + return AZ::Failure(AZStd::string("Data out by ExecutionIn must have child out slot")); } } @@ -3626,6 +3626,11 @@ namespace ScriptCanvas return {}; } + Grammar::MultipleFunctionCallFromSingleSlotInfo Node::GetMultipleFunctionCallFromSingleSlotInfo([[maybe_unused]] const Slot& slot) const + { + return {}; + } + VariableId Node::GetVariableIdRead(const Slot*) const { return {}; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h index a2d06686e9..42ffaec8da 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/Node.h @@ -715,6 +715,8 @@ namespace ScriptCanvas virtual PropertyFields GetPropertyFields() const; + virtual Grammar::MultipleFunctionCallFromSingleSlotInfo GetMultipleFunctionCallFromSingleSlotInfo(const Slot& slot) const; + virtual VariableId GetVariableIdRead(const Slot*) const; virtual VariableId GetVariableIdWritten(const Slot*) const; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h index 0193641d97..c1c9db6f8b 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/NodeableNode.h @@ -73,6 +73,8 @@ namespace ScriptCanvas void ConfigureSlots() override; + virtual void OnConfigureVisualExtensions() {} + AZ::Outcome GetBehaviorContextClass() const; ConstSlotsOutcome GetBehaviorContextOutName(const Slot& inSlot) const; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h index 8426e90515..7b74f3dd81 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Core/PureData.h @@ -46,6 +46,8 @@ namespace ScriptCanvas const AZStd::unordered_map>& GetPropertyNameSlotMap() const; + AZ_INLINE AZ::Outcome GetDependencies() const override { return AZ::Success(DependencyReport{}); } + ~PureData() override; protected: diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp index 732455857e..db45f6bfe3 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.cpp @@ -2249,6 +2249,10 @@ namespace ScriptCanvas } #endif AddAllVariablesPreParse(); + if (!IsErrorFree()) + { + return; + } for (auto& nodeEntity : m_source.m_graphData->m_nodes) { @@ -2270,6 +2274,16 @@ namespace ScriptCanvas { AddError(nullptr, ValidationConstPtr(aznew NullEntityInGraph())); } + + if (!IsErrorFree()) + { + return; + } + } + + if (!IsErrorFree()) + { + return; } ParseAutoConnectedEBusHandlerVariables(); @@ -2638,7 +2652,8 @@ namespace ScriptCanvas AZStd::vector inputVariableIds; AZStd::unordered_map inputVariablesById; - for (auto variable : GetVariables()) + auto& variables = GetVariables(); + for (auto variable : variables) { auto constructionRequirement = ParseConstructionRequirement(variable); @@ -2655,18 +2670,30 @@ namespace ScriptCanvas case VariableConstructionRequirement::InputNodeable: { + if (variable->m_datum.Empty()) + { + AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "Empty nodeable datum in variable, probably due to a problem with azrtti declarations")); + break; + } + // I solemnly swear no harm shall come to the nodeable const Nodeable* nodeableSource = reinterpret_cast(variable->m_datum.GetAsDanger()); - AZ_Assert(nodeableSource != nullptr, "the must be a raw nodeable held by this pointer"); - AZ_Assert(azrtti_typeid(nodeableSource) != azrtti_typeid(), "type problem with nodeable"); + + if (!nodeableSource) + { + AddError(nullptr, aznew Internal::ParseError(AZ::EntityId{}, "No raw nodeable held by variable")); + break; + } + nodeablesById.push_back({ variable->m_nodeableNodeId, const_cast(nodeableSource) }); } break; case VariableConstructionRequirement::InputVariable: { - inputVariableIds.push_back(variable->m_sourceVariableId); - inputVariablesById.insert({ variable->m_sourceVariableId, variable }); + auto variableID = variable->m_sourceVariableId.IsValid() ? variable->m_sourceVariableId : VariableId::MakeVariableId(); + inputVariableIds.push_back(variableID); + inputVariablesById.insert({ variableID, variable }); // sort revealed a datum copy issue: type is not preserved, workaround below // m_runtimeInputs.m_variables.emplace_back(variable->m_sourceVariableId, variable->m_datum); } @@ -4310,6 +4337,7 @@ namespace ScriptCanvas { if (auto variable = FindVariable(execution->GetNodeId())) { + execution->MarkInputHasThisPointer(); execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() }); } else @@ -4327,6 +4355,7 @@ namespace ScriptCanvas { auto variable = AZStd::make_shared(); variable->m_datum = Datum(eventHandling->m_handlerName); + execution->MarkInputHasThisPointer(); execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() }); } else @@ -4338,6 +4367,7 @@ namespace ScriptCanvas { if (auto variable = FindVariable(execution->GetNodeId())) { + execution->MarkInputHasThisPointer(); execution->AddInput({ nullptr, variable, DebugDataSource::FromInternal() }); } else @@ -4367,12 +4397,159 @@ namespace ScriptCanvas void AbstractCodeModel::ParseMultiExecutionPost(ExecutionTreePtr execution) { ParsePropertyExtractionsPost(execution); + ParseMultipleFunctionCallPost(execution); } void AbstractCodeModel::ParseMultiExecutionPre(ExecutionTreePtr execution) { ParsePropertyExtractionsPre(execution); - } + } + + void AbstractCodeModel::ParseMultipleFunctionCallPost(ExecutionTreePtr execution) + { + auto& id = execution->GetId(); + MultipleFunctionCallFromSingleSlotInfo info = id.m_node->GetMultipleFunctionCallFromSingleSlotInfo(*id.m_slot); + + if (info.functionCalls.empty()) + { + return; + } + + auto parent = execution->ModParent(); + + if (!parent) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), "Null parent in MultipleFunctionCall")); + return; + } + + size_t indexInParentCall = parent->FindChildIndex(execution); + if (indexInParentCall >= parent->GetChildrenCount()) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNoChildren)); + return; + } + + ExecutionChild* executionChildInParent = &parent->ModChild(indexInParentCall); + + const size_t executionInputCount = execution->GetInputCount(); + const size_t thisInputOffset = execution->InputHasThisPointer() ? 1 : 0; + + // the original index has ALL the input from the slots on the node + // create multiple calls with separate function call nodes, but ONLY take the inputs required + // as indicated by the function call info + + AZStd::unordered_set usedSlots; + bool variadicIsFound = false; + + auto createChild = [&](auto parentCall, ExecutionChild* childInParent, auto& functionCallInfo) + { + auto child = CreateChild(parentCall, id.m_node, id.m_slot); + child->SetSymbol(Symbol::FunctionCall); + child->SetName(functionCallInfo.functionName); + child->SetNameLexicalScope(functionCallInfo.lexicalScope); + childInParent->m_execution = child; + return child; + }; + + auto addThisInput = [&](auto functionCall) + { + if (thisInputOffset != 0) + { + if (executionInputCount == 0) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInputForThis)); + return; + } + + const ExecutionInput& input = execution->GetInput(0); + usedSlots.insert(input.m_slot); + functionCall->AddInput(input); + } + }; + + auto addSlotInput = [&](auto functionCall, size_t inputIndex) + { + if (inputIndex >= executionInputCount) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput)); + return; + } + + const ExecutionInput& input = execution->GetInput(inputIndex); + + if (usedSlots.contains(input.m_slot)) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotNotEnoughInput)); + return; + } + + usedSlots.insert(input.m_slot); + + if (input.m_value->m_source == execution) + { + input.m_value->m_source = functionCall; + } + + functionCall->AddInput(input); + }; + + auto addCall = [&](auto& functionCallInfo, auto childInParent, size_t startingIndex, size_t sentinel, size_t variadicOffset = 0) + { + auto child = createChild(parent, childInParent, functionCallInfo); + addThisInput(child); + + for (size_t index = startingIndex; index < sentinel; ++index) + { + const size_t inputIndex = index + thisInputOffset + variadicOffset; + addSlotInput(child, inputIndex); + } + + child->AddChild({}); + childInParent = &child->ModChild(0); + return AZStd::make_pair(childInParent, child); + }; + + // loop through each call... + for (auto& functionCallInfo : info.functionCalls) + { + // ...first add any pre-variadic calls, using the starting index and the number of args, since they could come in any order, not input slot order... + if (!functionCallInfo.isVariadic) + { + AZStd::pair childInParentAndParent = addCall(functionCallInfo, executionChildInParent, functionCallInfo.startingIndex, functionCallInfo.startingIndex + functionCallInfo.numArguments); + executionChildInParent = childInParentAndParent.first; + parent = childInParentAndParent.second; + } + else + { + // ...then add only one variadic call if there is one... + if (variadicIsFound) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotMultipleVariadic)); + return; + } + + variadicIsFound = true; + const size_t sentinel = executionInputCount == 0 ? 0 : executionInputCount - thisInputOffset; + // ... by looping through the remaining slots, striding by functionCallInfo.numArguments, making repeated calls to the function + for (size_t slotInputIndex = functionCallInfo.startingIndex; slotInputIndex < sentinel; slotInputIndex += functionCallInfo.numArguments) + { + AZStd::pair childInParentAndParent = addCall(functionCallInfo, executionChildInParent, 0, functionCallInfo.numArguments, slotInputIndex); + executionChildInParent = childInParentAndParent.first; + parent = childInParentAndParent.second; + } + } + } + + if (info.errorOnUnusedSlot && usedSlots.size() != executionInputCount) + { + AddError(execution, aznew Internal::ParseError(id.m_node->GetEntityId(), ParseErrors::MultipleFunctionCallFromSingleSlotUnused)); + } + + // parent now refers to the last child call created + parent->SwapChildren(execution); + execution->Clear(); + } void AbstractCodeModel::ParseNodelingVariables(const Node& node, NodelingType nodelingType) { @@ -5146,6 +5323,6 @@ namespace ScriptCanvas return type == Data::eType::BehaviorContextObject; } - } + } -} +} diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h index f1ad7b986b..8b8be27fab 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/AbstractCodeModel.h @@ -387,6 +387,8 @@ namespace ScriptCanvas void ParseMultiExecutionPre(ExecutionTreePtr execution); + void ParseMultipleFunctionCallPost(ExecutionTreePtr execution); + void ParseNodelingVariables(const Node& node, NodelingType nodelingType); void ParseOperatorArithmetic(ExecutionTreePtr execution); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp index e16a3bc815..131209a521 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/ParsingUtilities.cpp @@ -1132,14 +1132,14 @@ namespace ScriptCanvas } else if (variable->m_isExposedToConstruction) { - if (variable->m_sourceVariableId.IsValid()) - { - return VariableConstructionRequirement::InputVariable; - } - else if (variable->m_nodeableNodeId.IsValid()) + if (variable->m_nodeableNodeId.IsValid()) { return VariableConstructionRequirement::InputNodeable; } + else if (variable->m_sourceVariableId.IsValid()) + { + return VariableConstructionRequirement::InputVariable; + } else { AZ_Assert(false, "A member variable in the model has no valid id"); diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h index ab047fb580..d7e4fa0f72 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/Primitives.h @@ -164,6 +164,34 @@ namespace ScriptCanvas virtual void PostParseExecutionTreeBody(AbstractCodeModel& /*model*/, ExecutionTreePtr /*execution*/) {} }; + // for now, no return values supported + struct MultipleFunctionCallFromSingleSlotEntry + { + AZ_TYPE_INFO(MultipleFunctionCallFromSingleSlotEntry, "{360A23A3-C490-4047-B71E-64E290E441D3}"); + AZ_CLASS_ALLOCATOR(MultipleFunctionCallFromSingleSlotEntry, AZ::SystemAllocator, 0); + + bool isVariadic = false; + AZStd::string functionName; + LexicalScope lexicalScope; + size_t numArguments = 0; // stride in case isVariadic == true + size_t startingIndex = 0; // the index of the slot order + }; + + // for now, no return values supported + struct MultipleFunctionCallFromSingleSlotInfo + { + AZ_TYPE_INFO(MultipleFunctionCallFromSingleSlotInfo, "{DF51F08A-8B28-4851-9888-9AB7CC0B90D2}"); + AZ_CLASS_ALLOCATOR(MultipleFunctionCallFromSingleSlotInfo, AZ::SystemAllocator, 0); + + // this could likely be implemented, but needs care to duplicate input that the execution-slot created + // bool errorOnReusedSlot = false; + + bool errorOnUnusedSlot = false; + + // calls are executed in the order they arrive in the vector + AZStd::vector functionCalls; + }; + struct NodeableParse : public AZStd::enable_shared_from_this { diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp index ed61d7e838..66f609768d 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.cpp @@ -313,6 +313,11 @@ namespace ScriptCanvas return !m_returnValues.empty(); } + bool ExecutionTree::InputHasThisPointer() const + { + return m_inputHasThisPointer; + } + bool ExecutionTree::IsInfiniteLoopDetectionPoint() const { return m_isInfiniteLoopDetectionPoint; @@ -377,6 +382,11 @@ namespace ScriptCanvas m_isInfiniteLoopDetectionPoint = true; } + void ExecutionTree::MarkInputHasThisPointer() + { + m_inputHasThisPointer = true; + } + void ExecutionTree::MarkInputOutputPreprocessed() { m_isInputOutputPreprocessed = true; @@ -587,6 +597,32 @@ namespace ScriptCanvas m_symbol = val; } - } + void ExecutionTree::SwapChildren(ExecutionTreePtr execution) + { + if (execution) + { + m_children.swap(execution->m_children); + for (auto& child : m_children) + { + if (child.m_execution) + { + child.m_execution->SetParent(shared_from_this()); + } + } + + for (auto& orphan : execution->m_children) + { + if (orphan.m_execution) + { + orphan.m_execution->SetParent(execution); + } + } + } + else + { + ClearChildren(); + } + } + } } diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h index 3d0d75b313..17b86c8da4 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Grammar/PrimitivesExecution.h @@ -190,6 +190,8 @@ namespace ScriptCanvas bool HasReturnValues() const; + bool InputHasThisPointer() const; + bool IsInfiniteLoopDetectionPoint() const; void InsertChild(size_t index, const ExecutionChild& child); @@ -208,6 +210,8 @@ namespace ScriptCanvas void MarkInfiniteLoopDetectionPoint(); + void MarkInputHasThisPointer(); + void MarkInputOutputPreprocessed(); void MarkInternalOut(); @@ -262,6 +266,8 @@ namespace ScriptCanvas void SetSymbol(Symbol val); + void SwapChildren(ExecutionTreePtr execution); + private: // the (possible) slot(s) through which execution exited, along with associated output AZStd::vector m_children; @@ -275,6 +281,8 @@ namespace ScriptCanvas bool m_isInfiniteLoopDetectionPoint = false; + bool m_inputHasThisPointer = false; + bool m_isInputOutputPreprocessed = false; bool m_isInternalOut = false; diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h index def4fa7761..68c6a8442a 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h +++ b/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Results/ErrorText.h @@ -57,6 +57,12 @@ namespace ScriptCanvas constexpr const char* MissingVariableForEBusHandlerAddress = "missing variable for ebus handler address"; constexpr const char* MissingVariableForEBusHandlerAddressConnected = "missing variable for ebus handler address"; constexpr const char* MultipleExecutionOutConnections = "This node has multiple, unordered execution Out connections"; + constexpr const char* MultipleFunctionCallFromSingleSlotMultipleVariadic = "Only one variadic call (the last one) is supported in the multi-call per single slot."; + constexpr const char* MultipleFunctionCallFromSingleSlotNoChildren = "Node missing from parent children."; + constexpr const char* MultipleFunctionCallFromSingleSlotNotEnoughInput = "Not enough input to support multi call input information."; + constexpr const char* MultipleFunctionCallFromSingleSlotNotEnoughInputForThis = "Node doesn't have enough input for a parsed this pointer."; + constexpr const char* MultipleFunctionCallFromSingleSlotReused = "Multiple function slot reused an input slot"; + constexpr const char* MultipleFunctionCallFromSingleSlotUnused = "Multiple function slot left an input slot unused."; constexpr const char* MultipleSimulaneousInputValues = "Multiple values routed to the same single input with no way to discern which to take."; constexpr const char* MultipleStartNodes = "Multiple Start nodes in a single graph. Only one is allowed."; constexpr const char* NoChildrenAfterRoot = "No children after parsing function root"; diff --git a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas index 45b24bad8d..c3b3599325 100644 --- a/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas +++ b/Gems/ScriptCanvasTesting/Assets/ScriptCanvas/UnitTests/LY_SC_UnitTest_RunAllTransformNodes.scriptcanvas @@ -3,34 +3,34 @@ - + - + - + - + - + - + - + @@ -62,12 +62,13 @@ + - + @@ -99,12 +100,13 @@ + - + @@ -141,12 +143,13 @@ + - + @@ -178,6 +181,7 @@ + @@ -189,7 +193,7 @@ - + @@ -199,26 +203,26 @@ - + - + - + - + - + - + @@ -250,12 +254,13 @@ + - + @@ -287,12 +292,13 @@ + - + @@ -329,12 +335,646 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -371,12 +1011,13 @@ + - + @@ -408,6 +1049,7 @@ + @@ -419,7 +1061,7 @@ - + @@ -431,7 +1073,7 @@ - + @@ -441,26 +1083,26 @@ - + - + - + - + - + - + @@ -492,12 +1134,13 @@ + - + @@ -529,484 +1172,13 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1043,12 +1215,13 @@ + - + @@ -1080,21 +1253,10 @@ + - - - - - - - - - - - - @@ -1103,7 +1265,7 @@ - + @@ -1113,26 +1275,26 @@ - + - + - + - + - + - + @@ -1164,12 +1326,13 @@ + - + @@ -1201,200 +1364,13 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1431,12 +1407,56 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1446,10 +1466,10 @@ - + - + @@ -1468,6 +1488,7 @@ + @@ -1479,9 +1500,21 @@ - + - + + + + + + + + + + + + + @@ -1489,26 +1522,26 @@ - + - + - + - + - + - + @@ -1540,12 +1573,338 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1577,12 +1936,205 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1619,12 +2171,13 @@ + - + @@ -1639,7 +2192,7 @@ - + @@ -1661,12 +2214,13 @@ + - + @@ -1676,10 +2230,10 @@ - + - + @@ -1698,6 +2252,7 @@ + @@ -1709,19 +2264,19 @@ - + - + - + @@ -1731,26 +2286,26 @@ - + - + - + - + - + - + @@ -1782,12 +2337,13 @@ + - + @@ -1819,12 +2375,13 @@ + - + @@ -1839,7 +2396,7 @@ - + @@ -1861,12 +2418,56 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1898,6 +2499,7 @@ + @@ -1909,36 +2511,107 @@ - + + + + + + + + + + + + + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1970,12 +2643,13 @@ + - + @@ -2007,12 +2681,13 @@ + - + @@ -2049,12 +2724,205 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2091,12 +2959,13 @@ + - + @@ -2128,6 +2997,7 @@ + @@ -2139,7 +3009,7 @@ - + @@ -2151,7 +3021,7 @@ - + @@ -2161,26 +3031,26 @@ - + - + - + - + - + - + @@ -2212,12 +3082,13 @@ + - + @@ -2249,12 +3120,13 @@ + - + @@ -2291,12 +3163,56 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2328,6 +3244,7 @@ + @@ -2339,36 +3256,1196 @@ - + + + + + + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + @@ -2405,12 +4482,13 @@ + - + @@ -2447,12 +4525,13 @@ + - + @@ -2484,12 +4563,13 @@ + - + @@ -2521,6 +4601,7 @@ + @@ -2565,1709 +4646,26 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + @@ -4299,12 +4697,13 @@ + - + @@ -4336,12 +4735,13 @@ + - + @@ -4378,12 +4778,13 @@ + - + @@ -4420,12 +4821,13 @@ + - + @@ -4457,6 +4859,7 @@ + @@ -4480,7 +4883,7 @@ - + @@ -4490,26 +4893,26 @@ - + - + - + - + - + - + @@ -4541,12 +4944,13 @@ + - + @@ -4578,12 +4982,13 @@ + - + @@ -4620,850 +5025,13 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -5495,194 +5063,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -5694,9 +5075,9 @@ - + - + @@ -5704,703 +5085,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + - + - + - + @@ -6410,28 +5123,28 @@ - + - + - + - + - + - + - + @@ -6441,28 +5154,28 @@ - + - + - + - + - + - + - + @@ -6472,28 +5185,28 @@ - + - + - + - + - + - + - + @@ -6503,28 +5216,28 @@ - + - + - + - + - + - + - + @@ -6534,28 +5247,28 @@ - + - + - + - + - + - + - + @@ -6565,28 +5278,28 @@ - + - + - + - + - + - + - + @@ -6596,28 +5309,28 @@ - + - + - + - + - + - + - + @@ -6627,28 +5340,28 @@ - + - + - + - + - + - + - + @@ -6658,28 +5371,28 @@ - + - + - + - + - + - + - + @@ -6689,28 +5402,28 @@ - + - + - + - + - + - + @@ -6720,28 +5433,28 @@ - + - + - + - + - + - + - + @@ -6751,28 +5464,307 @@ - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6788,7 +5780,7 @@ - + @@ -6796,7 +5788,7 @@ - + @@ -6804,7 +5796,7 @@ - + @@ -6822,15 +5814,15 @@ - - - + + + - - - + + + @@ -6838,7 +5830,7 @@ - + @@ -6846,7 +5838,7 @@ - + @@ -6863,46 +5855,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -6911,18 +5867,12 @@ - - - - - - - + @@ -6930,637 +5880,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -7586,7 +5906,7 @@ - + @@ -7594,7 +5914,7 @@ - + @@ -7602,7 +5922,595 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7620,15 +6528,15 @@ - - - + + + - - - + + + @@ -7636,7 +6544,7 @@ - + @@ -7644,7 +6552,7 @@ - + @@ -7661,46 +6569,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -7709,18 +6581,12 @@ - - - - - - - + @@ -7728,7 +6594,7 @@ - + @@ -7746,15 +6612,15 @@ - - - + + + - - - + + + @@ -7762,7 +6628,7 @@ - + @@ -7771,9 +6637,9 @@ - - - + + + @@ -7783,7 +6649,7 @@ - + @@ -7791,49 +6657,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -7851,15 +6675,15 @@ - - - + + + - - - + + + @@ -7867,7 +6691,7 @@ - + @@ -7875,7 +6699,49 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7893,15 +6759,15 @@ - - - + + + - - - + + + @@ -7915,60 +6781,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -7980,7 +6798,35 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7988,25 +6834,33 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -8014,7 +6868,7 @@ - + From d90a3d46a7ac393a12807fe66eacd4f9bab7c8f1 Mon Sep 17 00:00:00 2001 From: Mike Balfour <82224783+mbalfour-amzn@users.noreply.github.com> Date: Thu, 3 Jun 2021 15:59:45 -0500 Subject: [PATCH 63/65] Support for nested slice conversions (#1121) This set of changes enables conversions for singly-nested slices. Multiple nesting hierarchies are only partially supported at this point. Conversion is also significantly more deterministic, which makes it easier to convert single slices without needing to reconvert every slice or level that relies on it as well. Changes: - Added version of Instance::AddInstance() that takes in an alias to allow for deterministic aliases - Added a "SliceConverterEditorEntityContextComponent" that's used to specifically disable entity activation on creation. The disabling is done this way vs adding a new public API, because the disable shouldn't be required in any normal case outside of this tool. - Disabled more AWS gems for the SliceConverter, as they're unneeded and cause issues if they're around in the tool. - Added a small null check to the Camera Controller. - Added the actual support for slice instance conversion. This instantiates the entities, applies the data patches, turns them into a prefab instance, and generates a JSON patch out of the changes. --- .../AzCore/AzCore/Slice/SliceComponent.h | 7 +- .../Prefab/Instance/Instance.cpp | 13 +- .../Prefab/Instance/Instance.h | 1 + .../SerializeContextTools/Application.cpp | 19 +- .../SerializeContextTools/SliceConverter.cpp | 230 +++++++++++++++--- .../SerializeContextTools/SliceConverter.h | 31 ++- ...iceConverterEditorEntityContextComponent.h | 65 +++++ Code/Tools/SerializeContextTools/main.cpp | 3 +- .../serializecontexttools_files.cmake | 1 + .../Code/Source/CameraComponentController.cpp | 5 +- .../gem_autoload.serializecontexttools.setreg | 6 + 11 files changed, 330 insertions(+), 51 deletions(-) create mode 100644 Code/Tools/SerializeContextTools/SliceConverterEditorEntityContextComponent.h diff --git a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h index 5ef8df0617..0c9bad6d4a 100644 --- a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h +++ b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h @@ -971,6 +971,10 @@ namespace AZ */ void RestoreCachedInstances(); + /// Returns data flags for use when instantiating an instance of this slice. + /// These data flags include those harvested from the entire slice ancestry. + const DataFlagsPerEntity& GetDataFlagsForInstances() const; + protected: ////////////////////////////////////////////////////////////////////////// @@ -1004,9 +1008,6 @@ namespace AZ DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId); const DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId) const; - /// Returns data flags for use when instantiating an instance of this slice. - /// These data flags include those harvested from the entire slice ancestry. - const DataFlagsPerEntity& GetDataFlagsForInstances() const; void BuildDataFlagsForInstances(); /** diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index 8f483ec818..766a293b4a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -49,8 +50,7 @@ namespace AzToolsFramework m_alias = GenerateInstanceAlias(); m_containerEntity = containerEntity ? AZStd::move(containerEntity) : AZStd::make_unique(); - EntityAlias containerEntityAlias = GenerateEntityAlias(); - RegisterEntity(m_containerEntity->GetId(), containerEntityAlias); + RegisterEntity(m_containerEntity->GetId(), PrefabDomUtils::ContainerEntityName); } Instance::~Instance() @@ -311,8 +311,15 @@ namespace AzToolsFramework Instance& Instance::AddInstance(AZStd::unique_ptr instance) { InstanceAlias newInstanceAlias = GenerateInstanceAlias(); + return AddInstance(AZStd::move(instance), newInstanceAlias); + } + + Instance& Instance::AddInstance(AZStd::unique_ptr instance, InstanceAlias newInstanceAlias) + { AZ_Assert(instance.get(), "instance argument is nullptr"); - AZ_Assert(m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), "InstanceAlias' unique id collision, this should never happen."); + AZ_Assert( + m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), + "InstanceAlias' unique id collision, this should never happen."); instance->m_parent = this; instance->m_alias = newInstanceAlias; return *(m_nestedInstances[newInstanceAlias] = std::move(instance)); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 68bc395012..4a69ade6d2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -93,6 +93,7 @@ namespace AzToolsFramework void Reset(); Instance& AddInstance(AZStd::unique_ptr instance); + Instance& AddInstance(AZStd::unique_ptr instance, InstanceAlias instanceAlias); AZStd::unique_ptr DetachNestedInstance(const InstanceAlias& instanceAlias); /** diff --git a/Code/Tools/SerializeContextTools/Application.cpp b/Code/Tools/SerializeContextTools/Application.cpp index 81cc314deb..918afd731c 100644 --- a/Code/Tools/SerializeContextTools/Application.cpp +++ b/Code/Tools/SerializeContextTools/Application.cpp @@ -17,6 +17,7 @@ #include #include +#include namespace AZ { @@ -34,6 +35,9 @@ namespace AZ Application::Application(int argc, char** argv) : AzToolsFramework::ToolsApplication(&argc, &argv) { + // We need a specialized variant of EditorEntityContextCompnent for the SliceConverter, so we register the descriptor here. + RegisterComponentDescriptor(AzToolsFramework::SliceConverterEditorEntityContextComponent::CreateDescriptor()); + AZ::IO::FixedMaxPath projectPath = AZ::Utils::GetProjectPath(); if (projectPath.empty()) { @@ -110,10 +114,21 @@ namespace AZ AZ::ComponentTypeList Application::GetRequiredSystemComponents() const { - // Use all of the default system components, but also add in the ThumbnailerNullComponent so that components requiring - // a ThumbnailService can still be started up. + // By default, we use all of the standard system components. AZ::ComponentTypeList components = AzToolsFramework::ToolsApplication::GetRequiredSystemComponents(); + + // Also add in the ThumbnailerNullComponent so that components requiring a ThumbnailService can still be started up. components.emplace_back(azrtti_typeid()); + + // The Slice Converter requires a specialized variant of the EditorEntityContextComponent that exposes the ability + // to disable the behavior of activating entities on creation. During conversion, the creation flow will be triggered, + // but entity activation requires a significant amount of subsystem initialization that's unneeded for conversion. + // So, to get around this, we swap out EditorEntityContextComponent with SliceConverterEditorEntityContextComponent. + components.erase( + AZStd::remove( + components.begin(), components.end(), azrtti_typeid()), + components.end()); + components.emplace_back(azrtti_typeid()); return components; } } // namespace SerializeContextTools diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index d06534e303..56b3689d6b 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -30,13 +30,16 @@ #include #include #include +#include #include #include #include #include #include +#include #include + // SliceConverter reads in a slice file (saved in an ObjectStream format), instantiates it, creates a prefab out of the data, // and saves the prefab in a JSON format. This can be used for one-time migrations of slices or slice-based levels to prefabs. // @@ -99,12 +102,26 @@ namespace AZ bool result = true; rapidjson::StringBuffer scratchBuffer; + // For slice conversion, disable the EditorEntityContextComponent logic that activates entities on creation. + // This prevents a lot of error messages and crashes during conversion due to lack of full environment and subsystem setup. + AzToolsFramework::SliceConverterEditorEntityContextComponent::DisableOnContextEntityLogic(); + // Loop through the list of requested files and convert them. AZStd::vector fileList = Utilities::ReadFileListFromCommandLine(application, "files"); for (AZStd::string& filePath : fileList) { bool convertResult = ConvertSliceFile(convertSettings.m_serializeContext, filePath, isDryRun); result = result && convertResult; + + // Clear out all registered prefab templates between each top-level file that gets processed. + auto prefabSystemComponentInterface = AZ::Interface::Get(); + for (auto templateId : m_createdTemplateIds) + { + // We don't just want to call RemoveAllTemplates() because the root template should remain between file conversions. + prefabSystemComponentInterface->RemoveTemplate(templateId); + } + m_aliasIdMapper.clear(); + m_createdTemplateIds.clear(); } DisconnectFromAssetProcessor(); @@ -114,6 +131,13 @@ namespace AZ bool SliceConverter::ConvertSliceFile( AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun) { + /* To convert a slice file, we read the input file in via ObjectStream, then use the "class ready" callback to convert + * the data in memory to a Prefab. + * If the input file is a level file (.ly), we actually need to load the level slice file ("levelentities.editor_xml") from + * within the level file, which effectively is a zip file of the level slice file and a bunch of legacy level files that won't + * be converted, since the systems that would use them no longer exist. + */ + bool result = true; bool packOpened = false; @@ -144,7 +168,7 @@ namespace AZ AZ_STRING_ARG(fileExtension.Native())); } - auto callback = [&outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context) + auto callback = [this, &outputPath, isDryRun](void* classPtr, const Uuid& classId, SerializeContext* context) { if (classId != azrtti_typeid()) { @@ -178,6 +202,13 @@ namespace AZ bool SliceConverter::ConvertSliceToPrefab( AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity) { + /* Given a root slice entity, we convert it to a prefab by doing the following: + * - Locate the SliceComponent + * - Take all the entities directly located on the slice, and put them into a prefab + * - Fix up any top-level entities to have the prefab container entity as their parent + * - If there are any nested slice instances, convert the nested slices to prefabs, then convert the instances. + */ + auto prefabSystemComponentInterface = AZ::Interface::Get(); // Find the slice from the root entity. @@ -192,9 +223,14 @@ namespace AZ SliceComponent::EntityList sliceEntities = sliceComponent->GetNewEntities(); AZ_Printf("Convert-Slice", " Slice contains %zu entities.\n", sliceEntities.size()); - // Create the Prefab with the entities from the slice + // Create the Prefab with the entities from the slice. + // The entities are added in a separate step so that we can give them deterministic entity aliases that match their entity Ids AZStd::unique_ptr sourceInstance( - prefabSystemComponentInterface->CreatePrefab(sliceEntities, {}, outputPath)); + prefabSystemComponentInterface->CreatePrefab({}, {}, outputPath)); + for (auto& entity : sliceEntities) + { + sourceInstance->AddEntity(*entity, AZStd::string::format("Entity_%s", entity->GetId().ToString().c_str())); + } // Dispatch events here, because prefab creation might trigger asset loads in rare circumstances. AZ::Data::AssetManager::Instance().DispatchEvents(); @@ -204,12 +240,28 @@ namespace AZ AzToolsFramework::Prefab::EntityOptionalReference container = sourceInstance->GetContainerEntity(); FixPrefabEntities(container->get(), sliceEntities); + // Keep track of the template Id we created, we're going to remove it at the end of slice file conversion to make sure + // the data doesn't stick around between file conversions. auto templateId = sourceInstance->GetTemplateId(); if (templateId == AzToolsFramework::Prefab::InvalidTemplateId) { AZ_Printf("Convert-Slice", " Path error. Path could be invalid, or the prefab may not be loaded in this level.\n"); return false; } + m_createdTemplateIds.emplace(templateId); + + // Save off a mapping of the original slice entity IDs to the new prefab template entity aliases. + // When converting nested slices, this mapping will be needed to fix up the parent entity hierarchy correctly. + auto entityAliases = sourceInstance->GetEntityAliases(); + for (auto& alias : entityAliases) + { + auto id = sourceInstance->GetEntityId(alias); + auto result = m_aliasIdMapper.emplace(TemplateEntityIdPair(templateId, id), alias); + if (!result.second) + { + AZ_Printf("Convert-Slice", " Duplicate entity alias -> entity id entries found, conversion may not be successful.\n"); + } + } // Update the prefab template with the fixed-up data in our prefab instance. AzToolsFramework::Prefab::PrefabDom prefabDom; @@ -254,21 +306,26 @@ namespace AZ // via an EditorRequests EBus in CreatePrefab, but the subsystem that listens for it isn't present in this tool.) AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, containerEntity); - containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent()); + if (containerEntity.FindComponent() == nullptr) + { + containerEntity.AddComponent(aznew AzToolsFramework::Prefab::EditorPrefabComponent()); + } + + // Make all the components on the container entity have deterministic component IDs, so that multiple runs of the tool + // on the same slice will produce the same prefab output. We're going to cheat a bit and just use the component type hash + // as the component ID. This would break if we had multiple components of the same type, but that currently doesn't + // happen for the container entity. + auto containerComponents = containerEntity.GetComponents(); + for (auto& component : containerComponents) + { + component->SetId(component->GetUnderlyingComponentType().GetHash()); + } // Reparent any root-level slice entities to the container entity. for (auto entity : sliceEntities) { - AzToolsFramework::Components::TransformComponent* transformComponent = - entity->FindComponent(); - if (transformComponent) - { - if (!transformComponent->GetParentId().IsValid()) - { - transformComponent->SetParent(containerEntity.GetId()); - transformComponent->UpdateCachedWorldTransform(); - } - } + constexpr bool onlySetIfInvalid = true; + SetParentEntity(*entity, containerEntity.GetId(), onlySetIfInvalid); } } @@ -276,9 +333,13 @@ namespace AZ SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance, AZ::SerializeContext* serializeContext, bool isDryRun) { + /* Given a root slice, find all the nested slices and convert them. */ + + // Get the list of nested slices that this slice uses. const SliceComponent::SliceList& sliceList = sliceComponent->GetSlices(); auto prefabSystemComponentInterface = AZ::Interface::Get(); + // For each nested slice, convert it. for (auto& slice : sliceList) { // Get the nested slice asset @@ -312,7 +373,7 @@ namespace AZ return false; } - // Load the prefab template for the newly-created nested prefab. + // Find the prefab template we created for the newly-created nested prefab. // To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path. AZ::IO::Path nestedPrefabPath = assetPath; nestedPrefabPath.ReplaceExtension("prefab"); @@ -346,11 +407,25 @@ namespace AZ } bool SliceConverter::ConvertSliceInstance( - [[maybe_unused]] AZ::SliceComponent::SliceInstance& instance, - [[maybe_unused]] AZ::Data::Asset& sliceAsset, + AZ::SliceComponent::SliceInstance& instance, + AZ::Data::Asset& sliceAsset, AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance) { + /* To convert a slice instance, it's important to understand the similarities and differences between slices and prefabs. + * Both slices and prefabs have the concept of instances of a nested slice/prefab, where each instance can have its own + * set of changed data (transforms, component values, etc). + * For slices, the changed data comes from applying a DataPatch to an instantiated set of entities from the nested slice. + * From prefabs, the changed data comes from Json patches that are applied to the instantiated set of entities from the + * nested prefab. The prefab instance entities also have different IDs than the slice instance entities, so we'll need + * to remap some of them along the way. + * To get from one to the other, we'll need to do the following: + * - Instantiate the nested slice and nested prefab + * - Patch the nested slice instance and fix up the entity ID references + * - Replace the nested prefab instance entities with the fixed-up slice ones + * - Add the nested instance (and the link patch) to the top-level prefab + */ + auto instanceToTemplateInterface = AZ::Interface::Get(); auto prefabSystemComponentInterface = AZ::Interface::Get(); @@ -371,22 +446,83 @@ namespace AZ AzToolsFramework::Prefab::PrefabDom unmodifiedNestedInstanceDom; instanceToTemplateInterface->GenerateDomForInstance(unmodifiedNestedInstanceDom, *(nestedInstance.get())); - // Currently, DataPatch conversions for nested slices aren't implemented, so all nested slice overrides will - // be lost. - AZ_Warning( - "Convert-Slice", false, " Nested slice instances will lose all of their override data during conversion.", - nestedTemplate->get().GetFilePath().c_str()); + // Instantiate a new instance of the nested slice + SliceComponent* dependentSlice = sliceAsset.Get()->GetComponent(); + [[maybe_unused]] AZ::SliceComponent::InstantiateResult instantiationResult = dependentSlice->Instantiate(); + AZ_Assert(instantiationResult == AZ::SliceComponent::InstantiateResult::Success, "Failed to instantiate instance"); - // Set the container entity of the nested prefab to have the top-level prefab as the parent. - // Once DataPatch conversions are supported, this will need to change to nest the prefab under the appropriate entity - // within the level. + // Apply the data patch for this instance of the nested slice. This will provide us with a version of the slice's entities + // with all data overrides applied to them. + DataPatch::FlagsMap sourceDataFlags = dependentSlice->GetDataFlagsForInstances().GetDataFlagsForPatching(); + DataPatch::FlagsMap targetDataFlags = instance.GetDataFlags().GetDataFlagsForPatching(&instance.GetEntityIdToBaseMap()); + AZ::ObjectStream::FilterDescriptor filterDesc(AZ::Data::AssetFilterNoAssetLoading); + + AZ::SliceComponent::InstantiatedContainer sourceObjects(false); + dependentSlice->GetEntities(sourceObjects.m_entities); + dependentSlice->GetAllMetadataEntities(sourceObjects.m_metadataEntities); + + const DataPatch& dataPatch = instance.GetDataPatch(); + auto instantiated = + dataPatch.Apply(&sourceObjects, dependentSlice->GetSerializeContext(), filterDesc, sourceDataFlags, targetDataFlags); + + // Run through all the instantiated entities and fix up their parent hierarchy: + // - Invalid parents need to get set to the container. + // - Valid parents into the top-level instance mean that the nested slice instance is also child-nested under an entity. + // Prefabs handle this type of nesting differently - we need to set the parent to the container, and the container's + // parent to that other instance. auto containerEntity = nestedInstance->GetContainerEntity(); - AzToolsFramework::Components::TransformComponent* transformComponent = - containerEntity->get().FindComponent(); - if (transformComponent) + auto containerEntityId = containerEntity->get().GetId(); + for (auto entity : instantiated->m_entities) { - transformComponent->SetParent(topLevelInstance->GetContainerEntityId()); - transformComponent->UpdateCachedWorldTransform(); + AzToolsFramework::Components::TransformComponent* transformComponent = + entity->FindComponent(); + if (transformComponent) + { + bool onlySetIfInvalid = true; + auto parentId = transformComponent->GetParentId(); + if (parentId.IsValid()) + { + auto parentAlias = m_aliasIdMapper.find(TemplateEntityIdPair(topLevelInstance->GetTemplateId(), parentId)); + if (parentAlias != m_aliasIdMapper.end()) + { + // Set the container's parent to this entity's parent, and set this entity's parent to the container + // (i.e. go from A->B to A->container->B) + auto newParentId = topLevelInstance->GetEntityId(parentAlias->second); + SetParentEntity(containerEntity->get(), newParentId, false); + onlySetIfInvalid = false; + } + } + + SetParentEntity(*entity, containerEntityId, onlySetIfInvalid); + } + } + + // Replace all the entities in the instance with the new patched ones. + // (This is easier than trying to figure out what the patched data changes are - we can let the JSON patch handle it for us) + nestedInstance->RemoveNestedEntities( + [](const AZStd::unique_ptr&) + { + return true; + }); + for (auto& entity : instantiated->m_entities) + { + auto entityAlias = m_aliasIdMapper.find(TemplateEntityIdPair(nestedInstance->GetTemplateId(), entity->GetId())); + if (entityAlias != m_aliasIdMapper.end()) + { + nestedInstance->AddEntity(*entity, entityAlias->second); + } + else + { + AZ_Assert(false, "Failed to find entity alias."); + nestedInstance->AddEntity(*entity); + } + } + + // Set the container entity of the nested prefab to have the top-level prefab as the parent if it hasn't already gotten + // another entity as its parent. + { + constexpr bool onlySetIfInvalid = true; + SetParentEntity(containerEntity->get(), topLevelInstance->GetContainerEntityId(), onlySetIfInvalid); } // Add the nested instance itself to the top-level prefab. To do this, we need to add it to our top-level instance, @@ -395,7 +531,22 @@ namespace AZ AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomBefore; instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomBefore, *topLevelInstance); - AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance)); + // When creating the new instance, we would like to have deterministic instance aliases. Prefabs that depend on this one + // will have patches that reference the alias, so if we reconvert this slice a second time, we would like it to produce + // the same results. To get a deterministic and unique alias, we rely on the slice instance. The slice instance contains + // a map of slice entity IDs to unique instance entity IDs. We'll just consistently use the first entry in the map as the + // unique instance ID. + AZStd::string instanceAlias; + auto entityIdMap = instance.GetEntityIdMap(); + if (!entityIdMap.empty()) + { + instanceAlias = AZStd::string::format("Instance_%s", entityIdMap.begin()->second.ToString().c_str()); + } + else + { + instanceAlias = AZStd::string::format("Instance_%s", AZ::Entity::MakeId().ToString().c_str()); + } + AzToolsFramework::Prefab::Instance& addedInstance = topLevelInstance->AddInstance(AZStd::move(nestedInstance), instanceAlias); AzToolsFramework::Prefab::PrefabDom topLevelInstanceDomAfter; instanceToTemplateInterface->GenerateDomForInstance(topLevelInstanceDomAfter, *topLevelInstance); @@ -418,9 +569,26 @@ namespace AZ AzToolsFramework::Prefab::InvalidLinkId); prefabSystemComponentInterface->PropagateTemplateChanges(topLevelInstance->GetTemplateId()); + AZ::Interface::Get()->UpdateTemplateInstancesInQueue(); + return true; } + void SliceConverter::SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid) + { + AzToolsFramework::Components::TransformComponent* transformComponent = + entity.FindComponent(); + if (transformComponent) + { + // Only set the parent if we didn't set the onlySetIfInvalid flag, or if we did and the parent is currently invalid + if (!onlySetIfInvalid || !transformComponent->GetParentId().IsValid()) + { + transformComponent->SetParent(parentId); + transformComponent->UpdateCachedWorldTransform(); + } + } + } + void SliceConverter::PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId) { auto prefabSystemComponentInterface = AZ::Interface::Get(); diff --git a/Code/Tools/SerializeContextTools/SliceConverter.h b/Code/Tools/SerializeContextTools/SliceConverter.h index bec893ff56..82dcf30383 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.h +++ b/Code/Tools/SerializeContextTools/SliceConverter.h @@ -39,24 +39,35 @@ namespace AZ class SliceConverter : public Converter { public: - static bool ConvertSliceFiles(Application& application); + bool ConvertSliceFiles(Application& application); private: - static bool ConnectToAssetProcessor(); - static void DisconnectFromAssetProcessor(); + using TemplateEntityIdPair = AZStd::pair; - static bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun); - static bool ConvertSliceToPrefab( + bool ConnectToAssetProcessor(); + void DisconnectFromAssetProcessor(); + + bool ConvertSliceFile(AZ::SerializeContext* serializeContext, const AZStd::string& slicePath, bool isDryRun); + bool ConvertSliceToPrefab( AZ::SerializeContext* serializeContext, AZ::IO::PathView outputPath, bool isDryRun, AZ::Entity* rootEntity); - static void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities); - static bool ConvertNestedSlices( + void FixPrefabEntities(AZ::Entity& containerEntity, SliceComponent::EntityList& sliceEntities); + bool ConvertNestedSlices( SliceComponent* sliceComponent, AzToolsFramework::Prefab::Instance* sourceInstance, AZ::SerializeContext* serializeContext, bool isDryRun); - static bool ConvertSliceInstance( + bool ConvertSliceInstance( AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset& sliceAsset, AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance); - static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId); - static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId); + void SetParentEntity(const AZ::Entity& entity, const AZ::EntityId& parentId, bool onlySetIfInvalid); + void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId); + bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId); + + // Track all of the entity IDs created and the prefab entity aliases that map to them. This mapping is used + // with nested slice conversion to remap parent entity IDs to the correct prefab entity IDs. + AZStd::unordered_map m_aliasIdMapper; + + // Track all of the created prefab template IDs on a slice conversion so that they can get removed at the end of the + // conversion for that file. + AZStd::unordered_set m_createdTemplateIds; }; } // namespace SerializeContextTools } // namespace AZ diff --git a/Code/Tools/SerializeContextTools/SliceConverterEditorEntityContextComponent.h b/Code/Tools/SerializeContextTools/SliceConverterEditorEntityContextComponent.h new file mode 100644 index 0000000000..166cb2abdf --- /dev/null +++ b/Code/Tools/SerializeContextTools/SliceConverterEditorEntityContextComponent.h @@ -0,0 +1,65 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include + +namespace AzToolsFramework +{ + // This class is an inelegant workaround for use by the Slice Converter to selectively disable entity add/remove logic + // during slice conversion in the EditorEntityContextComponent. Specifically, the standard versions of these methods will + // attempt to activate the entities as they're added. This is both unnecessary and undesirable during slice conversion, since + // entity activation requires a lot of subsystems to be active and valid. + // Instead, by selectively disabling this logic, the entities can remain in an initialized state, which is sufficient for conversion, + // without requiring those extra subsystems. + + // This problem also could have been solved by adding APIs to the EditorEntityContextComponent or the EntityContext, but there aren't + // any other known valid use cases for disabling this logic, so the extra APIs would simply encourage "bad behavior" by using them + // when they likely aren't necessary or desired. + + class SliceConverterEditorEntityContextComponent + : public EditorEntityContextComponent + { + public: + + AZ_COMPONENT(SliceConverterEditorEntityContextComponent, "{1CB0C38F-8E85-4422-91C6-E1F3B9B4B853}"); + + SliceConverterEditorEntityContextComponent() : EditorEntityContextComponent() {} + + // Simple API to selectively disable this logic *only* when performing slice to prefab conversion. + static void DisableOnContextEntityLogic() + { + m_enableOnContextEntityLogic = false; + } + + protected: + + void OnContextEntitiesAdded([[maybe_unused]] const EntityList& entities) override + { + if (m_enableOnContextEntityLogic) + { + EditorEntityContextComponent::OnContextEntitiesAdded(entities); + } + } + + void OnContextEntityRemoved([[maybe_unused]] const AZ::EntityId& id) override + { + if (m_enableOnContextEntityLogic) + { + EditorEntityContextComponent::OnContextEntityRemoved(id); + } + } + + // By default, act just like the EditorEntityContextComponent + static inline bool m_enableOnContextEntityLogic = true; + }; +} // namespace AzToolsFramework diff --git a/Code/Tools/SerializeContextTools/main.cpp b/Code/Tools/SerializeContextTools/main.cpp index d9f2cfbcc2..28eeb5ebe8 100644 --- a/Code/Tools/SerializeContextTools/main.cpp +++ b/Code/Tools/SerializeContextTools/main.cpp @@ -125,7 +125,8 @@ int main(int argc, char** argv) } else if (AZ::StringFunc::Equal("convert-slice", action.c_str())) { - result = SliceConverter::ConvertSliceFiles(application); + SliceConverter sliceConverter; + result = sliceConverter.ConvertSliceFiles(application); } else { diff --git a/Code/Tools/SerializeContextTools/serializecontexttools_files.cmake b/Code/Tools/SerializeContextTools/serializecontexttools_files.cmake index 814c55ea08..3427357149 100644 --- a/Code/Tools/SerializeContextTools/serializecontexttools_files.cmake +++ b/Code/Tools/SerializeContextTools/serializecontexttools_files.cmake @@ -17,6 +17,7 @@ set(FILES Dumper.h Dumper.cpp main.cpp + SliceConverterEditorEntityContextComponent.h SliceConverter.h SliceConverter.cpp Utilities.h diff --git a/Gems/Camera/Code/Source/CameraComponentController.cpp b/Gems/Camera/Code/Source/CameraComponentController.cpp index d0a124067b..cad666c1cd 100644 --- a/Gems/Camera/Code/Source/CameraComponentController.cpp +++ b/Gems/Camera/Code/Source/CameraComponentController.cpp @@ -178,7 +178,10 @@ namespace Camera if ((!m_viewSystem)||(!m_system)) { // perform first-time init - m_system = gEnv->pSystem; + if (gEnv) + { + m_system = gEnv->pSystem; + } if (m_system) { // Initialize local view. diff --git a/Registry/gem_autoload.serializecontexttools.setreg b/Registry/gem_autoload.serializecontexttools.setreg index e7e88dd6a6..0f74355084 100644 --- a/Registry/gem_autoload.serializecontexttools.setreg +++ b/Registry/gem_autoload.serializecontexttools.setreg @@ -10,6 +10,9 @@ "PythonAssetBuilder.Editor": { "AutoLoad": false }, + "AWSCore": { + "AutoLoad": false + }, "AWSCore.Editor": { "AutoLoad": false }, @@ -21,6 +24,9 @@ }, "AWSMetrics": { "AutoLoad": false + }, + "AWSMetrics.Editor": { + "AutoLoad": false } } } From fda28bb7b2504e7b2d2fc7cbdd344f373bf957ef Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Thu, 3 Jun 2021 14:02:40 -0700 Subject: [PATCH 64/65] LYN-1818 | [USE CASE] Reparenting between different prefab instances by drag/drop in the Outliner (#1088) * Add the last known parent to the prefab undo cache to detect changes in the owning instance. Still WIP. * Progress in handling reparenting. Still WIP, need a change in CreateLink that will be addressed in a separate branch and then merged back. * A few fixes, reparenting now works with entities. Still working on instances. * Fix assert crashing the Editor because of the arguments being in the wrong order. * Handle moving the patches when removing and recreating links when reparenting nested instances. * Rearrange some code to prevent including instance removal in instance update undo node, as it would be redundant and cause errors in some edge cases. * Reorder instance reparenting to account for correct order of operation during undo/redo * Fix order of operations to support multiple operations in one edit (reparenting to non-container entities while changing instance) * Add function to refresh patches on links to allow aliases to be restored correctly on reparenting. * Removed RefreshEntityPatchOnLink function. Introduced a simpler way of handling porting patches. * Removing unnecessary code that was left after testing. * Minor fixes to naming and comments. * Restore previous error, no longer printing the failed patch. * Remove unused includes. * Restore include removed by mistake. * Simplified patches retrieval by using internal function. Renamed some internal functions and variables to be more accurate. --- .../Prefab/PrefabPublicHandler.cpp | 221 +++++++++++++++--- .../Prefab/PrefabPublicHandler.h | 10 +- .../Prefab/PrefabSystemComponent.cpp | 4 +- .../Prefab/PrefabUndoCache.cpp | 24 +- .../AzToolsFramework/Prefab/PrefabUndoCache.h | 13 +- 5 files changed, 217 insertions(+), 55 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 90c3dd10ae..27c812ff9d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -297,7 +297,7 @@ namespace AzToolsFramework m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId); // Update the cache - this prevents these changes from being stored in the regular undo/redo nodes - m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); + m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter), parentEntityId); return AZStd::move(patch); } @@ -595,54 +595,199 @@ namespace AzToolsFramework { // Create Undo node on entities if they belong to an instance InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); - - if (owningInstance.has_value()) + if (!owningInstance.has_value()) { - PrefabDom afterState; - AZ::Entity* entity = GetEntityById(entityId); - if (entity) + return; + } + + AZ::Entity* entity = GetEntityById(entityId); + if (!entity) + { + m_prefabUndoCache.PurgeCache(entityId); + return; + } + + PrefabDom beforeState; + AZ::EntityId beforeParentId; + m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId); + + PrefabDom afterState; + AZ::EntityId afterParentId; + AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId); + + m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity); + + PrefabDom patch; + m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId); + + if (patch.IsArray() && !patch.Empty() && beforeState.IsObject()) + { + bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId); + bool isNewParentOwnedByDifferentInstance = false; + + if (beforeParentId != afterParentId) { - PrefabDom beforeState; - m_prefabUndoCache.Retrieve(entityId, beforeState); + // If the entity parent changed, verify if the owning instance changed too + InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId); + InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId); - m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity); - - PrefabDom patch; - m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState); - - if (patch.IsArray() && !patch.Empty() && beforeState.IsObject()) + if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() && + (&beforeOwningInstance->get() != &afterOwningInstance->get())) { - if (IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId)) - { - m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId); - - // Save these changes as patches to the link - PrefabUndoLinkUpdate* linkUpdate = - aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(entityId))); - linkUpdate->SetParent(parentUndoBatch); - linkUpdate->Capture(patch, owningInstance->get().GetLinkId()); - - linkUpdate->Redo(); - } - else - { - // Update the state of the entity - PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(entityId))); - state->SetParent(parentUndoBatch); - state->Capture(beforeState, afterState, entityId); - - state->Redo(); - } + isNewParentOwnedByDifferentInstance = true; } + } - // Update the cache - m_prefabUndoCache.Store(entityId, AZStd::move(afterState)); + if (isInstanceContainerEntity) + { + if (isNewParentOwnedByDifferentInstance) + { + Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId); + + PrefabDom afterStateafterReparenting; + m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity); + + PrefabDom newPatch; + m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId); + + InstanceOptionalReference owningInstanceAfterReparenting = + m_instanceEntityMapperInterface->FindOwningInstance(entityId); + + Internal_HandleContainerOverride( + parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId()); + } + else + { + Internal_HandleContainerOverride( + parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId()); + } } else { - m_prefabUndoCache.PurgeCache(entityId); + Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState); + + if (isNewParentOwnedByDifferentInstance) + { + Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId); + } } } + + m_prefabUndoCache.UpdateCache(entityId); + } + + void PrefabPublicHandler::Internal_HandleContainerOverride( + UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId) + { + // Save these changes as patches to the link + PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(entityId))); + linkUpdate->SetParent(undoBatch); + linkUpdate->Capture(patch, linkId); + + linkUpdate->Redo(); + } + + void PrefabPublicHandler::Internal_HandleEntityChange( + UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState) + { + // Update the state of the entity + PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(entityId))); + state->SetParent(undoBatch); + state->Capture(beforeState, afterState, entityId); + + state->Redo(); + } + + void PrefabPublicHandler::Internal_HandleInstanceChange( + UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId) + { + // If the entity parent changed, verify if the owning instance changed too + InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId); + InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId); + + EntityList entities; + AZStd::vector instances; + + // Retrieve all descendant entities and instances of this entity that belonged to the same owning instance. + RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances); + + AZStd::vector> instanceUniquePtrs; + AZStd::vector> instancePatches; + + // Remove Entities and Instances from the prior instance + { + // Remove Instances + for (Instance* nestedInstance : instances) + { + auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId()); + + PrefabDom oldLinkPatches; + + if (linkRef.has_value()) + { + auto patches = linkRef->get().GetLinkPatches(); + if (patches.has_value()) + { + oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator()); + } + } + + auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias()); + RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch); + + instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches))); + instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr)); + } + + // Get the previous state of the prior instance for undo/redo purposes + PrefabDom beforeInstanceDomBeforeRemoval; + m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get()); + + // Remove Entities + for (AZ::Entity* nestedEntity : entities) + { + beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release(); + } + + // Create the Update node for the prior owning instance + // Instance removal will be taken care of from the RemoveLink function for undo/redo purposes + PrefabUndoHelpers::UpdatePrefabInstance( + beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch); + } + + // Add Entities and Instances to new instance + { + // Add Instances + for (auto& instanceUniquePtr : instanceUniquePtrs) + { + afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr)); + } + + // Create Links + for (auto& instanceInfo : instancePatches) + { + // Add a new link with the old dom + CreateLink( + *instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch, + AZStd::move(instanceInfo.second)); + } + + // Get the previous state of the new instance for undo/redo purposes + PrefabDom afterInstanceDomBeforeAdd; + m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get()); + + // Add Entities + for (AZ::Entity* nestedEntity : entities) + { + afterOwningInstance->get().AddEntity(*nestedEntity); + } + + // Create the Update node for the new owning instance + PrefabUndoHelpers::UpdatePrefabInstance( + afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch); + } } bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index e7b6f8c932..99fe8e5b67 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -90,8 +90,8 @@ namespace AzToolsFramework /** * Creates a link between the templates of an instance and its parent. * - * \param sourceInstance The instance that corresponds to the source template of the link. - * \param targetInstance The id of the target template. + * \param sourceInstance The instance that corresponds to the source template of the link (child). + * \param targetInstance The id of the target template (parent). * \param undoBatch The undo batch to set as parent for this create link action. * \param patch The patch to store in the newly created link dom. * \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not. @@ -134,6 +134,12 @@ namespace AzToolsFramework bool IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); + static void Internal_HandleContainerOverride( + UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId); + static void Internal_HandleEntityChange( + UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState); + void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId); + void UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index c42dbe792a..5f5564b4e1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -706,14 +706,14 @@ namespace AzToolsFramework "Prefab - PrefabSystemComponent::RemoveLink - " "Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' " "from TemplateToLinkIdsMap.", - linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str()); + linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId()); result = RemoveLinkFromTargetTemplate(linkId, link); AZ_Assert(result, "Prefab - PrefabSystemComponent::RemoveLink - " "Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' " "from target Template with Id '%llu'.", - linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str(), link.GetTargetTemplateId()); + linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId(), link.GetTargetTemplateId()); m_linkIdMap.erase(linkId); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.cpp index 7891e2c2e0..78c76cc8e5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.cpp @@ -73,14 +73,16 @@ namespace AzToolsFramework } PrefabDom oldData; - Retrieve(entityId, oldData); + AZ::EntityId oldParentId; + Retrieve(entityId, oldData, oldParentId); UpdateCache(entityId); PrefabDom newData; - Retrieve(entityId, newData); + AZ::EntityId newParentId; + Retrieve(entityId, newData, newParentId); - if (newData != oldData) + if (newData != oldData || oldParentId != newParentId) { // display a useful message AZ::Entity* entity = nullptr; @@ -106,7 +108,7 @@ namespace AzToolsFramework // Clear out newly generated data and // replace with original data to ensure debug mode has the same data as profile/release // in the event of the consistency check failing. - m_entitySavedStates[entityId] = AZStd::move(oldData); + m_entitySavedStates[entityId] = {AZStd::move(oldData), oldParentId}; #endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS } @@ -140,10 +142,13 @@ namespace AzToolsFramework return; } + AZ::EntityId parentId; + AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId); + // Capture it PrefabDom entityDom; m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity); - m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(entityDom))); + m_entitySavedStates[entityId] = {AZStd::move(entityDom), parentId}; AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast(entityId), entity->GetName().c_str()); @@ -155,7 +160,7 @@ namespace AzToolsFramework m_entitySavedStates.erase(entityId); } - bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom) + bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId) { auto it = m_entitySavedStates.find(entityId); @@ -164,14 +169,15 @@ namespace AzToolsFramework return false; } - outDom = AZStd::move(m_entitySavedStates[entityId]); + outDom = AZStd::move(m_entitySavedStates[entityId].dom); + parentId = m_entitySavedStates[entityId].parentId; m_entitySavedStates.erase(entityId); return true; } - void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom) + void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId) { - m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(dom))); + m_entitySavedStates[entityId] = {AZStd::move(dom), parentId}; } void PrefabUndoCache::Clear() diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.h index 924c93f44b..3a3ac92d7c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndoCache.h @@ -46,14 +46,19 @@ namespace AzToolsFramework void Validate(const AZ::EntityId& entityId) override; // Retrieve the last known state for an entity - bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom); + bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId); // Store dom as the cached state of entityId - void Store(const AZ::EntityId& entityId, PrefabDom&& dom); + void Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId); private: - typedef AZStd::unordered_map EntityDomMap; - EntityDomMap m_entitySavedStates; + struct PrefabUndoCacheItem + { + PrefabDom dom; + AZ::EntityId parentId; + }; + typedef AZStd::unordered_map EntityCache; + EntityCache m_entitySavedStates; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr; From 3634277317fa1777cda9849175f1a3b0aeda1423 Mon Sep 17 00:00:00 2001 From: Eric Phister <52085794+amzn-phist@users.noreply.github.com> Date: Thu, 3 Jun 2021 16:15:46 -0500 Subject: [PATCH 65/65] Project dll is not loaded by the AP when opened from the launcher (#1123) * Fixes locating the project dll when using SDK SDK engine usage has project dll in the project build path, but searching for module filepaths for loading would have a passing SystemFile::Exists check but no full filepath was amended to the module. This causes the module to fail to load. * Fix locating project module for UnixLike platforms Fixes the issue with project-centric workflows running GameLauncher, and it opens AP which can't find the project dynamic module. From AP's perspective, the project module is not in the executable directory, which is in engine bin. The SystemFile::Exists check is true on the file because it uses the 'cwd'. In that situation, an absolute path must be obtained for the module to be loaded. * Add missing header to fix UnixLike builds * Applies a suggested change from PR Use operator-> on the AZStd::optional * Add semicolon to a class macro line Prevent auto formatting indenting the following line. --- .../Module/DynamicModuleHandle_UnixLike.cpp | 23 +++++++++++++------ .../Module/DynamicModuleHandle_WinAPI.cpp | 16 ++++++++++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp index 8e2b40aaca..f9da818b4f 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Module/DynamicModuleHandle_UnixLike.cpp @@ -13,9 +13,10 @@ #include #include #include - #include #include +#include + #include #include @@ -61,10 +62,11 @@ namespace AZ // If it doesn't attempt to append the path to the executable path if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str())) { - auto candidatePath = Platform::GetModulePath() / fullFilePath; + AZ::IO::FixedMaxPath candidatePath = Platform::GetModulePath() / fullFilePath; if (AZ::IO::SystemFile::Exists(candidatePath.c_str())) { - fullFilePath = candidatePath; + m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size()); + return; } } @@ -74,19 +76,26 @@ namespace AZ { if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { - if(AZ::IO::FixedMaxPath projectModulePath; + if (AZ::IO::FixedMaxPath projectModulePath; settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath)) { projectModulePath /= fullFilePath; if (AZ::IO::SystemFile::Exists(projectModulePath.c_str())) { - fullFilePath = projectModulePath; + m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size()); } } } } - - m_fileName = AZStd::string_view{fullFilePath.Native()}; + else + { + // The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded. + AZStd::optional absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName); + if (absPathOptional.has_value()) + { + m_fileName.assign(absPathOptional->c_str(), absPathOptional->size()); + } + } } ~DynamicModuleHandleUnixLike() override diff --git a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Module/DynamicModuleHandle_WinAPI.cpp b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Module/DynamicModuleHandle_WinAPI.cpp index 9daabfb86b..aeb8a81d09 100644 --- a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Module/DynamicModuleHandle_WinAPI.cpp +++ b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Module/DynamicModuleHandle_WinAPI.cpp @@ -24,9 +24,9 @@ namespace AZ : public DynamicModuleHandle { public: - AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0) + AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0); - DynamicModuleHandleWindows(const char* fullFileName) + DynamicModuleHandleWindows(const char* fullFileName) : DynamicModuleHandle(fullFileName) , m_handle(nullptr) { @@ -52,6 +52,7 @@ namespace AZ if (AZ::IO::SystemFile::Exists(candidatePath.c_str())) { m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size()); + return; } } } @@ -65,7 +66,7 @@ namespace AZ // Therefore an existence check is needed if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { - if(AZ::IO::FixedMaxPath projectModulePath; + if (AZ::IO::FixedMaxPath projectModulePath; settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath)) { projectModulePath /= AZStd::string_view(m_fileName); @@ -76,6 +77,15 @@ namespace AZ } } } + else + { + // The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded. + AZStd::optional absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName); + if (absPathOptional.has_value()) + { + m_fileName.assign(absPathOptional->c_str(), absPathOptional->size()); + } + } } ~DynamicModuleHandleWindows() override