diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h index d55f38f65c..e7d13504cd 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -12,6 +12,7 @@ #pragma once +#include #include #include diff --git a/Gems/AWSGameLift/CMakeLists.txt b/Gems/AWSGameLift/CMakeLists.txt new file mode 100644 index 0000000000..cb6bd1dc1e --- /dev/null +++ b/Gems/AWSGameLift/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# 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. +# + +add_subdirectory(Code) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/CMakeLists.txt b/Gems/AWSGameLift/Code/AWSGameLiftClient/CMakeLists.txt new file mode 100644 index 0000000000..754bcf2436 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/CMakeLists.txt @@ -0,0 +1,89 @@ +# +# 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. +# + +set(awsgameliftclient_compile_definition $,AWSGAMELIFT_RELEASE,AWSGAMELIFT_DEV>) + +ly_add_target( + NAME AWSGameLift.Client.Static STATIC + NAMESPACE Gem + FILES_CMAKE + awsgamelift_client_files.cmake + INCLUDE_DIRECTORIES + PUBLIC + Include + PRIVATE + Source + ../AWSGameLiftCommon/Source + COMPILE_DEFINITIONS + PRIVATE + ${awsgameliftclient_compile_definition} + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzFramework + Gem::AWSCore + 3rdParty::AWSNativeSDK::GameLiftClient +) + +ly_add_target( + NAME AWSGameLift.Clients ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + awsgamelift_client_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + 3rdParty::AWSNativeSDK::GameLiftClient + PUBLIC + Gem::AWSGameLift.Client.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore +) + +# Load the "Gem::AWSGameLift" module in all types of applications. +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSGameLift.Tools NAMESPACE Gem TARGETS Gem::AWSGameLift.Clients) + ly_create_alias(NAME AWSGameLift.Builders NAMESPACE Gem TARGETS Gem::AWSGameLift.Clients) +endif() + +################################################################################ +# Tests +################################################################################ +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME AWSGameLift.Client.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + awsgamelift_client_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Include + Tests + Source + ../AWSGameLiftCommon/Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzFramework + AZ::AzTest + Gem::AWSCore + Gem::AWSGameLift.Client.Static + 3rdParty::AWSNativeSDK::GameLiftClient + AZ::AWSNativeSDKInit + ) + # Add AWSGameLift.Client.Tests to googletest + ly_add_googletest( + NAME Gem::AWSGameLift.Client.Tests + ) +endif() diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h new file mode 100644 index 0000000000..f0ae90ef94 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h @@ -0,0 +1,39 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AWSGameLift +{ + //! AWSGameLiftCreateSessionOnQueueRequest + //! GameLift create session on queue request which corresponds to Amazon GameLift + //! StartGameSessionPlacement + struct AWSGameLiftCreateSessionOnQueueRequest + : public AzFramework::CreateSessionRequest + { + public: + AZ_RTTI(AWSGameLiftCreateSessionOnQueueRequest, "{2B99E594-CE81-4EB0-8888-74EF4242B59F}", AzFramework::CreateSessionRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftCreateSessionOnQueueRequest() = default; + virtual ~AWSGameLiftCreateSessionOnQueueRequest() = default; + + // Name of the queue to use to place the new game session. You can use either the queue name or ARN value. + AZStd::string m_queueName; + + // A unique identifier to assign to the new game session placement. This value is developer-defined. + // The value must be unique across all Regions and cannot be reused unless you are resubmitting a canceled or timed-out placement request. + AZStd::string m_placementId; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h new file mode 100644 index 0000000000..f1bafd83a7 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftCreateSessionRequest.h @@ -0,0 +1,42 @@ +/* + * 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 AWSGameLift +{ + //! AWSGameLiftCreateSessionRequest + //! GameLift create session on fleet request which corresponds to Amazon GameLift + //! CreateGameSessionRequest + struct AWSGameLiftCreateSessionRequest + : public AzFramework::CreateSessionRequest + { + public: + AZ_RTTI(AWSGameLiftCreateSessionRequest, "{69612D5D-F899-4DEB-AD63-4C497ABC5C0D}", AzFramework::CreateSessionRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftCreateSessionRequest() = default; + virtual ~AWSGameLiftCreateSessionRequest() = default; + + // A unique identifier for the alias associated with the fleet to create a game session in. + AZStd::string m_aliasId; + + // A unique identifier for the fleet to create a game session in. + AZStd::string m_fleetId; + + // Custom string that uniquely identifies the new game session request. + // This is useful for ensuring that game session requests with the same idempotency token are processed only once. + AZStd::string m_idempotencyToken; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h new file mode 100644 index 0000000000..7bcbbf692b --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftJoinSessionRequest.h @@ -0,0 +1,33 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +namespace AWSGameLift +{ + //! AWSGameLiftJoinSessionRequest + //! GameLift join session request which corresponds to Amazon GameLift CreatePlayerSessionRequest. + //! Once player session has been created successfully in game session, gamelift client manager will + //! signal Multiplayer Gem to setup networking connection. + struct AWSGameLiftJoinSessionRequest + : public AzFramework::JoinSessionRequest + { + public: + AZ_RTTI(AWSGameLiftJoinSessionRequest, "{6EED6D15-531A-4956-90D0-2EDA31AC9CBA}", AzFramework::JoinSessionRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftJoinSessionRequest() = default; + virtual ~AWSGameLiftJoinSessionRequest() = default; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h new file mode 100644 index 0000000000..5d510a0312 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/AWSGameLiftSearchSessionsRequest.h @@ -0,0 +1,42 @@ +/* + * 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 AWSGameLift +{ + //! AWSGameLiftSearchSessionsRequest + //! GameLift search sessions request which corresponds to Amazon GameLift + //! SearchSessionsRequest + struct AWSGameLiftSearchSessionsRequest + : public AzFramework::SearchSessionsRequest + { + public: + AZ_RTTI(AWSGameLiftSearchSessionsRequest, "{864C91C0-CA53-4585-BF07-066C0DF3E198}", AzFramework::SearchSessionsRequest); + static void Reflect(AZ::ReflectContext* context); + + AWSGameLiftSearchSessionsRequest() = default; + virtual ~AWSGameLiftSearchSessionsRequest() = default; + + // A unique identifier for the alias associated with the fleet to search for active game sessions. + AZStd::string m_aliasId; + + // A unique identifier for the fleet to search for active game sessions. + AZStd::string m_fleetId; + + // A fleet location to search for game sessions. + AZStd::string m_location; + }; +} // namespace AWSGameLift + diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h new file mode 100644 index 0000000000..07ac4a3de0 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Include/Request/IAWSGameLiftRequests.h @@ -0,0 +1,78 @@ +/* + * 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 +#include +#include + +namespace AWSGameLift +{ + //! IAWSGameLiftRequests + //! GameLift Gem interfaces to configure client manager + class IAWSGameLiftRequests + { + public: + AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}"); + + IAWSGameLiftRequests() = default; + virtual ~IAWSGameLiftRequests() = default; + + //! ConfigureGameLiftClient + //! Configure GameLift client to interact with Amazon GameLift service + //! @param region Specifies the AWS region to use + //! @return True if client configuration succeeds, false otherwise + virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0; + + //! CreatePlayerId + //! Create a new, random ID number for every player in every new game session. + //! @param includeBrackets Whether includes brackets in player id + //! @param includeDashes Whether includes dashes in player id + //! @return The player id to use in game session + virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0; + }; + + // IAWSGameLiftRequests EBus wrapper for scripting + class AWSGameLiftRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftRequestBus = AZ::EBus; + + // ISessionAsyncRequests EBus wrapper for scripting + class AWSGameLiftSessionAsyncRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionAsyncRequestBus = AZ::EBus; + + // ISessionRequests EBus wrapper for scripting + class AWSGameLiftSessionRequests + : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + }; + using AWSGameLiftSessionRequestBus = AZ::EBus; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp new file mode 100644 index 0000000000..34c7ab56f9 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.cpp @@ -0,0 +1,360 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace AWSGameLift +{ +#if defined(AWSGAMELIFT_DEV) + AZ_CVAR(AZ::CVarFixedString, cl_gameliftLocalEndpoint, "", nullptr, AZ::ConsoleFunctorFlags::Null, "The local endpoint to test with GameLiftLocal SDK."); +#endif + + AWSGameLiftClientManager::AWSGameLiftClientManager() + { + m_gameliftClient.reset(); + } + + void AWSGameLiftClientManager::ActivateManager() + { + AZ::Interface::Register(this); + AWSGameLiftRequestBus::Handler::BusConnect(); + + AZ::Interface::Register(this); + AWSGameLiftSessionAsyncRequestBus::Handler::BusConnect(); + + AZ::Interface::Register(this); + AWSGameLiftSessionRequestBus::Handler::BusConnect(); + } + + void AWSGameLiftClientManager::DeactivateManager() + { + AWSGameLiftSessionRequestBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); + + AWSGameLiftSessionAsyncRequestBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); + + AWSGameLiftRequestBus::Handler::BusDisconnect(); + AZ::Interface::Unregister(this); + } + + bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region) + { + m_gameliftClient.reset(); + + Aws::Client::ClientConfiguration clientConfig; + // Set up client endpoint or region + AZStd::string localEndpoint = ""; +#if defined(AWSGAMELIFT_DEV) + localEndpoint = static_cast(cl_gameliftLocalEndpoint); +#endif + if (!localEndpoint.empty()) + { + // The attribute needs to override to interact with GameLiftLocal + clientConfig.endpointOverride = localEndpoint.c_str(); + } + else if (!region.empty()) + { + clientConfig.region = region.c_str(); + } + else + { + AZStd::string clientRegion; + AWSCore::AWSResourceMappingRequestBus::BroadcastResult(clientRegion, &AWSCore::AWSResourceMappingRequests::GetDefaultRegion); + if (clientRegion.empty()) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientRegionMissingErrorMessage); + return false; + } + clientConfig.region = clientRegion.c_str(); + } + + // Fetch AWS credential for client + AWSCore::AWSCredentialResult credentialResult; + AWSCore::AWSCredentialRequestBus::BroadcastResult(credentialResult, &AWSCore::AWSCredentialRequests::GetCredentialsProvider); + if (!localEndpoint.empty()) + { + credentialResult.result = std::make_shared(); + } + else if (!credentialResult.result) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientCredentialMissingErrorMessage); + return false; + } + m_gameliftClient = AZStd::make_shared(credentialResult.result, clientConfig); + return true; + } + + AZStd::string AWSGameLiftClientManager::CreatePlayerId(bool includeBrackets, bool includeDashes) + { + return AZ::Uuid::CreateRandom().ToString(includeBrackets, includeDashes); + } + + AZStd::string AWSGameLiftClientManager::CreateSession(const AzFramework::CreateSessionRequest& createSessionRequest) + { + AZStd::string result = ""; + if (CreateSessionActivity::ValidateCreateSessionRequest(createSessionRequest)) + { + const AWSGameLiftCreateSessionRequest& gameliftCreateSessionRequest = + static_cast(createSessionRequest); + result = CreateSessionHelper(gameliftCreateSessionRequest); + } + else if (CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(createSessionRequest)) + { + const AWSGameLiftCreateSessionOnQueueRequest& gameliftCreateSessionOnQueueRequest = + static_cast(createSessionRequest); + result = CreateSessionOnQueueHelper(gameliftCreateSessionOnQueueRequest); + } + else + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftCreateSessionRequestInvalidErrorMessage); + } + + return result; + } + + void AWSGameLiftClientManager::CreateSessionAsync(const AzFramework::CreateSessionRequest& createSessionRequest) + { + if (CreateSessionActivity::ValidateCreateSessionRequest(createSessionRequest)) + { + const AWSGameLiftCreateSessionRequest& gameliftCreateSessionRequest = + static_cast(createSessionRequest); + + AZ::JobContext* jobContext = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext); + AZ::Job* createSessionJob = AZ::CreateJobFunction( + [this, gameliftCreateSessionRequest]() + { + AZStd::string result = CreateSessionHelper(gameliftCreateSessionRequest); + + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnCreateSessionAsyncComplete, result); + }, + true, jobContext); + createSessionJob->Start(); + } + else if (CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(createSessionRequest)) + { + const AWSGameLiftCreateSessionOnQueueRequest& gameliftCreateSessionOnQueueRequest = + static_cast(createSessionRequest); + + AZ::JobContext* jobContext = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext); + AZ::Job* createSessionOnQueueJob = AZ::CreateJobFunction( + [this, gameliftCreateSessionOnQueueRequest]() + { + AZStd::string result = CreateSessionOnQueueHelper(gameliftCreateSessionOnQueueRequest); + + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnCreateSessionAsyncComplete, result); + }, + true, jobContext); + createSessionOnQueueJob->Start(); + } + else + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftCreateSessionRequestInvalidErrorMessage); + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnCreateSessionAsyncComplete, ""); + } + } + + AZStd::string AWSGameLiftClientManager::CreateSessionHelper( + const AWSGameLiftCreateSessionRequest& createSessionRequest) + { + AZStd::shared_ptr gameLiftClient = m_gameliftClient; + AZStd::string result = ""; + if (!gameLiftClient) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage); + } + else + { + result = CreateSessionActivity::CreateSession(*gameLiftClient, createSessionRequest); + } + return result; + } + + AZStd::string AWSGameLiftClientManager::CreateSessionOnQueueHelper( + const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest) + { + AZStd::shared_ptr gameliftClient = m_gameliftClient; + AZStd::string result; + if (!gameliftClient) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage); + } + else + { + result = CreateSessionOnQueueActivity::CreateSessionOnQueue(*gameliftClient, createSessionOnQueueRequest); + } + return result; + } + + bool AWSGameLiftClientManager::JoinSession(const AzFramework::JoinSessionRequest& joinSessionRequest) + { + bool result = false; + if (JoinSessionActivity::ValidateJoinSessionRequest(joinSessionRequest)) + { + const AWSGameLiftJoinSessionRequest& gameliftJoinSessionRequest = + static_cast(joinSessionRequest); + result = JoinSessionHelper(gameliftJoinSessionRequest); + } + + return result; + } + + void AWSGameLiftClientManager::JoinSessionAsync(const AzFramework::JoinSessionRequest& joinSessionRequest) + { + if (!JoinSessionActivity::ValidateJoinSessionRequest(joinSessionRequest)) + { + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnJoinSessionAsyncComplete, false); + return; + } + + const AWSGameLiftJoinSessionRequest& gameliftJoinSessionRequest = + static_cast(joinSessionRequest); + + AZ::JobContext* jobContext = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext); + AZ::Job* joinSessionJob = AZ::CreateJobFunction( + [this, gameliftJoinSessionRequest]() + { + bool result = JoinSessionHelper(gameliftJoinSessionRequest); + + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnJoinSessionAsyncComplete, result); + }, + true, jobContext); + + joinSessionJob->Start(); + } + + bool AWSGameLiftClientManager::JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest) + { + AZStd::shared_ptr gameliftClient = m_gameliftClient; + bool result = false; + if (!gameliftClient) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage); + } + else + { + auto createPlayerSessionOutcome = JoinSessionActivity::CreatePlayerSession(*gameliftClient, joinSessionRequest); + + result = JoinSessionActivity::RequestPlayerJoinSession(createPlayerSessionOutcome); + } + return result; + } + + void AWSGameLiftClientManager::LeaveSession() + { + AWSGameLift::LeaveSessionActivity::LeaveSession(); + } + + void AWSGameLiftClientManager::LeaveSessionAsync() + { + AZ::JobContext* jobContext = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext); + AZ::Job* leaveSessionJob = AZ::CreateJobFunction( + [this]() + { + LeaveSession(); + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnLeaveSessionAsyncComplete); + }, + true, jobContext); + + leaveSessionJob->Start(); + } + + AzFramework::SearchSessionsResponse AWSGameLiftClientManager::SearchSessions( + const AzFramework::SearchSessionsRequest& searchSessionsRequest) const + { + AzFramework::SearchSessionsResponse response; + if (SearchSessionsActivity::ValidateSearchSessionsRequest(searchSessionsRequest)) + { + const AWSGameLiftSearchSessionsRequest& gameliftSearchSessionsRequest = + static_cast(searchSessionsRequest); + response = SearchSessionsHelper(gameliftSearchSessionsRequest); + } + + return response; + } + + void AWSGameLiftClientManager::SearchSessionsAsync(const AzFramework::SearchSessionsRequest& searchSessionsRequest) const + { + if (!SearchSessionsActivity::ValidateSearchSessionsRequest(searchSessionsRequest)) + { + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnSearchSessionsAsyncComplete, AzFramework::SearchSessionsResponse()); + return; + } + + const AWSGameLiftSearchSessionsRequest& gameliftSearchSessionsRequest = + static_cast(searchSessionsRequest); + + AZ::JobContext* jobContext = nullptr; + AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext); + AZ::Job* searchSessionsJob = AZ::CreateJobFunction( + [this, gameliftSearchSessionsRequest]() + { + AzFramework::SearchSessionsResponse response = SearchSessionsHelper(gameliftSearchSessionsRequest); + + AzFramework::SessionAsyncRequestNotificationBus::Broadcast( + &AzFramework::SessionAsyncRequestNotifications::OnSearchSessionsAsyncComplete, response); + }, + true, jobContext); + + searchSessionsJob->Start(); + } + + AzFramework::SearchSessionsResponse AWSGameLiftClientManager::SearchSessionsHelper( + const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) const + { + AZStd::shared_ptr gameliftClient = m_gameliftClient; + + AzFramework::SearchSessionsResponse response; + if (!gameliftClient) + { + AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage); + } + else + { + response = SearchSessionsActivity::SearchSessions(*gameliftClient, searchSessionsRequest); + } + return response; + } + + void AWSGameLiftClientManager::SetGameLiftClient(AZStd::shared_ptr gameliftClient) + { + m_gameliftClient.swap(gameliftClient); + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h new file mode 100644 index 0000000000..ef128b6562 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientManager.h @@ -0,0 +1,122 @@ +/* + * 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 Aws +{ + namespace GameLift + { + class GameLiftClient; + } +} + +namespace AWSGameLift +{ + struct AWSGameLiftCreateSessionRequest; + struct AWSGameLiftCreateSessionOnQueueRequest; + struct AWSGameLiftJoinSessionRequest; + struct AWSGameLiftSearchSessionsRequest; + + // SessionAsyncRequestNotificationBus EBus handler for scripting + class AWSGameLiftSessionAsyncRequestNotificationBusHandler + : public AzFramework::SessionAsyncRequestNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER( + AWSGameLiftSessionAsyncRequestNotificationBusHandler, + "{6E13FC73-53DC-4B6B-AEA7-9038DE4C9635}", + AZ::SystemAllocator, + OnCreateSessionAsyncComplete, + OnSearchSessionsAsyncComplete, + OnJoinSessionAsyncComplete, + OnLeaveSessionAsyncComplete); + + void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) override + { + Call(FN_OnCreateSessionAsyncComplete, createSessionReponse); + } + + void OnSearchSessionsAsyncComplete(const AzFramework::SearchSessionsResponse& searchSessionsResponse) override + { + Call(FN_OnSearchSessionsAsyncComplete, searchSessionsResponse); + } + + void OnJoinSessionAsyncComplete(bool joinSessionsResponse) override + { + Call(FN_OnJoinSessionAsyncComplete, joinSessionsResponse); + } + + void OnLeaveSessionAsyncComplete() override + { + Call(FN_OnLeaveSessionAsyncComplete); + } + }; + + //! AWSGameLiftClientManager + //! GameLift client manager to support game and player session related client requests + class AWSGameLiftClientManager + : public AWSGameLiftRequestBus::Handler + , public AWSGameLiftSessionAsyncRequestBus::Handler + , public AWSGameLiftSessionRequestBus::Handler + { + public: + static constexpr const char AWSGameLiftClientManagerName[] = "AWSGameLiftClientManager"; + static constexpr const char AWSGameLiftClientRegionMissingErrorMessage[] = + "Missing AWS region for GameLift client."; + static constexpr const char AWSGameLiftClientCredentialMissingErrorMessage[] = + "Missing AWS credential for GameLift client."; + static constexpr const char AWSGameLiftClientMissingErrorMessage[] = + "GameLift client is not configured yet."; + + static constexpr const char AWSGameLiftCreateSessionRequestInvalidErrorMessage[] = + "Invalid GameLift CreateSession or CreateSessionOnQueue request."; + + AWSGameLiftClientManager(); + virtual ~AWSGameLiftClientManager() = default; + + virtual void ActivateManager(); + virtual void DeactivateManager(); + + // AWSGameLiftRequestBus interface implementation + bool ConfigureGameLiftClient(const AZStd::string& region) override; + AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) override; + + // AWSGameLiftSessionAsyncRequestBus interface implementation + void CreateSessionAsync(const AzFramework::CreateSessionRequest& createSessionRequest) override; + void JoinSessionAsync(const AzFramework::JoinSessionRequest& joinSessionRequest) override; + void SearchSessionsAsync(const AzFramework::SearchSessionsRequest& searchSessionsRequest) const override; + void LeaveSessionAsync() override; + + // AWSGameLiftSessionRequestBus interface implementation + AZStd::string CreateSession(const AzFramework::CreateSessionRequest& createSessionRequest) override; + bool JoinSession(const AzFramework::JoinSessionRequest& joinSessionRequest) override; + AzFramework::SearchSessionsResponse SearchSessions(const AzFramework::SearchSessionsRequest& searchSessionsRequest) const override; + void LeaveSession() override; + + protected: + // Use for automation tests only to inject mock objects. + void SetGameLiftClient(AZStd::shared_ptr gameliftClient); + + private: + AZStd::string CreateSessionHelper(const AWSGameLiftCreateSessionRequest& createSessionRequest); + AZStd::string CreateSessionOnQueueHelper(const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest); + bool JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest); + AzFramework::SearchSessionsResponse SearchSessionsHelper(const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) const; + + AZStd::shared_ptr m_gameliftClient; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientModule.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientModule.cpp new file mode 100644 index 0000000000..dc3c8b80f0 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientModule.cpp @@ -0,0 +1,49 @@ +/* + * 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. + * + */ + +#include +#include + +#include + +namespace AWSGameLift +{ + //! Provide the entry point for the gem and register the system component. + class AWSGameLiftClientModule + : public AZ::Module + { + public: + AZ_RTTI(AWSGameLiftClientModule, "{7b920f3e-2b23-482e-a1b6-16bd278d126c}", AZ::Module); + AZ_CLASS_ALLOCATOR(AWSGameLiftClientModule, AZ::SystemAllocator, 0); + + AWSGameLiftClientModule() + : AZ::Module() + { + // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. + m_descriptors.insert(m_descriptors.end(), { + AWSGameLiftClientSystemComponent::CreateDescriptor(), + }); + } + + /** + * Add required SystemComponents to the SystemEntity. + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList { + azrtti_typeid(), + }; + } + }; +}// namespace AWSGameLift + +AZ_DECLARE_MODULE_CLASS(Gem_AWSGameLift_Client, AWSGameLift::AWSGameLiftClientModule) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp new file mode 100644 index 0000000000..18a0ae739e --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.cpp @@ -0,0 +1,186 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace AWSGameLift +{ + AWSGameLiftClientSystemComponent::AWSGameLiftClientSystemComponent() + { + m_gameliftClientManager = AZStd::make_unique(); + } + + void AWSGameLiftClientSystemComponent::Reflect(AZ::ReflectContext* context) + { + ReflectCreateSessionRequest(context); + AWSGameLiftCreateSessionOnQueueRequest::Reflect(context); + AWSGameLiftCreateSessionRequest::Reflect(context); + AWSGameLiftJoinSessionRequest::Reflect(context); + AWSGameLiftSearchSessionsRequest::Reflect(context); + ReflectSearchSessionsResponse(context); + + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ; + + if (AZ::EditContext* editContext = serialize->GetEditContext()) + { + editContext + ->Class( + "AWSGameLiftClient", + "Create the GameLift client manager that handles communication between game clients and the GameLift service.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("AWSGameLiftRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("ConfigureGameLiftClient", &AWSGameLiftRequestBus::Events::ConfigureGameLiftClient, + {{{"Region", ""}}}) + ->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId, + {{{"IncludeBrackets", ""}, + {"IncludeDashes", ""}}}) + ; + behaviorContext->EBus("AWSGameLiftSessionAsyncRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync, + {{{"CreateSessionRequest", ""}}}) + ->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, + {{{"JoinSessionRequest", ""}}}) + ->Event("SearchSessionsAsync", &AWSGameLiftSessionAsyncRequestBus::Events::SearchSessionsAsync, + {{{"SearchSessionsRequest", ""}}}) + ->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync) + ; + behaviorContext + ->EBus("AWSGameLiftSessionAsyncRequestNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Handler() + ; + behaviorContext->EBus("AWSGameLiftSessionRequestBus") + ->Attribute(AZ::Script::Attributes::Category, "AWSGameLift") + ->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, + {{{"CreateSessionRequest", ""}}}) + ->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, + {{{"JoinSessionRequest", ""}}}) + ->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, + {{{"SearchSessionsRequest", ""}}}) + ->Event("LeaveSession", &AWSGameLiftSessionRequestBus::Events::LeaveSession) + ; + } + } + + void AWSGameLiftClientSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("AWSGameLiftClientService")); + } + + void AWSGameLiftClientSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("AWSGameLiftClientService")); + } + + void AWSGameLiftClientSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("AWSCoreService")); + } + + void AWSGameLiftClientSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } + + void AWSGameLiftClientSystemComponent::Init() + { + } + + void AWSGameLiftClientSystemComponent::Activate() + { + m_gameliftClientManager->ActivateManager(); + } + + void AWSGameLiftClientSystemComponent::Deactivate() + { + m_gameliftClientManager->DeactivateManager(); + } + + void AWSGameLiftClientSystemComponent::ReflectCreateSessionRequest(AZ::ReflectContext* context) + { + AzFramework::CreateSessionRequest::Reflect(context); + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("CreateSessionRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + // Expose base type to BehaviorContext, but hide it to be used directly + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ; + } + } + + void AWSGameLiftClientSystemComponent::ReflectSearchSessionsResponse(AZ::ReflectContext* context) + { + // As it is a common response type, reflection could be moved to AzFramework to avoid duplication + AzFramework::SessionConfig::Reflect(context); + AzFramework::SearchSessionsResponse::Reflect(context); + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("SessionConfig") + ->Attribute(AZ::Script::Attributes::Category, "Session") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("CreationTime", BehaviorValueProperty(&AzFramework::SessionConfig::m_creationTime)) + ->Property("CreatorId", BehaviorValueProperty(&AzFramework::SessionConfig::m_creatorId)) + ->Property("CurrentPlayer", BehaviorValueProperty(&AzFramework::SessionConfig::m_currentPlayer)) + ->Property("DnsName", BehaviorValueProperty(&AzFramework::SessionConfig::m_dnsName)) + ->Property("IpAddress", BehaviorValueProperty(&AzFramework::SessionConfig::m_ipAddress)) + ->Property("MaxPlayer", BehaviorValueProperty(&AzFramework::SessionConfig::m_maxPlayer)) + ->Property("Port", BehaviorValueProperty(&AzFramework::SessionConfig::m_port)) + ->Property("SessionId", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionId)) + ->Property("SessionName", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionName)) + ->Property("SessionProperties", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionProperties)) + ->Property("Status", BehaviorValueProperty(&AzFramework::SessionConfig::m_status)) + ->Property("StatusReason", BehaviorValueProperty(&AzFramework::SessionConfig::m_statusReason)) + ->Property("TerminationTime", BehaviorValueProperty(&AzFramework::SessionConfig::m_terminationTime)) + ; + behaviorContext->Class("SearchSessionsResponse") + ->Attribute(AZ::Script::Attributes::Category, "Session") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("NextToken", BehaviorValueProperty(&AzFramework::SearchSessionsResponse::m_nextToken)) + ->Property("SessionConfigs", BehaviorValueProperty(&AzFramework::SearchSessionsResponse::m_sessionConfigs)) + ; + } + } + + void AWSGameLiftClientSystemComponent::SetGameLiftClientManager(AZStd::unique_ptr gameliftClientManager) + { + m_gameliftClientManager.reset(); + m_gameliftClientManager = AZStd::move(gameliftClientManager); + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h new file mode 100644 index 0000000000..220eb00ba6 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/AWSGameLiftClientSystemComponent.h @@ -0,0 +1,56 @@ +/* + * 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 AWSGameLift +{ + class AWSGameLiftClientManager; + + //! Gem client system component. Responsible for creating the gamelift client manager. + class AWSGameLiftClientSystemComponent + : public AZ::Component + { + public: + AZ_COMPONENT(AWSGameLiftClientSystemComponent, "{d481c15c-732a-4eea-9853-4965ed1bc2be}"); + + AWSGameLiftClientSystemComponent(); + virtual ~AWSGameLiftClientSystemComponent() = default; + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + protected: + //////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + void Deactivate() override; + //////////////////////////////////////////////////////////////////////// + + void SetGameLiftClientManager(AZStd::unique_ptr gameliftClientManager); + + private: + static void ReflectCreateSessionRequest(AZ::ReflectContext* context); + static void ReflectSearchSessionsResponse(AZ::ReflectContext* context); + + AZStd::unique_ptr m_gameliftClientManager; + }; + +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.cpp new file mode 100644 index 0000000000..67b110cf33 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.cpp @@ -0,0 +1,90 @@ +/* + * 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. + * + */ + +#include +#include + +namespace AWSGameLift +{ + namespace CreateSessionActivity + { + Aws::GameLift::Model::CreateGameSessionRequest BuildAWSGameLiftCreateGameSessionRequest( + const AWSGameLiftCreateSessionRequest& createSessionRequest) + { + Aws::GameLift::Model::CreateGameSessionRequest request; + // Optional attributes + if (!createSessionRequest.m_creatorId.empty()) + { + request.SetCreatorId(createSessionRequest.m_creatorId.c_str()); + } + if (!createSessionRequest.m_sessionName.empty()) + { + request.SetName(createSessionRequest.m_sessionName.c_str()); + } + if (!createSessionRequest.m_idempotencyToken.empty()) + { + request.SetIdempotencyToken(createSessionRequest.m_idempotencyToken.c_str()); + } + for (auto iter = createSessionRequest.m_sessionProperties.begin(); + iter != createSessionRequest.m_sessionProperties.end(); iter++) + { + Aws::GameLift::Model::GameProperty sessionProperty; + sessionProperty.SetKey(iter->first.c_str()); + sessionProperty.SetValue(iter->second.c_str()); + request.AddGameProperties(sessionProperty); + } + + // Required attributes + if (!createSessionRequest.m_aliasId.empty()) + { + request.SetAliasId(createSessionRequest.m_aliasId.c_str()); + } + if (!createSessionRequest.m_fleetId.empty()) + { + request.SetFleetId(createSessionRequest.m_fleetId.c_str()); + } + request.SetMaximumPlayerSessionCount(createSessionRequest.m_maxPlayer); + + return request; + } + + AZStd::string CreateSession( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftCreateSessionRequest& createSessionRequest) + { + AZ_TracePrintf(AWSGameLiftCreateSessionActivityName, "Requesting CreateGameSession against Amazon GameLift service ..."); + + AZStd::string result = ""; + Aws::GameLift::Model::CreateGameSessionRequest request = BuildAWSGameLiftCreateGameSessionRequest(createSessionRequest); + auto createSessionOutcome = gameliftClient.CreateGameSession(request); + + if (createSessionOutcome.IsSuccess()) + { + result = AZStd::string(createSessionOutcome.GetResult().GetGameSession().GetGameSessionId().c_str()); + } + else + { + AZ_Error(AWSGameLiftCreateSessionActivityName, false, AWSGameLiftErrorMessageTemplate, + createSessionOutcome.GetError().GetExceptionName().c_str(), createSessionOutcome.GetError().GetMessage().c_str()); + } + return result; + } + + bool ValidateCreateSessionRequest(const AzFramework::CreateSessionRequest& createSessionRequest) + { + auto gameliftCreateSessionRequest = azrtti_cast(&createSessionRequest); + + return gameliftCreateSessionRequest && gameliftCreateSessionRequest->m_maxPlayer >= 0 && + (!gameliftCreateSessionRequest->m_aliasId.empty() || !gameliftCreateSessionRequest->m_fleetId.empty()); + } + } // namespace CreateSessionActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.h new file mode 100644 index 0000000000..236ae3f08d --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionActivity.h @@ -0,0 +1,39 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include +#include + +namespace AWSGameLift +{ + namespace CreateSessionActivity + { + static constexpr const char AWSGameLiftCreateSessionActivityName[] = "AWSGameLiftCreateSessionActivity"; + + // Build AWS GameLift CreateGameSessionRequest by using AWSGameLiftCreateSessionRequest + Aws::GameLift::Model::CreateGameSessionRequest BuildAWSGameLiftCreateGameSessionRequest(const AWSGameLiftCreateSessionRequest& createSessionRequest); + + // Create CreateGameSessionRequest and make a CreateGameSession call through GameLift client + AZStd::string CreateSession( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftCreateSessionRequest& createSessionRequest); + + // Validate CreateSessionRequest and check required request parameters + bool ValidateCreateSessionRequest(const AzFramework::CreateSessionRequest& createSessionRequest); + + } // namespace CreateSessionActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.cpp new file mode 100644 index 0000000000..994d5e0281 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.cpp @@ -0,0 +1,80 @@ +/* + * 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. + * + */ + +#include +#include + +namespace AWSGameLift +{ + namespace CreateSessionOnQueueActivity + { + Aws::GameLift::Model::StartGameSessionPlacementRequest BuildAWSGameLiftStartGameSessionPlacementRequest( + const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest) + { + Aws::GameLift::Model::StartGameSessionPlacementRequest request; + // Optional attributes + if (!createSessionOnQueueRequest.m_sessionName.empty()) + { + request.SetGameSessionName(createSessionOnQueueRequest.m_sessionName.c_str()); + } + for (auto iter = createSessionOnQueueRequest.m_sessionProperties.begin(); + iter != createSessionOnQueueRequest.m_sessionProperties.end(); iter++) + { + Aws::GameLift::Model::GameProperty sessionProperty; + sessionProperty.SetKey(iter->first.c_str()); + sessionProperty.SetValue(iter->second.c_str()); + request.AddGameProperties(sessionProperty); + } + + // Required attributes + request.SetGameSessionQueueName(createSessionOnQueueRequest.m_queueName.c_str()); + request.SetMaximumPlayerSessionCount(createSessionOnQueueRequest.m_maxPlayer); + request.SetPlacementId(createSessionOnQueueRequest.m_placementId.c_str()); + + return request; + } + + AZStd::string CreateSessionOnQueue( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest) + { + AZ_TracePrintf(AWSGameLiftCreateSessionOnQueueActivityName, + "Requesting StartGameSessionPlacement against Amazon GameLift service ..."); + + AZStd::string result = ""; + Aws::GameLift::Model::StartGameSessionPlacementRequest request = + BuildAWSGameLiftStartGameSessionPlacementRequest(createSessionOnQueueRequest); + auto createSessionOnQueueOutcome = gameliftClient.StartGameSessionPlacement(request); + + if (createSessionOnQueueOutcome.IsSuccess()) + { + result = AZStd::string(createSessionOnQueueOutcome.GetResult().GetGameSessionPlacement().GetPlacementId().c_str()); + } + else + { + AZ_Error(AWSGameLiftCreateSessionOnQueueActivityName, false, AWSGameLiftErrorMessageTemplate, + createSessionOnQueueOutcome.GetError().GetExceptionName().c_str(), + createSessionOnQueueOutcome.GetError().GetMessage().c_str()); + } + return result; + } + + bool ValidateCreateSessionOnQueueRequest(const AzFramework::CreateSessionRequest& createSessionRequest) + { + auto gameliftCreateSessionOnQueueRequest = + azrtti_cast(&createSessionRequest); + + return gameliftCreateSessionOnQueueRequest && gameliftCreateSessionOnQueueRequest->m_maxPlayer >= 0 && + !gameliftCreateSessionOnQueueRequest->m_queueName.empty() && !gameliftCreateSessionOnQueueRequest->m_placementId.empty(); + } + } // namespace CreateSessionOnQueueActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.h new file mode 100644 index 0000000000..1cb337650d --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.h @@ -0,0 +1,40 @@ +/* + * 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 +#include +#include + +namespace AWSGameLift +{ + namespace CreateSessionOnQueueActivity + { + static constexpr const char AWSGameLiftCreateSessionOnQueueActivityName[] = "AWSGameLiftCreateSessionOnQueueActivity"; + + // Build AWS GameLift StartGameSessionPlacementRequest by using AWSGameLiftCreateSessionOnQueueRequest + Aws::GameLift::Model::StartGameSessionPlacementRequest BuildAWSGameLiftStartGameSessionPlacementRequest( + const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest); + + // Create StartGameSessionPlacementRequest and make a CreateGameSession call through GameLift client + AZStd::string CreateSessionOnQueue( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest); + + // Validate CreateSessionOnQueueRequest and check required request parameters + bool ValidateCreateSessionOnQueueRequest(const AzFramework::CreateSessionRequest& createSessionRequest); + + } // namespace CreateSessionOnQueueActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.cpp new file mode 100644 index 0000000000..f4deebd8e6 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.cpp @@ -0,0 +1,112 @@ +/* + * 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. + * + */ + +#include +#include + +#include +#include + +namespace AWSGameLift +{ + namespace JoinSessionActivity + { + Aws::GameLift::Model::CreatePlayerSessionRequest BuildAWSGameLiftCreatePlayerSessionRequest( + const AWSGameLiftJoinSessionRequest& joinSessionRequest) + { + Aws::GameLift::Model::CreatePlayerSessionRequest request; + // Optional attributes + if (!joinSessionRequest.m_playerData.empty()) + { + request.SetPlayerData(joinSessionRequest.m_playerData.c_str()); + } + // Required attributes + request.SetPlayerId(joinSessionRequest.m_playerId.c_str()); + request.SetGameSessionId(joinSessionRequest.m_sessionId.c_str()); + return request; + } + + AzFramework::SessionConnectionConfig BuildSessionConnectionConfig( + const Aws::GameLift::Model::CreatePlayerSessionOutcome& createPlayerSessionOutcome) + { + AzFramework::SessionConnectionConfig sessionConnectionConfig; + auto createPlayerSessionResult = createPlayerSessionOutcome.GetResult(); + // TODO: AWSNativeSDK needs to be updated to support this attribute, and it is a must have for TLS certificate enabled fleet + //sessionConnectionConfig.m_dnsName = createPlayerSessionResult.GetPlayerSession().GetDnsName().c_str(); + sessionConnectionConfig.m_ipAddress = createPlayerSessionResult.GetPlayerSession().GetIpAddress().c_str(); + sessionConnectionConfig.m_playerSessionId = createPlayerSessionResult.GetPlayerSession().GetPlayerSessionId().c_str(); + sessionConnectionConfig.m_port = createPlayerSessionResult.GetPlayerSession().GetPort(); + return sessionConnectionConfig; + } + + Aws::GameLift::Model::CreatePlayerSessionOutcome CreatePlayerSession( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftJoinSessionRequest& joinSessionRequest) + { + AZ_TracePrintf(AWSGameLiftJoinSessionActivityName, + "Requesting CreatePlayerSession for player %s against Amazon GameLift service ...", + joinSessionRequest.m_playerId.c_str()); + + Aws::GameLift::Model::CreatePlayerSessionRequest request = + BuildAWSGameLiftCreatePlayerSessionRequest(joinSessionRequest); + auto createPlayerSessionOutcome = gameliftClient.CreatePlayerSession(request); + + if (!createPlayerSessionOutcome.IsSuccess()) + { + AZ_Error(AWSGameLiftJoinSessionActivityName, false, AWSGameLiftErrorMessageTemplate, + createPlayerSessionOutcome.GetError().GetExceptionName().c_str(), + createPlayerSessionOutcome.GetError().GetMessage().c_str()); + } + return createPlayerSessionOutcome; + } + + bool RequestPlayerJoinSession(const Aws::GameLift::Model::CreatePlayerSessionOutcome& createPlayerSessionOutcome) + { + bool result = false; + if (createPlayerSessionOutcome.IsSuccess()) + { + auto clientRequestHandler = AZ::Interface::Get(); + if (clientRequestHandler) + { + AZ_TracePrintf(AWSGameLiftJoinSessionActivityName, "Requesting player to connect to game session ..."); + + AzFramework::SessionConnectionConfig sessionConnectionConfig = + BuildSessionConnectionConfig(createPlayerSessionOutcome); + result = clientRequestHandler->RequestPlayerJoinSession(sessionConnectionConfig); + } + else + { + AZ_Error(AWSGameLiftJoinSessionActivityName, false, AWSGameLiftJoinSessionMissingRequestHandlerErrorMessage); + } + } + return result; + } + + bool ValidateJoinSessionRequest(const AzFramework::JoinSessionRequest& joinSessionRequest) + { + auto gameliftJoinSessionRequest = azrtti_cast(&joinSessionRequest); + + if (gameliftJoinSessionRequest && + !gameliftJoinSessionRequest->m_playerId.empty() && + !gameliftJoinSessionRequest->m_sessionId.empty()) + { + return true; + } + else + { + AZ_Error(AWSGameLiftJoinSessionActivityName, false, AWSGameLiftJoinSessionRequestInvalidErrorMessage); + + return false; + } + } + } // namespace JoinSessionActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.h new file mode 100644 index 0000000000..abb372df16 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftJoinSessionActivity.h @@ -0,0 +1,54 @@ +/* + * 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 + +#include +#include +#include + +namespace AWSGameLift +{ + namespace JoinSessionActivity + { + static constexpr const char AWSGameLiftJoinSessionActivityName[] = "AWSGameLiftJoinSessionActivity"; + static constexpr const char AWSGameLiftJoinSessionRequestInvalidErrorMessage[] = + "Invalid GameLift JoinSession request."; + static constexpr const char AWSGameLiftJoinSessionMissingRequestHandlerErrorMessage[] = + "Missing GameLift JoinSession request handler, please make sure Multiplayer Gem is enabled and registered as handler."; + + // Build AWS GameLift CreatePlayerSessionRequest by using AWSGameLiftJoinSessionRequest + Aws::GameLift::Model::CreatePlayerSessionRequest BuildAWSGameLiftCreatePlayerSessionRequest( + const AWSGameLiftJoinSessionRequest& joinSessionRequest); + + // Build session connection config by using CreatePlayerSessionOutcome + AzFramework::SessionConnectionConfig BuildSessionConnectionConfig( + const Aws::GameLift::Model::CreatePlayerSessionOutcome& createPlayerSessionOutcome); + + // Create CreatePlayerSessionRequest and make a CreatePlayerSession call through GameLift client + Aws::GameLift::Model::CreatePlayerSessionOutcome CreatePlayerSession( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftJoinSessionRequest& joinSessionRequest); + + // Request to setup networking connection for player + bool RequestPlayerJoinSession( + const Aws::GameLift::Model::CreatePlayerSessionOutcome& createPlayerSessionOutcome); + + // Validate JoinSessionRequest and check required request parameters + bool ValidateJoinSessionRequest(const AzFramework::JoinSessionRequest& joinSessionRequest); + + } // namespace JoinSessionActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.cpp new file mode 100644 index 0000000000..b6d4697a7d --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.cpp @@ -0,0 +1,38 @@ +/* + * 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. + * + */ + +#include + +#include +#include + +namespace AWSGameLift +{ + namespace LeaveSessionActivity + { + void LeaveSession() + { + auto clientRequestHandler = AZ::Interface::Get(); + if (clientRequestHandler) + { + AZ_TracePrintf(AWSGameLiftLeaveSessionActivityName, "Requesting to leave the current session..."); + + clientRequestHandler->RequestPlayerLeaveSession(); + } + else + { + AZ_Error(AWSGameLiftLeaveSessionActivityName, false, AWSGameLiftLeaveSessionMissingRequestHandlerErrorMessage); + } + } + + } // namespace LeaveSessionActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.h new file mode 100644 index 0000000000..59b9fea055 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftLeaveSessionActivity.h @@ -0,0 +1,27 @@ +/* + * 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 + +namespace AWSGameLift +{ + namespace LeaveSessionActivity + { + static constexpr const char AWSGameLiftLeaveSessionActivityName[] = "AWSGameLiftLeaveSessionActivity"; + static constexpr const char AWSGameLiftLeaveSessionMissingRequestHandlerErrorMessage[] = + "Missing GameLift LeaveSession request handler, please make sure Multiplayer Gem is enabled and registered as handler."; + + // Request to leave the current session + void LeaveSession(); + } // namespace LeaveSessionActivity +} // namespace AWSGameLift + diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.cpp new file mode 100644 index 0000000000..3d64c72f8d --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.cpp @@ -0,0 +1,130 @@ +/* + * 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. + * + */ + +#include + +#include +#include + +namespace AWSGameLift +{ + namespace SearchSessionsActivity + { + Aws::GameLift::Model::SearchGameSessionsRequest BuildAWSGameLiftSearchGameSessionsRequest( + const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) + { + Aws::GameLift::Model::SearchGameSessionsRequest request; + // Optional attributes + if (!searchSessionsRequest.m_filterExpression.empty()) + { + request.SetFilterExpression(searchSessionsRequest.m_filterExpression.c_str()); + } + if (!searchSessionsRequest.m_sortExpression.empty()) + { + request.SetSortExpression(searchSessionsRequest.m_sortExpression.c_str()); + } + if (searchSessionsRequest.m_maxResult > 0) + { + request.SetLimit(searchSessionsRequest.m_maxResult); + } + if (!searchSessionsRequest.m_nextToken.empty()) + { + request.SetNextToken(searchSessionsRequest.m_nextToken.c_str()); + } + // Required attributes + if (!searchSessionsRequest.m_aliasId.empty()) + { + request.SetAliasId(searchSessionsRequest.m_aliasId.c_str()); + } + if (!searchSessionsRequest.m_fleetId.empty()) + { + request.SetFleetId(searchSessionsRequest.m_fleetId.c_str()); + } + // TODO: Update the AWS Native SDK to accept the new request parameter. + //request.SetLocation(searchSessionsRequest.m_location.c_str()); + return request; + } + + AzFramework::SearchSessionsResponse SearchSessions( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) + { + AZ_TracePrintf(AWSGameLiftSearchSessionsActivityName, "Requesting SearchGameSessions against Amazon GameLift service ..."); + + AzFramework::SearchSessionsResponse response; + Aws::GameLift::Model::SearchGameSessionsRequest request = BuildAWSGameLiftSearchGameSessionsRequest(searchSessionsRequest); + Aws::GameLift::Model::SearchGameSessionsOutcome outcome = gameliftClient.SearchGameSessions(request); + + if (outcome.IsSuccess()) + { + response = SearchSessionsActivity::ParseResponse(outcome.GetResult()); + } + else + { + AZ_Error(AWSGameLiftSearchSessionsActivityName, false, AWSGameLiftErrorMessageTemplate, + outcome.GetError().GetExceptionName().c_str(), outcome.GetError().GetMessage().c_str()); + } + + return response; + } + + AzFramework::SearchSessionsResponse ParseResponse( + const Aws::GameLift::Model::SearchGameSessionsResult& gameLiftSearchSessionsResult) + { + AzFramework::SearchSessionsResponse response; + response.m_nextToken = gameLiftSearchSessionsResult.GetNextToken().c_str(); + + for (const Aws::GameLift::Model::GameSession& gameSession : gameLiftSearchSessionsResult.GetGameSessions()) + { + AzFramework::SessionConfig session; + session.m_creationTime = gameSession.GetCreationTime().Millis(); + session.m_creatorId = gameSession.GetCreatorId().c_str(); + session.m_currentPlayer = gameSession.GetCurrentPlayerSessionCount(); + session.m_ipAddress = gameSession.GetIpAddress().c_str(); + session.m_maxPlayer = gameSession.GetMaximumPlayerSessionCount(); + session.m_port = gameSession.GetPort(); + session.m_sessionId = gameSession.GetGameSessionId().c_str(); + session.m_sessionName = gameSession.GetName().c_str(); + session.m_status = AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()]; + session.m_statusReason = AWSGameLiftSessionStatusReasons[(int)gameSession.GetStatusReason()]; + session.m_terminationTime = gameSession.GetTerminationTime().Millis(); + // TODO: Update the AWS Native SDK to get the new game session attributes. + //session.m_dnsName = gameSession.GetDnsName(); + + for (const auto& gameProperty : gameSession.GetGameProperties()) + { + session.m_sessionProperties[gameProperty.GetKey().c_str()] = gameProperty.GetValue().c_str(); + } + + response.m_sessionConfigs.emplace_back(AZStd::move(session)); + } + + return response; + }; + + bool ValidateSearchSessionsRequest(const AzFramework::SearchSessionsRequest& searchSessionsRequest) + { + auto gameliftSearchSessionsRequest = azrtti_cast(&searchSessionsRequest); + if (gameliftSearchSessionsRequest && + (!gameliftSearchSessionsRequest->m_aliasId.empty() || !gameliftSearchSessionsRequest->m_fleetId.empty())) + { + return true; + } + else + { + AZ_Error(AWSGameLiftSearchSessionsActivityName, false, AWSGameLiftSearchSessionsRequestInvalidErrorMessage); + + return false; + } + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.h new file mode 100644 index 0000000000..da755bda71 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Activity/AWSGameLiftSearchSessionsActivity.h @@ -0,0 +1,45 @@ +/* + * 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 +#include +#include + +namespace AWSGameLift +{ + namespace SearchSessionsActivity + { + static constexpr const char AWSGameLiftSearchSessionsActivityName[] = "AWSGameLiftSearchSessionsActivity"; + static constexpr const char AWSGameLiftSearchSessionsRequestInvalidErrorMessage[] = + "Invalid GameLift SearchSessions request."; + + // Build AWS GameLift SearchGameSessionsRequest by using AWSGameLiftSearchSessionsRequest + Aws::GameLift::Model::SearchGameSessionsRequest BuildAWSGameLiftSearchGameSessionsRequest( + const AWSGameLiftSearchSessionsRequest& searchSessionsRequest); + + // Create SearchGameSessionsRequest and make a SeachGameSessions call through GameLift client + AzFramework::SearchSessionsResponse SearchSessions( + const Aws::GameLift::GameLiftClient& gameliftClient, + const AWSGameLiftSearchSessionsRequest& searchSessionsRequest); + + // Convert from Aws::GameLift::Model::SearchGameSessionsResult to AzFramework::SearchSessionsResponse. + AzFramework::SearchSessionsResponse ParseResponse( + const Aws::GameLift::Model::SearchGameSessionsResult& gameLiftSearchSessionsResult); + + // Validate SearchSessionsRequest and check required request parameters + bool ValidateSearchSessionsRequest(const AzFramework::SearchSessionsRequest& searchSessionsRequest); + } // namespace SearchSessionsActivity +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionOnQueueRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionOnQueueRequest.cpp new file mode 100644 index 0000000000..d3a2b88dbe --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionOnQueueRequest.cpp @@ -0,0 +1,58 @@ +/* + * 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. + * + */ + +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftCreateSessionOnQueueRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("queueName", &AWSGameLiftCreateSessionOnQueueRequest::m_queueName) + ->Field("placementId", &AWSGameLiftCreateSessionOnQueueRequest::m_placementId) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftCreateSessionOnQueueRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftCreateSessionOnQueueRequest::m_queueName, "QueueName (Required)", + "Name of the queue to use to place the new game session") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftCreateSessionOnQueueRequest::m_placementId, "PlacementId (Required)", + "A unique identifier to assign to the new game session placement") + ; + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftCreateSessionOnQueueRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("CreatorId", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_creatorId)) + ->Property("SessionProperties", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_sessionProperties)) + ->Property("SessionName", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_sessionName)) + ->Property("MaxPlayer", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_maxPlayer)) + ->Property("QueueName", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_queueName)) + ->Property("PlacementId", BehaviorValueProperty(&AWSGameLiftCreateSessionOnQueueRequest::m_placementId)) + ; + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionRequest.cpp new file mode 100644 index 0000000000..3c5939f98f --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftCreateSessionRequest.cpp @@ -0,0 +1,63 @@ +/* + * 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. + * + */ + +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftCreateSessionRequest::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("aliasId", &AWSGameLiftCreateSessionRequest::m_aliasId) + ->Field("fleetId", &AWSGameLiftCreateSessionRequest::m_fleetId) + ->Field("idempotencyToken", &AWSGameLiftCreateSessionRequest::m_idempotencyToken) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftCreateSessionRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftCreateSessionRequest::m_aliasId, "AliasId (Required, or FleetId)", + "A unique identifier for the alias associated with the fleet to create a game session in") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftCreateSessionRequest::m_fleetId, "FleetId (Required, or AliasId)", + "A unique identifier for the fleet to create a game session in") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftCreateSessionRequest::m_idempotencyToken, "IdempotencyToken", + "Custom string that uniquely identifies the new game session request") + ; + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AWSGameLiftCreateSessionRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("CreatorId", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_creatorId)) + ->Property("SessionProperties", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_sessionProperties)) + ->Property("SessionName", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_sessionName)) + ->Property("MaxPlayer", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_maxPlayer)) + ->Property("AliasId", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_aliasId)) + ->Property("FleetId", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_fleetId)) + ->Property("IdempotencyToken", BehaviorValueProperty(&AWSGameLiftCreateSessionRequest::m_idempotencyToken)) + ; + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftJoinSessionRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftJoinSessionRequest.cpp new file mode 100644 index 0000000000..8c7165a095 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftJoinSessionRequest.cpp @@ -0,0 +1,54 @@ +/* + * 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. + * + */ + +#include +#include + +#include + +namespace AWSGameLift +{ + void AWSGameLiftJoinSessionRequest::Reflect(AZ::ReflectContext* context) + { + AzFramework::JoinSessionRequest::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ; + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftJoinSessionRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ; + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("JoinSessionRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + // Expose base type to BehaviorContext, but hide it to be used directly + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ; + behaviorContext->Class("AWSGameLiftJoinSessionRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("PlayerData", BehaviorValueProperty(&AWSGameLiftJoinSessionRequest::m_playerData)) + ->Property("PlayerId", BehaviorValueProperty(&AWSGameLiftJoinSessionRequest::m_playerId)) + ->Property("SessionId", BehaviorValueProperty(&AWSGameLiftJoinSessionRequest::m_sessionId)) + ; + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftSearchSessionsRequest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftSearchSessionsRequest.cpp new file mode 100644 index 0000000000..3e13793b37 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Source/Request/AWSGameLiftSearchSessionsRequest.cpp @@ -0,0 +1,69 @@ +/* + * 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. + * + */ + +#include +#include +#include + +#include +#include + +namespace AWSGameLift +{ + void AWSGameLiftSearchSessionsRequest::Reflect(AZ::ReflectContext* context) + { + AzFramework::SearchSessionsRequest::Reflect(context); + + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(0) + ->Field("aliasId", &AWSGameLiftSearchSessionsRequest::m_aliasId) + ->Field("fleetId", &AWSGameLiftSearchSessionsRequest::m_fleetId) + ->Field("location", &AWSGameLiftSearchSessionsRequest::m_location); + + if (AZ::EditContext* editContext = serializeContext->GetEditContext()) + { + editContext->Class("AWSGameLiftSearchSessionsRequest", "") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftSearchSessionsRequest::m_aliasId, "AliasId (Required, or FleetId)", + "A unique identifier for the alias associated with the fleet to search for active game sessions.") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftSearchSessionsRequest::m_fleetId, "FleetId (Required, or AliasId)", + "A unique identifier for the fleet to search for active game sessions.") + ->DataElement( + AZ::Edit::UIHandlers::Default, &AWSGameLiftSearchSessionsRequest::m_location, "Location", + "A fleet location to search for game sessions."); + } + } + + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("SearchSessionsRequest") + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + // Expose base type to BehaviorContext, but hide it to be used directly + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All); + behaviorContext->Class("AWSGameLiftSearchSessionsRequest") + + ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) + ->Property("FilterExpression", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_filterExpression)) + ->Property("SortExpression", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_sortExpression)) + ->Property("MaxResult", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_maxResult)) + ->Property("NextToken", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_nextToken)) + ->Property("AliasId", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_aliasId)) + ->Property("FleetId", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_fleetId)) + ->Property("Location", BehaviorValueProperty(&AWSGameLiftSearchSessionsRequest::m_location)); + } + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientFixture.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientFixture.h new file mode 100644 index 0000000000..5fefa34a58 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientFixture.h @@ -0,0 +1,63 @@ +/* + * 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 +#include +#include +#include +#include + +class AWSGameLiftClientFixture + : public UnitTest::ScopedAllocatorSetupFixture +{ +public: + AWSGameLiftClientFixture() {} + virtual ~AWSGameLiftClientFixture() = default; + + void SetUp() override + { + AZ::AllocatorInstance::Create(); + AZ::AllocatorInstance::Create(); + + AZ::JobManagerDesc jobManagerDesc; + AZ::JobManagerThreadDesc threadDesc; + + m_jobManager.reset(aznew AZ::JobManager(jobManagerDesc)); + m_jobCancelGroup.reset(aznew AZ::JobCancelGroup()); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + m_jobContext.reset(aznew AZ::JobContext(*m_jobManager, *m_jobCancelGroup)); + AZ::JobContext::SetGlobalContext(m_jobContext.get()); + + AWSNativeSDKInit::InitializationManager::InitAwsApi(); + } + + void TearDown() override + { + AWSNativeSDKInit::InitializationManager::Shutdown(); + + AZ::JobContext::SetGlobalContext(nullptr); + m_jobContext.reset(); + m_jobCancelGroup.reset(); + m_jobManager.reset(); + AZ::AllocatorInstance::Destroy(); + AZ::AllocatorInstance::Destroy(); + } + + AZStd::unique_ptr m_jobContext; + AZStd::unique_ptr m_jobCancelGroup; + AZStd::unique_ptr m_jobManager; +}; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp new file mode 100644 index 0000000000..90e058f21c --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientManagerTest.cpp @@ -0,0 +1,773 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace AWSGameLift; + +MATCHER_P(SearchSessionsResponseMatcher, expectedResponse, "") +{ + // Custome matcher for checking the SearchSessionsResponse type argument. + AZ_UNUSED(result_listener); + + bool result = arg.m_nextToken == expectedResponse.m_nextToken; + result &= arg.m_sessionConfigs.size() == expectedResponse.m_sessionConfigs.size(); + + for (int index = 0; index < arg.m_sessionConfigs.size(); ++index) + { + result &= arg.m_sessionConfigs[index].m_creationTime == expectedResponse.m_sessionConfigs[index].m_creationTime; + result &= arg.m_sessionConfigs[index].m_terminationTime == expectedResponse.m_sessionConfigs[index].m_terminationTime; + result &= arg.m_sessionConfigs[index].m_creatorId == expectedResponse.m_sessionConfigs[index].m_creatorId; + result &= arg.m_sessionConfigs[index].m_sessionProperties == expectedResponse.m_sessionConfigs[index].m_sessionProperties; + result &= arg.m_sessionConfigs[index].m_sessionId == expectedResponse.m_sessionConfigs[index].m_sessionId; + result &= arg.m_sessionConfigs[index].m_sessionName == expectedResponse.m_sessionConfigs[index].m_sessionName; + result &= arg.m_sessionConfigs[index].m_dnsName == expectedResponse.m_sessionConfigs[index].m_dnsName; + result &= arg.m_sessionConfigs[index].m_ipAddress == expectedResponse.m_sessionConfigs[index].m_ipAddress; + result &= arg.m_sessionConfigs[index].m_port == expectedResponse.m_sessionConfigs[index].m_port; + result &= arg.m_sessionConfigs[index].m_maxPlayer == expectedResponse.m_sessionConfigs[index].m_maxPlayer; + result &= arg.m_sessionConfigs[index].m_currentPlayer == expectedResponse.m_sessionConfigs[index].m_currentPlayer; + result &= arg.m_sessionConfigs[index].m_status == expectedResponse.m_sessionConfigs[index].m_status; + result &= arg.m_sessionConfigs[index].m_statusReason == expectedResponse.m_sessionConfigs[index].m_statusReason; + } + + return result; +} + +class AWSResourceMappingRequestsHandlerMock + : public AWSCore::AWSResourceMappingRequestBus::Handler +{ +public: + AWSResourceMappingRequestsHandlerMock() + { + AWSCore::AWSResourceMappingRequestBus::Handler::BusConnect(); + } + + ~ AWSResourceMappingRequestsHandlerMock() + { + AWSCore::AWSResourceMappingRequestBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetDefaultRegion, AZStd::string()); + MOCK_CONST_METHOD0(GetDefaultAccountId, AZStd::string()); + MOCK_CONST_METHOD1(GetResourceAccountId, AZStd::string(const AZStd::string&)); + MOCK_CONST_METHOD1(GetResourceNameId, AZStd::string(const AZStd::string&)); + MOCK_CONST_METHOD1(GetResourceRegion, AZStd::string(const AZStd::string&)); + MOCK_CONST_METHOD1(GetResourceType, AZStd::string(const AZStd::string&)); + MOCK_CONST_METHOD1(GetServiceUrlByServiceName, AZStd::string(const AZStd::string&)); + MOCK_CONST_METHOD2(GetServiceUrlByRESTApiIdAndStage, AZStd::string(const AZStd::string&, const AZStd::string&)); + MOCK_METHOD1(ReloadConfigFile, void(bool)); +}; + +class AWSCredentialRequestsHandlerMock + : public AWSCore::AWSCredentialRequestBus::Handler +{ +public: + AWSCredentialRequestsHandlerMock() + { + AWSCore::AWSCredentialRequestBus::Handler::BusConnect(); + } + + ~AWSCredentialRequestsHandlerMock() + { + AWSCore::AWSCredentialRequestBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetCredentialHandlerOrder, int()); + MOCK_METHOD0(GetCredentialsProvider, std::shared_ptr()); +}; + +class AWSCoreRequestsHandlerMock + : public AWSCore::AWSCoreRequestBus::Handler +{ +public: + AWSCoreRequestsHandlerMock() + { + AWSCore::AWSCoreRequestBus::Handler::BusConnect(); + } + + ~AWSCoreRequestsHandlerMock() + { + AWSCore::AWSCoreRequestBus::Handler::BusDisconnect(); + } + + MOCK_METHOD0(GetDefaultJobContext, AZ::JobContext*()); + MOCK_METHOD0(GetDefaultConfig, AWSCore::AwsApiJobConfig*()); +}; + +class TestAWSGameLiftClientManager + : public AWSGameLiftClientManager +{ +public: + TestAWSGameLiftClientManager() + { + m_gameliftClientMockPtr = nullptr; + } + ~TestAWSGameLiftClientManager() + { + m_gameliftClientMockPtr = nullptr; + } + + void SetUpMockClient() + { + m_gameliftClientMockPtr = AZStd::make_shared(); + SetGameLiftClient(m_gameliftClientMockPtr); + } + + AZStd::shared_ptr m_gameliftClientMockPtr; +}; + +class AWSGameLiftClientManagerTest + : public AWSGameLiftClientFixture +{ +protected: + void SetUp() override + { + AWSGameLiftClientFixture::SetUp(); + + m_gameliftClientManager = AZStd::make_unique(); + m_gameliftClientManager->SetUpMockClient(); + m_gameliftClientManager->ActivateManager(); + } + + void TearDown() override + { + m_gameliftClientManager->DeactivateManager(); + m_gameliftClientManager.reset(); + + AWSGameLiftClientFixture::TearDown(); + } + + AWSGameLiftSearchSessionsRequest GetValidSearchSessionsRequest() + { + AWSGameLiftSearchSessionsRequest request; + request.m_aliasId = "dummyAliasId"; + request.m_fleetId = "dummyFleetId"; + request.m_location = "dummyLocation"; + request.m_filterExpression = "dummyFilterExpression"; + request.m_sortExpression = "dummySortExpression"; + request.m_maxResult = 1; + request.m_nextToken = "dummyNextToken"; + + return request; + } + + Aws::GameLift::Model::SearchGameSessionsOutcome GetValidSearchGameSessionsOutcome() + { + Aws::GameLift::Model::GameProperty gameProperty; + gameProperty.SetKey("dummyKey"); + gameProperty.SetValue("dummyValue"); + Aws::Vector gameProperties = { gameProperty }; + + Aws::GameLift::Model::GameSession gameSession; + gameSession.SetCreationTime(Aws::Utils::DateTime(0.0)); + gameSession.SetTerminationTime(Aws::Utils::DateTime(0.0)); + gameSession.SetCreatorId("dummyCreatorId"); + gameSession.SetGameProperties(gameProperties); + gameSession.SetGameSessionId("dummyGameSessionId"); + gameSession.SetName("dummyGameSessionName"); + gameSession.SetIpAddress("dummyIpAddress"); + gameSession.SetPort(0); + gameSession.SetMaximumPlayerSessionCount(2); + gameSession.SetCurrentPlayerSessionCount(1); + gameSession.SetStatus(Aws::GameLift::Model::GameSessionStatus::TERMINATED); + gameSession.SetStatusReason(Aws::GameLift::Model::GameSessionStatusReason::INTERRUPTED); + // TODO: Update the AWS Native SDK to set the new game session attributes. + // gameSession.SetDnsName("dummyDnsName"); + + Aws::GameLift::Model::SearchGameSessionsResult result; + result.SetNextToken("dummyNextToken"); + result.SetGameSessions({ gameSession }); + + return Aws::GameLift::Model::SearchGameSessionsOutcome(result); + } + + AzFramework::SearchSessionsResponse GetValidSearchSessionsResponse() + { + AzFramework::SessionConfig sessionConfig; + sessionConfig.m_creationTime = 0; + sessionConfig.m_terminationTime = 0; + sessionConfig.m_creatorId = "dummyCreatorId"; + sessionConfig.m_sessionProperties["dummyKey"] = "dummyValue"; + sessionConfig.m_sessionId = "dummyGameSessionId"; + sessionConfig.m_sessionName = "dummyGameSessionName"; + sessionConfig.m_ipAddress = "dummyIpAddress"; + sessionConfig.m_port = 0; + sessionConfig.m_maxPlayer = 2; + sessionConfig.m_currentPlayer = 1; + sessionConfig.m_status = "Terminated"; + sessionConfig.m_statusReason = "Interrupted"; + // TODO: Update the AWS Native SDK to set the new game session attributes. + // sessionConfig.m_dnsName = "dummyDnsName"; + + AzFramework::SearchSessionsResponse response; + response.m_nextToken = "dummyNextToken"; + response.m_sessionConfigs = { sessionConfig }; + + return response; + } + +public: + AZStd::unique_ptr m_gameliftClientManager; +}; + +TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_gameliftClientManager->ConfigureGameLiftClient(""); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult) +{ + AWSResourceMappingRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2")); + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_gameliftClientManager->ConfigureGameLiftClient(""); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult) +{ + AWSCredentialRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetCredentialsProvider()) + .Times(1) + .WillOnce(::testing::Return(std::make_shared("dummyAccess", "dummySecret", ""))); + auto result = m_gameliftClientManager->ConfigureGameLiftClient("us-west-2"); + EXPECT_TRUE(result); +} + +TEST_F(AWSGameLiftClientManagerTest, CreatePlayerId_CreateWithoutBracketsOrDashes_GetExpectedResult) +{ + auto result = m_gameliftClientManager->CreatePlayerId(false, false); + EXPECT_FALSE(result.starts_with("{")); + EXPECT_FALSE(result.ends_with("}")); + EXPECT_FALSE(result.contains("-")); +} + +TEST_F(AWSGameLiftClientManagerTest, CreatePlayerId_CreateWithBrackets_GetExpectedResult) +{ + auto result = m_gameliftClientManager->CreatePlayerId(true, false); + EXPECT_TRUE(result.starts_with("{")); + EXPECT_TRUE(result.ends_with("}")); + EXPECT_FALSE(result.contains("-")); +} + +TEST_F(AWSGameLiftClientManagerTest, CreatePlayerId_CreateWithDashes_GetExpectedResult) +{ + auto result = m_gameliftClientManager->CreatePlayerId(false, true); + EXPECT_FALSE(result.starts_with("{")); + EXPECT_FALSE(result.ends_with("}")); + EXPECT_TRUE(result.contains("-")); +} + +TEST_F(AWSGameLiftClientManagerTest, CreatePlayerId_CreateWithBracketsAndDashes_GetExpectedResult) +{ + auto result = m_gameliftClientManager->CreatePlayerId(true, true); + EXPECT_TRUE(result.starts_with("{")); + EXPECT_TRUE(result.ends_with("}")); + EXPECT_TRUE(result.contains("-")); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithoutClientSetup_GetEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->ConfigureGameLiftClient(""); + AWSGameLiftCreateSessionRequest request; + request.m_aliasId = "dummyAlias"; + auto response = m_gameliftClientManager->CreateSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); // capture 2 error message + EXPECT_TRUE(response == ""); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithInvalidRequest_GetEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + auto response = m_gameliftClientManager->CreateSession(AzFramework::CreateSessionRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_TRUE(response == ""); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithValidRequest_GetSuccessOutcome) +{ + AWSGameLiftCreateSessionRequest request; + request.m_aliasId = "dummyAlias"; + Aws::GameLift::Model::CreateGameSessionResult result; + result.SetGameSession(Aws::GameLift::Model::GameSession()); + Aws::GameLift::Model::CreateGameSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + m_gameliftClientManager->CreateSession(request); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSession_CallWithValidRequest_GetErrorOutcome) +{ + AWSGameLiftCreateSessionRequest request; + request.m_aliasId = "dummyAlias"; + Aws::Client::AWSError error; + Aws::GameLift::Model::CreateGameSessionOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->CreateSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionAsync_CallWithInvalidRequest_GetNotificationWithEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnCreateSessionAsyncComplete(AZStd::string())).Times(1); + m_gameliftClientManager->CreateSessionAsync(AzFramework::CreateSessionRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionAsync_CallWithValidRequest_GetNotificationWithSuccessOutcome) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftCreateSessionRequest request; + request.m_aliasId = "dummyAlias"; + Aws::GameLift::Model::CreateGameSessionResult result; + result.SetGameSession(Aws::GameLift::Model::GameSession()); + Aws::GameLift::Model::CreateGameSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnCreateSessionAsyncComplete(::testing::_)).Times(1); + m_gameliftClientManager->CreateSessionAsync(request); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionAsync_CallWithValidRequest_GetNotificationWithErrorOutcome) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftCreateSessionRequest request; + request.m_aliasId = "dummyAlias"; + Aws::Client::AWSError error; + Aws::GameLift::Model::CreateGameSessionOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreateGameSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnCreateSessionAsyncComplete(AZStd::string(""))).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->CreateSessionAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueue_CallWithoutClientSetup_GetEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->ConfigureGameLiftClient(""); + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_queueName = "dummyQueue"; + request.m_placementId = "dummyPlacementId"; + auto response = m_gameliftClientManager->CreateSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); // capture 2 error message + EXPECT_TRUE(response == ""); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueue_CallWithValidRequest_GetSuccessOutcome) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_queueName = "dummyQueue"; + request.m_placementId = "dummyPlacementId"; + Aws::GameLift::Model::StartGameSessionPlacementResult result; + result.SetGameSessionPlacement(Aws::GameLift::Model::GameSessionPlacement()); + Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + m_gameliftClientManager->CreateSession(request); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueue_CallWithValidRequest_GetErrorOutcome) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_queueName = "dummyQueue"; + request.m_placementId = "dummyPlacementId"; + Aws::Client::AWSError error; + Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->CreateSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueueAsync_CallWithValidRequest_GetNotificationWithSuccessOutcome) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_queueName = "dummyQueue"; + request.m_placementId = "dummyPlacementId"; + Aws::GameLift::Model::StartGameSessionPlacementResult result; + result.SetGameSessionPlacement(Aws::GameLift::Model::GameSessionPlacement()); + Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnCreateSessionAsyncComplete(::testing::_)).Times(1); + m_gameliftClientManager->CreateSessionAsync(request); +} + +TEST_F(AWSGameLiftClientManagerTest, CreateSessionOnQueueAsync_CallWithValidRequest_GetNotificationWithErrorOutcome) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_queueName = "dummyQueue"; + request.m_placementId = "dummyPlacementId"; + Aws::Client::AWSError error; + Aws::GameLift::Model::StartGameSessionPlacementOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), StartGameSessionPlacement(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnCreateSessionAsyncComplete(AZStd::string(""))).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->CreateSessionAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithoutClientSetup_GetFalseResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->ConfigureGameLiftClient(""); + AWSGameLiftJoinSessionRequest request; + request.m_playerId = "dummyPlayerId"; + request.m_sessionId = "dummySessionId"; + auto response = m_gameliftClientManager->JoinSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(2); // capture 2 error message + EXPECT_FALSE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithInvalidRequest_GetFalseResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + auto response = m_gameliftClientManager->JoinSession(AzFramework::JoinSessionRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestButNoRequestHandler_GetSuccessOutcomeButFalseResponse) +{ + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + AZ_TEST_START_TRACE_SUPPRESSION; + auto response = m_gameliftClientManager->JoinSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequest_GetErrorOutcomeAndFalseResponse) +{ + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::Client::AWSError error; + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + AZ_TEST_START_TRACE_SUPPRESSION; + auto response = m_gameliftClientManager->JoinSession(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestAndRequestHandler_GetSuccessOutcomeButFalseResponse) +{ + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(false)); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + auto response = m_gameliftClientManager->JoinSession(request); + EXPECT_FALSE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSession_CallWithValidRequestAndRequestHandler_GetSuccessOutcomeAndTrueResponse) +{ + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true)); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + auto response = m_gameliftClientManager->JoinSession(request); + EXPECT_TRUE(response); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithInvalidRequest_GetNotificationWithFalseResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnJoinSessionAsyncComplete(false)).Times(1); + m_gameliftClientManager->JoinSessionAsync(AzFramework::JoinSessionRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestButNoRequestHandler_GetSuccessOutcomeButNotificationWithFalseResponse) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnJoinSessionAsyncComplete(false)).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->JoinSessionAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequest_GetErrorOutcomeAndNotificationWithFalseResponse) +{ + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::Client::AWSError error; + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnJoinSessionAsyncComplete(false)).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->JoinSessionAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestAndRequestHandler_GetSuccessOutcomeButNotificationWithFalseResponse) +{ + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(false)); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnJoinSessionAsyncComplete(false)).Times(1); + m_gameliftClientManager->JoinSessionAsync(request); +} + +TEST_F(AWSGameLiftClientManagerTest, JoinSessionAsync_CallWithValidRequestAndRequestHandler_GetSuccessOutcomeAndNotificationWithTrueResponse) +{ + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true)); + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + request.m_playerId = "dummyPlayerId"; + Aws::GameLift::Model::CreatePlayerSessionResult result; + result.SetPlayerSession(Aws::GameLift::Model::PlayerSession()); + Aws::GameLift::Model::CreatePlayerSessionOutcome outcome(result); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), CreatePlayerSession(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnJoinSessionAsyncComplete(true)).Times(1); + m_gameliftClientManager->JoinSessionAsync(request); +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessions_CallWithValidRequestAndErrorOutcome_GetErrorWithEmptyResponse) +{ + AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); + + Aws::Client::AWSError error; + Aws::GameLift::Model::SearchGameSessionsOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_gameliftClientManager->SearchSessions(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_TRUE(result.m_sessionConfigs.size() == 0); +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessions_CallWithValidRequestAndSuccessOutcome_GetNotificationWithValidResponse) +{ + AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); + + Aws::GameLift::Model::SearchGameSessionsOutcome outcome = GetValidSearchGameSessionsOutcome(); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AzFramework::SearchSessionsResponse expectedResponse = GetValidSearchSessionsResponse(); + auto result = m_gameliftClientManager->SearchSessions(request); + EXPECT_TRUE(result.m_sessionConfigs.size() != 0); +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithoutClientSetup_GetErrorWithEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + EXPECT_FALSE(m_gameliftClientManager->ConfigureGameLiftClient("")); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + + AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); + + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, + OnSearchSessionsAsyncComplete(SearchSessionsResponseMatcher(AzFramework::SearchSessionsResponse()))).Times(1); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->SearchSessionsAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithInvalidRequest_GetErrorWithEmptyResponse) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, + OnSearchSessionsAsyncComplete(SearchSessionsResponseMatcher(AzFramework::SearchSessionsResponse()))).Times(1); + + m_gameliftClientManager->SearchSessionsAsync(AzFramework::SearchSessionsRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithValidRequestAndErrorOutcome_GetErrorWithEmptyResponse) +{ + AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); + + AWSCoreRequestsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + + Aws::Client::AWSError error; + Aws::GameLift::Model::SearchGameSessionsOutcome outcome(error); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, + OnSearchSessionsAsyncComplete(SearchSessionsResponseMatcher(AzFramework::SearchSessionsResponse()))).Times(1); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->SearchSessionsAsync(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, SearchSessionsAsync_CallWithValidRequestAndSuccessOutcome_GetNotificationWithValidResponse) +{ + AWSGameLiftSearchSessionsRequest request = GetValidSearchSessionsRequest(); + + AWSCoreRequestsHandlerMock coreHandlerMock; + EXPECT_CALL(coreHandlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get())); + + Aws::GameLift::Model::SearchGameSessionsOutcome outcome = GetValidSearchGameSessionsOutcome(); + EXPECT_CALL(*(m_gameliftClientManager->m_gameliftClientMockPtr), SearchGameSessions(::testing::_)) + .Times(1) + .WillOnce(::testing::Return(outcome)); + + AzFramework::SearchSessionsResponse expectedResponse = GetValidSearchSessionsResponse(); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, + OnSearchSessionsAsyncComplete(SearchSessionsResponseMatcher(expectedResponse))).Times(1); + + m_gameliftClientManager->SearchSessionsAsync(request); +} + +TEST_F(AWSGameLiftClientManagerTest, LeaveSession_CallWithInterfaceNotRegistered_GetExpectedError) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->LeaveSession(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, LeaveSession_CallWithInterfaceRegistered_LeaveSessionRequestSent) +{ + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerLeaveSession).Times(1); + + m_gameliftClientManager->LeaveSession(); +} + +TEST_F(AWSGameLiftClientManagerTest, LeaveSessionAsync_CallWithInterfaceNotRegistered_GetExpectedError) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + m_gameliftClientManager->LeaveSessionAsync(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message +} + +TEST_F(AWSGameLiftClientManagerTest, LeaveSessionAsync_CallWithInterfaceRegistered_LeaveSessionAsyncRequestSentAndGetNotification) +{ + SessionHandlingClientRequestsMock handlerMock; + EXPECT_CALL(handlerMock, RequestPlayerLeaveSession).Times(1); + SessionAsyncRequestNotificationsHandlerMock sessionHandlerMock; + EXPECT_CALL(sessionHandlerMock, OnLeaveSessionAsyncComplete()).Times(1); + + m_gameliftClientManager->LeaveSessionAsync(); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h new file mode 100644 index 0000000000..e8699a5221 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientMocks.h @@ -0,0 +1,86 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Aws::GameLift; + +class GameLiftClientMock + : public GameLiftClient +{ +public: + GameLiftClientMock() + : GameLiftClient(Aws::Auth::AWSCredentials()) + { + } + + MOCK_CONST_METHOD1(CreateGameSession, Model::CreateGameSessionOutcome(const Model::CreateGameSessionRequest&)); + MOCK_CONST_METHOD1(CreatePlayerSession, Model::CreatePlayerSessionOutcome(const Model::CreatePlayerSessionRequest&)); + MOCK_CONST_METHOD1(SearchGameSessions, Model::SearchGameSessionsOutcome(const Model::SearchGameSessionsRequest&)); + MOCK_CONST_METHOD1(StartGameSessionPlacement, Model::StartGameSessionPlacementOutcome(const Model::StartGameSessionPlacementRequest&)); +}; + +class SessionAsyncRequestNotificationsHandlerMock + : public AzFramework::SessionAsyncRequestNotificationBus::Handler +{ +public: + SessionAsyncRequestNotificationsHandlerMock() + { + AzFramework::SessionAsyncRequestNotificationBus::Handler::BusConnect(); + } + + ~SessionAsyncRequestNotificationsHandlerMock() + { + AzFramework::SessionAsyncRequestNotificationBus::Handler::BusDisconnect(); + } + + MOCK_METHOD1(OnCreateSessionAsyncComplete, void(const AZStd::string&)); + MOCK_METHOD1(OnSearchSessionsAsyncComplete, void(const AzFramework::SearchSessionsResponse&)); + MOCK_METHOD1(OnJoinSessionAsyncComplete, void(bool)); + MOCK_METHOD0(OnLeaveSessionAsyncComplete, void()); +}; + +class SessionHandlingClientRequestsMock + : public AzFramework::ISessionHandlingClientRequests +{ +public: + SessionHandlingClientRequestsMock() + { + AZ::Interface::Register(this); + } + + virtual ~SessionHandlingClientRequestsMock() + { + AZ::Interface::Unregister(this); + } + + MOCK_METHOD1(RequestPlayerJoinSession, bool(const AzFramework::SessionConnectionConfig&)); + MOCK_METHOD0(RequestPlayerLeaveSession, void()); +}; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp new file mode 100644 index 0000000000..f47a2cc60c --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientSystemComponentTest.cpp @@ -0,0 +1,156 @@ +/* +* 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. +* +*/ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace AWSGameLift; + +class AWSGameLiftClientManagerMock + : public AWSGameLiftClientManager +{ +public: + AWSGameLiftClientManagerMock() = default; + ~AWSGameLiftClientManagerMock() = default; + + MOCK_METHOD0(ActivateManager, void()); + MOCK_METHOD0(DeactivateManager, void()); +}; + +class TestAWSGameLiftClientSystemComponent + : public AWSGameLiftClientSystemComponent +{ +public: + TestAWSGameLiftClientSystemComponent() + { + m_gameliftClientManagerMockPtr = nullptr; + } + ~TestAWSGameLiftClientSystemComponent() + { + m_gameliftClientManagerMockPtr = nullptr; + } + + void SetUpMockManager() + { + AZStd::unique_ptr gameliftClientManagerMock = AZStd::make_unique(); + m_gameliftClientManagerMockPtr = gameliftClientManagerMock.get(); + SetGameLiftClientManager(AZStd::move(gameliftClientManagerMock)); + } + + AWSGameLiftClientManagerMock* m_gameliftClientManagerMockPtr; +}; + +class AWSCoreSystemComponentMock + : public AZ::Component +{ +public: + AZ_COMPONENT(AWSCoreSystemComponentMock, "{52DB1342-30C6-412F-B7CC-B23F8B0629EA}"); + + static void Reflect(AZ::ReflectContext* context) + { + AZ_UNUSED(context); + } + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("AWSCoreService")); + } + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + AZ_UNUSED(incompatible); + } + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + AZ_UNUSED(required); + } + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } + + AWSCoreSystemComponentMock() = default; + ~AWSCoreSystemComponentMock() = default; + + void Init() override {} + void Activate() override {} + void Deactivate() override {} +}; + +class AWSGameLiftClientSystemComponentTest + : public AWSGameLiftClientFixture +{ +protected: + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_coreComponentDescriptor; + AZStd::unique_ptr m_gameliftClientComponentDescriptor; + + void SetUp() override + { + AWSGameLiftClientFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + m_serializeContext->CreateEditContext(); + m_behaviorContext = AZStd::make_unique(); + m_coreComponentDescriptor.reset(AWSCoreSystemComponentMock::CreateDescriptor()); + m_gameliftClientComponentDescriptor.reset(TestAWSGameLiftClientSystemComponent::CreateDescriptor()); + m_gameliftClientComponentDescriptor->Reflect(m_serializeContext.get()); + m_gameliftClientComponentDescriptor->Reflect(m_behaviorContext.get()); + + m_entity = aznew AZ::Entity(); + m_coreSystemComponent = AZStd::make_unique(); + m_entity->AddComponent(m_coreSystemComponent.get()); + m_gameliftClientSystemComponent = AZStd::make_unique(); + m_gameliftClientSystemComponent->SetUpMockManager(); + m_entity->AddComponent(m_gameliftClientSystemComponent.get()); + } + + void TearDown() override + { + m_entity->RemoveComponent(m_gameliftClientSystemComponent.get()); + m_gameliftClientSystemComponent.reset(); + m_entity->RemoveComponent(m_coreSystemComponent.get()); + m_coreSystemComponent.reset(); + delete m_entity; + m_entity = nullptr; + + m_gameliftClientComponentDescriptor.reset(); + m_coreComponentDescriptor.reset(); + m_behaviorContext.reset(); + m_serializeContext.reset(); + + AWSGameLiftClientFixture::TearDown(); + } + +public: + AZStd::unique_ptr m_coreSystemComponent; + AZStd::unique_ptr m_gameliftClientSystemComponent; + AZ::Entity* m_entity; +}; + +TEST_F(AWSGameLiftClientSystemComponentTest, ActivateDeactivate_Call_GameLiftClientManagerGetsInvoked) +{ + m_entity->Init(); + EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientManagerMockPtr), ActivateManager()).Times(1); + m_entity->Activate(); + + EXPECT_CALL(*(m_gameliftClientSystemComponent->m_gameliftClientManagerMockPtr), DeactivateManager()).Times(1); + m_entity->Deactivate(); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientTest.cpp new file mode 100644 index 0000000000..dfe3edaa96 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/AWSGameLiftClientTest.cpp @@ -0,0 +1,15 @@ +/* + * 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. + * + */ + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionActivityTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionActivityTest.cpp new file mode 100644 index 0000000000..4ae89eada7 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionActivityTest.cpp @@ -0,0 +1,81 @@ +/* + * 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. + * + */ + +#include +#include + +using namespace AWSGameLift; + +using AWSGameLiftCreateSessionActivityTest = AWSGameLiftClientFixture; + +TEST_F(AWSGameLiftCreateSessionActivityTest, BuildAWSGameLiftCreateGameSessionRequest_Call_GetExpectedResult) +{ + AWSGameLiftCreateSessionRequest request; + request.m_creatorId = "dummyCreatorId"; + request.m_sessionName = "dummySessionName"; + request.m_maxPlayer = 1; + request.m_sessionProperties.emplace("dummyKey", "dummyValue"); + request.m_aliasId = "dummyAliasId"; + request.m_fleetId = "dummyFleetId"; + request.m_idempotencyToken = "dummyIdempotencyToken"; + auto awsRequest = CreateSessionActivity::BuildAWSGameLiftCreateGameSessionRequest(request); + + EXPECT_TRUE(strcmp(awsRequest.GetCreatorId().c_str(), request.m_creatorId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetName().c_str(), request.m_sessionName.c_str()) == 0); + EXPECT_TRUE(awsRequest.GetMaximumPlayerSessionCount() == request.m_maxPlayer); + EXPECT_TRUE(strcmp(awsRequest.GetGameProperties()[0].GetKey().c_str(), request.m_sessionProperties.begin()->first.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetGameProperties()[0].GetValue().c_str(), request.m_sessionProperties.begin()->second.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetAliasId().c_str(), request.m_aliasId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetFleetId().c_str(), request.m_fleetId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetIdempotencyToken().c_str(), request.m_idempotencyToken.c_str()) == 0); +} + +TEST_F(AWSGameLiftCreateSessionActivityTest, ValidateCreateSessionRequest_CallWithBaseType_GetFalseResult) +{ + auto result = CreateSessionActivity::ValidateCreateSessionRequest(AzFramework::CreateSessionRequest()); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionActivityTest, ValidateCreateSessionRequest_CallWithNegativeMaxPlayer_GetFalseResult) +{ + AWSGameLiftCreateSessionRequest request; + request.m_maxPlayer = -1; + + auto result = CreateSessionActivity::ValidateCreateSessionRequest(request); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionActivityTest, ValidateCreateSessionRequest_CallWithoutAliasOrFleetId_GetFalseResult) +{ + AWSGameLiftCreateSessionRequest request; + request.m_maxPlayer = 1; + auto result = CreateSessionActivity::ValidateCreateSessionRequest(request); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionActivityTest, ValidateCreateSessionRequest_CallWithAliasId_GetTrueResult) +{ + AWSGameLiftCreateSessionRequest request; + request.m_maxPlayer = 1; + request.m_aliasId = "dummyAliasId"; + auto result = CreateSessionActivity::ValidateCreateSessionRequest(request); + EXPECT_TRUE(result); +} + +TEST_F(AWSGameLiftCreateSessionActivityTest, ValidateCreateSessionRequest_CallWithFleetId_GetTrueResult) +{ + AWSGameLiftCreateSessionRequest request; + request.m_maxPlayer = 1; + request.m_fleetId = "dummyFleetId"; + auto result = CreateSessionActivity::ValidateCreateSessionRequest(request); + EXPECT_TRUE(result); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionOnQueueActivityTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionOnQueueActivityTest.cpp new file mode 100644 index 0000000000..98eebfca77 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftCreateSessionOnQueueActivityTest.cpp @@ -0,0 +1,79 @@ +/* + * 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. + * + */ + +#include +#include + +using namespace AWSGameLift; + +using AWSGameLiftCreateSessionOnQueueActivityTest = AWSGameLiftClientFixture; + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, BuildAWSGameLiftCreateGameSessionRequest_Call_GetExpectedResult) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_sessionName = "dummySessionName"; + request.m_maxPlayer = 1; + request.m_sessionProperties.emplace("dummyKey", "dummyValue"); + request.m_queueName = "dummyQueueName"; + request.m_placementId = "dummyPlacementId"; + auto awsRequest = CreateSessionOnQueueActivity::BuildAWSGameLiftStartGameSessionPlacementRequest(request); + + EXPECT_TRUE(strcmp(awsRequest.GetGameSessionName().c_str(), request.m_sessionName.c_str()) == 0); + EXPECT_TRUE(awsRequest.GetMaximumPlayerSessionCount() == request.m_maxPlayer); + EXPECT_TRUE(strcmp(awsRequest.GetGameProperties()[0].GetKey().c_str(), request.m_sessionProperties.begin()->first.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetGameProperties()[0].GetValue().c_str(), request.m_sessionProperties.begin()->second.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetGameSessionQueueName().c_str(), request.m_queueName.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetPlacementId().c_str(), request.m_placementId.c_str()) == 0); +} + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, ValidateCreateSessionOnQueueRequest_CallWithBaseType_GetFalseResult) +{ + auto result = CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(AzFramework::CreateSessionRequest()); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, ValidateCreateSessionOnQueueRequest_CallWithNegativeMaxPlayer_GetFalseResult) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_maxPlayer = -1; + + auto result = CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(request); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, ValidateCreateSessionOnQueueRequest_CallWithoutQueueName_GetFalseResult) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_maxPlayer = 1; + request.m_placementId = "dummyPlacementId"; + auto result = CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(request); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, ValidateCreateSessionOnQueueRequest_CallWithoutPlacementId_GetFalseResult) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_maxPlayer = 1; + request.m_queueName = "dummyQueueName"; + auto result = CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(request); + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftCreateSessionOnQueueActivityTest, ValidateCreateSessionOnQueueRequest_CallWithValidRequest_GetTrueResult) +{ + AWSGameLiftCreateSessionOnQueueRequest request; + request.m_maxPlayer = 1; + request.m_queueName = "dummyQueueName"; + request.m_placementId = "dummyPlacementId"; + auto result = CreateSessionOnQueueActivity::ValidateCreateSessionOnQueueRequest(request); + EXPECT_TRUE(result); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftJoinSessionActivityTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftJoinSessionActivityTest.cpp new file mode 100644 index 0000000000..8dcafe18aa --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftJoinSessionActivityTest.cpp @@ -0,0 +1,84 @@ +/* + * 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. + * + */ + +#include +#include + +using namespace AWSGameLift; + +using AWSGameLiftJoinSessionActivityTest = AWSGameLiftClientFixture; + +TEST_F(AWSGameLiftJoinSessionActivityTest, BuildAWSGameLiftCreatePlayerSessionRequest_Call_GetExpectedResult) +{ + AWSGameLiftJoinSessionRequest request; + request.m_playerData = "dummyPlayerData"; + request.m_playerId = "dummyPlayerId"; + request.m_sessionId = "dummySessionId"; + auto awsRequest = JoinSessionActivity::BuildAWSGameLiftCreatePlayerSessionRequest(request); + + EXPECT_TRUE(strcmp(awsRequest.GetPlayerData().c_str(), request.m_playerData.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetPlayerId().c_str(), request.m_playerId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetGameSessionId().c_str(), request.m_sessionId.c_str()) == 0); +} + +TEST_F(AWSGameLiftJoinSessionActivityTest, BuildSessionConnectionConfig_Call_GetExpectedResult) +{ + Aws::GameLift::Model::PlayerSession playerSession; + playerSession.SetIpAddress("dummyIpAddress"); + playerSession.SetPlayerSessionId("dummyPlayerSessionId"); + playerSession.SetPort(123); + Aws::GameLift::Model::CreatePlayerSessionResult createPlayerSessionResult; + createPlayerSessionResult.SetPlayerSession(playerSession); + Aws::GameLift::Model::CreatePlayerSessionOutcome createPlayerSessionOutcome(createPlayerSessionResult); + auto connectionConfig = JoinSessionActivity::BuildSessionConnectionConfig(createPlayerSessionOutcome); + + EXPECT_TRUE(strcmp(connectionConfig.m_ipAddress.c_str(), playerSession.GetIpAddress().c_str()) == 0); + EXPECT_TRUE(strcmp(connectionConfig.m_playerSessionId.c_str(), playerSession.GetPlayerSessionId().c_str()) == 0); + EXPECT_TRUE(connectionConfig.m_port == playerSession.GetPort()); +} + +TEST_F(AWSGameLiftJoinSessionActivityTest, ValidateJoinSessionRequest_CallWithBaseType_GetFalseResult) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = JoinSessionActivity::ValidateJoinSessionRequest(AzFramework::JoinSessionRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftJoinSessionActivityTest, ValidateJoinSessionRequest_CallWithEmptyPlayerId_GetFalseResult) +{ + AWSGameLiftJoinSessionRequest request; + request.m_sessionId = "dummySessionId"; + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = JoinSessionActivity::ValidateJoinSessionRequest(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftJoinSessionActivityTest, ValidateJoinSessionRequest_CallWithEmptySessionId_GetFalseResult) +{ + AWSGameLiftJoinSessionRequest request; + request.m_playerId = "dummyPlayerId"; + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = JoinSessionActivity::ValidateJoinSessionRequest(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftJoinSessionActivityTest, ValidateJoinSessionRequest_CallWithPlayerAndSessionId_GetTrueResult) +{ + AWSGameLiftJoinSessionRequest request; + request.m_playerId = "dummyPlayerId"; + request.m_sessionId = "dummySessionId"; + auto result = JoinSessionActivity::ValidateJoinSessionRequest(request); + EXPECT_TRUE(result); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftSearchSessionsActivityTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftSearchSessionsActivityTest.cpp new file mode 100644 index 0000000000..8ca2727dec --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/Tests/Activity/AWSGameLiftSearchSessionsActivityTest.cpp @@ -0,0 +1,126 @@ +/* + * 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. + * + */ + +#include + +#include +#include +#include + +using namespace AWSGameLift; + +using AWSGameLiftSearchSessionsActivityTest = AWSGameLiftClientFixture; + +TEST_F(AWSGameLiftSearchSessionsActivityTest, BuildAWSGameLiftSearchGameSessionsRequest_Call_GetExpectedResult) +{ + AWSGameLiftSearchSessionsRequest request; + request.m_aliasId = "dummyAliasId"; + request.m_fleetId = "dummyFleetId"; + request.m_location = "dummyLocation"; + request.m_filterExpression = "dummyFilterExpression"; + request.m_sortExpression = "dummySortExpression"; + request.m_maxResult = 1; + request.m_nextToken = "dummyNextToken"; + + auto awsRequest = SearchSessionsActivity::BuildAWSGameLiftSearchGameSessionsRequest(request); + + EXPECT_TRUE(strcmp(awsRequest.GetFleetId().c_str(), request.m_fleetId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetAliasId().c_str(), request.m_aliasId.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetFilterExpression().c_str(), request.m_filterExpression.c_str()) == 0); + EXPECT_TRUE(strcmp(awsRequest.GetSortExpression().c_str(), request.m_sortExpression.c_str()) == 0); + EXPECT_TRUE(awsRequest.GetLimit() == request.m_maxResult); + EXPECT_TRUE(strcmp(awsRequest.GetNextToken().c_str(), request.m_nextToken.c_str()) == 0); + // TODO: Update the AWS Native SDK to get the new request attributes. + //EXPECT_TRUE(strcmp(awsRequest.GetLocation().c_str(), request.m_location.c_str()) == 0); +} + +TEST_F(AWSGameLiftSearchSessionsActivityTest, ValidateSearchSessionsRequest_CallWithBaseType_GetFalseResult) +{ + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = SearchSessionsActivity::ValidateSearchSessionsRequest(AzFramework::SearchSessionsRequest()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftSearchSessionsActivityTest, ValidateSearchSessionsRequest_CallWithoutAliasOrFleetId_GetFalseResult) +{ + AWSGameLiftSearchSessionsRequest request; + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = SearchSessionsActivity::ValidateSearchSessionsRequest(request); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message + EXPECT_FALSE(result); +} + +TEST_F(AWSGameLiftSearchSessionsActivityTest, ValidateSearchSessionsRequest_CallWithAliasId_GetTrueResult) +{ + AWSGameLiftSearchSessionsRequest request; + request.m_aliasId = "dummyAliasId"; + auto result = SearchSessionsActivity::ValidateSearchSessionsRequest(request); + EXPECT_TRUE(result); +} + +TEST_F(AWSGameLiftSearchSessionsActivityTest, ValidateSearchSessionsRequest_CallWithFleetId_GetTrueResult) +{ + AWSGameLiftSearchSessionsRequest request; + request.m_fleetId = "dummyFleetId"; + auto result = SearchSessionsActivity::ValidateSearchSessionsRequest(request); + EXPECT_TRUE(result); +} + +TEST_F(AWSGameLiftSearchSessionsActivityTest, ParseResponse_Call_GetExpectedResult) +{ + Aws::GameLift::Model::GameProperty gameProperty; + gameProperty.SetKey("dummyKey"); + gameProperty.SetValue("dummyValue"); + Aws::Vector gameProperties = { gameProperty }; + + Aws::GameLift::Model::GameSession gameSession; + gameSession.SetCreationTime(Aws::Utils::DateTime(0.0)); + gameSession.SetTerminationTime(Aws::Utils::DateTime(0.0)); + gameSession.SetCreatorId("dummyCreatorId"); + gameSession.SetGameProperties(gameProperties); + gameSession.SetGameSessionId("dummyGameSessionId"); + gameSession.SetName("dummyGameSessionName"); + gameSession.SetIpAddress("dummyIpAddress"); + gameSession.SetPort(0); + gameSession.SetMaximumPlayerSessionCount(2); + gameSession.SetCurrentPlayerSessionCount(1); + gameSession.SetStatus(Aws::GameLift::Model::GameSessionStatus::TERMINATED); + gameSession.SetStatusReason(Aws::GameLift::Model::GameSessionStatusReason::INTERRUPTED); + // TODO: Update the AWS Native SDK to set the new game session attributes. + //gameSession.SetDnsName("dummyDnsName"); + Aws::Vector gameSessions = { gameSession }; + + Aws::GameLift::Model::SearchGameSessionsResult result; + result.SetNextToken("dummyNextToken"); + result.SetGameSessions(gameSessions); + + auto response = SearchSessionsActivity::ParseResponse(result); + + EXPECT_TRUE(strcmp(response.m_nextToken.c_str(), result.GetNextToken().c_str()) == 0); + EXPECT_EQ(response.m_sessionConfigs.size(), 1); + + const auto& sessionConfig = response.m_sessionConfigs[0]; + EXPECT_EQ(gameSession.GetCreationTime().Millis(), sessionConfig.m_creationTime); + EXPECT_EQ(gameSession.GetTerminationTime().Millis(), sessionConfig.m_terminationTime); + EXPECT_TRUE(strcmp(gameSession.GetCreatorId().c_str(), sessionConfig.m_creatorId.c_str()) == 0); + EXPECT_TRUE(strcmp(gameSession.GetGameSessionId().c_str(), sessionConfig.m_sessionId.c_str()) == 0); + EXPECT_TRUE(strcmp(gameSession.GetName().c_str(), sessionConfig.m_sessionName.c_str()) == 0); + EXPECT_TRUE(strcmp(gameSession.GetIpAddress().c_str(), sessionConfig.m_ipAddress.c_str()) == 0); + EXPECT_EQ(gameSession.GetPort(), 0); + EXPECT_EQ(gameSession.GetMaximumPlayerSessionCount(), 2); + EXPECT_EQ(gameSession.GetCurrentPlayerSessionCount(), 1); + EXPECT_TRUE(strcmp(AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()], sessionConfig.m_status.c_str()) == 0); + EXPECT_TRUE(strcmp(AWSGameLiftSessionStatusReasons[(int)gameSession.GetStatusReason()], sessionConfig.m_statusReason.c_str()) == 0); + // TODO: Update the AWS Native SDK to get the new game session attributes. + // EXPECT_TRUE(strcmp(gameSession.GetDnsName().c_str(), sessionConfig.m_dnsName.c_str()) == 0); +} diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake new file mode 100644 index 0000000000..78a69268f4 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_files.cmake @@ -0,0 +1,36 @@ +# +# 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. +# + +set(FILES + Include/Request/AWSGameLiftCreateSessionOnQueueRequest.h + Include/Request/AWSGameLiftCreateSessionRequest.h + Include/Request/AWSGameLiftJoinSessionRequest.h + Include/Request/AWSGameLiftSearchSessionsRequest.h + Include/Request/IAWSGameLiftRequests.h + Source/Activity/AWSGameLiftCreateSessionActivity.cpp + Source/Activity/AWSGameLiftCreateSessionActivity.h + Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.cpp + Source/Activity/AWSGameLiftCreateSessionOnQueueActivity.h + Source/Activity/AWSGameLiftJoinSessionActivity.cpp + Source/Activity/AWSGameLiftJoinSessionActivity.h + Source/Activity/AWSGameLiftLeaveSessionActivity.cpp + Source/Activity/AWSGameLiftLeaveSessionActivity.h + Source/Activity/AWSGameLiftSearchSessionsActivity.cpp + Source/Activity/AWSGameLiftSearchSessionsActivity.h + Source/AWSGameLiftClientManager.cpp + Source/AWSGameLiftClientManager.h + Source/AWSGameLiftClientSystemComponent.cpp + Source/AWSGameLiftClientSystemComponent.h + Source/Request/AWSGameLiftCreateSessionOnQueueRequest.cpp + Source/Request/AWSGameLiftCreateSessionRequest.cpp + Source/Request/AWSGameLiftJoinSessionRequest.cpp + Source/Request/AWSGameLiftSearchSessionsRequest.cpp +) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_shared_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_shared_files.cmake new file mode 100644 index 0000000000..73caae35e1 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_shared_files.cmake @@ -0,0 +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. +# + +set(FILES + Source/AWSGameLiftClientModule.cpp +) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake new file mode 100644 index 0000000000..5cd34f02d3 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftClient/awsgamelift_client_tests_files.cmake @@ -0,0 +1,22 @@ +# +# 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. +# + +set(FILES + Tests/Activity/AWSGameLiftCreateSessionActivityTest.cpp + Tests/Activity/AWSGameLiftCreateSessionOnQueueActivityTest.cpp + Tests/Activity/AWSGameLiftJoinSessionActivityTest.cpp + Tests/Activity/AWSGameLiftSearchSessionsActivityTest.cpp + Tests/AWSGameLiftClientFixture.h + Tests/AWSGameLiftClientManagerTest.cpp + Tests/AWSGameLiftClientMocks.h + Tests/AWSGameLiftClientSystemComponentTest.cpp + Tests/AWSGameLiftClientTest.cpp +) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h b/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h new file mode 100644 index 0000000000..527c939b8c --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h @@ -0,0 +1,24 @@ +/* + * 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 + +namespace AWSGameLift +{ + // Reference https://sdk.amazonaws.com/cpp/api/LATEST/_game_session_status_8h_source.html + static const char* AWSGameLiftSessionStatusNames[6] = { "NotSet", "Active", "Activating", "Terminated", "Terminating", "Error"}; + + // Reference https://sdk.amazonaws.com/cpp/api/LATEST/_game_session_status_reason_8h.html + static const char* AWSGameLiftSessionStatusReasons[2] = { "NotSet", "Interrupted" }; + + static constexpr const char AWSGameLiftErrorMessageTemplate[] = "Exception: %s, Message: %s"; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/CMakeLists.txt b/Gems/AWSGameLift/Code/AWSGameLiftServer/CMakeLists.txt new file mode 100644 index 0000000000..b527010ab9 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/CMakeLists.txt @@ -0,0 +1,72 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if (NOT PAL_TRAIT_BUILD_SERVER_SUPPORTED) + return() +endif() + +ly_add_target( + NAME AWSGameLift.Server.Static STATIC + NAMESPACE Gem + FILES_CMAKE + awsgamelift_server_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + ../AWSGameLiftCommon/Source + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzFramework + 3rdParty::AWSGameLiftServerSDK + ) + +ly_add_target( + NAME AWSGameLift.Servers ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + awsgamelift_server_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + BUILD_DEPENDENCIES + PUBLIC + AZ::AzCore + Gem::AWSGameLift.Server.Static + 3rdParty::AWSGameLiftServerSDK +) + +################################################################################ +# Tests +################################################################################ +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_target( + NAME AWSGameLift.Server.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + awsgamelift_server_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + BUILD_DEPENDENCIES + PRIVATE + AZ::AzCore + AZ::AzFramework + AZ::AzTest + Gem::AWSGameLift.Server.Static + 3rdParty::AWSGameLiftServerSDK + ) + # Add AWSGameLift.Server.Tests to googletest + ly_add_googletest( + NAME Gem::AWSGameLift.Server.Tests + ) +endif() diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.cpp new file mode 100644 index 0000000000..0ac3326757 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.cpp @@ -0,0 +1,319 @@ +/* + * 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. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace AWSGameLift +{ + AWSGameLiftServerManager::AWSGameLiftServerManager() + : m_serverSDKInitialized(false) + , m_gameLiftServerSDKWrapper(AZStd::make_unique()) + , m_connectedPlayers() + { + } + + AWSGameLiftServerManager::~AWSGameLiftServerManager() + { + m_gameLiftServerSDKWrapper.reset(); + m_connectedPlayers.clear(); + } + + bool AWSGameLiftServerManager::AddConnectedPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) + { + AZStd::lock_guard lock(m_gameliftMutex); + if (m_connectedPlayers.contains(playerConnectionConfig.m_playerConnectionId)) + { + if (m_connectedPlayers[playerConnectionConfig.m_playerConnectionId] != playerConnectionConfig.m_playerSessionId) + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerPlayerConnectionRegisteredErrorMessage, + playerConnectionConfig.m_playerConnectionId, playerConnectionConfig.m_playerSessionId.c_str()); + } + return false; + } + else + { + m_connectedPlayers.emplace(playerConnectionConfig.m_playerConnectionId, playerConnectionConfig.m_playerSessionId); + return true; + } + } + + AzFramework::SessionConfig AWSGameLiftServerManager::BuildSessionConfig(const Aws::GameLift::Server::Model::GameSession& gameSession) + { + AzFramework::SessionConfig sessionConfig; + + sessionConfig.m_dnsName = gameSession.GetDnsName().c_str(); + AZStd::string propertiesOutput = ""; + for (const auto& gameProperty : gameSession.GetGameProperties()) + { + sessionConfig.m_sessionProperties.emplace(gameProperty.GetKey().c_str(), gameProperty.GetValue().c_str()); + propertiesOutput += AZStd::string::format("{Key=%s,Value=%s},", gameProperty.GetKey().c_str(), gameProperty.GetValue().c_str()); + } + if (!propertiesOutput.empty()) + { + propertiesOutput = propertiesOutput.substr(0, propertiesOutput.size() - 1); // Trim last comma to fit array format + } + sessionConfig.m_sessionId = gameSession.GetGameSessionId().c_str(); + sessionConfig.m_ipAddress = gameSession.GetIpAddress().c_str(); + sessionConfig.m_maxPlayer = gameSession.GetMaximumPlayerSessionCount(); + sessionConfig.m_sessionName = gameSession.GetName().c_str(); + sessionConfig.m_port = gameSession.GetPort(); + sessionConfig.m_status = AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()]; + + AZ_TracePrintf(AWSGameLiftServerManagerName, + "Built SessionConfig with Name=%s, Id=%s, Status=%s, DnsName=%s, IpAddress=%s, Port=%d, MaxPlayer=%d and Properties=%s", + sessionConfig.m_sessionName.c_str(), + sessionConfig.m_sessionId.c_str(), + sessionConfig.m_status.c_str(), + sessionConfig.m_dnsName.c_str(), + sessionConfig.m_ipAddress.c_str(), + sessionConfig.m_port, + sessionConfig.m_maxPlayer, + AZStd::string::format("[%s]", propertiesOutput.c_str()).c_str()); + + return sessionConfig; + } + + AZ::IO::Path AWSGameLiftServerManager::GetExternalSessionCertificate() + { + // TODO: Add support to get TLS cert file path + return AZ::IO::Path(); + } + + AZ::IO::Path AWSGameLiftServerManager::GetInternalSessionCertificate() + { + // GameLift doesn't support it, return empty path + return AZ::IO::Path(); + } + + bool AWSGameLiftServerManager::InitializeGameLiftServerSDK() + { + if (m_serverSDKInitialized) + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKAlreadyInitErrorMessage); + return false; + } + + AZ_TracePrintf(AWSGameLiftServerManagerName, "Initiating Amazon GameLift Server SDK..."); + Aws::GameLift::Server::InitSDKOutcome initOutcome = m_gameLiftServerSDKWrapper->InitSDK(); + m_serverSDKInitialized = initOutcome.IsSuccess(); + + AZ_Error(AWSGameLiftServerManagerName, m_serverSDKInitialized, + AWSGameLiftServerInitSDKErrorMessage, initOutcome.GetError().GetErrorMessage().c_str()); + + return m_serverSDKInitialized; + } + + void AWSGameLiftServerManager::HandleDestroySession() + { + // No further request should be handled by GameLift server manager at this point + if (AZ::Interface::Get()) + { + AZ::Interface::Unregister(this); + } + + AZ_TracePrintf(AWSGameLiftServerManagerName, "Server process is scheduled to be shut down at %s", + m_gameLiftServerSDKWrapper->GetTerminationTime().c_str()); + + // Send notifications to handler(s) to gracefully shut down the server process. + bool destroySessionResult = true; + AZ::EBusReduceResult> result(destroySessionResult); + AzFramework::SessionNotificationBus::BroadcastResult(result, &AzFramework::SessionNotifications::OnDestroySessionBegin); + + if (!destroySessionResult) + { + AZ_Error("AWSGameLift", destroySessionResult, AWSGameLiftServerGameSessionDestroyErrorMessage); + return; + } + + AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ending..."); + Aws::GameLift::GenericOutcome processEndingOutcome = m_gameLiftServerSDKWrapper->ProcessEnding(); + bool processEndingIsSuccess = processEndingOutcome.IsSuccess(); + + AZ_Error(AWSGameLiftServerManagerName, processEndingIsSuccess, AWSGameLiftServerProcessEndingErrorMessage, + processEndingOutcome.GetError().GetErrorMessage().c_str()); + } + + void AWSGameLiftServerManager::HandlePlayerLeaveSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) + { + AZStd::string playerSessionId = ""; + RemoveConnectedPlayer(playerConnectionConfig.m_playerConnectionId, playerSessionId); + if (playerSessionId.empty()) + { + return; + } + + Aws::GameLift::GenericOutcome disconnectOutcome = m_gameLiftServerSDKWrapper->RemovePlayerSession(playerSessionId); + AZ_Error(AWSGameLiftServerManagerName, disconnectOutcome.IsSuccess(), AWSGameLiftServerRemovePlayerSessionErrorMessage, + playerSessionId.c_str(), disconnectOutcome.GetError().GetErrorMessage().c_str()); + } + + bool AWSGameLiftServerManager::NotifyGameLiftProcessReady(const GameLiftServerProcessDesc& desc) + { + if (!m_serverSDKInitialized) + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage); + return false; + } + + AZ_Warning(AWSGameLiftServerManagerName, desc.m_port != 0, AWSGameLiftServerTempPortErrorMessage); + + AZ::JobContext* jobContext = nullptr; + AZ::JobManagerBus::BroadcastResult(jobContext, &AZ::JobManagerEvents::GetGlobalContext); + AZ::Job* processReadyJob = AZ::CreateJobFunction( + [this, desc]() { + // The GameLift ProcessParameters object expects an vector (std::vector) of standard strings (std::string) as the log paths. + std::vector logPaths; + for (const AZStd::string& path : desc.m_logPaths) + { + logPaths.push_back(path.c_str()); + } + + Aws::GameLift::Server::ProcessParameters processReadyParameter = Aws::GameLift::Server::ProcessParameters( + AZStd::bind(&AWSGameLiftServerManager::OnStartGameSession, this, AZStd::placeholders::_1), + AZStd::bind(&AWSGameLiftServerManager::OnUpdateGameSession, this), + AZStd::bind(&AWSGameLiftServerManager::OnProcessTerminate, this), + AZStd::bind(&AWSGameLiftServerManager::OnHealthCheck, this), desc.m_port, + Aws::GameLift::Server::LogParameters(logPaths)); + + AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ready..."); + auto processReadyOutcome = m_gameLiftServerSDKWrapper->ProcessReady(processReadyParameter); + + if (!processReadyOutcome.IsSuccess()) + { + AZ_Error(AWSGameLiftServerManagerName, false, + AWSGameLiftServerProcessReadyErrorMessage, processReadyOutcome.GetError().GetErrorMessage().c_str()); + this->HandleDestroySession(); + } + }, true, jobContext); + processReadyJob->Start(); + return true; + } + + void AWSGameLiftServerManager::OnStartGameSession(const Aws::GameLift::Server::Model::GameSession& gameSession) + { + AzFramework::SessionConfig sessionConfig = BuildSessionConfig(gameSession); + + bool createSessionResult = true; + AZ::EBusReduceResult> result(createSessionResult); + AzFramework::SessionNotificationBus::BroadcastResult( + result, &AzFramework::SessionNotifications::OnCreateSessionBegin, sessionConfig); + + if (createSessionResult) + { + AZ_TracePrintf(AWSGameLiftServerManagerName, "Activating GameLift game session..."); + Aws::GameLift::GenericOutcome activationOutcome = m_gameLiftServerSDKWrapper->ActivateGameSession(); + + if (activationOutcome.IsSuccess()) + { + // Register server manager as handler once game session has been activated + if (!AZ::Interface::Get()) + { + AZ::Interface::Register(this); + } + } + else + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerActivateGameSessionErrorMessage, + activationOutcome.GetError().GetErrorMessage().c_str()); + HandleDestroySession(); + } + } + else + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerGameInitErrorMessage); + HandleDestroySession(); + } + } + + void AWSGameLiftServerManager::OnProcessTerminate() + { + AZ_TracePrintf(AWSGameLiftServerManagerName, "GameLift is shutting down server process..."); + + HandleDestroySession(); + } + + bool AWSGameLiftServerManager::OnHealthCheck() + { + bool healthCheckResult = true; + AZ::EBusReduceResult> result(healthCheckResult); + AzFramework::SessionNotificationBus::BroadcastResult(result, &AzFramework::SessionNotifications::OnSessionHealthCheck); + + return m_serverSDKInitialized && healthCheckResult; + } + + void AWSGameLiftServerManager::OnUpdateGameSession() + { + // TODO: Perform game-specific tasks to prep for newly matched players + return; + } + + bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId) + { + AZStd::lock_guard lock(m_gameliftMutex); + if (m_connectedPlayers.contains(playerConnectionId)) + { + outPlayerSessionId = m_connectedPlayers[playerConnectionId]; + m_connectedPlayers.erase(playerConnectionId); + return true; + } + else + { + outPlayerSessionId = ""; + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerPlayerConnectionMissingErrorMessage, playerConnectionId); + return false; + } + } + + void AWSGameLiftServerManager::SetGameLiftServerSDKWrapper(AZStd::unique_ptr gameLiftServerSDKWrapper) + { + m_gameLiftServerSDKWrapper.reset(); + m_gameLiftServerSDKWrapper = AZStd::move(gameLiftServerSDKWrapper); + } + + bool AWSGameLiftServerManager::ValidatePlayerJoinSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) + { + uint32_t playerConnectionId = playerConnectionConfig.m_playerConnectionId; + AZStd::string playerSessionId = playerConnectionConfig.m_playerSessionId; + if (playerSessionId.empty()) + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerInvalidConnectionConfigErrorMessage, + playerConnectionId, playerSessionId.c_str()); + return false; + } + + if (!AddConnectedPlayer(playerConnectionConfig)) + { + return false; + } + + AZ_TracePrintf(AWSGameLiftServerManagerName, "Attempting to accept player session connection with Amazon GameLift service..."); + auto acceptPlayerSessionOutcome = m_gameLiftServerSDKWrapper->AcceptPlayerSession(playerSessionId.c_str()); + + if (!acceptPlayerSessionOutcome.IsSuccess()) + { + AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerAcceptPlayerSessionErrorMessage, + playerSessionId.c_str(), acceptPlayerSessionOutcome.GetError().GetErrorMessage().c_str()); + RemoveConnectedPlayer(playerConnectionId, playerSessionId); + return false; + } + return true; + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h new file mode 100644 index 0000000000..2457425ab1 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.h @@ -0,0 +1,128 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +namespace AWSGameLift +{ + class GameLiftServerSDKWrapper; + + //! GameLift server process settings. + struct GameLiftServerProcessDesc + { + AZStd::vector m_logPaths; //!< Log paths the servers will write to. Both relative to the game root folder and absolute paths supported. + + uint16_t m_port = 0; //!< The port the server will be listening on. + }; + + //! Manage the server process for hosting game sessions via GameLiftServerSDK. + class AWSGameLiftServerManager + : public AzFramework::ISessionHandlingProviderRequests + { + public: + static constexpr const char AWSGameLiftServerManagerName[] = "AWSGameLiftServerManager"; + static constexpr const char AWSGameLiftServerSDKNotInitErrorMessage[] = + "Amazon GameLift Server SDK is not initialized yet."; + static constexpr const char AWSGameLiftServerSDKAlreadyInitErrorMessage[] = + "Amazon GameLift Server SDK has already been initialized."; + static constexpr const char AWSGameLiftServerTempPortErrorMessage[] = + "No server port specified, server will be listening on ephemeral port."; + static constexpr const char AWSGameLiftServerGameInitErrorMessage[] = + "Failed to process game dependent initialization during OnStartGameSession."; + static constexpr const char AWSGameLiftServerGameSessionDestroyErrorMessage[] = + "Failed to destroy game session during OnProcessTerminate."; + static constexpr const char AWSGameLiftServerPlayerConnectionRegisteredErrorMessage[] = + "Player connection id %d is already registered to player session id %s. Remove connected player first."; + static constexpr const char AWSGameLiftServerPlayerConnectionMissingErrorMessage[] = + "Player connection id %d does not exist."; + + static constexpr const char AWSGameLiftServerInitSDKErrorMessage[] = + "Failed to initialize Amazon GameLift Server SDK. ErrorMessage: %s"; + static constexpr const char AWSGameLiftServerProcessReadyErrorMessage[] = + "Failed to notify GameLift server process ready. ErrorMessage: %s"; + static constexpr const char AWSGameLiftServerActivateGameSessionErrorMessage[] = + "Failed to activate GameLift game session. ErrorMessage: %s"; + static constexpr const char AWSGameLiftServerProcessEndingErrorMessage[] = + "Failed to notify GameLift server process ending. ErrorMessage: %s"; + static constexpr const char AWSGameLiftServerAcceptPlayerSessionErrorMessage[] = + "Failed to validate player session connection with id %s. ErrorMessage: %s"; + static constexpr const char AWSGameLiftServerInvalidConnectionConfigErrorMessage[] = + "Invalid player connection config, player connection id: %d, player session id: %s"; + static constexpr const char AWSGameLiftServerRemovePlayerSessionErrorMessage[] = + "Failed to notify GameLift that the player with the player session id %s has disconnected from the server process. ErrorMessage: %s"; + + AWSGameLiftServerManager(); + virtual ~AWSGameLiftServerManager(); + + //! Initialize GameLift API client by calling InitSDK(). + //! @return Whether the initialization is successful. + bool InitializeGameLiftServerSDK(); + + //! Notify GameLift that the server process is ready to host a game session. + //! @param desc GameLift server process settings. + //! @return Whether the ProcessReady notification is sent to GameLift. + bool NotifyGameLiftProcessReady(const GameLiftServerProcessDesc& desc); + + // ISessionHandlingProviderRequests interface implementation + void HandleDestroySession() override; + bool ValidatePlayerJoinSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) override; + void HandlePlayerLeaveSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) override; + AZ::IO::Path GetExternalSessionCertificate() override; + AZ::IO::Path GetInternalSessionCertificate() override; + + protected: + void SetGameLiftServerSDKWrapper(AZStd::unique_ptr gameLiftServerSDKWrapper); + + //! Add connected player session id. + bool AddConnectedPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig); + + private: + //! Build session config by using AWS GameLift Server GameSession Model. + AzFramework::SessionConfig BuildSessionConfig(const Aws::GameLift::Server::Model::GameSession& gameSession); + + //! Callback function that the GameLift service invokes to activate a new game session. + void OnStartGameSession(const Aws::GameLift::Server::Model::GameSession& gameSession); + + //! Callback function that the GameLift service invokes to pass an updated game session object to the server process. + void OnUpdateGameSession(); + + //! Callback function that the server process or GameLift service invokes to force the server process to shut down. + void OnProcessTerminate(); + + //! Callback function that the GameLift service invokes to request a health status report from the server process. + //! @return Whether the server process is healthy. + bool OnHealthCheck(); + + //! Remove connected player session id. + //! @param playerConnectionId Connection id of the player to remove. + //! @param outPlayerSessionId Session id of the removed player. Empty if the player cannot be removed. + //! @return Whether the player is removed successfully. + bool RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId); + + AZStd::unique_ptr m_gameLiftServerSDKWrapper; + bool m_serverSDKInitialized; + + AZStd::mutex m_gameliftMutex; + using PlayerConnectionId = uint32_t; + using PlayerSessionId = AZStd::string; + AZStd::unordered_map m_connectedPlayers; + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerModule.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerModule.cpp new file mode 100644 index 0000000000..32368efdad --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerModule.cpp @@ -0,0 +1,49 @@ +/* + * 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. + * + */ + +#include +#include + +#include + +namespace AWSGameLift +{ + //! Provide the entry point for the gem and register the system component. + class AWSGameLiftServerModule + : public AZ::Module + { + public: + AZ_RTTI(AWSGameLiftServerModule, "{898416ca-dc11-4731-87de-afe285aedb04}", AZ::Module); + AZ_CLASS_ALLOCATOR(AWSGameLiftServerModule, AZ::SystemAllocator, 0); + + AWSGameLiftServerModule() + : AZ::Module() + { + // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. + m_descriptors.insert(m_descriptors.end(), { + AWSGameLiftServerSystemComponent::CreateDescriptor(), + }); + } + + /** + * Add required SystemComponents to the SystemEntity. + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList { + azrtti_typeid(), + }; + } + }; +}// namespace AWSGameLift + +AZ_DECLARE_MODULE_CLASS(Gem_AWSGameLift_Server, AWSGameLift::AWSGameLiftServerModule) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.cpp new file mode 100644 index 0000000000..fce8c883f3 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.cpp @@ -0,0 +1,129 @@ +/* + * 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. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace AWSGameLift +{ + AWSGameLiftServerSystemComponent::AWSGameLiftServerSystemComponent() + : m_gameLiftServerManager(AZStd::make_unique()) + { + } + + AWSGameLiftServerSystemComponent::~AWSGameLiftServerSystemComponent() + { + m_gameLiftServerManager.reset(); + } + + void AWSGameLiftServerSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class() + ->Version(0) + ; + + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("AWSGameLiftServer", "Create the GameLift server manager which manages the server process for hosting a game session via GameLiftServerSDK.") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true) + ; + } + } + } + + void AWSGameLiftServerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("AWSGameLiftServerService")); + } + + void AWSGameLiftServerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("AWSGameLiftServerService")); + } + + void AWSGameLiftServerSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + AZ_UNUSED(required); + } + + void AWSGameLiftServerSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } + + void AWSGameLiftServerSystemComponent::Init() + { + } + + void AWSGameLiftServerSystemComponent::Activate() + { + if (m_gameLiftServerManager->InitializeGameLiftServerSDK()) + { + GameLiftServerProcessDesc serverProcessDesc; + UpdateGameLiftServerProcessDesc(serverProcessDesc); + m_gameLiftServerManager->NotifyGameLiftProcessReady(serverProcessDesc); + } + } + + void AWSGameLiftServerSystemComponent::Deactivate() + { + m_gameLiftServerManager->HandleDestroySession(); + } + + void AWSGameLiftServerSystemComponent::UpdateGameLiftServerProcessDesc(GameLiftServerProcessDesc& serverProcessDesc) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetDirectInstance(); + if (fileIO) + { + const char pathToLogFolder[] = "@log@/"; + char resolvedPath[AZ_MAX_PATH_LEN]; + if (fileIO->ResolvePath(pathToLogFolder, resolvedPath, AZ_ARRAY_SIZE(resolvedPath))) + { + serverProcessDesc.m_logPaths.push_back(resolvedPath); + } + else + { + AZ_Error("AWSGameLift", false, "Failed to resolve the path to the log folder."); + } + } + else + { + AZ_Error("AWSGameLift", false, "Failed to get File IO."); + } + + if (auto console = AZ::Interface::Get(); console != nullptr) + { + AZ::GetValueResult getCvarResult = console->GetCvarValue("sv_port", serverProcessDesc.m_port); + AZ_Error( + "AWSGameLift", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sv_port' console variable failed with error %s", + AZ::GetEnumString(getCvarResult)); + } + } + + void AWSGameLiftServerSystemComponent::SetGameLiftServerManager(AZStd::unique_ptr gameLiftServerManager) + { + m_gameLiftServerManager.reset(); + m_gameLiftServerManager = AZStd::move(gameLiftServerManager); + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.h new file mode 100644 index 0000000000..910d6907aa --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerSystemComponent.h @@ -0,0 +1,57 @@ +/* + * 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 AWSGameLift +{ + struct GameLiftServerProcessDesc; + class AWSGameLiftServerManager; + + //! Gem server system component. Responsible for managing the server process for hosting game sessions via the GameLift server manager. + class AWSGameLiftServerSystemComponent + : public AZ::Component + { + public: + AZ_COMPONENT(AWSGameLiftServerSystemComponent, "{fa2b46d6-82a9-408d-abab-62bae5ab38c9}"); + + AWSGameLiftServerSystemComponent(); + virtual ~AWSGameLiftServerSystemComponent(); + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + protected: + //////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + void Deactivate() override; + //////////////////////////////////////////////////////////////////////// + + void SetGameLiftServerManager(AZStd::unique_ptr gameLiftServerManager); + + private: + //! Update the serverProcessDesc with appropriate server port number and log paths. + //! @param serverProcessDesc Desc object to update. + void UpdateGameLiftServerProcessDesc(GameLiftServerProcessDesc& serverProcessDesc); + + AZStd::unique_ptr m_gameLiftServerManager; + }; + +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.cpp new file mode 100644 index 0000000000..407914164e --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.cpp @@ -0,0 +1,72 @@ +/* + * 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. + * + */ + +#include + +#include + +#pragma warning(disable : 4996) + +namespace AWSGameLift +{ + Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::AcceptPlayerSession(const std::string& playerSessionId) + { + return Aws::GameLift::Server::AcceptPlayerSession(playerSessionId); + } + + Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::ActivateGameSession() + { + return Aws::GameLift::Server::ActivateGameSession(); + } + + Aws::GameLift::Server::InitSDKOutcome GameLiftServerSDKWrapper::InitSDK() + { + return Aws::GameLift::Server::InitSDK(); + } + + Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::ProcessReady( + const Aws::GameLift::Server::ProcessParameters& processParameters) + { + return Aws::GameLift::Server::ProcessReady(processParameters); + } + + Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::ProcessEnding() + { + return Aws::GameLift::Server::ProcessEnding(); + } + + AZStd::string GameLiftServerSDKWrapper::GetTerminationTime() + { + // Timestamp format is using the UTC ISO8601 format + std::time_t terminationTime; + Aws::GameLift::AwsLongOutcome GetTerminationTimeOutcome = Aws::GameLift::Server::GetTerminationTime(); + if (GetTerminationTimeOutcome.IsSuccess()) + { + terminationTime = GetTerminationTimeOutcome.GetResult(); + } + else + { + // Use the current system time if the termination time is not available from GameLift. + time(&terminationTime); + } + + char buffer[50]; + strftime(buffer, sizeof(buffer), "%FT%TZ", gmtime(&terminationTime)); + + return AZStd::string(buffer); + } + + Aws::GameLift::GenericOutcome GameLiftServerSDKWrapper::RemovePlayerSession(const AZStd::string& playerSessionId) + { + return Aws::GameLift::Server::RemovePlayerSession(playerSessionId.c_str()); + } +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.h new file mode 100644 index 0000000000..8188b69f50 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Source/GameLiftServerSDKWrapper.h @@ -0,0 +1,64 @@ +/* + * 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 AWSGameLift +{ + /* Wrapper to use to GameLift Server SDK. + */ + class GameLiftServerSDKWrapper + { + public: + GameLiftServerSDKWrapper() = default; + virtual ~GameLiftServerSDKWrapper() = default; + + //! Processes and validates a player session connection. + //! This method should be called when a client requests a connection to the server. + //! @param playerSessionId the ID of the joining player's session. + //! @return Returns a generic outcome consisting of success or failure with an error message. + virtual Aws::GameLift::GenericOutcome AcceptPlayerSession(const std::string& playerSessionId); + + //! Reports to GameLift that the server process is now ready to receive player sessions. + //! Should be called once all GameSession initialization has finished. + //! @return Returns a generic outcome consisting of success or failure with an error message. + virtual Aws::GameLift::GenericOutcome ActivateGameSession(); + + //! Initializes the GameLift SDK. + //! Should be called when the server starts, before any GameLift-dependent initialization happens. + //! @return If successful, returns an InitSdkOutcome object indicating that the server process is ready to call ProcessReady(). + virtual Aws::GameLift::Server::InitSDKOutcome InitSDK(); + + //! Notifies the GameLift service that the server process is ready to host game sessions. + //! @param processParameters A ProcessParameters object communicating the names of callback methods, port number and game + //! session-specific log files about the server process. + //! @return Returns a generic outcome consisting of success or failure with an error message. + virtual Aws::GameLift::GenericOutcome ProcessReady(const Aws::GameLift::Server::ProcessParameters& processParameters); + + //! Notifies the GameLift service that the server process is shutting down. + //! @return Returns a generic outcome consisting of success or failure with an error message. + virtual Aws::GameLift::GenericOutcome ProcessEnding(); + + //! Returns the time that a server process is scheduled to be shut down. + //! @return Timestamp using the UTC ISO8601 format. + virtual AZStd::string GetTerminationTime(); + + //! Notifies the GameLift service that a player with the specified player session ID has disconnected from the server process. + //! @param playerSessionId Unique ID issued by the Amazon GameLift service in response to a call to the AWS SDK Amazon GameLift API action CreatePlayerSession. + //! @return Returns a generic outcome consisting of success or failure with an error message. + virtual Aws::GameLift::GenericOutcome RemovePlayerSession(const AZStd::string& playerSessionId); + }; +} // namespace AWSGameLift diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerFixture.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerFixture.h new file mode 100644 index 0000000000..fb1811d483 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerFixture.h @@ -0,0 +1,58 @@ +/* + * 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 +#include +#include +#include + +class AWSGameLiftServerFixture + : public UnitTest::ScopedAllocatorSetupFixture +{ +public: + AWSGameLiftServerFixture() {} + virtual ~AWSGameLiftServerFixture() = default; + + void SetUp() override + { + AZ::AllocatorInstance::Create(); + AZ::AllocatorInstance::Create(); + + AZ::JobManagerDesc jobManagerDesc; + AZ::JobManagerThreadDesc threadDesc; + + m_jobManager.reset(aznew AZ::JobManager(jobManagerDesc)); + m_jobCancelGroup.reset(aznew AZ::JobCancelGroup()); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + m_jobContext.reset(aznew AZ::JobContext(*m_jobManager, *m_jobCancelGroup)); + AZ::JobContext::SetGlobalContext(m_jobContext.get()); + } + + void TearDown() override + { + AZ::JobContext::SetGlobalContext(nullptr); + m_jobContext.reset(); + m_jobCancelGroup.reset(); + m_jobManager.reset(); + AZ::AllocatorInstance::Destroy(); + AZ::AllocatorInstance::Destroy(); + } + + AZStd::unique_ptr m_jobContext; + AZStd::unique_ptr m_jobCancelGroup; + AZStd::unique_ptr m_jobManager; +}; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerManagerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerManagerTest.cpp new file mode 100644 index 0000000000..54d318f195 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerManagerTest.cpp @@ -0,0 +1,421 @@ +/* + * 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. + * + */ + +#include +#include + +#include +#include +#include + +namespace UnitTest +{ + class SessionNotificationsHandlerMock + : public AzFramework::SessionNotificationBus::Handler + { + public: + SessionNotificationsHandlerMock() + { + AzFramework::SessionNotificationBus::Handler::BusConnect(); + } + + ~SessionNotificationsHandlerMock() + { + AzFramework::SessionNotificationBus::Handler::BusDisconnect(); + } + + MOCK_METHOD0(OnSessionHealthCheck, bool()); + MOCK_METHOD1(OnCreateSessionBegin, bool(const AzFramework::SessionConfig&)); + MOCK_METHOD0(OnDestroySessionBegin, bool()); + }; + + class GameLiftServerManagerTest + : public AWSGameLiftServerFixture + { + public: + void SetUp() override + { + AWSGameLiftServerFixture::SetUp(); + + GameLiftServerProcessDesc serverDesc; + m_serverManager = AZStd::make_unique>(); + } + + void TearDown() override + { + m_serverManager.reset(); + + AWSGameLiftServerFixture::TearDown(); + } + + AZStd::unique_ptr> m_serverManager; + }; + + TEST_F(GameLiftServerManagerTest, InitializeGameLiftServerSDK_InitializeTwice_InitSDKCalledOnce) + { + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), InitSDK()).Times(1); + + EXPECT_TRUE(m_serverManager->InitializeGameLiftServerSDK()); + + AZ_TEST_START_TRACE_SUPPRESSION; + EXPECT_FALSE(m_serverManager->InitializeGameLiftServerSDK()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, NotifyGameLiftProcessReady_SDKNotInitialized_FailToNotifyGameLift) + { + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessReady(testing::_)).Times(0); + + AZ_TEST_START_TRACE_SUPPRESSION; + EXPECT_FALSE(m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc())); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, NotifyGameLiftProcessReady_SDKInitialized_ProcessReadyNotificationSent) + { + EXPECT_TRUE(m_serverManager->InitializeGameLiftServerSDK()); + + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessReady(testing::_)).Times(1); + + EXPECT_TRUE(m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc())); + } + + TEST_F(GameLiftServerManagerTest, NotifyGameLiftProcessReady_ProcessReadyFails_TerminationNotificationSent) + { + EXPECT_TRUE(m_serverManager->InitializeGameLiftServerSDK()); + + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessReady(testing::_)) + .Times(1) + .WillOnce(testing::Return(Aws::GameLift::GenericOutcome())); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + EXPECT_TRUE(m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc())); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, OnProcessTerminate_OnDestroySessionBeginReturnsFalse_FailToNotifyGameLift) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + if (!AZ::Interface::Get()) + { + AZ::Interface::Register(m_serverManager.get()); + } + + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(false)); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(0); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc(); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + + EXPECT_FALSE(AZ::Interface::Get()); + } + + TEST_F(GameLiftServerManagerTest, OnProcessTerminate_OnDestroySessionBeginReturnsTrue_TerminationNotificationSent) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + if (!AZ::Interface::Get()) + { + AZ::Interface::Register(m_serverManager.get()); + } + + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1); + + m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc(); + + EXPECT_FALSE(AZ::Interface::Get()); + } + + TEST_F(GameLiftServerManagerTest, OnHealthCheck_OnSessionHealthCheckReturnsTrue_CallbackFunctionReturnsTrue) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnSessionHealthCheck()).Times(1).WillOnce(testing::Return(true)); + EXPECT_TRUE(m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_healthCheckFunc()); + } + + TEST_F(GameLiftServerManagerTest, OnHealthCheck_OnSessionHealthCheckReturnsFalseAndTrue_CallbackFunctionReturnsFalse) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock1; + EXPECT_CALL(handlerMock1, OnSessionHealthCheck()).Times(1).WillOnce(testing::Return(false)); + SessionNotificationsHandlerMock handlerMock2; + EXPECT_CALL(handlerMock2, OnSessionHealthCheck()).Times(1).WillOnce(testing::Return(true)); + EXPECT_FALSE(m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_healthCheckFunc()); + } + + TEST_F(GameLiftServerManagerTest, OnHealthCheck_OnSessionHealthCheckReturnsFalse_CallbackFunctionReturnsFalse) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnSessionHealthCheck()).Times(1).WillOnce(testing::Return(false)); + EXPECT_FALSE(m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_healthCheckFunc()); + } + + TEST_F(GameLiftServerManagerTest, OnStartGameSession_OnCreateSessionBeginReturnsFalse_TerminationNotificationSent) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(false)); + EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onStartGameSessionFunc(Aws::GameLift::Server::Model::GameSession()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, OnStartGameSession_ActivateGameSessionSucceeds_RegisterAsHandler) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession()) + .Times(1) + .WillOnce(testing::Return(Aws::GameLift::GenericOutcome(nullptr))); + Aws::GameLift::Server::Model::GameSession testSession; + Aws::GameLift::Server::Model::GameProperty testProperty; + testProperty.SetKey("testKey"); + testProperty.SetValue("testValue"); + testSession.AddGameProperties(testProperty); + m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onStartGameSessionFunc(testSession); + EXPECT_TRUE(AZ::Interface::Get()); + m_serverManager->HandleDestroySession(); + } + + TEST_F(GameLiftServerManagerTest, OnStartGameSession_ActivateGameSessionFails_TerminationNotificationSent) + { + m_serverManager->InitializeGameLiftServerSDK(); + m_serverManager->NotifyGameLiftProcessReady(GameLiftServerProcessDesc()); + SessionNotificationsHandlerMock handlerMock; + EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true)); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession()) + .Times(1) + .WillOnce(testing::Return(Aws::GameLift::GenericOutcome())); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onStartGameSessionFunc(Aws::GameLift::Server::Model::GameSession()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithInvalidConnectionConfig_GetFalseResultAndExpectedErrorLog) + { + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_serverManager->ValidatePlayerJoinSession(AzFramework::PlayerConnectionConfig()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + EXPECT_FALSE(result); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithDuplicatedConnectionId_GetFalseResultAndExpectedErrorLog) + { + AzFramework::PlayerConnectionConfig connectionConfig1; + connectionConfig1.m_playerConnectionId = 123; + connectionConfig1.m_playerSessionId = "dummyPlayerSessionId1"; + GenericOutcome successOutcome(nullptr); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), AcceptPlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(successOutcome)); + m_serverManager->ValidatePlayerJoinSession(connectionConfig1); + AzFramework::PlayerConnectionConfig connectionConfig2; + connectionConfig2.m_playerConnectionId = 123; + connectionConfig2.m_playerSessionId = "dummyPlayerSessionId2"; + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_serverManager->ValidatePlayerJoinSession(connectionConfig2); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + EXPECT_FALSE(result); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithValidConnectionConfigButErrorOutcome_GetFalseResultAndExpectedErrorLog) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId1"; + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), AcceptPlayerSession(testing::_)).Times(1); + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_serverManager->ValidatePlayerJoinSession(connectionConfig); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + EXPECT_FALSE(result); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithValidConnectionConfigAndSuccessOutcome_GetTrueResult) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId1"; + GenericOutcome successOutcome(nullptr); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), AcceptPlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(successOutcome)); + auto result = m_serverManager->ValidatePlayerJoinSession(connectionConfig); + EXPECT_TRUE(result); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithFirstErrorSecondSuccess_GetFirstFalseSecondTrueResult) + { + AzFramework::PlayerConnectionConfig connectionConfig1; + connectionConfig1.m_playerConnectionId = 123; + connectionConfig1.m_playerSessionId = "dummyPlayerSessionId1"; + GenericOutcome successOutcome(nullptr); + Aws::GameLift::GameLiftError error; + GenericOutcome errorOutcome(error); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), AcceptPlayerSession(testing::_)) + .Times(2) + .WillOnce(Return(errorOutcome)) + .WillOnce(Return(successOutcome)); + AZ_TEST_START_TRACE_SUPPRESSION; + auto result = m_serverManager->ValidatePlayerJoinSession(connectionConfig1); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + EXPECT_FALSE(result); + AzFramework::PlayerConnectionConfig connectionConfig2; + connectionConfig2.m_playerConnectionId = 123; + connectionConfig2.m_playerSessionId = "dummyPlayerSessionId2"; + result = m_serverManager->ValidatePlayerJoinSession(connectionConfig2); + EXPECT_TRUE(result); + } + + TEST_F(GameLiftServerManagerTest, ValidatePlayerJoinSession_CallWithMultithread_GetFirstTrueAndRestFalse) + { + int testThreadNumber = 5; + GenericOutcome successOutcome(nullptr); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), AcceptPlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(successOutcome)); + AZStd::vector testThreadPool; + AZStd::atomic trueCount = 0; + for (int index = 0; index < testThreadNumber; index++) + { + testThreadPool.emplace_back(AZStd::thread([&]() { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId"; + auto result = m_serverManager->ValidatePlayerJoinSession(connectionConfig); + if (result) + { + trueCount++; + } + })); + } + for (auto& testThread : testThreadPool) + { + testThread.join(); + } + EXPECT_TRUE(trueCount == 1); + } + + TEST_F(GameLiftServerManagerTest, HandlePlayerLeaveSession_CallWithInvalidConnectionConfig_GetExpectedErrorLog) + { + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), RemovePlayerSession(testing::_)).Times(0); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->HandlePlayerLeaveSession(AzFramework::PlayerConnectionConfig()); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, HandlePlayerLeaveSession_CallWithNonExistentPlayerConnectionId_GetExpectedErrorLog) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId"; + auto result = m_serverManager->AddConnectedTestPlayer(connectionConfig); + EXPECT_TRUE(result); + + AzFramework::PlayerConnectionConfig connectionConfig1; + connectionConfig1.m_playerConnectionId = 456; + connectionConfig1.m_playerSessionId = "dummyPlayerSessionId"; + + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), RemovePlayerSession(testing::_)).Times(0); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->HandlePlayerLeaveSession(connectionConfig1); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, HandlePlayerLeaveSession_CallWithValidConnectionConfigButErrorOutcome_GetExpectedErrorLog) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId"; + auto result = m_serverManager->AddConnectedTestPlayer(connectionConfig); + EXPECT_TRUE(result); + + Aws::GameLift::GameLiftError error; + GenericOutcome errorOutcome(error); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), RemovePlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(errorOutcome)); + + AZ_TEST_START_TRACE_SUPPRESSION; + m_serverManager->HandlePlayerLeaveSession(connectionConfig); + AZ_TEST_STOP_TRACE_SUPPRESSION(1); + } + + TEST_F(GameLiftServerManagerTest, HandlePlayerLeaveSession_CallWithValidConnectionConfigAndSuccessOutcome_RemovePlayerSessionNotificationSent) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId"; + auto result = m_serverManager->AddConnectedTestPlayer(connectionConfig); + EXPECT_TRUE(result); + + GenericOutcome successOutcome(nullptr); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), RemovePlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(successOutcome)); + + m_serverManager->HandlePlayerLeaveSession(connectionConfig); + } + + TEST_F(GameLiftServerManagerTest, HandlePlayerLeaveSession_CallWithMultithread_OnlyOneNotificationIsSent) + { + AzFramework::PlayerConnectionConfig connectionConfig; + connectionConfig.m_playerConnectionId = 123; + connectionConfig.m_playerSessionId = "dummyPlayerSessionId"; + auto result = m_serverManager->AddConnectedTestPlayer(connectionConfig); + EXPECT_TRUE(result); + + int testThreadNumber = 5; + GenericOutcome successOutcome(nullptr); + EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), RemovePlayerSession(testing::_)) + .Times(1) + .WillOnce(Return(successOutcome)); + + AZStd::vector testThreadPool; + AZStd::atomic trueCount = 0; + AZ_TEST_START_TRACE_SUPPRESSION; + for (int index = 0; index < testThreadNumber; index++) + { + testThreadPool.emplace_back(AZStd::thread( + [&]() + { + m_serverManager->HandlePlayerLeaveSession(connectionConfig); + })); + } + for (auto& testThread : testThreadPool) + { + testThread.join(); + } + AZ_TEST_STOP_TRACE_SUPPRESSION(testThreadNumber - 1); // The player is only disconnected once. + } +} // namespace UnitTest diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerMocks.h b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerMocks.h new file mode 100644 index 0000000000..f5d4d794d5 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerMocks.h @@ -0,0 +1,127 @@ +/* + * 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 +#include +#include +#include + +using namespace Aws::GameLift; +using namespace AWSGameLift; +using testing::_; +using testing::Invoke; +using testing::Return; +using testing::NiceMock; +using testing::Eq; + +namespace UnitTest +{ + class GameLiftServerSDKWrapperMock + : public GameLiftServerSDKWrapper + { + public: + GameLiftServerSDKWrapperMock() + { + GenericOutcome successOutcome(nullptr); + Server::InitSDKOutcome sdkOutcome(nullptr); + + ON_CALL(*this, InitSDK()).WillByDefault(Return(sdkOutcome)); + ON_CALL(*this, ProcessReady(_)).WillByDefault(Invoke(this, &GameLiftServerSDKWrapperMock::ProcessReadyMock)); + ON_CALL(*this, ProcessEnding()).WillByDefault(Return(successOutcome)); + } + + MOCK_METHOD1(AcceptPlayerSession, GenericOutcome(const std::string&)); + MOCK_METHOD0(ActivateGameSession, GenericOutcome()); + MOCK_METHOD0(InitSDK, Server::InitSDKOutcome()); + MOCK_METHOD1(ProcessReady, GenericOutcome(const Server::ProcessParameters& processParameters)); + MOCK_METHOD0(ProcessEnding, GenericOutcome()); + MOCK_METHOD1(RemovePlayerSession, GenericOutcome(const AZStd::string& playerSessionId)); + MOCK_METHOD0(GetTerminationTime, AZStd::string()); + + GenericOutcome ProcessReadyMock(const Server::ProcessParameters& processParameters) + { + m_healthCheckFunc = processParameters.getOnHealthCheck(); + m_onStartGameSessionFunc = processParameters.getOnStartGameSession(); + m_onProcessTerminateFunc = processParameters.getOnProcessTerminate(); + + GenericOutcome successOutcome(nullptr); + return successOutcome; + } + + AZStd::function m_healthCheckFunc; + AZStd::function m_onProcessTerminateFunc; + AZStd::function m_onStartGameSessionFunc; + }; + + class AWSGameLiftServerManagerMock + : public AWSGameLiftServerManager + { + public: + AWSGameLiftServerManagerMock() + { + AZStd::unique_ptr> gameLiftServerSDKWrapper = + AZStd::make_unique>(); + m_gameLiftServerSDKWrapperMockPtr = gameLiftServerSDKWrapper.get(); + SetGameLiftServerSDKWrapper(AZStd::move(gameLiftServerSDKWrapper)); + } + + ~AWSGameLiftServerManagerMock() + { + m_gameLiftServerSDKWrapperMockPtr = nullptr; + } + + bool AddConnectedTestPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig) + { + return AddConnectedPlayer(playerConnectionConfig); + } + + NiceMock* m_gameLiftServerSDKWrapperMockPtr; + }; + + class AWSGameLiftServerSystemComponentMock + : public AWSGameLift::AWSGameLiftServerSystemComponent + { + public: + AWSGameLiftServerSystemComponentMock() + { + SetGameLiftServerManager(AZStd::make_unique>()); + + ON_CALL(*this, Init()).WillByDefault(testing::Invoke(this, &AWSGameLiftServerSystemComponentMock::InitMock)); + ON_CALL(*this, Activate()).WillByDefault(testing::Invoke(this, &AWSGameLiftServerSystemComponentMock::ActivateMock)); + ON_CALL(*this, Deactivate()).WillByDefault(testing::Invoke(this, &AWSGameLiftServerSystemComponentMock::DeactivateMock)); + } + + void InitMock() + { + AWSGameLift::AWSGameLiftServerSystemComponent::Init(); + } + + void ActivateMock() + { + AWSGameLift::AWSGameLiftServerSystemComponent::Activate(); + } + + void DeactivateMock() + { + AWSGameLift::AWSGameLiftServerSystemComponent::Deactivate(); + } + + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(Activate, void()); + MOCK_METHOD0(Deactivate, void()); + + GameLiftServerProcessDesc m_serverProcessDesc; + }; +}; diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerSystemComponentTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerSystemComponentTest.cpp new file mode 100644 index 0000000000..805b386320 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerSystemComponentTest.cpp @@ -0,0 +1,99 @@ +/* +* 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. +* +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class AWSGameLiftServerSystemComponentTest + : public AWSGameLiftServerFixture + { + public: + void SetUp() override + { + AWSGameLiftServerFixture::SetUp(); + + m_serializeContext = AZStd::make_unique(); + m_serializeContext->CreateEditContext(); + m_behaviorContext = AZStd::make_unique(); + m_componentDescriptor.reset(AWSGameLift::AWSGameLiftServerSystemComponent::CreateDescriptor()); + m_componentDescriptor->Reflect(m_serializeContext.get()); + m_componentDescriptor->Reflect(m_behaviorContext.get()); + + m_entity = aznew AZ::Entity(); + + m_AWSGameLiftServerSystemsComponent = aznew NiceMock(); + m_entity->AddComponent(m_AWSGameLiftServerSystemsComponent); + + // Set up the file IO and alias + m_localFileIO = aznew AZ::IO::LocalFileIO(); + m_priorFileIO = AZ::IO::FileIOBase::GetInstance(); + + AZ::IO::FileIOBase::SetInstance(nullptr); + AZ::IO::FileIOBase::SetInstance(m_localFileIO); + m_localFileIO->SetAlias("@log@", AZ_TRAIT_TEST_ROOT_FOLDER); + } + + void TearDown() override + { + AZ::IO::FileIOBase::SetInstance(nullptr); + delete m_localFileIO; + AZ::IO::FileIOBase::SetInstance(m_priorFileIO); + + m_entity->RemoveComponent(m_AWSGameLiftServerSystemsComponent); + delete m_AWSGameLiftServerSystemsComponent; + delete m_entity; + + m_componentDescriptor.reset(); + m_behaviorContext.reset(); + m_serializeContext.reset(); + + AWSGameLiftServerFixture::TearDown(); + } + + AZStd::unique_ptr m_componentDescriptor; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_behaviorContext; + + AZ::Entity* m_entity; + NiceMock* m_AWSGameLiftServerSystemsComponent; + + AZ::IO::FileIOBase* m_priorFileIO; + AZ::IO::FileIOBase* m_localFileIO; + }; + + TEST_F(AWSGameLiftServerSystemComponentTest, ActivateDeactivateComponent_ExecuteInOrder_Success) + { + testing::Sequence s1, s2; + + EXPECT_CALL(*m_AWSGameLiftServerSystemsComponent, Init()).Times(1).InSequence(s1); + EXPECT_CALL(*m_AWSGameLiftServerSystemsComponent, Activate()).Times(1).InSequence(s1); + + EXPECT_CALL(*m_AWSGameLiftServerSystemsComponent, Deactivate()).Times(1).InSequence(s2); + + // activate component + m_entity->Init(); + m_entity->Activate(); + + // deactivate component + m_entity->Deactivate(); + } + +} // namespace UnitTest diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerTest.cpp b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerTest.cpp new file mode 100644 index 0000000000..dfe3edaa96 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/Tests/AWSGameLiftServerTest.cpp @@ -0,0 +1,15 @@ +/* + * 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. + * + */ + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake new file mode 100644 index 0000000000..3a2b3b6295 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_files.cmake @@ -0,0 +1,20 @@ +# +# 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. +# + +set(FILES + ../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h + Source/AWSGameLiftServerManager.cpp + Source/AWSGameLiftServerManager.h + Source/AWSGameLiftServerSystemComponent.cpp + Source/AWSGameLiftServerSystemComponent.h + Source/GameLiftServerSDKWrapper.cpp + Source/GameLiftServerSDKWrapper.h +) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_shared_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_shared_files.cmake new file mode 100644 index 0000000000..4182aeae26 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_shared_files.cmake @@ -0,0 +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. +# + +set(FILES + Source/AWSGameLiftServerModule.cpp +) diff --git a/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_tests_files.cmake b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_tests_files.cmake new file mode 100644 index 0000000000..cffec47098 --- /dev/null +++ b/Gems/AWSGameLift/Code/AWSGameLiftServer/awsgamelift_server_tests_files.cmake @@ -0,0 +1,18 @@ +# +# 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. +# + +set(FILES + Tests/AWSGameLiftServerFixture.h + Tests/AWSGameLiftServerMocks.h + Tests/AWSGameLiftServerManagerTest.cpp + Tests/AWSGameLiftServerTest.cpp + Tests/AWSGameLiftServerSystemComponentTest.cpp +) diff --git a/Gems/AWSGameLift/Code/CMakeLists.txt b/Gems/AWSGameLift/Code/CMakeLists.txt new file mode 100644 index 0000000000..972a42c7e2 --- /dev/null +++ b/Gems/AWSGameLift/Code/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# 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. +# + +add_subdirectory(AWSGameLiftClient) +add_subdirectory(AWSGameLiftServer) diff --git a/Gems/AWSGameLift/cdk/.gitignore b/Gems/AWSGameLift/cdk/.gitignore new file mode 100644 index 0000000000..383cdd5040 --- /dev/null +++ b/Gems/AWSGameLift/cdk/.gitignore @@ -0,0 +1,10 @@ +*.swp +package-lock.json +__pycache__ +.pytest_cache +.env +*.egg-info + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/Gems/AWSGameLift/cdk/README.md b/Gems/AWSGameLift/cdk/README.md new file mode 100644 index 0000000000..34933d3651 --- /dev/null +++ b/Gems/AWSGameLift/cdk/README.md @@ -0,0 +1,99 @@ + +# Welcome to O3DE GameLift Sample Project! + +This is an optional CDK application that provides two stacks: + + * A GameLift stack that contains all the GameLift resources required to host game servers + * An optional support stack which is used to upload local build files and create GameLift builds + +The `cdk.json` file tells the CDK Toolkit how to execute this application. + +This project is set up like a standard Python project. The initialization +process also creates a virtualenv within this project, stored under the `.env` +directory. To create the virtualenv it assumes that there is a `python3` +(or `python` for Windows) (Python 3.7+) executable in your path with access to the `venv` +package. If for any reason the automatic creation of the virtualenv fails, +you can create the virtualenv manually. + +See https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html about for information about how to set up +the prerequisites for CDK development. + +To manually create a virtualenv on MacOS and Linux: + +``` +$ python -m venv .env +``` + +Once the virtualenv is created, you can use the following step to activate your virtualenv. + +``` +$ source .env/bin/activate +``` + +If you are a Windows platform, you would activate the virtualenv like this: + +``` +% .env\Scripts\activate.bat +``` + +Once the virtualenv is activated, you can install the required dependencies. + +``` +$ pip install -r requirements.txt +``` + +## Set environment variables or accept defaults + +* O3DE_AWS_DEPLOY_REGION*: The region to deploy the stacks into, will default to CDK_DEFAULT_REGION +* O3DE_AWS_DEPLOY_ACCOUNT*: The account to deploy stacks into, will default to CDK_DEFAULT_ACCOUNT +* O3DE_AWS_PROJECT_NAME*: The name of the O3DE project stacks should be deployed for will default to AWS-PROJECT + +See https://docs.aws.amazon.com/cdk/latest/guide/environments.html for more information including how to pass parameters +to use for environment variables. + +## Edit the sample fleet configurations + +Before deploy the CDK application, please update the sample fleet configurations defined in the +[sample fleet configurations](aws_gamelift/fleet_configurations.py) + with project specific settings. + +## Synthesize the project + +At this point you can now synthesize the CloudFormation template for this code. + +``` +$ cdk synth +``` + +## Optional features +To create a game session queue using this CDK application, provide the following context variable +when synthesize the CloudFormation template or deploy the application: + +``` +$ cdk deploy -c create_game_session_queue=true +``` + +You can also deploy a support stack which is used to upload local build files to S3 and provide GameLift access +to the S3 objects when create GameLift builds: + +``` +$ cdk deploy -c upload-with-support-stack=true --all +``` + +You may need todo a one time bootstrap, once per account, per region. The CDK application will prompt you on this. + +To add additional dependencies, for example other CDK libraries, just add +them to your `setup.py` file and rerun the `pip install -r requirements.txt` +command. + +## Useful commands + + * `cdk ls` list all stacks in the app + * `cdk synth` emits the synthesized CloudFormation template + * `cdk deploy` deploy this stack to your default AWS account/region + * `cdk diff` compare deployed stack with current state + * `cdk docs` open CDK documentation + +## GameLift reference and best practise +https://docs.aws.amazon.com/gamelift/index.html + diff --git a/Gems/AWSGameLift/cdk/app.py b/Gems/AWSGameLift/cdk/app.py new file mode 100644 index 0000000000..5b94bdbba9 --- /dev/null +++ b/Gems/AWSGameLift/cdk/app.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +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 + +from aws_cdk import core + +from aws_gamelift.aws_gamelift_construct import AWSGameLift + +"""Configuration""" +REGION = os.environ.get('O3DE_AWS_DEPLOY_REGION', os.environ.get('CDK_DEFAULT_REGION')) +ACCOUNT = os.environ.get('O3DE_AWS_DEPLOY_ACCOUNT', os.environ.get('CDK_DEFAULT_ACCOUNT')) + +# Set the common prefix to group stacks in a project together. +PROJECT_NAME = os.environ.get('O3DE_AWS_PROJECT_NAME', f'O3DE-AWS-PROJECT').upper() + +# The name of this feature +FEATURE_NAME = 'AWSGameLift' + +# The name of this CDK application +PROJECT_FEATURE_NAME = f'{PROJECT_NAME}-{FEATURE_NAME}' + +# Standard Tag Key for project based tags +O3DE_PROJECT_TAG_NAME = 'O3DEProject' +# Standard Tag Key for feature based tags +O3DE_FEATURE_TAG_NAME = 'O3DEFeature' + +"""End of Configuration""" + +# Set-up regions to deploy stack to, or use default if not set +env = core.Environment( + account=ACCOUNT, + region=REGION) + +app = core.App() +feature_struct = AWSGameLift( + app, + id_=PROJECT_FEATURE_NAME, + project_name=PROJECT_NAME, + feature_name=FEATURE_NAME, + tags={O3DE_PROJECT_TAG_NAME: PROJECT_NAME, O3DE_FEATURE_TAG_NAME: FEATURE_NAME}, + env=env +) +app.synth() diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/__init__.py b/Gems/AWSGameLift/cdk/aws_gamelift/__init__.py new file mode 100644 index 0000000000..cdee4b5a56 --- /dev/null +++ b/Gems/AWSGameLift/cdk/aws_gamelift/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/aws_gamelift_construct.py b/Gems/AWSGameLift/cdk/aws_gamelift/aws_gamelift_construct.py new file mode 100644 index 0000000000..a9935b72eb --- /dev/null +++ b/Gems/AWSGameLift/cdk/aws_gamelift/aws_gamelift_construct.py @@ -0,0 +1,59 @@ +""" +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 copy + +from aws_cdk import core + +from aws_gamelift.fleet_configurations import FLEET_CONFIGURATIONS +from aws_gamelift.gamelift_stack import GameLiftStack +from aws_gamelift.support_stack import SupportStack + + +class AWSGameLift(core.Construct): + """ + Orchestrates setting up the AWS GameLift Stack(s) + """ + def __init__(self, + scope: core.Construct, + id_: str, + project_name: str, + feature_name: str, + tags: dict, + env: core.Environment) -> None: + super().__init__(scope, id_) + + stack_name = f'{project_name}-{feature_name}-{env.region}' + + fleet_configurations = copy.deepcopy(FLEET_CONFIGURATIONS) + if self.node.try_get_context('upload-with-support-stack') == 'true': + # Create an optional support stack for generating GameLift builds with local build files + self._support_stack = SupportStack( + scope, + f'{stack_name}-Support', + stack_name=stack_name, + fleet_configurations=fleet_configurations, + description='(Optional) Contains resources for creating GameLift builds with local files', + tags=tags, + env=env + ) + + # Create the GameLift Stack + self._feature_stack = GameLiftStack( + scope, + f'{stack_name}', + stack_name=stack_name, + fleet_configurations=fleet_configurations, + create_game_session_queue=self.node.try_get_context('create_game_session_queue') == 'true', + description=f'Contains resources for the AWS GameLift Gem stack as part of the {project_name} project', + tags=tags, + env=env + ) diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py b/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py new file mode 100644 index 0000000000..d5390ae7b3 --- /dev/null +++ b/Gems/AWSGameLift/cdk/aws_gamelift/fleet_configurations.py @@ -0,0 +1,147 @@ +""" +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. +""" + +# Configurations for the fleets to deploy. +# Modify the fleet configuration fields below before deploying the CDK application. +# To select the right combination of hosting resources and learn how to configure them to best suit to your application, +# please check: https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-design.html +FLEET_CONFIGURATIONS = [ + { + # (Optional) An alias for an Amazon GameLift fleet destination. + # By using aliases instead of specific fleet IDs, customers can more easily and seamlessly switch + # player traffic from one fleet to another by changing the alias's target location. + 'alias_configuration': { + # (Required) A descriptive label that is associated with an alias. Alias names do not need to be unique. + 'name': '', + # (Conditional) A type of routing strategy for the GameLift fleet alias if exists. + # Required if alias_configuration is provided. + 'routing_strategy': { + # The message text to be used with a terminal routing strategy. + # If you specify TERMINAL for the Type property, you must specify this property. + # Required if specify TERMINAL for the Type property, + 'message': '', + # (Required) A type of routing strategy. + 'type': 'SIMPLE | TERMINAL' + } + }, + # (Required) Information about a game server build that is installed and + # run on instances in an Amazon GameLift fleet. + 'build_configuration': { + # (Conditional) A unique identifier for a build to be deployed on the new fleet. + # This parameter is required unless the parameters build_path and operating_system are defined and + # the conditional variable upload-with-support-stack is set to true + 'build_id': '', + # (Conditional) The disk location of the local build file(zip). + # This parameter is required unless the parameter build_id is defined. + 'build_path': '', + # (Conditional) The operating system that the game server binaries are built to run on. + # This parameter is required if the parameter build_path is defined. + 'operating_system': 'AMAZON_LINUX | AMAZON_LINUX_2 | WINDOWS_2012' + }, + # (Optional) Information about the use of a TLS/SSL certificate for a fleet. + 'certificate_configuration': { + # (Required) Indicates whether a TLS/SSL certificate is generated for the fleet. + 'certificate_type': 'DISABLED | GENERATED', + }, + # A human-readable description of the fleet. + 'description': 'Amazon GameLift fleet to host game servers.', + # (Optional) A range of IP addresses and port settings that allow inbound traffic to connect to + # server processes on an Amazon GameLift server. + # This should be the same port range as the server is configured for. + 'ec2_inbound_permissions': [ + { + # (Required) A starting value for a range of allowed port numbers. + # 30090 is the default server port defined by the Multiplayer Gem. + 'from_port': 30090, + # (Required) A range of allowed IP addresses. + 'ip_range': '', + # (Required) The network communication protocol used by the fleet. + 'protocol': 'UDP', + # (Required) An ending value for a range of allowed port numbers. + 'to_port': 30090 + }, + { + # Open the debug port for remote into a Windows fleet. + 'from_port': 3389, + 'ip_range': '', + 'protocol': 'TCP', + 'to_port': 3389 + }, + { + # Open the debug port for remote into a Linux fleet. + 'from_port': 22, + 'ip_range': '', + 'protocol': 'TCP', + 'to_port': 22 + } + ], + # (Optional) The GameLift-supported EC2 instance type to use for all fleet instances. + 'ec2_instance_type': 'c3.2xlarge | c3.4xlarge | c3.8xlarge | c3.large | c3.xlarge | c4.2xlarge | c4.4xlarge |' + ' c4.8xlarge | c4.large | c4.xlarge | c5.12xlarge | c5.18xlarge | c5.24xlarge |' + ' c5.2xlarge | c5.4xlarge | c5.9xlarge | c5.large | c5.xlarge | c5a.12xlarge |' + ' c5a.16xlarge | c5a.24xlarge | c5a.2xlarge | c5a.4xlarge | c5a.8xlarge | c5a.large |' + ' c5a.xlarge | m3.2xlarge | m3.large | m3.medium | m3.xlarge | m4.10xlarge | m4.2xlarge |' + ' m4.4xlarge | m4.large | m4.xlarge | m5.12xlarge | m5.16xlarge | m5.24xlarge |' + ' m5.2xlarge | m5.4xlarge | m5.8xlarge | m5.large | m5.xlarge | m5a.12xlarge |' + ' m5a.16xlarge | m5a.24xlarge | m5a.2xlarge | m5a.4xlarge | m5a.8xlarge | m5a.large |' + ' m5a.xlarge | r3.2xlarge | r3.4xlarge | r3.8xlarge | r3.large | r3.xlarge | r4.16xlarge |' + ' r4.2xlarge | r4.4xlarge | r4.8xlarge | r4.large | r4.xlarge | r5.12xlarge |' + ' r5.16xlarge | r5.24xlarge | r5.2xlarge | r5.4xlarge | r5.8xlarge | r5.large |' + ' r5.xlarge | r5a.12xlarge | r5a.16xlarge | r5a.24xlarge | r5a.2xlarge | r5a.4xlarge |' + ' r5a.8xlarge | r5a.large | r5a.xlarge | t2.large | t2.medium | t2.micro | t2.small', + # (Optional) Indicates whether to use On-Demand or Spot instances for this fleet. + 'fleet_type': 'ON_DEMAND | SPOT', + # (Optional) A game session protection policy to apply to all game sessions hosted on instances in this fleet. + 'new_game_session_protection_policy': 'FullProtection | NoProtection', + # (Optional) A policy that limits the number of game sessions that an individual player + # can create on instances in this fleet within a specified span of time. + 'resource_creation_limit_policy': { + # (Optional) The maximum number of game sessions that an individual can create during the policy period. + # Provide any integer not less than 0. + 'new_game_sessions_per_creator': 3, + # (Optional) The time span used in evaluating the resource creation limit policy. + # Provide any integer not less than 0. + 'policy_period_in_minutes': 15 + }, + # (Conditional) Instructions for launching server processes on each instance in the fleet. + # This parameter is required unless the parameters ServerLaunchPath and ServerLaunchParameters are defined. + 'runtime_configuration': { + # (Optional) The maximum amount of time (in seconds) allowed to launch a new game session and + # have it report ready to host players. + # Provide an integer from 1 to 600. + 'game_session_activation_timeout_seconds': 300, + # (Optional) The number of game sessions in status ACTIVATING to allow on an instance. + # Provide an integer from 1 to 2147483647. + 'max_concurrent_game_session_activations': 2, + # (Optional) A collection of server process configurations that identify what server processes + # to run on each instance in a fleet. To set up a fleet's runtime configuration to + # run multiple game server processes per instance, please check the following document: + # https://docs.aws.amazon.com/gamelift/latest/developerguide/fleets-multiprocess.html + 'server_processes': [ + { + # (Required) The number of server processes using this configuration that + # run concurrently on each instance. + # Provide any integer not less than 1. + 'concurrent_executions': 1, + # (Required) The location of a game build executable or the Realtime script file that + # contains the Init() function. + 'launch_path': '(Windows) | ' + '(Linux) /local/game/MyGame/', + # (Optional) An optional list of parameters to pass to the server executable + # or Realtime script on launch. + 'parameters': '' + } + ] + } + # For additional fleet configurations, please check: + # # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/AWS_GameLift.html + } +] diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/gamelift_stack.py b/Gems/AWSGameLift/cdk/aws_gamelift/gamelift_stack.py new file mode 100644 index 0000000000..99702f054a --- /dev/null +++ b/Gems/AWSGameLift/cdk/aws_gamelift/gamelift_stack.py @@ -0,0 +1,184 @@ +""" +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 typing + +from aws_cdk import core +from aws_cdk import aws_gamelift as gamelift + + +class GameLiftStack(core.Stack): + """ + The AWS GameLift stack + + Defines GameLift resources to use in project + """ + def __init__(self, scope: core.Construct, id_: str, + stack_name: str, fleet_configurations: dict, + create_game_session_queue: bool, **kwargs) -> None: + super().__init__(scope, id_, **kwargs) + + self._stack_name = stack_name + + fleet_ids = [] + queue_destinations = [] + for index in range(len(fleet_configurations)): + fleet_configuration = fleet_configurations[index] + # Create a new GameLift fleet using the configuration + fleet_ids.append(self._create_fleet(fleet_configuration, index).attr_fleet_id) + destination_arn = core.Fn.sub( + body='arn:${AWS::Partition}:gamelift:${AWS::Region}::fleet/${FleetId}', + variables={ + 'FleetId': fleet_ids[index], + } + ) + + if fleet_configuration.get('alias_configuration'): + # Create an alias for the fleet if the alias configuration is provided + alias = self._create_alias(fleet_configuration['alias_configuration'], fleet_ids[index]) + destination_arn = core.Fn.sub( + body='arn:${AWS::Partition}:gamelift:${AWS::Region}::alias/${AliasId}', + variables={ + 'AliasId': alias.attr_alias_id, + } + ) + + queue_destinations.append(destination_arn) + + # Export the GameLift fleet ids as a stack output + fleets_output = core.CfnOutput( + self, + id='GameLiftFleets', + description='List of GameLift fleet ids', + export_name=f'{self._stack_name}:GameLiftFleets', + value=','.join(fleet_ids) + ) + + if create_game_session_queue: + # Create a game session queue which fulfills game session placement requests using the fleets + game_session_queue = self._create_game_session_queue(queue_destinations) + + # Export the game session queue name as a stack output + game_session_queue_output = core.CfnOutput( + self, + id='GameSessionQueue', + description='Name of the game session queue', + export_name=f'{self._stack_name}:GameSessionQueue', + value=game_session_queue.name) + + def _create_fleet(self, fleet_configuration: dict, identifier: int) -> gamelift.CfnFleet: + """ + Create an Amazon GameLift fleet to host game servers. + :param fleet_configuration: Configuration of the fleet. + :param identifier: Unique identifier of the fleet which will be included in the resource id. + :return: Generated GameLift fleet. + """ + fleet = gamelift.CfnFleet( + self, + id=f'{self._stack_name}-GameLiftFleet{identifier}', + build_id=self._get_gamelift_build_id(fleet_configuration.get('build_configuration', {}), identifier), + certificate_configuration=gamelift.CfnFleet.CertificateConfigurationProperty( + certificate_type=fleet_configuration['certificate_configuration'].get('certificate_type') + ) if fleet_configuration.get('certificate_configuration') else None, + description=fleet_configuration.get('description'), + ec2_inbound_permissions=[ + gamelift.CfnFleet.IpPermissionProperty( + **inbound_permission + ) for inbound_permission in fleet_configuration.get('ec2_inbound_permissions', []) + ], + ec2_instance_type=fleet_configuration.get('ec2_instance_type'), + fleet_type=fleet_configuration.get('fleet_type'), + name=f'{self._stack_name}-GameLiftFleet{identifier}', + new_game_session_protection_policy=fleet_configuration.get('new_game_session_protection_policy'), + resource_creation_limit_policy=gamelift.CfnFleet.ResourceCreationLimitPolicyProperty( + **fleet_configuration['resource_creation_limit_policy'] + ) if fleet_configuration.get('resource_creation_limit_policy') else None, + runtime_configuration=gamelift.CfnFleet.RuntimeConfigurationProperty( + game_session_activation_timeout_seconds=fleet_configuration['runtime_configuration'].get( + 'game_session_activation_timeout_seconds'), + max_concurrent_game_session_activations=fleet_configuration['runtime_configuration'].get( + 'max_concurrent_game_session_activations'), + server_processes=[ + gamelift.CfnFleet.ServerProcessProperty( + **server_process + ) for server_process in fleet_configuration['runtime_configuration'].get('server_processes', []) + ] + ) if fleet_configuration.get('runtime_configuration') else None, + ) + + return fleet + + def _get_gamelift_build_id(self, build_configuration: dict, identifier: int) -> str: + """ + Get the GameLift build id. + Create the GameLift build from the storage location information if the build doesn't exist. + :param build_configuration: Configuration of the GameLift build. + :param identifier: Unique identifier of the build which will be included in the resource id. + :return: Build id. + """ + if build_configuration.get('build_id'): + # GameLift build already exists + return build_configuration['build_id'] + elif build_configuration.get('storage_location'): + # Create the GameLift build using the storage location information. + build = gamelift.CfnBuild( + self, + id=f'{self._stack_name}-GameLiftBuild{identifier}', + name=f'{self._stack_name}-GameLiftBuild{identifier}', + operating_system=build_configuration.get('operating_system'), + storage_location=gamelift.CfnBuild.S3LocationProperty( + **build_configuration['storage_location'] + ) + ) + return build.ref + + return '' + + def _create_alias(self, alias_configuration: dict, fleet_id: str) -> gamelift.CfnAlias: + """ + Create an alias for an Amazon GameLift fleet destination. + :param alias_configuration: Configuration of the alias + :param fleet_id: Fleet id that the alias points to. + :return: Generated GameLift fleet alias. + """ + alias = gamelift.CfnAlias( + self, + id=f'{self._stack_name}-GameLiftAlias', + name=alias_configuration.get('name'), + routing_strategy=gamelift.CfnAlias.RoutingStrategyProperty( + **alias_configuration.get('routing_strategy', {}), + fleet_id=fleet_id + ) + ) + + return alias + + def _create_game_session_queue(self, destinations: typing.List) -> gamelift.CfnGameSessionQueue: + """ + Create a placement queue that processes requests for new game sessions. + :param destinations: Destinations of the queue. + :return: Generated GameLift game session queue. + """ + game_session_queue = gamelift.CfnGameSessionQueue( + self, + id=f'{self._stack_name}-GameLiftQueue', + name=f'{self._stack_name}-game-session-queue', + destinations=[ + gamelift.CfnGameSessionQueue.DestinationProperty( + destination_arn=resource_arn + ) for resource_arn in destinations + ] + ) + + return game_session_queue + + + diff --git a/Gems/AWSGameLift/cdk/aws_gamelift/support_stack.py b/Gems/AWSGameLift/cdk/aws_gamelift/support_stack.py new file mode 100644 index 0000000000..717b220ca7 --- /dev/null +++ b/Gems/AWSGameLift/cdk/aws_gamelift/support_stack.py @@ -0,0 +1,71 @@ +""" +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. +""" + +from aws_cdk import aws_iam as iam +from aws_cdk import aws_s3_assets as assets +from aws_cdk import core + + +class SupportStack(core.Stack): + """ + The support stack + + Defines AWS resources that help to create GameLift builds from local files + """ + def __init__(self, scope: core.Construct, id_: str, + stack_name: str, fleet_configurations: dict, **kwargs) -> None: + super().__init__(scope, id_, **kwargs) + self._stack_name = stack_name + self._support_iam_role = self._create_support_iam_role() + + for index in range(len(fleet_configurations)): + # Update the fleet configuration to include the corresponding build id + fleet_configurations[index]['build_configuration']['storage_location'] = self._upload_build_asset( + fleet_configurations[index].get('build_configuration', {}), index) + + def _create_support_iam_role(self) -> iam.Role: + """ + Create an IAM role for GameLift to read build files stored in S3. + :return: Generated IAM role. + """ + support_role = iam.Role( + self, + id=f'{self._stack_name}-SupportRole', + assumed_by=iam.ServicePrincipal( + service='gamelift.amazonaws.com' + ) + ) + + return support_role + + def _upload_build_asset(self, build_configuration: dict, identifier: int) -> dict: + """ + Upload the local build files to S3 for a creating GameLift build. + :param build_configuration: Configuration of the GameLift build. + :param identifier: Unique identifier of the asset which will be included in the resource id. + :return: Storage location of the S3 object. + """ + build_asset = assets.Asset( + self, + id=f'{self._stack_name}-Asset{identifier}', + path=build_configuration.get('build_path') + ) + # Grant the support IAM role permission to read the asset + build_asset.grant_read(self._support_iam_role) + + storage_location = { + 'bucket': build_asset.s3_bucket_name, + 'key': build_asset.s3_object_key, + 'role_arn': self._support_iam_role.role_arn + } + + return storage_location + diff --git a/Gems/AWSGameLift/cdk/cdk.json b/Gems/AWSGameLift/cdk/cdk.json new file mode 100644 index 0000000000..ff5bd91e35 --- /dev/null +++ b/Gems/AWSGameLift/cdk/cdk.json @@ -0,0 +1,17 @@ +{ + "app": "python app.py", + "context": { + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/core:enableStackNameDuplicates": "true", + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true, + "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true, + "@aws-cdk/aws-kms:defaultKeyPolicies": true, + "@aws-cdk/aws-s3:grantWriteWithoutAcl": true, + "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-lambda:recognizeVersionProps": true + } +} diff --git a/Gems/AWSGameLift/cdk/requirements.txt b/Gems/AWSGameLift/cdk/requirements.txt new file mode 100644 index 0000000000..f04d2b9aa4 --- /dev/null +++ b/Gems/AWSGameLift/cdk/requirements.txt @@ -0,0 +1,4 @@ +aws-cdk.core>=1.91.0 +aws-cdk.aws_gamelift>=1.91.0 +aws-cdk.aws_iam>=1.91.0 +aws-cdk.aws_s3_assets>=1.91.0 diff --git a/Gems/AWSGameLift/gem.json b/Gems/AWSGameLift/gem.json new file mode 100644 index 0000000000..586d09968b --- /dev/null +++ b/Gems/AWSGameLift/gem.json @@ -0,0 +1,14 @@ +{ + "gem_name": "AWSGameLift", + "origin": "The primary repo for AWSGameLift goes here: i.e. http://www.mydomain.com", + "license": "What license AWSGameLift uses goes here: i.e. https://opensource.org/licenses/MIT", + "display_name": "AWSGameLift", + "summary": "The AWSGameLift Gem provides a framework to extend O3DE networking layer to work with GameLift resources via GameLift server and client SDK.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "AWSGameLift" + ], + "icon_path": "preview.png" +} diff --git a/Gems/AWSGameLift/preview.png b/Gems/AWSGameLift/preview.png new file mode 100644 index 0000000000..fc6c77b3dd --- /dev/null +++ b/Gems/AWSGameLift/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7aca75b5e82f55478f4f8c575d384ff3ef67260dde2107bd45d377516e6104f7 +size 22196 diff --git a/engine.json b/engine.json index 8449e763a9..e1ba5e8a5d 100644 --- a/engine.json +++ b/engine.json @@ -18,6 +18,7 @@ "Gems/AutomatedLauncherTesting", "Gems/AWSClientAuth", "Gems/AWSCore", + "Gems/AWSGameLift", "Gems/AWSMetrics", "Gems/Blast", "Gems/Camera",