From 4c2933b38d36dca8e2e0a2d412114a1bd1ab0d92 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Mon, 17 May 2021 18:44:32 -0400 Subject: [PATCH 01/43] 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 802943bbb3bf0553bfaf311608587ed295ff3866 Mon Sep 17 00:00:00 2001 From: karlberg Date: Thu, 27 May 2021 19:54:56 -0700 Subject: [PATCH 02/43] 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 3fcc1b64fce369a6129b9419187f2d78d7d39339 Mon Sep 17 00:00:00 2001 From: mnaumov Date: Tue, 1 Jun 2021 20:24:23 -0700 Subject: [PATCH 03/43] [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 04/43] 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 ee0ecc2fa03b73cb95e3987d56a63441cfc8fe0a Mon Sep 17 00:00:00 2001 From: mnaumov Date: Wed, 2 Jun 2021 11:49:08 -0700 Subject: [PATCH 05/43] [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 a69db3bf7681e2e6cef653919544ec3828b195c4 Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 14:44:02 -0700 Subject: [PATCH 06/43] 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 8ef2bd751821c56ed841bb15d6b9765a05d75b8a Mon Sep 17 00:00:00 2001 From: karlberg Date: Wed, 2 Jun 2021 14:47:05 -0700 Subject: [PATCH 07/43] 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 08/43] 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 09/43] 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 10/43] 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 c752b9d0fc19ca1f5f5cbc713184b0aeaedd1d9a Mon Sep 17 00:00:00 2001 From: pereslav Date: Thu, 3 Jun 2021 01:04:01 +0100 Subject: [PATCH 11/43] 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 12/43] 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 13/43] 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 14/43] 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 9dbe596e400bbeac8fcecc34227821fceab378b1 Mon Sep 17 00:00:00 2001 From: Doug McDiarmid Date: Wed, 2 Jun 2021 18:39:05 -0700 Subject: [PATCH 15/43] Added reflection probe cubemap quality levels --- .../Config/IBLSpecular.preset | 15 +- .../Config/IBLSpecularHigh.preset | 135 ++++++++++++++++++ .../Config/IBLSpecularLow.preset | 135 ++++++++++++++++++ .../Config/IBLSpecularVeryHigh.preset | 135 ++++++++++++++++++ .../Config/IBLSpecularVeryLow.preset | 135 ++++++++++++++++++ .../EditorReflectionProbeComponent.cpp | 20 ++- .../EditorReflectionProbeComponent.h | 1 + .../ReflectionProbeComponentController.cpp | 1 + .../ReflectionProbeComponentController.h | 24 ++++ 9 files changed, 595 insertions(+), 6 deletions(-) create mode 100644 Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset create mode 100644 Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset create mode 100644 Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset create mode 100644 Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset index d940f425c2..4f935c73ff 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecular.preset @@ -8,7 +8,8 @@ "Name": "IBLSpecular", "Description": "The input cubemap generates an IBL specular output cubemap.", "FileMasks": [ - "_iblspecularcm" + "_iblspecularcm", + "_iblspecularcm256" ], "SourceColor": "Linear", "DestColor": "Linear", @@ -34,7 +35,8 @@ "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", "FileMasks": [ - "_iblspecularcm" + "_iblspecularcm", + "_iblspecularcm256" ], "SourceColor": "Linear", "DestColor": "Linear", @@ -59,7 +61,8 @@ "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", "FileMasks": [ - "_iblspecularcm" + "_iblspecularcm", + "_iblspecularcm256" ], "SourceColor": "Linear", "DestColor": "Linear", @@ -84,7 +87,8 @@ "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", "FileMasks": [ - "_iblspecularcm" + "_iblspecularcm", + "_iblspecularcm256" ], "SourceColor": "Linear", "DestColor": "Linear", @@ -109,7 +113,8 @@ "UUID": "{908DA68C-97FB-4C4A-97BC-5A55F30F14FA}", "Name": "IBLSpecular", "FileMasks": [ - "_iblspecularcm" + "_iblspecularcm", + "_iblspecularcm256" ], "SourceColor": "Linear", "DestColor": "Linear", diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset new file mode 100644 index 0000000000..ff4e143326 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularHigh.preset @@ -0,0 +1,135 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "MultiplatformPresetSettings", + "ClassData": { + "DefaultPreset": { + "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", + "Name": "IBLSpecularHigh", + "Description": "The input cubemap generates an IBL specular output cubemap.", + "FileMasks": [ + "_iblspecularcm512" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 512, + "MaxTextureSize": 512, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "PlatformsPresets": { + "android": { + "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", + "Name": "IBLSpecularHigh", + "FileMasks": [ + "_iblspecularcm512" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 512, + "MaxTextureSize": 512, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "ios": { + "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", + "Name": "IBLSpecularHigh", + "FileMasks": [ + "_iblspecularcm512" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 512, + "MaxTextureSize": 512, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "mac": { + "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", + "Name": "IBLSpecularHigh", + "FileMasks": [ + "_iblspecularcm512" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 512, + "MaxTextureSize": 512, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "provo": { + "UUID": "{B66395E1-8D0E-4159-989B-FC2B9F091B75}", + "Name": "IBLSpecularHigh", + "FileMasks": [ + "_iblspecularcm512" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 512, + "MaxTextureSize": 512, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + } + } + } +} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset new file mode 100644 index 0000000000..ee9ddd6ac7 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularLow.preset @@ -0,0 +1,135 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "MultiplatformPresetSettings", + "ClassData": { + "DefaultPreset": { + "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", + "Name": "IBLSpecularLow", + "Description": "The input cubemap generates an IBL specular output cubemap.", + "FileMasks": [ + "_iblspecularcm128" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 128, + "MaxTextureSize": 128, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "PlatformsPresets": { + "android": { + "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", + "Name": "IBLSpecularLow", + "FileMasks": [ + "_iblspecularcm128" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 128, + "MaxTextureSize": 128, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "ios": { + "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", + "Name": "IBLSpecularLow", + "FileMasks": [ + "_iblspecularcm128" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 128, + "MaxTextureSize": 128, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "mac": { + "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", + "Name": "IBLSpecularLow", + "FileMasks": [ + "_iblspecularcm128" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 128, + "MaxTextureSize": 128, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "provo": { + "UUID": "{7273ACAE-6E34-487C-AF71-99423A6E1CB0}", + "Name": "IBLSpecularLow", + "FileMasks": [ + "_iblspecularcm128" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 128, + "MaxTextureSize": 128, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + } + } + } +} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset new file mode 100644 index 0000000000..08d9416935 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryHigh.preset @@ -0,0 +1,135 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "MultiplatformPresetSettings", + "ClassData": { + "DefaultPreset": { + "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", + "Name": "IBLSpecularVeryHigh", + "Description": "The input cubemap generates an IBL specular output cubemap.", + "FileMasks": [ + "_iblspecularcm1024" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 1024, + "MaxTextureSize": 1024, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "PlatformsPresets": { + "android": { + "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", + "Name": "IBLSpecularVeryHigh", + "FileMasks": [ + "_iblspecularcm1024" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 1024, + "MaxTextureSize": 1024, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "ios": { + "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", + "Name": "IBLSpecularVeryHigh", + "FileMasks": [ + "_iblspecularcm1024" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 1024, + "MaxTextureSize": 1024, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "mac": { + "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", + "Name": "IBLSpecularVeryHigh", + "FileMasks": [ + "_iblspecularcm1024" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 1024, + "MaxTextureSize": 1024, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "provo": { + "UUID": "{5CD1AFA6-915B-4716-893C-A5B1F4074C22}", + "Name": "IBLSpecularVeryHigh", + "FileMasks": [ + "_iblspecularcm1024" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 1024, + "MaxTextureSize": 1024, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + } + } + } +} diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset new file mode 100644 index 0000000000..c5c0788848 --- /dev/null +++ b/Gems/Atom/Asset/ImageProcessingAtom/Config/IBLSpecularVeryLow.preset @@ -0,0 +1,135 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "MultiplatformPresetSettings", + "ClassData": { + "DefaultPreset": { + "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", + "Name": "IBLSpecularVeryLow", + "Description": "The input cubemap generates an IBL specular output cubemap.", + "FileMasks": [ + "_iblspecularcm64" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 64, + "MaxTextureSize": 64, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "PlatformsPresets": { + "android": { + "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", + "Name": "IBLSpecularVeryLow", + "FileMasks": [ + "_iblspecularcm64" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 64, + "MaxTextureSize": 64, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "ios": { + "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", + "Name": "IBLSpecularVeryLow", + "FileMasks": [ + "_iblspecularcm64" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 64, + "MaxTextureSize": 64, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "mac": { + "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", + "Name": "IBLSpecularVeryLow", + "FileMasks": [ + "_iblspecularcm64" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 64, + "MaxTextureSize": 64, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + }, + "provo": { + "UUID": "{8293C236-D3E8-4352-8B18-C2E82EEE6547}", + "Name": "IBLSpecularVeryLow", + "FileMasks": [ + "_iblspecularcm64" + ], + "SourceColor": "Linear", + "DestColor": "Linear", + "SuppressEngineReduce": true, + "PixelFormat": "R9G9B9E5", + "DiscardAlpha": true, + "MinTextureSize": 64, + "MaxTextureSize": 64, + "IsPowerOf2": true, + "CubemapSettings": { + "Filter": "GGX", + "MipAngle": 7.0, + "MipSlope": 2.299999952316284, + "EdgeFixup": -431602080.0, + "SubId": 2000 + }, + "MipMapSetting": { + "MipGenType": "Box" + } + } + } + } +} diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp index 7880d5e88c..f3932322b1 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.cpp @@ -39,6 +39,7 @@ namespace AZ serializeContext->Class() ->Version(2, ConvertToEditorRenderComponentAdapter<1>) ->Field("useBakedCubemap", &EditorReflectionProbeComponent::m_useBakedCubemap) + ->Field("bakedCubeMapQualityLevel", &EditorReflectionProbeComponent::m_bakedCubeMapQualityLevel) ->Field("bakedCubeMapRelativePath", &EditorReflectionProbeComponent::m_bakedCubeMapRelativePath) ->Field("authoredCubeMapAsset", &EditorReflectionProbeComponent::m_authoredCubeMapAsset) ; @@ -67,6 +68,13 @@ namespace AZ ->DataElement(AZ::Edit::UIHandlers::Default, &EditorReflectionProbeComponent::m_useBakedCubemap, "Use Baked Cubemap", "Selects between a cubemap that captures the environment at location in the scene or a preauthored cubemap") ->Attribute(AZ::Edit::Attributes::ChangeValidate, &EditorReflectionProbeComponent::OnUseBakedCubemapValidate) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorReflectionProbeComponent::OnUseBakedCubemapChanged) + ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorReflectionProbeComponent::m_bakedCubeMapQualityLevel, "Baked Cubemap Quality", "Resolution of the baked cubemap") + ->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting) + ->EnumAttribute(BakedCubeMapQualityLevel::VeryLow, "Very Low") + ->EnumAttribute(BakedCubeMapQualityLevel::Low, "Low") + ->EnumAttribute(BakedCubeMapQualityLevel::Medium, "Medium") + ->EnumAttribute(BakedCubeMapQualityLevel::High, "High") + ->EnumAttribute(BakedCubeMapQualityLevel::VeryHigh, "Very High") ->DataElement(AZ::Edit::UIHandlers::MultiLineEdit, &EditorReflectionProbeComponent::m_bakedCubeMapRelativePath, "Baked Cubemap Path", "Baked Cubemap Path") ->Attribute(AZ::Edit::Attributes::ReadOnly, true) ->Attribute(AZ::Edit::Attributes::Visibility, &EditorReflectionProbeComponent::GetBakedCubemapVisibilitySetting) @@ -332,6 +340,12 @@ namespace AZ // clear it to force the generation of a new filename cubeMapRelativePath.clear(); } + + // if the quality level changed we need to generate a new filename + if (m_controller.m_configuration.m_bakedCubeMapQualityLevel != m_bakedCubeMapQualityLevel) + { + cubeMapRelativePath.clear(); + } } // build a new cubemap path if necessary @@ -345,7 +359,10 @@ namespace AZ AZStd::string uuidString; uuid.ToString(uuidString); - cubeMapRelativePath = "ReflectionProbes/" + entity->GetName() + "_" + uuidString + "_iblspecularcm.dds"; + // determine the filemask suffix from the cubemap quality level setting + AZStd::string fileSuffix = BakedCubeMapFileSuffixes[aznumeric_cast(m_bakedCubeMapQualityLevel)]; + + cubeMapRelativePath = "ReflectionProbes/" + entity->GetName() + "_" + uuidString + fileSuffix; // replace any invalid filename characters auto invalidCharacters = [](char letter) @@ -384,6 +401,7 @@ namespace AZ // save the relative source path in the configuration AzToolsFramework::ScopedUndoBatch undoBatch("Cubemap path changed."); m_controller.m_configuration.m_bakedCubeMapRelativePath = cubeMapRelativePath; + m_controller.m_configuration.m_bakedCubeMapQualityLevel = m_bakedCubeMapQualityLevel; SetDirty(); // update UI cubemap path display diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h index 23fd391818..bc45eae11b 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/EditorReflectionProbeComponent.h @@ -77,6 +77,7 @@ namespace AZ // UI settings // the user can select between a baked cubemap or an authored cubemap asset bool m_useBakedCubemap = true; + BakedCubeMapQualityLevel m_bakedCubeMapQualityLevel = BakedCubeMapQualityLevel::Medium; AZStd::string m_bakedCubeMapRelativePath; Data::Asset m_bakedCubeMapAsset; Data::Asset m_authoredCubeMapAsset; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp index 9b0ff29bb8..873810c016 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.cpp @@ -46,6 +46,7 @@ namespace AZ ->Field("InnerLength", &ReflectionProbeComponentConfig::m_innerLength) ->Field("InnerWidth", &ReflectionProbeComponentConfig::m_innerWidth) ->Field("UseBakedCubemap", &ReflectionProbeComponentConfig::m_useBakedCubemap) + ->Field("BakedCubemapQualityLevel", &ReflectionProbeComponentConfig::m_bakedCubeMapQualityLevel) ->Field("BakedCubeMapRelativePath", &ReflectionProbeComponentConfig::m_bakedCubeMapRelativePath) ->Field("BakedCubeMapAsset", &ReflectionProbeComponentConfig::m_bakedCubeMapAsset) ->Field("AuthoredCubeMapAsset", &ReflectionProbeComponentConfig::m_authoredCubeMapAsset) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h index 0a57fde882..97a7fbebb4 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/ReflectionProbe/ReflectionProbeComponentController.h @@ -24,6 +24,29 @@ namespace AZ { namespace Render { + enum class BakedCubeMapQualityLevel : uint32_t + { + VeryLow, // 64 + Low, // 128 + Medium, // 256 + High, // 512 + VeryHigh, // 1024 + + Count + }; + + static const char* BakedCubeMapFileSuffixes[] = + { + "_iblspecularcm64.dds", + "_iblspecularcm128.dds", + "_iblspecularcm256.dds", + "_iblspecularcm512.dds", + "_iblspecularcm1024.dds" + }; + + static_assert(AZ_ARRAY_SIZE(BakedCubeMapFileSuffixes) == aznumeric_cast(BakedCubeMapQualityLevel::Count), + "BakedCubeMapFileSuffixes must have the same number of entries as BakedCubeMapQualityLevel"); + class ReflectionProbeComponentConfig final : public AZ::ComponentConfig { @@ -43,6 +66,7 @@ namespace AZ bool m_showVisualization = true; bool m_useBakedCubemap = true; + BakedCubeMapQualityLevel m_bakedCubeMapQualityLevel = BakedCubeMapQualityLevel::Medium; AZStd::string m_bakedCubeMapRelativePath; Data::Asset m_bakedCubeMapAsset; Data::Asset m_authoredCubeMapAsset; From 35fed7722305c8a3eaadb91f45836628d5d2ba33 Mon Sep 17 00:00:00 2001 From: mgwynn Date: Wed, 2 Jun 2021 21:56:03 -0400 Subject: [PATCH 16/43] 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 17/43] 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 18/43] 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 19/43] 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 20/43] 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 21/43] 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 22/43] 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 23/43] 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 24/43] [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 25/43] 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 26/43] 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 27/43] 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 28/43] 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 29/43] [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 30/43] 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 31/43] 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 32/43] 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 33/43] 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 34/43] 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 35/43] 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 36/43] 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 37/43] 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 38/43] 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 39/43] 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 40/43] 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 From 816d05ef2d85843c86fd47265cc8cf1a3b55c3c5 Mon Sep 17 00:00:00 2001 From: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com> Date: Thu, 3 Jun 2021 18:16:22 -0500 Subject: [PATCH 41/43] Updating manifest.py template query functions (#1113) * Updating manifest.py template query functions The get_project_templates, get_gem_templates and get_generic_templates methods have been renamed to indicate that the methods return the templates that can be used in a create-project, create-gem and create-from-template command of the engine_template.py Updated the print_registration.py script to support outputing project specific gems and templates. Add a unit test script for the manifest.py script. Added unit test to validate the new functions: `get_templates_for_project_creation` `get_templates_for_gem_creation` `get_templates_for_generic_creation` * Implementing the project print registration methods Added implementations of the project print registration methods and tested them locally Removed implementations of the download print registration methods, since they have not went through app-sec review. * Renaming get_restricted_data to get_restricted_json_data Fixed the get_registered method in manifest.py when looking up projects * Updated the print_manifest_json_data calls to return the result --- .../ProjectManager/Source/PythonBindings.cpp | 215 ++++---- scripts/o3de/o3de/manifest.py | 97 ++-- scripts/o3de/o3de/print_registration.py | 496 +++++++++--------- scripts/o3de/tests/CMakeLists.txt | 7 + scripts/o3de/tests/unit_test_manifest.py | 111 ++++ scripts/o3de/tests/unit_test_utils.py | 2 +- 6 files changed, 542 insertions(+), 386 deletions(-) create mode 100644 scripts/o3de/tests/unit_test_manifest.py diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 7062d886da..18b29c496f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -294,7 +294,8 @@ namespace O3DE::ProjectManager RegisterThisEngine(); return result == 0 && !PyErr_Occurred(); - } catch ([[maybe_unused]] const std::exception& e) + } + catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("ProjectManagerWindow", false, "Py_Initialize() failed with %s", e.what()); return false; @@ -320,25 +321,25 @@ namespace O3DE::ProjectManager bool registrationResult = true; // already registered is considered successful bool pythonResult = ExecuteWithLock( [&] + { + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_manifest.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) { - // check current engine path against all other registered engines - // to see if we are already registered - auto allEngines = m_manifest.attr("get_engines")(); - if (pybind11::isinstance(allEngines)) + for (auto engine : allEngines) { - for (auto engine : allEngines) + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); - if (enginePath.Compare(m_enginePath) == 0) - { - return; - } + return; } } + } - 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); AZ_Assert(finalResult, "Registration of this engine failed!"); @@ -378,12 +379,12 @@ namespace O3DE::ProjectManager auto o3deData = m_manifest.attr("load_o3de_manifest")(); if (pybind11::isinstance(o3deData)) { - engineInfo.m_path = Py_To_String(enginePath); - engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]); - engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]); + engineInfo.m_path = Py_To_String(enginePath); + engineInfo.m_defaultGemsFolder = Py_To_String(o3deData["default_gems_folder"]); + engineInfo.m_defaultProjectsFolder = Py_To_String(o3deData["default_projects_folder"]); engineInfo.m_defaultRestrictedFolder = Py_To_String(o3deData["default_restricted_folder"]); - engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]); - engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData,"third_party_path",""); + engineInfo.m_defaultTemplatesFolder = Py_To_String(o3deData["default_templates_folder"]); + engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData, "third_party_path", ""); } auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath); @@ -391,8 +392,8 @@ namespace O3DE::ProjectManager { try { - engineInfo.m_version = Py_To_String_Optional(engineData,"O3DEVersion","0.0.0.0"); - engineInfo.m_name = Py_To_String_Optional(engineData,"engine_name","O3DE"); + engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0"); + engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE"); } catch ([[maybe_unused]] const std::exception& e) { @@ -416,19 +417,19 @@ namespace O3DE::ProjectManager bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo) { bool result = ExecuteWithLock([&] { - pybind11::str enginePath = engineInfo.m_path.toStdString(); - pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString(); - pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString(); + pybind11::str enginePath = engineInfo.m_path.toStdString(); + pybind11::str defaultProjectsFolder = engineInfo.m_defaultProjectsFolder.toStdString(); + pybind11::str defaultGemsFolder = engineInfo.m_defaultGemsFolder.toStdString(); pybind11::str defaultTemplatesFolder = engineInfo.m_defaultTemplatesFolder.toStdString(); auto registrationResult = m_register.attr("register")( - enginePath, // engine_path - pybind11::none(), // project_path + enginePath, // engine_path + pybind11::none(), // project_path pybind11::none(), // gem_path - pybind11::none(), // external_subdir_path - pybind11::none(), // template_path - pybind11::none(), // restricted_path - pybind11::none(), // repo_uri + pybind11::none(), // external_subdir_path + pybind11::none(), // template_path + pybind11::none(), // restricted_path + pybind11::none(), // repo_uri pybind11::none(), // default_engines_folder defaultProjectsFolder, defaultGemsFolder, @@ -477,12 +478,12 @@ namespace O3DE::ProjectManager QVector gems; auto result = ExecuteWithLockErrorHandling([&] + { + for (auto path : m_manifest.attr("get_engine_gems")()) { - for (auto path : m_manifest.attr("get_engine_gems")()) - { - gems.push_back(GemInfoFromPath(path)); - } - }); + gems.push_back(GemInfoFromPath(path)); + } + }); if (!result.IsSuccess()) { return AZ::Failure(result.GetError().c_str()); @@ -497,13 +498,13 @@ namespace O3DE::ProjectManager QVector gems; auto result = ExecuteWithLockErrorHandling([&] + { + pybind11::str pyProjectPath = projectPath.toStdString(); + for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) { - pybind11::str pyProjectPath = projectPath.toStdString(); - for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) - { - gems.push_back(GemInfoFromPath(path)); - } - }); + gems.push_back(GemInfoFromPath(path)); + } + }); if (!result.IsSuccess()) { return AZ::Failure(result.GetError().c_str()); @@ -518,12 +519,12 @@ namespace O3DE::ProjectManager // Retrieve the path to the cmake file that lists the enabled gems. pybind11::str enabledGemsFilename; auto result = ExecuteWithLockErrorHandling([&] - { - const pybind11::str pyProjectPath = projectPath.toStdString(); - enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")( - pybind11::none(), // project_name - pyProjectPath); // project_path - }); + { + const pybind11::str pyProjectPath = projectPath.toStdString(); + enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")( + pybind11::none(), // project_name + pyProjectPath); // project_path + }); if (!result.IsSuccess()) { return AZ::Failure(result.GetError().c_str()); @@ -532,13 +533,13 @@ namespace O3DE::ProjectManager // Retrieve the actual list of names from the cmake file. QVector gemNames; result = ExecuteWithLockErrorHandling([&] + { + const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename); + for (auto gemName : pyGemNames) { - const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename); - for (auto gemName : pyGemNames) - { - gemNames.push_back(Py_To_String(gemName)); - } - }); + gemNames.push_back(Py_To_String(gemName)); + } + }); if (!result.IsSuccess()) { return AZ::Failure(result.GetError().c_str()); @@ -552,13 +553,13 @@ namespace O3DE::ProjectManager bool registrationResult = false; bool result = ExecuteWithLock( [&] - { - pybind11::str projectPath = path.toStdString(); - auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath); + { + pybind11::str projectPath = path.toStdString(); + auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath); - // Returns an exit code so boolify it then invert result - registrationResult = !pythonRegistrationResult.cast(); - }); + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); return result && registrationResult; } @@ -568,30 +569,30 @@ namespace O3DE::ProjectManager bool registrationResult = false; bool result = ExecuteWithLock( [&] - { - pybind11::str projectPath = path.toStdString(); - auto pythonRegistrationResult = m_register.attr("register")( - pybind11::none(), // engine_path - projectPath, // project_path - pybind11::none(), // gem_path - pybind11::none(), // external_subdir_path - pybind11::none(), // template_path - pybind11::none(), // restricted_path - pybind11::none(), // repo_uri - pybind11::none(), // default_engines_folder - pybind11::none(), // default_projects_folder - pybind11::none(), // default_gems_folder - pybind11::none(), // default_templates_folder - pybind11::none(), // default_restricted_folder - pybind11::none(), // external_subdir_engine_path - pybind11::none(), // external_subdir_project_path - true, // remove - false // force + { + pybind11::str projectPath = path.toStdString(); + auto pythonRegistrationResult = m_register.attr("register")( + pybind11::none(), // engine_path + projectPath, // project_path + pybind11::none(), // gem_path + pybind11::none(), // external_subdir_path + pybind11::none(), // template_path + pybind11::none(), // restricted_path + pybind11::none(), // repo_uri + pybind11::none(), // default_engines_folder + pybind11::none(), // default_projects_folder + pybind11::none(), // default_gems_folder + pybind11::none(), // default_templates_folder + pybind11::none(), // default_restricted_folder + pybind11::none(), // external_subdir_engine_path + pybind11::none(), // external_subdir_project_path + true, // remove + false // force ); - - // Returns an exit code so boolify it then invert result - registrationResult = !pythonRegistrationResult.cast(); - }); + + // Returns an exit code so boolify it then invert result + registrationResult = !pythonRegistrationResult.cast(); + }); return result && registrationResult; } @@ -649,12 +650,12 @@ namespace O3DE::ProjectManager try { // required - gemInfo.m_name = Py_To_String(data["gem_name"]); + gemInfo.m_name = Py_To_String(data["gem_name"]); // optional gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name); - gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); - gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); + gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); + gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); if (data.contains("Tags")) { @@ -685,7 +686,7 @@ namespace O3DE::ProjectManager try { projectInfo.m_projectName = Py_To_String(projectData["project_name"]); - projectInfo.m_displayName = Py_To_String_Optional(projectData,"display_name", projectInfo.m_projectName); + projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName); } catch ([[maybe_unused]] const std::exception& e) { @@ -727,33 +728,33 @@ namespace O3DE::ProjectManager AZ::Outcome PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) { return ExecuteWithLockErrorHandling([&] - { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_enableGemProject.attr("enable_gem_in_project")( - pybind11::none(), // gem name not needed as path is provided - pyGemPath, - pybind11::none(), // project name not needed as path is provided - pyProjectPath + m_enableGemProject.attr("enable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath ); - }); + }); } AZ::Outcome PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) { return ExecuteWithLockErrorHandling([&] - { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_disableGemProject.attr("disable_gem_in_project")( - pybind11::none(), // gem name not needed as path is provided - pyGemPath, - pybind11::none(), // project name not needed as path is provided - pyProjectPath + m_disableGemProject.attr("disable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath ); - }); + }); } bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) @@ -773,8 +774,8 @@ namespace O3DE::ProjectManager { // required templateInfo.m_displayName = Py_To_String(data["display_name"]); - templateInfo.m_name = Py_To_String(data["template_name"]); - templateInfo.m_summary = Py_To_String(data["summary"]); + templateInfo.m_name = Py_To_String(data["template_name"]); + templateInfo.m_summary = Py_To_String(data["summary"]); // optional if (data.contains("canonical_tags")) @@ -806,7 +807,7 @@ namespace O3DE::ProjectManager QVector templates; bool result = ExecuteWithLock([&] { - for (auto path : m_manifest.attr("get_project_templates")()) + for (auto path : m_manifest.attr("get_templates_for_project_creation")()) { templates.push_back(ProjectTemplateInfoFromPath(path)); } diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index edcd44c525..bcb331d9f4 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -302,64 +302,95 @@ def get_project_external_subdirectories(project_path: pathlib.Path) -> list: project_object['external_subdirectories'])) if 'external_subdirectories' in project_object else [] +def get_project_templates(project_path: pathlib.Path) -> list: + project_object = get_project_json_data(project_path=project_path) + return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(), + project_object['templates'])) + + +def get_project_restricted(project_path: pathlib.Path) -> list: + project_object = get_project_json_data(project_path=project_path) + return list(map(lambda rel_path: (pathlib.Path(project_path) / rel_path).as_posix(), + project_object['restricted'])) if 'restricted' in project_object else [] + + # Combined manifest queries def get_all_projects() -> list: - projects_data = set(get_projects()) - projects_data.update(get_engine_projects()) - return list(projects_data) + projects_data = get_projects() + projects_data.extend(get_engine_projects()) + # Remove duplicates from the list + return list(dict.fromkeys(projects_data)) def get_all_gems(project_path: pathlib.Path = None) -> list: - gems_data = set(get_gems()) - gems_data.update(get_engine_gems()) + gems_data = get_gems() + gems_data.extend(get_engine_gems()) if project_path: - gems_data.update(get_project_gems(project_path)) - return list(gems_data) + gems_data.extend(get_project_gems(project_path)) + return list(dict.fromkeys(gems_data)) def get_all_external_subdirectories(project_path: pathlib.Path = None) -> list: - external_subdirectories_data = set(get_external_subdirectories()) - external_subdirectories_data.update(get_engine_external_subdirectories()) + external_subdirectories_data = get_external_subdirectories() + external_subdirectories_data.extend(get_engine_external_subdirectories()) if project_path: - external_subdirectories_data.update(get_project_external_subdirectories(project_path)) - return list(templates_data) + external_subdirectories_data.extend(get_project_external_subdirectories(project_path)) + return list(dict.fromkeys(external_subdirectories_data)) -def get_all_templates() -> list: - templates_data = set(get_templates()) - templates_data.update(get_engine_templates()) - return list(templates_data) +def get_all_templates(project_path: pathlib.Path = None) -> list: + templates_data = get_templates() + templates_data.extend(get_engine_templates()) + if project_path: + templates_data.extend(get_project_templates(project_path)) + return list(dict.fromkeys(templates_data)) def get_all_restricted() -> list: - restricted_data = set(get_restricted()) - restricted_data.update(get_engine_restricted()) - return list(gems_data) + restricted_data = get_restricted() + restricted_data.extend(get_engine_restricted()) + if project_path: + restricted_data.extend(get_project_restricted(project_path)) + return list(dict.fromkeys(restricted_data)) # Template functions -def get_project_templates(): # temporary until we have a better way to do this... maybe template_type element +def get_templates_for_project_creation(): project_templates = [] - for template in get_all_templates(): - if 'Project' in template: - project_templates.append(template) + for template_path in get_all_templates(): + template_path = pathlib.Path(template_path) + template_json_path = pathlib.Path(template_path) / 'template.json' + if not validation.valid_o3de_template_json(template_json_path): + continue + + project_json_path = template_path / 'Template' / 'project.json' + if validation.valid_o3de_project_json(project_json_path): + project_templates.append(template_path) return project_templates -def get_gem_templates(): # temporary until we have a better way to do this... maybe template_type element +def get_templates_for_gem_creation(): gem_templates = [] - for template in get_all_templates(): - if 'Gem' in template: - gem_templates.append(template) + for template_path in get_all_templates(): + template_path = pathlib.Path(template_path) + template_json_path = pathlib.Path(template_path) / 'template.json' + if not validation.valid_o3de_template_json(template_json_path): + continue + + gem_json_path = template_path / 'Template' / 'gem.json' + if validation.valid_o3de_gem_json(gem_json_path): + gem_templates.append(template_path) return gem_templates -def get_generic_templates(): # temporary until we have a better way to do this... maybe template_type element - generic_templates = [] - for template in get_all_templates(): - if 'Project' not in template and 'Gem' not in template: - generic_templates.append(template) - return generic_templates +def get_templates_for_generic_creation(): # temporary until we have a better way to do this... maybe template_type element + def filter_project_and_gem_templates_out(template_path, + templates_for_project_creation = get_templates_for_project_creation(), + templates_for_gem_creation = get_templates_for_gem_creation()): + template_path = pathlib.Path(template_path) + return template_path not in templates_for_project_creation and template_path not in templates_for_gem_creation + + return list(filter(filter_project_and_gem_templates_out, get_all_templates())) def get_all_restricted() -> list: @@ -515,7 +546,7 @@ def get_template_json_data(template_name: str = None, return None -def get_restricted_data(restricted_name: str = None, +def get_restricted_json_data(restricted_name: str = None, restricted_path: str or pathlib.Path = None) -> dict or None: if not restricted_name and not restricted_path: logger.error('Must specify either a Restricted name or Restricted Path.') diff --git a/scripts/o3de/o3de/print_registration.py b/scripts/o3de/o3de/print_registration.py index 292f2224bc..e5c7a1afc0 100644 --- a/scripts/o3de/o3de/print_registration.py +++ b/scripts/o3de/o3de/print_registration.py @@ -13,6 +13,7 @@ import argparse import json import hashlib import logging +import pathlib import sys import urllib.parse @@ -21,219 +22,251 @@ from o3de import manifest, validation logger = logging.getLogger() logging.basicConfig() -def print_this_engine(verbose: int) -> None: + +def get_project_path(project_path: pathlib.Path, project_name: str) -> pathlib.Path: + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return None + + if not project_path: + project_path = manifest.get_registered(project_name=project_name) + if not project_path: + logger.error(f'Unable to locate project path from the registered manifest json files:' + f' {str(pathlib.Path("~/.o3de/o3de_manifest.json").expanduser())}, engine.json') + return None + + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return None + + return project_path + + +def print_this_engine(verbose: int) -> int: engine_data = manifest.get_this_engine() print(json.dumps(engine_data, indent=4)) + result = True if verbose > 0: - print_engines_data(engine_data) + result = print_manifest_json_data(engine_data, 'engine.json', 'This Engine', + manifest.get_engine_json_data, 'engine_path') + return 0 if result else 1 def print_engines(verbose: int) -> None: engines_data = manifest.get_engines() print(json.dumps(engines_data, indent=4)) + if verbose > 0: - print_engines_data(engines_data) + return print_manifest_json_data(engines_data, 'engine.json', 'Engines', + manifest.get_engine_json_data, 'engine_path') + return 0 -def print_projects(verbose: int) -> None: +def print_projects(verbose: int) -> int: projects_data = manifest.get_projects() print(json.dumps(projects_data, indent=4)) + if verbose > 0: - print_projects_data(projects_data) + return print_manifest_json_data(projects_data, 'project.json', 'Projects', + manifest.get_project_json_data, 'project_path') + return 0 -def print_gems(verbose: int) -> None: +def print_gems(verbose: int) -> int: gems_data = manifest.get_gems() print(json.dumps(gems_data, indent=4)) + if verbose > 0: - print_gems_data(gems_data) + return print_manifest_json_data(gems_data, 'gem.json', 'Gems', + manifest.get_gem_json_data, 'gem_path') + return 0 -def print_templates(verbose: int) -> None: +def print_templates(verbose: int) -> int: templates_data = manifest.get_templates() print(json.dumps(templates_data, indent=4)) + if verbose > 0: - print_templates_data(templates_data) + return print_manifest_json_data(templates_data, 'template.json', 'Templates', + manifest.get_template_json_data, 'template_path') + return 0 -def print_restricted(verbose: int) -> None: +def print_restricted(verbose: int) -> int: restricted_data = manifest.get_restricted() print(json.dumps(restricted_data, indent=4)) - if verbose > 0: - print_restricted_data(restricted_data) -def print_engine_projects(verbose: int) -> None: + if verbose > 0: + return print_manifest_json_data(restricted_data, 'restricted.json', 'Restricted', + manifest.get_restricted_json_data, 'restricted_path') + return 0 + + +# Engine output methods +def print_engine_projects(verbose: int) -> int: engine_projects_data = manifest.get_engine_projects() print(json.dumps(engine_projects_data, indent=4)) + if verbose > 0: - print_projects_data(engine_projects_data) + return print_manifest_json_data(engine_projects_data, 'project.json', 'Projects', + manifest.get_project_json_data, 'project_path') + return 0 -def print_engine_gems(verbose: int) -> None: +def print_engine_gems(verbose: int) -> int: engine_gems_data = manifest.get_engine_gems() print(json.dumps(engine_gems_data, indent=4)) + if verbose > 0: - print_gems_data(engine_gems_data) + return print_manifest_json_data(engine_gems_data, 'gem.json', 'Gems', + manifest.get_gem_json_data, 'gem_path') + return 0 -def print_engine_templates(verbose: int) -> None: +def print_engine_templates(verbose: int) -> int: engine_templates_data = manifest.get_engine_templates() print(json.dumps(engine_templates_data, indent=4)) + if verbose > 0: - print_templates_data(engine_templates_data) + return print_manifest_json_data(engine_templates_data, 'template.json', 'Templates', + manifest.get_template_json_data, 'template_path') + return 0 -def print_engine_restricted(verbose: int) -> None: +def print_engine_restricted(verbose: int) -> int: engine_restricted_data = manifest.get_engine_restricted() print(json.dumps(engine_restricted_data, indent=4)) + if verbose > 0: - print_restricted_data(engine_restricted_data) + return print_manifest_json_data(engine_restricted_data, 'restricted.json', 'Restricted', + manifest.get_restricted_json_data, 'restricted_path') + return 0 -def print_engine_external_subdirectories(verbose: int) -> None: +def print_engine_external_subdirectories() -> int: external_subdirs_data = manifest.get_engine_external_subdirectories() print(json.dumps(external_subdirs_data, indent=4)) + return 0 -def print_all_projects(verbose: int) -> None: +# Project output methods +def print_project_gems(verbose: int, project_path: pathlib.Path, project_name: str) -> int: + project_path = get_project_path(project_path, project_name) + if not project_path: + return 1 + + project_gems_data = manifest.get_project_gems(project_path) + print(json.dumps(project_gems_data, indent=4)) + + if verbose > 0: + return print_manifest_json_data(project_gems_data, 'gem.json', 'Gems', + manifest.get_gem_json_data, 'gem_path') + return 0 + + +def print_project_external_subdirectories(project_path: pathlib.Path, project_name: str) -> int: + project_path = get_project_path(project_path, project_name) + if not project_path: + return 1 + + external_subdirs_data = manifest.get_project_external_subdirectories(project_path) + print(json.dumps(external_subdirs_data, indent=4)) + return 0 + + +def print_project_templates(verbose: int, project_path: pathlib.Path, project_name: str) -> int: + project_path = get_project_path(project_path, project_name) + if not project_path: + return 1 + + project_templates_data = manifest.get_project_templates(project_path) + print(json.dumps(project_templates_data, indent=4)) + if verbose > 0: + return print_manifest_json_data(project_templates_data, 'template.json', 'Templates', + manifest.get_template_json_data, 'template_path') + return 0 + + +def print_project_restricted(verbose: int, project_path: pathlib.Path, project_name: str) -> int: + project_path = get_project_path(project_path, project_name) + if not project_path: + return 1 + + project_restricted_data = manifest.get_project_restricted(project_path) + print(json.dumps(project_restricted_data, indent=4)) + if verbose > 0: + return print_manifest_json_data(project_restricted_data, 'restricted.json', 'Restricted', + manifest.get_restricted_json_data, 'restricted_path') + return 0 + + +def print_all_projects(verbose: int) -> int: all_projects_data = manifest.get_all_projects() print(json.dumps(all_projects_data, indent=4)) + if verbose > 0: - print_projects_data(all_projects_data) + return print_manifest_json_data(all_projects_data, 'project.json', 'Projects', + manifest.get_project_json_data, 'project_path') + return 0 -def print_all_gems(verbose: int) -> None: +def print_all_gems(verbose: int) -> int: all_gems_data = manifest.get_all_gems() print(json.dumps(all_gems_data, indent=4)) + if verbose > 0: - print_gems_data(all_gems_data) + return print_manifest_json_data(all_gems_data, 'gem.json', 'Gems', + manifest.get_gem_json_data, 'gem_path') + return 0 -def print_all_templates(verbose: int) -> None: +def print_all_external_subdirectories() -> int: + all_external_subdirectories_data = manifest.get_all_external_subdirectories() + print(json.dumps(all_external_subdirectories_data, indent=4)) + return 0 + +def print_all_templates(verbose: int) -> int: all_templates_data = manifest.get_all_templates() print(json.dumps(all_templates_data, indent=4)) + if verbose > 0: - print_templates_data(all_templates_data) + return print_manifest_json_data(all_templates_data, 'template.json', 'Templates', + manifest.get_template_json_data, 'template_path') + return 0 -def print_all_restricted(verbose: int) -> None: +def print_all_restricted(verbose: int) -> int: all_restricted_data = manifest.get_all_restricted() print(json.dumps(all_restricted_data, indent=4)) + if verbose > 0: - print_restricted_data(all_restricted_data) + return print_manifest_json_data(all_restricted_data, 'restricted.json', 'Restricted', + manifest.get_restricted_json_data, 'restricted_path') + return 0 -def print_engines_data(engines_data: dict) -> None: +def print_manifest_json_data(uri_json_data: dict, json_filename: str, + print_prefix: str, get_json_func: callable, get_json_data_kw: str) -> int: print('\n') - print("Engines================================================") - for engine_object in engines_data: + print(f"{print_prefix}================================================") + for manifest_uri in uri_json_data: # if it's not local it should be in the cache - engine_uri = engine_object['path'] - parsed_uri = urllib.parse.urlparse(engine_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(engine_uri.encode()) + parsed_uri = urllib.parse.urlparse(manifest_uri) + if parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: + repo_sha256 = hashlib.sha256(manifest_uri.encode()) cache_folder = manifest.get_o3de_cache_folder() - engine = cache_folder / str(repo_sha256.hexdigest() + '.json') - print(f'{engine_uri}/engine.json cached as:') + manifest_json_path = cache_folder / str(repo_sha256.hexdigest() + '.json') else: - engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json' + manifest_json_path = pathlib.Path(manifest_uri).resolve() / json_filename - with engine_json.open('r') as f: - try: - engine_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warn(f'{engine_json} failed to load: {str(e)}') - else: - print(engine_json) - print(json.dumps(engine_json_data, indent=4)) - print('\n') + json_data = get_json_func(**{get_json_data_kwargs: manifest_json_path}) + if json_data: + print(manifest_json_path) + print(json.dumps(json_data, indent=4) + '\n') + return 0 -def print_projects_data(projects_data: dict) -> None: - print('\n') - print("Projects================================================") - for project_uri in projects_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(project_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(project_uri.encode()) - cache_folder = manifest.get_o3de_cache_folder() - project_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - project_json = pathlib.Path(project_uri).resolve() / 'project.json' - - with project_json.open('r') as f: - try: - project_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warn(f'{project_json} failed to load: {str(e)}') - else: - print(project_json) - print(json.dumps(project_json_data, indent=4)) - print('\n') - - -def print_gems_data(gems_data: dict) -> None: - print('\n') - print("Gems================================================") - for gem_uri in gems_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(gem_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(gem_uri.encode()) - cache_folder = manifest.get_o3de_cache_folder() - gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json' - - with gem_json.open('r') as f: - try: - gem_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warn(f'{gem_json} failed to load: {str(e)}') - else: - print(gem_json) - print(json.dumps(gem_json_data, indent=4)) - print('\n') - - -def print_templates_data(templates_data: dict) -> None: - print('\n') - print("Templates================================================") - for template_uri in templates_data: - # if it's not local it should be in the cache - parsed_uri = urllib.parse.urlparse(template_uri) - if parsed_uri.scheme == 'http' or \ - parsed_uri.scheme == 'https' or \ - parsed_uri.scheme == 'ftp' or \ - parsed_uri.scheme == 'ftps': - repo_sha256 = hashlib.sha256(template_uri.encode()) - cache_folder = manifest.get_o3de_cache_folder() - template_json = cache_folder / str(repo_sha256.hexdigest() + '.json') - else: - template_json = pathlib.Path(template_uri).resolve() / 'template.json' - - with template_json.open('r') as f: - try: - template_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warn(f'{template_json} failed to load: {str(e)}') - else: - print(template_json) - print(json.dumps(template_json_data, indent=4)) - print('\n') - - -def print_repos_data(repos_data: dict) -> None: +def print_repos_data(repos_data: dict) -> int: print('\n') print("Repos================================================") cache_folder = manifest.get_o3de_cache_folder() @@ -251,29 +284,16 @@ def print_repos_data(repos_data: dict) -> None: print(cache_file) print(json.dumps(repo_json_data, indent=4)) print('\n') - - -def print_restricted_data(restricted_data: dict) -> None: - print('\n') - print("Restricted================================================") - for restricted_path in restricted_data: - restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json' - with restricted_json.open('r') as f: - try: - restricted_json_data = json.load(f) - except json.JSONDecodeError as e: - logger.warn(f'{restricted_json} failed to load: {str(e)}') - else: - print(restricted_json) - print(json.dumps(restricted_json_data, indent=4)) - print('\n') + return 0 def register_show_repos(verbose: int) -> None: - repos_data = get_repos() + repos_data = manifest.get_repos() print(json.dumps(repos_data, indent=4)) + if verbose > 0: - print_repos_data(repos_data) + return print_repos_data(repos_data) == 0 + return 0 def register_show(verbose: int) -> None: @@ -281,13 +301,15 @@ def register_show(verbose: int) -> None: print(f"{manifest.get_o3de_manifest()}:") print(json.dumps(json_data, indent=4)) + result = True if verbose > 0: - print_engines_data(manifest.get_engines()) - print_projects_data(manifest.get_all_projects()) - print_gems_data(manifest.get_gems()) - print_templates_data(manifest.get_all_templates()) - print_restricted_data(manifest.get_all_restricted()) - print_repos_data(manifest.get_repos()) + result = print_manifest_json_data(manifest.get_engines()) == 0 and result + result = print_manifest_json_data(manifest.get_all_projects()) == 0 and result + result = print_manifest_json_data(manifest.get_gems()) == 0 and result + result = print_manifest_json_data(manifest.get_all_templates()) == 0 and result + result = print_manifest_json_data(manifest.get_all_restricted()) == 0 and result + result = print_repos_data(manifest.get_repos()) == 0 and result + return 0 if result else 1 def _run_register_show(args: argparse) -> int: @@ -295,75 +317,53 @@ def _run_register_show(args: argparse) -> int: manifest.override_home_folder = args.override_home_folder if args.this_engine: - print_this_engine(args.verbose) - return 0 - + return print_this_engine(args.verbose) elif args.engines: - print_engines(args.verbose) - return 0 + return print_engines(args.verbose) elif args.projects: - print_projects(args.verbose) - return 0 + return print_projects(args.verbose) elif args.gems: - print_gems(args.verbose) - return 0 + return print_gems(args.verbose) elif args.templates: - print_templates(args.verbose) - return 0 + return print_templates(args.verbose) elif args.repos: - register_show_repos(args.verbose) - return 0 + return register_show_repos(args.verbose) elif args.restricted: - print_restricted(args.verbose) - return 0 + return print_restricted(args.verbose) elif args.engine_projects: - print_engine_projects(args.verbose) - return 0 + return print_engine_projects(args.verbose) elif args.engine_gems: - print_engine_gems(args.verbose) - return 0 - elif args.engine_templates: - print_engine_templates(args.verbose) - return 0 - elif args.engine_restricted: - print_engine_restricted(args.verbose) - return 0 + return print_engine_gems(args.verbose) elif args.engine_external_subdirectories: - print_engine_external_subdirectories(args.verbose) - return 0 + return print_engine_external_subdirectories() + elif args.engine_templates: + return print_engine_templates(args.verbose) + elif args.engine_restricted: + return print_engine_restricted(args.verbose) + + elif args.project_gems: + return print_project_gems(args.verbose, args.project_path, args.project_name) + elif args.project_external_subdirectories: + return print_project_external_subdirectories(args.project_path, args.project_name) + elif args.project_templates: + return print_project_templates(args.verbose, args.project_path, args.project_name) + elif args.project_restricted: + return print_project_restricted(args.verbose, args.project_path, args.project_name) elif args.all_projects: - print_all_projects(args.verbose) - return 0 + return print_all_projects(args.verbose) elif args.all_gems: - print_all_gems(args.verbose) - return 0 + return print_all_gems(args.verbose) + elif args.all_external_subdirectories: + return print_all_external_subdirectories() elif args.all_templates: - print_all_templates(args.verbose) - return 0 + return print_all_templates(args.verbose) elif args.all_restricted: - print_all_restricted(args.verbose) - return 0 + return print_all_restricted(args.verbose) - elif args.downloadables: - print_downloadables(args.verbose) - return 0 - if args.downloadable_engines: - print_downloadable_engines(args.verbose) - return 0 - elif args.downloadable_projects: - print_downloadable_projects(args.verbose) - return 0 - elif args.downloadable_gems: - print_downloadable_gems(args.verbose) - return 0 - elif args.downloadable_templates: - print_downloadable_templates(args.verbose) - return 0 else: - register_show(args.verbose) - return 0 + return register_show(args.verbose) def add_parser_args(parser): @@ -376,78 +376,84 @@ def add_parser_args(parser): group = parser.add_mutually_exclusive_group(required=False) group.add_argument('-te', '--this-engine', action='store_true', required=False, default=False, - help='Just the local engines.') + help='Output the current engine path.') group.add_argument('-e', '--engines', action='store_true', required=False, default=False, - help='Just the local engines.') + help='Output the engines registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-p', '--projects', action='store_true', required=False, default=False, - help='Just the local projects.') + help='Output the projects registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-g', '--gems', action='store_true', required=False, default=False, - help='Just the local gems.') + help='Output the gems registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-t', '--templates', action='store_true', required=False, default=False, - help='Just the local templates.') + help='Output the templates registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-r', '--repos', action='store_true', required=False, default=False, - help='Just the local repos. Ignores repos.') + help='Output the repos registered in the global ~/.o3de/o3de_manifest.json. Ignores repos.') group.add_argument('-rs', '--restricted', action='store_true', required=False, default=False, - help='The local restricted folders.') + help='Output the restricted directories registered in the global ~/.o3de/o3de_manifest.json.') group.add_argument('-ep', '--engine-projects', action='store_true', required=False, default=False, - help='Just the local projects. Ignores repos.') + help='Output the projects registered in the current engine engine.json. Ignores repos.') group.add_argument('-eg', '--engine-gems', action='store_true', required=False, default=False, - help='Just the local gems. Ignores repos') + help='Output the gems registered in the current engine engine.json. Ignores repos') group.add_argument('-et', '--engine-templates', action='store_true', required=False, default=False, - help='Just the local templates. Ignores repos.') + help='Output the templates registered in the current engine engine.json. Ignores repos.') group.add_argument('-ers', '--engine-restricted', action='store_true', required=False, default=False, - help='The restricted folders.') - group.add_argument('-x', '--engine-external-subdirectories', action='store_true', required=False, + help='Output the restricted directories registered in the current engine engine.json.') + group.add_argument('-ees', '--engine-external-subdirectories', action='store_true', required=False, default=False, - help='The external subdirectories.') + help='Output the external subdirectories registered in the current engine engine.json.') + + group.add_argument('-pg', '--project-gems', action='store_true', + default=False, + help='Returns the gems registered with the project.json.') + group.add_argument('-pt', '--project-templates', action='store_true', + default=False, + help='Returns the templates registered with the project.json.') + group.add_argument('-prs', '--project-restricted', action='store_true', + default=False, + help='Returns the restricted directories registered with the project.json.') + group.add_argument('-pes', '--project-external-subdirectories', action='store_true', + default=False, + help='Returns the external subdirectories register with the project.json.') group.add_argument('-ap', '--all-projects', action='store_true', required=False, default=False, - help='Just the local projects. Ignores repos.') + help='Output all projects registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') group.add_argument('-ag', '--all-gems', action='store_true', required=False, default=False, - help='Just the local gems. Ignores repos') + help='Output all gems registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos') group.add_argument('-at', '--all-templates', action='store_true', required=False, default=False, - help='Just the local templates. Ignores repos.') - group.add_argument('-ars', '--all-restricted', action='store_true', required=False, + help='Output all templates registered in the ~/.o3de/o3de_manifest.json and the current engine.json. Ignores repos.') + group.add_argument('-ares', '--all-restricted', action='store_true', required=False, default=False, - help='The restricted folders.') - - group.add_argument('-d', '--downloadables', action='store_true', required=False, + help='Output all restricted directory registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') + group.add_argument('-aes', '--all-external-subdirectories', action='store_true', default=False, - help='Combine all repos into a single list of resources.') - group.add_argument('-de', '--downloadable-engines', action='store_true', required=False, - default=False, - help='Combine all repos engines into a single list of resources.') - group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False, - default=False, - help='Combine all repos projects into a single list of resources.') - group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False, - default=False, - help='Combine all repos gems into a single list of resources.') - group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False, - default=False, - help='Combine all repos templates into a single list of resources.') + help='Output all external subdirectories registered in the ~/.o3de/o3de_manifest.json and the current engine.json.') parser.add_argument('-v', '--verbose', action='count', required=False, - default=0, - help='How verbose do you want the output to be.') + default=0, + help='How verbose do you want the output to be.') + + project_group = parser.add_mutually_exclusive_group(required=False) + project_group.add_argument('-pp', '--project-path', type=pathlib.Path, + help='The path to a project.') + project_group.add_argument('-pn', '--project-name', type=str, + help='The name of a project.') parser.add_argument('-ohf', '--override-home-folder', type=str, required=False, - help='By default the home folder is the user folder, override it to this folder.') + help='By default the home folder is the user folder, override it to this folder.') parser.set_defaults(func=_run_register_show) diff --git a/scripts/o3de/tests/CMakeLists.txt b/scripts/o3de/tests/CMakeLists.txt index 0526c7740d..82b994e387 100644 --- a/scripts/o3de/tests/CMakeLists.txt +++ b/scripts/o3de/tests/CMakeLists.txt @@ -34,3 +34,10 @@ ly_add_pytest( TEST_SUITE smoke EXCLUDE_TEST_RUN_TARGET_FROM_IDE ) + +ly_add_pytest( + NAME o3de_manifest + PATH ${CMAKE_CURRENT_LIST_DIR}/unit_test_manifest.py + TEST_SUITE smoke + EXCLUDE_TEST_RUN_TARGET_FROM_IDE +) diff --git a/scripts/o3de/tests/unit_test_manifest.py b/scripts/o3de/tests/unit_test_manifest.py new file mode 100644 index 0000000000..98430c227b --- /dev/null +++ b/scripts/o3de/tests/unit_test_manifest.py @@ -0,0 +1,111 @@ +# +# 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 logging +import pytest +import pathlib +from unittest.mock import patch + +from o3de import manifest + + +@pytest.mark.parametrize("valid_project_json_paths, valid_gem_json_paths", [ + pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject/Template/project.json')], + [pathlib.Path('D:/o3de/Templates/DefaultGem/Template/gem.json')]) +]) +class TestGetTemplatesForCreation: + @staticmethod + def get_templates() -> list: + return [] + + @staticmethod + def get_project_templates() -> list: + return [] + + @staticmethod + def get_engine_templates() -> list: + return [pathlib.Path('D:/o3de/Templates/DefaultProject'), pathlib.Path('D:/o3de/Templates/DefaultGem')] + + + @pytest.mark.parametrize("expected_template_paths", [ + pytest.param([]) + ] + ) + def test_get_templates_for_generic_creation(self, valid_project_json_paths, valid_gem_json_paths, + expected_template_paths): + def validate_project_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_project_json_paths + + def validate_gem_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_gem_json_paths + + with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \ + patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates)\ + as get_project_templates_patch, \ + patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates)\ + as get_engine_templates_patch, \ + patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json,\ + patch('o3de.validation.valid_o3de_project_json', side_effect=validate_project_json) as validate_project_json,\ + patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json: + templates = manifest.get_templates_for_generic_creation() + assert templates == expected_template_paths + + + @pytest.mark.parametrize("expected_template_paths", [ + pytest.param([pathlib.Path('D:/o3de/Templates/DefaultProject')]) + ] + ) + def test_get_templates_for_gem_creation(self, valid_project_json_paths, valid_gem_json_paths, + expected_template_paths): + def validate_project_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_project_json_paths + + def validate_gem_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_gem_json_paths + + with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \ + patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \ + as get_project_templates_patch, \ + patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \ + as get_engine_templates_patch, \ + patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \ + patch('o3de.validation.valid_o3de_project_json', + side_effect=validate_project_json) as validate_project_json, \ + patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json: + templates = manifest.get_templates_for_project_creation() + assert templates == expected_template_paths + + + @pytest.mark.parametrize("expected_template_paths", [ + pytest.param([pathlib.Path('D:/o3de/Templates/DefaultGem')]) + ] + ) + def test_get_templates_for_project_creation(self, valid_project_json_paths, valid_gem_json_paths, + expected_template_paths): + def validate_project_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_project_json_paths + + def validate_gem_json(template_path) -> bool: + return pathlib.Path(template_path) in valid_gem_json_paths + + with patch('o3de.manifest.get_templates', side_effect=self.get_templates) as get_templates_patch, \ + patch('o3de.manifest.get_project_templates', side_effect=self.get_project_templates) \ + as get_project_templates_patch, \ + patch('o3de.manifest.get_engine_templates', side_effect=self.get_engine_templates) \ + as get_engine_templates_patch, \ + patch('o3de.validation.valid_o3de_template_json', return_value=True) as validate_template_json, \ + patch('o3de.validation.valid_o3de_project_json', + side_effect=validate_project_json) as validate_project_json, \ + patch('o3de.validation.valid_o3de_gem_json', side_effect=validate_gem_json) as validate_gem_json: + templates = manifest.get_templates_for_gem_creation() + assert templates == expected_template_paths \ No newline at end of file diff --git a/scripts/o3de/tests/unit_test_utils.py b/scripts/o3de/tests/unit_test_utils.py index e7d59b507b..4fe22b1f72 100755 --- a/scripts/o3de/tests/unit_test_utils.py +++ b/scripts/o3de/tests/unit_test_utils.py @@ -11,7 +11,7 @@ import pytest -from . import utils +from o3de import utils @pytest.mark.parametrize( "value, expected_result", [ From deb3c5e74a9cfa1725c77711bc584952b4231a79 Mon Sep 17 00:00:00 2001 From: cgalvan Date: Thu, 3 Jun 2021 18:20:59 -0500 Subject: [PATCH 42/43] [LYN-2446] Implemented support for duplicating instances. (#1097) * [LYN-2446] Implemented support for duplicating instances. * [LYN-2446] Addressed PR feedback. * [LYN-2446] Addressed additional PR feedback. --- .../Prefab/Instance/Instance.h | 4 +- .../Instance/InstanceUpdateExecutor.cpp | 24 ++ .../Prefab/PrefabPublicHandler.cpp | 290 +++++++++++++----- .../Prefab/PrefabPublicHandler.h | 27 ++ 4 files changed, 259 insertions(+), 86 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 4a69ade6d2..d14203e2e5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -174,6 +174,8 @@ namespace AzToolsFramework static EntityAlias GenerateEntityAlias(); AliasPath GetAbsoluteInstanceAliasPath() const; + static InstanceAlias GenerateInstanceAlias(); + protected: /** * Gets the entities owned by this instance @@ -190,8 +192,6 @@ namespace AzToolsFramework bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias); AZStd::unique_ptr DetachEntity(const EntityAlias& entityAlias); - static InstanceAlias GenerateInstanceAlias(); - // Provide access to private data members in the serializer friend class JsonInstanceSerializer; friend class InstanceEntityIdMapper; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp index e0d29f2ab1..6194adf784 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp @@ -152,6 +152,30 @@ namespace AzToolsFramework Instance::EntityList newEntities; if (PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, newEntities, currentTemplate.GetPrefabDom())) { + // If a link was created for a nested instance before the changes were propagated, + // then we associate it correctly here + instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr& nestedInstance) { + if (nestedInstance->GetLinkId() != InvalidLinkId) + { + return; + } + + for (auto linkId : currentTemplate.GetLinks()) + { + LinkReference nestedLink = m_prefabSystemComponentInterface->FindLink(linkId); + if (!nestedLink.has_value()) + { + continue; + } + + if (nestedLink->get().GetInstanceName() == nestedInstance->GetInstanceAlias()) + { + nestedInstance->SetLinkId(linkId); + break; + } + } + }); + AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 27c812ff9d..4656dcf48f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -899,16 +899,29 @@ namespace AzToolsFramework if (!EntitiesBelongToSameInstance(entityIds)) { - return AZ::Failure(AZStd::string("Cannot duplicate multiple " - "entities belonging to different instances with one operation.")); + return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation." + "Change your selection to contain entities in the same instance.")); } // We've already verified the entities are all owned by the same instance, // so we can just retrieve our instance from the first entity in the list. - InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]); - AZ_Assert( - commonEntityOwningInstance.has_value(), - "Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided"); + AZ::EntityId firstEntityIdToDuplicate = entityIds[0]; + InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate); + if (!commonOwningInstance.has_value()) + { + return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided.")); + } + + // If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you + // cannot duplicate an instance from itself. + if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDuplicate) + { + commonOwningInstance = commonOwningInstance->get().GetParentInstance(); + } + if (!commonOwningInstance.has_value()) + { + return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided.")); + } // This will cull out any entities that have ancestors in the list, since we will end up duplicating // the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances @@ -921,105 +934,63 @@ namespace AzToolsFramework { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities"); - // Take a snapshot of the instance DOM before we manipulate it - Prefab::PrefabDom instanceDomBefore; - m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get()); - AZStd::vector entities; AZStd::vector instances; - // Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting. EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet); - bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances); + bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); if (!success) { return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication")); } - // Make a copy of our before instance DOM where we will add our duplicated entities - Prefab::PrefabDom instanceDomAfter; + // Take a snapshot of the instance DOM before we manipulate it + PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get()); + + // Make a copy of our before instance DOM where we will add our duplicated entities and/or instances + PrefabDom instanceDomAfter; instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator()); - AZStd::unordered_map oldAliasToNewAliasMap; - AZStd::unordered_map aliasToEntityDomMap; + EntityIdList duplicatedEntityAndInstanceIds; - for (AZ::Entity* entity : entities) - { - EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId()); - AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM"); - EntityAlias oldAlias = oldAliasRef.value(); + // Duplicate any nested entities and instances as requested + AZStd::unordered_map newInstanceAliasToOldInstanceMap; + DuplicateNestedEntitiesInInstance(commonOwningInstance->get(), + entities, instanceDomAfter, duplicatedEntityAndInstanceIds); + DuplicateNestedInstancesInInstance(commonOwningInstance->get(), + instances, instanceDomAfter, duplicatedEntityAndInstanceIds, + newInstanceAliasToOldInstanceMap); - // Give this the outer allocator so that the memory reference will be valid when - // it gets used for AddMember - Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator()); - m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity); - - // Keep track of the old alias <-> new alias mapping for this duplicated entity - // so we can fixup references later - EntityAlias newEntityAlias = Instance::GenerateEntityAlias(); - oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias)); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - entityDomBefore.Accept(writer); - - // Store our duplicated Entity DOM with its new alias as a string - // so that we can fixup entity alias references before adding it - // to the Entities member of our instance DOM - QString entityDomString(buffer.GetString()); - aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString)); - } - - auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName); - AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member."); - - // Now that all the duplicated Entity DOMs have been created, we need to iterate - // through them and replace any previous EntityAlias references with the new ones. - // These are more than just parent entity references for nested entities, this will - // also cover any EntityId references that were made in the components between them. - for (auto aliasEntityPair : aliasToEntityDomMap) - { - EntityAlias newEntityAlias = aliasEntityPair.first; - QString newEntityDomString = aliasEntityPair.second; - - // Replace all of the old alias references with the new ones - // We bookend the aliases with \" and also with a / as an extra precaution to prevent - // inadvertently replacing a matching string vs. where an actual EntityId is expected - // This will cover both cases where an alias could be used in a normal entity vs. an instance - for (auto aliasMapIter : oldAliasToNewAliasMap) - { - ReplaceOldAliases(newEntityDomString, aliasMapIter.first, aliasMapIter.second); - } - - // Create the new Entity DOM from parsing the JSON string - Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator()); - entityDomAfter.Parse(newEntityDomString.toUtf8().constData()); - - // Add the new Entity DOM to the Entities member of the instance - rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator()); - entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator()); - } - - PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication"); + PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication"); command->SetParent(undoBatch.GetUndoBatch()); - command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId()); - command->RunRedo(); + command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId()); + command->Redo(); - EntityIdList duplicatedEntityIds; - for (auto aliasMapIter : oldAliasToNewAliasMap) + // Create links for our duplicated instances (if any were duplicated) + for (auto [newInstanceAlias, oldInstance] : newInstanceAliasToOldInstanceMap) { - EntityAlias newEntityAlias = aliasMapIter.second; + LinkId oldLinkId = oldInstance->GetLinkId(); + auto linkRef = m_prefabSystemComponentInterface->FindLink(oldLinkId); + AZ_Assert( + linkRef.has_value(), "Unable to find link with id '%llu' during instance duplication.", + oldLinkId); - AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath(); - absoluteEntityPath.Append(newEntityAlias); + PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches(); + AZ_Assert( + linkPatches.has_value(), "Link with id '%llu' is missing patches.", + oldLinkId); - AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath); - duplicatedEntityIds.push_back(newEntityId); + PrefabDom linkPatchesCopy; + linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); + + m_prefabSystemComponentInterface->CreateLink( + commonOwningInstance->get().GetTemplateId(), oldInstance->GetTemplateId(), newInstanceAlias, linkPatchesCopy); } - // Select the duplicated entities - auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities"); + // Select the duplicated entities/instances + auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances"); selectionUndo->SetParent(undoBatch.GetUndoBatch()); ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo); } @@ -1508,8 +1479,159 @@ namespace AzToolsFramework return true; } + void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance, + const AZStd::vector& entities, PrefabDom& domToAddDuplicatedEntitiesUnder, + EntityIdList& duplicatedEntityIds) + { + if (entities.empty()) + { + return; + } + + AZStd::unordered_map oldAliasToNewAliasMap; + AZStd::unordered_map aliasToEntityDomMap; + + for (AZ::Entity* entity : entities) + { + EntityAliasOptionalReference oldAliasRef = commonOwningInstance.GetEntityAlias(entity->GetId()); + AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM"); + EntityAlias oldAlias = oldAliasRef.value(); + + // Give this the outer allocator so that the memory reference will be valid when + // it gets used for AddMember + PrefabDom entityDomBefore(&domToAddDuplicatedEntitiesUnder.GetAllocator()); + m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity); + + // Keep track of the old alias <-> new alias mapping for this duplicated entity + // so we can fixup references later + EntityAlias newEntityAlias = Instance::GenerateEntityAlias(); + oldAliasToNewAliasMap.emplace(oldAlias, newEntityAlias); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + entityDomBefore.Accept(writer); + + // Store our duplicated Entity DOM with its new alias as a string + // so that we can fixup entity alias references before adding it + // to the Entities member of our instance DOM + QString entityDomString(buffer.GetString()); + aliasToEntityDomMap.emplace(newEntityAlias, entityDomString); + } + + auto entitiesIter = domToAddDuplicatedEntitiesUnder.FindMember(PrefabDomUtils::EntitiesName); + AZ_Assert(entitiesIter != domToAddDuplicatedEntitiesUnder.MemberEnd(), "Instance DOM missing the Entities member."); + + // Now that all the duplicated Entity DOMs have been created, we need to iterate + // through them and replace any previous EntityAlias references with the new ones. + // These are more than just parent entity references for nested entities, this will + // also cover any EntityId references that were made in the components between them. + for (auto [newEntityAlias, newEntityDomString] : aliasToEntityDomMap) + { + // Replace all of the old alias references with the new ones + for (auto [oldAlias, newAlias] : oldAliasToNewAliasMap) + { + ReplaceOldAliases(newEntityDomString, oldAlias, newAlias); + } + + // Create the new Entity DOM from parsing the JSON string + PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator()); + entityDomAfter.Parse(newEntityDomString.toUtf8().constData()); + + // Add the new Entity DOM to the Entities member of the instance + rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), domToAddDuplicatedEntitiesUnder.GetAllocator()); + entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator()); + } + + for (auto aliasMapIter : oldAliasToNewAliasMap) + { + EntityAlias newEntityAlias = aliasMapIter.second; + + AliasPath absoluteEntityPath = commonOwningInstance.GetAbsoluteInstanceAliasPath(); + absoluteEntityPath.Append(newEntityAlias); + + AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath); + duplicatedEntityIds.push_back(newEntityId); + } + } + + void PrefabPublicHandler::DuplicateNestedInstancesInInstance(Instance& commonOwningInstance, + const AZStd::vector& instances, PrefabDom& domToAddDuplicatedInstancesUnder, + EntityIdList& duplicatedEntityIds, AZStd::unordered_map& newInstanceAliasToOldInstanceMap) + { + if (instances.empty()) + { + return; + } + + AZStd::unordered_map oldInstanceAliasToNewInstanceAliasMap; + AZStd::unordered_map aliasToInstanceDomMap; + + for (auto instance : instances) + { + PrefabDom nestedInstanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(nestedInstanceDomBefore, *instance); + + // Keep track of the old alias <-> new alias mapping for this duplicated instance + // so we can fixup references later + InstanceAlias oldAlias = instance->GetInstanceAlias(); + InstanceAlias newInstanceAlias = Instance::GenerateInstanceAlias(); + oldInstanceAliasToNewInstanceAliasMap.emplace(oldAlias, newInstanceAlias); + + // Keep track of our new instance alias with the Instance it was duplicated from, + // so that after all instances are duplicated, we can go back and create links for them + newInstanceAliasToOldInstanceMap.emplace(newInstanceAlias, instance); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + nestedInstanceDomBefore.Accept(writer); + + // Store our duplicated Instance DOM with its new alias as a string + // so that we can fixup instance alias references before adding it + // to the Instances member of our instance DOM + QString instanceDomString(buffer.GetString()); + aliasToInstanceDomMap.emplace(newInstanceAlias, instanceDomString); + } + + auto instancesIter = domToAddDuplicatedInstancesUnder.FindMember(PrefabDomUtils::InstancesName); + AZ_Assert(instancesIter != domToAddDuplicatedInstancesUnder.MemberEnd(), "Instance DOM missing the Instances member."); + + // Now that all the duplicated Instance DOMs have been created, we need to iterate + // through them and replace any previous InstanceAlias references with the new ones. + for (auto [newInstanceAlias, newInstanceDomString]: aliasToInstanceDomMap) + { + // Replace all of the old alias references with the new ones + for (auto [oldAlias, newAlias] : oldInstanceAliasToNewInstanceAliasMap) + { + ReplaceOldAliases(newInstanceDomString, oldAlias, newAlias); + } + + // Create the new Instance DOM from parsing the JSON string + PrefabDom nestedInstanceDomAfter(&domToAddDuplicatedInstancesUnder.GetAllocator()); + nestedInstanceDomAfter.Parse(newInstanceDomString.toUtf8().constData()); + + // Add the new Instance DOM to the Instances member of the instance + rapidjson::Value aliasName(newInstanceAlias.c_str(), newInstanceAlias.length(), domToAddDuplicatedInstancesUnder.GetAllocator()); + instancesIter->value.AddMember(AZStd::move(aliasName), nestedInstanceDomAfter, domToAddDuplicatedInstancesUnder.GetAllocator()); + } + + for (auto aliasMapIter : oldInstanceAliasToNewInstanceAliasMap) + { + InstanceAlias newInstanceAlias = aliasMapIter.second; + + AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath(); + absoluteInstancePath.Append(newInstanceAlias); + + AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath); + duplicatedEntityIds.push_back(newEntityId); + } + } + void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) { + // Replace all of the old alias references with the new ones + // We bookend the aliases with \" and also with a / as an extra precaution to prevent + // inadvertently replacing a matching string vs. where an actual EntityId is expected + // This will cover both cases where an alias could be used in a normal entity vs. an instance QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 99fe8e5b67..167791d1c1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -73,6 +73,33 @@ namespace AzToolsFramework InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const; bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const; + + /** + * Duplicate a list of entities owned by a common owning instance by directly + * copying/modifying their entries in the instance DOM + * + * \param commonOwningInstance The common owning instance of all the entities being duplicated. + * \param entities The list of Entities that will be duplicated. + * \param domToAddDuplicatedEntitiesUnder The DOM of the common owning instance where the duplicated + * entity DOM values will be added to. + * \param duplicatedEntityIds A list of EntityIds corresponding to the entities that were duplicated. + */ + void DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance, + const AZStd::vector& entities, PrefabDom& domToAddDuplicatedEntitiesUnder, + EntityIdList& duplicatedEntityIds); + /** + * Duplicate a list of instances owned by a common owning instance by directly + * copying/modifying their entries in the instance DOM + * + * \param commonOwningInstance The common owning instance of all the instances being duplicated. + * \param entities The list of Instances that will be duplicated. + * \param domToAddDuplicatedInstancesUnder The DOM of the common owning instance where the duplicated + * instance DOM values will be added to. + * \param duplicatedEntityIds A list of EntityIds corresponding to the instances that were duplicated. + */ + void DuplicateNestedInstancesInInstance(Instance& commonOwningInstance, + const AZStd::vector& instances, PrefabDom& domToAddDuplicatedInstancesUnder, + EntityIdList& duplicatedEntityIds, AZStd::unordered_map& newInstanceAliasToOldInstanceMap); /** * Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch. From 29ac17a090912f483ddb1d6853eea18af8058324 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Thu, 3 Jun 2021 18:05:05 -0700 Subject: [PATCH 43/43] SPEC-7135 Provide a method to re-trigger compiler detection for changes that require it (#1092) * SPEC-7135 Provide a method to re-trigger compiler detection for changes that require it * removing check (is wrong and is not necessary) * Invert existance check * add empty lines at the end * Clean is triggering on each build * clearing if the variable is false * test code to understand what is going on... * yeap, looks good * handling "false" in linux/mac * fix for linux/mac * Fixing typos --- scripts/build/Platform/Linux/build_linux.sh | 3 +- scripts/build/Platform/Linux/clean_linux.sh | 37 ++++++++++++++++- scripts/build/Platform/Mac/build_mac.sh | 1 + scripts/build/Platform/Mac/clean_mac.sh | 37 ++++++++++++++++- .../build/Platform/Windows/build_windows.cmd | 3 +- .../build/Platform/Windows/clean_windows.cmd | 41 ++++++++++++++++++- 6 files changed, 116 insertions(+), 6 deletions(-) diff --git a/scripts/build/Platform/Linux/build_linux.sh b/scripts/build/Platform/Linux/build_linux.sh index d96fd32371..ef1b1dcb96 100755 --- a/scripts/build/Platform/Linux/build_linux.sh +++ b/scripts/build/Platform/Linux/build_linux.sh @@ -14,6 +14,7 @@ set -o errexit # exit on the first failure encountered BASEDIR=$(dirname "$0") source $BASEDIR/env_linux.sh +source $BASEDIR/clean_linux.sh mkdir -p ${OUTPUT_DIRECTORY} SOURCE_DIRECTORY=${PWD} @@ -46,4 +47,4 @@ fi echo [ci_build] cmake --build . --target ${CMAKE_TARGET} --config ${CONFIGURATION} -j $(grep -c processor /proc/cpuinfo) -- ${CMAKE_NATIVE_BUILD_ARGS} cmake --build . --target ${CMAKE_TARGET} --config ${CONFIGURATION} -j $(grep -c processor /proc/cpuinfo) -- ${CMAKE_NATIVE_BUILD_ARGS} -popd \ No newline at end of file +popd diff --git a/scripts/build/Platform/Linux/clean_linux.sh b/scripts/build/Platform/Linux/clean_linux.sh index a21527d319..a314859877 100755 --- a/scripts/build/Platform/Linux/clean_linux.sh +++ b/scripts/build/Platform/Linux/clean_linux.sh @@ -12,6 +12,16 @@ set -o errexit # exit on the first failure encountered +# Jenkins defines environment variables for parameters and passes "false" to variables +# that are not set. Here we clear them if they are false so we can also just define them +# from command line +if [[ "${CLEAN_ASSETS}" == "false" ]]; then + CLEAN_ASSETS= +fi +if [[ "${CLEAN_OUTPUT_DIRECTORY}" == "false" ]]; then + CLEAN_OUTPUT_DIRECTORY= +fi + if [[ -n "$CLEAN_ASSETS" ]]; then echo "[ci_build] CLEAN_ASSETS option set" for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g") @@ -23,10 +33,35 @@ if [[ -n "$CLEAN_ASSETS" ]]; then done fi +# If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc +LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt +if [[ -n "$NODE_LABEL" ]]; then + if [[ -d $OUTPUT_DIRECTORY ]]; then + pushd $OUTPUT_DIRECTORY + if [[ -e ${LAST_CONFIGURE_NODE_LABEL_FILE} ]]; then + LAST_NODE_LABEL=$(<${LAST_CONFIGURE_NODE_LABEL_FILE}) + else + LAST_NODE_LABEL= + fi + # Detect if the node label has changed + if [[ "${LAST_NODE_LABEL}" != "${NODE_LABEL}" ]]; then + echo [ci_build] Last run was done with node label \"${LAST_NODE_LABEL}\", new node label is \"${NODE_LABEL}\", forcing CLEAN_OUTPUT_DIRECTORY + CLEAN_OUTPUT_DIRECTORY=1 + fi + popd + fi +fi + if [[ -n "$CLEAN_OUTPUT_DIRECTORY" ]]; then echo "[ci_build] CLEAN_OUTPUT_DIRECTORY option set" if [[ -d $OUTPUT_DIRECTORY ]]; then echo "[ci_build] Deleting \"${OUTPUT_DIRECTORY}\"" rm -rf ${OUTPUT_DIRECTORY} fi -fi \ No newline at end of file +fi + +mkdir -p ${OUTPUT_DIRECTORY} +# Save the node label +pushd $OUTPUT_DIRECTORY +echo "${NODE_LABEL}" > ${LAST_CONFIGURE_NODE_LABEL_FILE} +popd diff --git a/scripts/build/Platform/Mac/build_mac.sh b/scripts/build/Platform/Mac/build_mac.sh index 473a968d98..4a61f97fe4 100755 --- a/scripts/build/Platform/Mac/build_mac.sh +++ b/scripts/build/Platform/Mac/build_mac.sh @@ -14,6 +14,7 @@ set -o errexit # exit on the first failure encountered BASEDIR=$(dirname "$0") source $BASEDIR/env_mac.sh +source $BASEDIR/clean_mac.sh mkdir -p ${OUTPUT_DIRECTORY} SOURCE_DIRECTORY=${PWD} diff --git a/scripts/build/Platform/Mac/clean_mac.sh b/scripts/build/Platform/Mac/clean_mac.sh index a21527d319..a314859877 100755 --- a/scripts/build/Platform/Mac/clean_mac.sh +++ b/scripts/build/Platform/Mac/clean_mac.sh @@ -12,6 +12,16 @@ set -o errexit # exit on the first failure encountered +# Jenkins defines environment variables for parameters and passes "false" to variables +# that are not set. Here we clear them if they are false so we can also just define them +# from command line +if [[ "${CLEAN_ASSETS}" == "false" ]]; then + CLEAN_ASSETS= +fi +if [[ "${CLEAN_OUTPUT_DIRECTORY}" == "false" ]]; then + CLEAN_OUTPUT_DIRECTORY= +fi + if [[ -n "$CLEAN_ASSETS" ]]; then echo "[ci_build] CLEAN_ASSETS option set" for project in $(echo $CMAKE_LY_PROJECTS | sed "s/;/ /g") @@ -23,10 +33,35 @@ if [[ -n "$CLEAN_ASSETS" ]]; then done fi +# If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc +LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt +if [[ -n "$NODE_LABEL" ]]; then + if [[ -d $OUTPUT_DIRECTORY ]]; then + pushd $OUTPUT_DIRECTORY + if [[ -e ${LAST_CONFIGURE_NODE_LABEL_FILE} ]]; then + LAST_NODE_LABEL=$(<${LAST_CONFIGURE_NODE_LABEL_FILE}) + else + LAST_NODE_LABEL= + fi + # Detect if the node label has changed + if [[ "${LAST_NODE_LABEL}" != "${NODE_LABEL}" ]]; then + echo [ci_build] Last run was done with node label \"${LAST_NODE_LABEL}\", new node label is \"${NODE_LABEL}\", forcing CLEAN_OUTPUT_DIRECTORY + CLEAN_OUTPUT_DIRECTORY=1 + fi + popd + fi +fi + if [[ -n "$CLEAN_OUTPUT_DIRECTORY" ]]; then echo "[ci_build] CLEAN_OUTPUT_DIRECTORY option set" if [[ -d $OUTPUT_DIRECTORY ]]; then echo "[ci_build] Deleting \"${OUTPUT_DIRECTORY}\"" rm -rf ${OUTPUT_DIRECTORY} fi -fi \ No newline at end of file +fi + +mkdir -p ${OUTPUT_DIRECTORY} +# Save the node label +pushd $OUTPUT_DIRECTORY +echo "${NODE_LABEL}" > ${LAST_CONFIGURE_NODE_LABEL_FILE} +popd diff --git a/scripts/build/Platform/Windows/build_windows.cmd b/scripts/build/Platform/Windows/build_windows.cmd index 3e995e1905..474c1720df 100644 --- a/scripts/build/Platform/Windows/build_windows.cmd +++ b/scripts/build/Platform/Windows/build_windows.cmd @@ -13,6 +13,7 @@ REM SETLOCAL EnableDelayedExpansion CALL %~dp0env_windows.cmd +CALL %~dp0clean_windows.cmd IF NOT EXIST "%OUTPUT_DIRECTORY%" ( MKDIR %OUTPUT_DIRECTORY%. @@ -70,4 +71,4 @@ EXIT /b 0 :error POPD -EXIT /b 1 \ No newline at end of file +EXIT /b 1 diff --git a/scripts/build/Platform/Windows/clean_windows.cmd b/scripts/build/Platform/Windows/clean_windows.cmd index 38c1d45c21..60ad445d4d 100644 --- a/scripts/build/Platform/Windows/clean_windows.cmd +++ b/scripts/build/Platform/Windows/clean_windows.cmd @@ -12,6 +12,16 @@ REM SETLOCAL EnableDelayedExpansion +REM Jenkins defines environment variables for parameters and passes "false" to variables +REM that are not set. Here we clear them if they are false so we can also just define them +REM from command line +IF "%CLEAN_ASSETS%"=="false" ( + set CLEAN_ASSETS= +) +IF "%CLEAN_OUTPUT_DIRECTORY%"=="false" ( + set CLEAN_OUTPUT_DIRECTORY= +) + IF DEFINED CLEAN_ASSETS ( ECHO [ci_build] CLEAN_ASSETS option set FOR %%P in (%CMAKE_LY_PROJECTS%) do ( @@ -19,7 +29,26 @@ IF DEFINED CLEAN_ASSETS ( ECHO [ci_build] Deleting "%%P\Cache" DEL /s /q /f %%P\Cache 1>nul ) - ) + ) +) + +REM If the node label changes, we issue a clean output since node changes can change SDK/CMake/toolchains/etc +SET LAST_CONFIGURE_NODE_LABEL_FILE=ci_last_node_label.txt +IF DEFINED NODE_LABEL ( + IF EXIST %OUTPUT_DIRECTORY% ( + PUSHD %OUTPUT_DIRECTORY% + IF EXIST !LAST_CONFIGURE_NODE_LABEL_FILE! ( + FOR /F "delims=" %%x in (%LAST_CONFIGURE_NODE_LABEL_FILE%) DO SET LAST_NODE_LABEL=%%x + ) ELSE ( + SET LAST_NODE_LABEL= + ) + REM Detect if the node label has changed + IF !LAST_NODE_LABEL! NEQ !NODE_LABEL! ( + ECHO [ci_build] Last run was done with node label "!LAST_NODE_LABEL!", new node label is "!NODE_LABEL!", forcing CLEAN_OUTPUT_DIRECTORY + SET CLEAN_OUTPUT_DIRECTORY=1 + ) + POPD + ) ) IF DEFINED CLEAN_OUTPUT_DIRECTORY ( @@ -28,4 +57,12 @@ IF DEFINED CLEAN_OUTPUT_DIRECTORY ( ECHO [ci_build] Deleting "%OUTPUT_DIRECTORY%" DEL /s /q /f %OUTPUT_DIRECTORY% 1>nul ) -) \ No newline at end of file +) + +IF NOT EXIST "%OUTPUT_DIRECTORY%" ( + MKDIR %OUTPUT_DIRECTORY%. +) +REM Save the node label +PUSHD %OUTPUT_DIRECTORY% +ECHO !NODE_LABEL!> !LAST_CONFIGURE_NODE_LABEL_FILE! +POPD