Gems/AWSGameLift/Code/AWSGameLiftServer/Source/AWSGameLiftServerManager.cpp (756 lines of code) (raw):

/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include <AWSGameLiftServerManager.h> #include <AWSGameLiftSessionConstants.h> #include <GameLiftServerSDKWrapper.h> #include <AzCore/Console/IConsole.h> #include <AzCore/Debug/Trace.h> #include <AzCore/Interface/Interface.h> #include <AzCore/IO/FileIO.h> #include <AzCore/IO/SystemFile.h> #include <AzCore/Jobs/JobFunction.h> #include <AzCore/Jobs/JobManagerBus.h> #include <AzCore/JSON/error/en.h> #include <AzCore/JSON/stringbuffer.h> #include <AzCore/JSON/writer.h> #include <AzCore/std/bind/bind.h> #include <Multiplayer/Session/SessionNotifications.h> AZ_CVAR( bool, sv_gameliftAnywhereEnabled, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Enable GameLift Anywhere support for server testing." ); AZ_CVAR( AZ::CVarFixedString, sv_gameliftAnywhereWebSocketUrl, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "WebSocketUrl to the Anywhere fleet. Required if GameLift Anywhere support is enabled." ); AZ_CVAR( AZ::CVarFixedString, sv_gameliftAnywhereAuthToken, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "AuthToken of the Compute the server is running on. Required if GameLift Anywhere support is enabled." ); AZ_CVAR( AZ::CVarFixedString, sv_gameliftAnywhereFleetId, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "FleetId setting for GameLift Anywhere." "FleetId of the Anywhere fleet. Required if GameLift Anywhere support is enabled." ); AZ_CVAR( AZ::CVarFixedString, sv_gameliftAnywhereHostId, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "HostId (ComputeId) of the Compute the server is running on. Required if GameLift Anywhere support is enabled."); AZ_CVAR( AZ::CVarFixedString, sv_gameliftAnywhereProcessId, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Unique string to indentify the process. Defaults to a string generated from the current timestamp." ); namespace AWSGameLift { AWSGameLiftServerManager::AWSGameLiftServerManager() : m_serverSDKInitialized(false) , m_gameLiftServerSDKWrapper(AZStd::make_unique<GameLiftServerSDKWrapper>()) , m_connectedPlayers() { } AWSGameLiftServerManager::~AWSGameLiftServerManager() { m_gameLiftServerSDKWrapper.reset(); m_connectedPlayers.clear(); } void AWSGameLiftServerManager::ActivateManager() { AZ::Interface<IAWSGameLiftServerRequests>::Register(this); AWSGameLiftServerRequestBus::Handler::BusConnect(); } void AWSGameLiftServerManager::DeactivateManager() { AWSGameLiftServerRequestBus::Handler::BusDisconnect(); AZ::Interface<IAWSGameLiftServerRequests>::Unregister(this); } bool AWSGameLiftServerManager::AddConnectedPlayer(const Multiplayer::PlayerConnectionConfig& playerConnectionConfig) { AZStd::lock_guard<AZStd::mutex> 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; } } GameLiftServerProcessDesc AWSGameLiftServerManager::BuildGameLiftServerProcessDesc() { 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(AWSGameLiftServerManagerName, false, "Failed to resolve the path to the log folder."); } } else { AZ_Error(AWSGameLiftServerManagerName, false, "Failed to get File IO."); } if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr) { [[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sv_port", serverProcessDesc.m_port); AZ_Error(AWSGameLiftServerManagerName, getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sv_port' console variable failed with error %s", AZ::GetEnumString(getCvarResult)); } return serverProcessDesc; } Multiplayer::SessionConfig AWSGameLiftServerManager::BuildSessionConfig(const Aws::GameLift::Server::Model::GameSession& gameSession) { Multiplayer::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_matchmakingData = gameSession.GetMatchmakerData().c_str(); 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 = static_cast<uint16_t>(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; } bool AWSGameLiftServerManager::BuildServerMatchBackfillPlayer( const AWSGameLiftPlayer& player, Aws::GameLift::Server::Model::Player& outBackfillPlayer) { outBackfillPlayer.SetPlayerId(player.m_playerId.c_str()); outBackfillPlayer.SetTeam(player.m_team.c_str()); for (auto latencyPair : player.m_latencyInMs) { outBackfillPlayer.AddLatencyInMs(latencyPair.first.c_str(), latencyPair.second); } for (auto attributePair : player.m_playerAttributes) { Aws::GameLift::Server::Model::AttributeValue playerAttribute; rapidjson::Document attributeDocument; rapidjson::ParseResult parseResult = attributeDocument.Parse(attributePair.second.c_str()); // player attribute json content should always be a single member object if (parseResult && attributeDocument.IsObject() && attributeDocument.MemberCount() == 1) { if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSTypeName) || attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSServerTypeName)) && attributeDocument.MemberBegin()->value.IsString()) { playerAttribute = Aws::GameLift::Server::Model::AttributeValue( attributeDocument.MemberBegin()->value.GetString()); } else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeNTypeName) || attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeNServerTypeName)) && attributeDocument.MemberBegin()->value.IsNumber()) { playerAttribute = Aws::GameLift::Server::Model::AttributeValue( attributeDocument.MemberBegin()->value.GetDouble()); } else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSDMTypeName) || attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSDMServerTypeName)) && attributeDocument.MemberBegin()->value.IsObject()) { playerAttribute = Aws::GameLift::Server::Model::AttributeValue::ConstructStringDoubleMap(); for (auto iter = attributeDocument.MemberBegin()->value.MemberBegin(); iter != attributeDocument.MemberBegin()->value.MemberEnd(); iter++) { if (iter->name.IsString() && iter->value.IsNumber()) { playerAttribute.AddStringAndDouble(iter->name.GetString(), iter->value.GetDouble()); } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage, player.m_playerId.c_str(), "String double map key must be string type and value must be number type"); return false; } } } else if ((attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSLTypeName) || attributeDocument.HasMember(AWSGameLiftMatchmakingPlayerAttributeSLServerTypeName)) && attributeDocument.MemberBegin()->value.IsArray()) { playerAttribute = Aws::GameLift::Server::Model::AttributeValue::ConstructStringList(); for (auto iter = attributeDocument.MemberBegin()->value.Begin(); iter != attributeDocument.MemberBegin()->value.End(); iter++) { if (iter->IsString()) { playerAttribute.AddString(iter->GetString()); } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage, player.m_playerId.c_str(), "String list element must be string type"); return false; } } } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage, player.m_playerId.c_str(), "S, N, SDM or SLM is expected as attribute type."); return false; } } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingPlayerAttributeInvalidErrorMessage, player.m_playerId.c_str(), rapidjson::GetParseError_En(parseResult.Code())); return false; } outBackfillPlayer.AddPlayerAttribute(attributePair.first.c_str(), playerAttribute); } return true; } AZStd::vector<AWSGameLiftPlayer> AWSGameLiftServerManager::GetActiveServerMatchBackfillPlayers() { AZStd::vector<AWSGameLiftPlayer> activePlayers; // Keep processing only when game session has matchmaking data if (IsMatchmakingDataValid()) { auto activePlayerSessions = GetActivePlayerSessions(); for (auto playerSession : activePlayerSessions) { AWSGameLiftPlayer player; if (BuildActiveServerMatchBackfillPlayer(playerSession.GetPlayerId().c_str(), player)) { activePlayers.push_back(player); } } } return activePlayers; } bool AWSGameLiftServerManager::IsMatchmakingDataValid() { return m_matchmakingData.IsObject() && m_matchmakingData.HasMember(AWSGameLiftMatchmakingConfigurationKeyName) && m_matchmakingData.HasMember(AWSGameLiftMatchmakingTeamsKeyName); } AZStd::vector<Aws::GameLift::Server::Model::PlayerSession> AWSGameLiftServerManager::GetActivePlayerSessions() { Aws::GameLift::Server::Model::DescribePlayerSessionsRequest describeRequest; describeRequest.SetGameSessionId(m_gameSession.GetGameSessionId()); describeRequest.SetPlayerSessionStatusFilter( Aws::GameLift::Server::Model::PlayerSessionStatusMapper::GetNameForPlayerSessionStatus( Aws::GameLift::Server::Model::PlayerSessionStatus::ACTIVE)); int maxPlayerSession = m_gameSession.GetMaximumPlayerSessionCount(); AZStd::vector<Aws::GameLift::Server::Model::PlayerSession> activePlayerSessions; if (maxPlayerSession <= AWSGameLiftDescribePlayerSessionsPageSize) { describeRequest.SetLimit(maxPlayerSession); auto outcome = m_gameLiftServerSDKWrapper->DescribePlayerSessions(describeRequest); if (outcome.IsSuccess()) { for (auto playerSession : outcome.GetResult().GetPlayerSessions()) { activePlayerSessions.push_back(playerSession); } } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftDescribePlayerSessionsErrorMessage, outcome.GetError().GetErrorMessage().c_str()); } } else { describeRequest.SetLimit(AWSGameLiftDescribePlayerSessionsPageSize); while (true) { auto outcome = m_gameLiftServerSDKWrapper->DescribePlayerSessions(describeRequest); if (outcome.IsSuccess()) { for (auto playerSession : outcome.GetResult().GetPlayerSessions()) { activePlayerSessions.push_back(playerSession); } if (outcome.GetResult().GetNextToken().empty()) { break; } else { describeRequest.SetNextToken(outcome.GetResult().GetNextToken()); } } else { activePlayerSessions.clear(); AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftDescribePlayerSessionsErrorMessage, outcome.GetError().GetErrorMessage().c_str()); break; } } } return activePlayerSessions; } bool AWSGameLiftServerManager::BuildActiveServerMatchBackfillPlayer(const AZStd::string& playerId, AWSGameLiftPlayer& outPlayer) { // As data is from GameLift service, assume it is always in correct format rapidjson::Value& teams = m_matchmakingData[AWSGameLiftMatchmakingTeamsKeyName]; // Iterate through teams to find target player for (rapidjson::SizeType teamIndex = 0; teamIndex < teams.Size(); ++teamIndex) { rapidjson::Value& players = teams[teamIndex][AWSGameLiftMatchmakingPlayersKeyName]; // Iterate through players under the team to find target player for (rapidjson::SizeType playerIndex = 0; playerIndex < players.Size(); ++playerIndex) { if (std::strcmp(players[playerIndex][AWSGameLiftMatchmakingPlayerIdKeyName].GetString(), playerId.c_str()) == 0) { outPlayer.m_playerId = playerId; outPlayer.m_team = teams[teamIndex][AWSGameLiftMatchmakingTeamNameKeyName].GetString(); // Get player attributes if target player has if (players[playerIndex].HasMember(AWSGameLiftMatchmakingPlayerAttributesKeyName)) { BuildServerMatchBackfillPlayerAttributes( players[playerIndex][AWSGameLiftMatchmakingPlayerAttributesKeyName], outPlayer); } return true; } } } return false; } void AWSGameLiftServerManager::BuildServerMatchBackfillPlayerAttributes( const rapidjson::Value& playerAttributes, AWSGameLiftPlayer& outPlayer) { for (auto iter = playerAttributes.MemberBegin(); iter != playerAttributes.MemberEnd(); iter++) { AZStd::string attributeName = iter->name.GetString(); rapidjson::StringBuffer jsonStringBuffer; rapidjson::Writer<rapidjson::StringBuffer> writer(jsonStringBuffer); iter->value[AWSGameLiftMatchmakingPlayerAttributeValueKeyName].Accept(writer); AZStd::string attributeType = iter->value[AWSGameLiftMatchmakingPlayerAttributeTypeKeyName].GetString(); AZStd::string attributeValue = AZStd::string::format("{\"%s\": %s}", attributeType.c_str(), jsonStringBuffer.GetString()); outPlayer.m_playerAttributes.emplace(attributeName, attributeValue); } } bool AWSGameLiftServerManager::BuildStartMatchBackfillRequest( const AZStd::string& ticketId, const AZStd::vector<AWSGameLiftPlayer>& players, Aws::GameLift::Server::Model::StartMatchBackfillRequest& outRequest) { outRequest.SetGameSessionArn(m_gameSession.GetGameSessionId()); outRequest.SetMatchmakingConfigurationArn(m_matchmakingData[AWSGameLiftMatchmakingConfigurationKeyName].GetString()); if (!ticketId.empty()) { outRequest.SetTicketId(ticketId.c_str()); } AZStd::vector<AWSGameLiftPlayer> requestPlayers(players); if (players.size() == 0) { requestPlayers = GetActiveServerMatchBackfillPlayers(); } for (auto player : requestPlayers) { Aws::GameLift::Server::Model::Player backfillPlayer; if (BuildServerMatchBackfillPlayer(player, backfillPlayer)) { outRequest.AddPlayer(backfillPlayer); } else { return false; } } return true; } void AWSGameLiftServerManager::BuildStopMatchBackfillRequest( const AZStd::string& ticketId, Aws::GameLift::Server::Model::StopMatchBackfillRequest& outRequest) { outRequest.SetGameSessionArn(m_gameSession.GetGameSessionId()); outRequest.SetMatchmakingConfigurationArn(m_matchmakingData[AWSGameLiftMatchmakingConfigurationKeyName].GetString()); if (!ticketId.empty()) { outRequest.SetTicketId(ticketId.c_str()); } } AZ::IO::Path AWSGameLiftServerManager::GetInternalSessionCertificate() { // GameLift doesn't support it, return empty path return AZ::IO::Path(); } AZ::IO::Path AWSGameLiftServerManager::GetExternalSessionCertificate() { auto certificateOutcome = m_gameLiftServerSDKWrapper->GetComputeCertificate(); if (certificateOutcome.IsSuccess()) { return AZ::IO::Path(certificateOutcome.GetResult().GetCertificatePath().c_str()); } else { AZ_Error( AWSGameLiftServerManagerName, false, AZStd::string::format( "%s - %s", AWSGameLiftServerInstanceCertificateErrorMessage, certificateOutcome.GetError().GetErrorMessage().c_str() ).c_str() ); return AZ::IO::Path(); } } void AWSGameLiftServerManager::InitializeGameLiftServerSDK() { if (m_serverSDKInitialized) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKAlreadyInitErrorMessage); return; } Aws::GameLift::Server::Model::ServerParameters serverParameters; if (sv_gameliftAnywhereEnabled) { if (auto webSocketUrl = static_cast<AZ::CVarFixedString>(sv_gameliftAnywhereWebSocketUrl); !webSocketUrl.empty()) { serverParameters.SetWebSocketUrl(webSocketUrl.c_str()); } if (auto authToken = static_cast<AZ::CVarFixedString>(sv_gameliftAnywhereAuthToken); !authToken.empty()) { serverParameters.SetAuthToken(authToken.c_str()); } if (auto fleetId = static_cast<AZ::CVarFixedString>(sv_gameliftAnywhereFleetId); !fleetId.empty()) { serverParameters.SetFleetId(fleetId.c_str()); } if (auto hostId = static_cast<AZ::CVarFixedString>(sv_gameliftAnywhereHostId); !hostId.empty()) { serverParameters.SetHostId(hostId.c_str()); } if (auto processId = static_cast<AZ::CVarFixedString>(sv_gameliftAnywhereProcessId); !processId.empty()) { serverParameters.SetProcessId(processId.c_str()); } else { // If ProcessId isn't defined, provide a unique string by default. AZStd::string defaultProcessId = AZStd::string::format("ProcessId_%i", aznumeric_cast<int>(std::time(nullptr))); serverParameters.SetProcessId(defaultProcessId.c_str()); AZ_Trace( AWSGameLiftServerManagerName, "Generated default AWS GameLift ProcessId value: %s\n", defaultProcessId.c_str()); } } AZ_Trace(AWSGameLiftServerManagerName, "Initiating AWS GameLift Server SDK ...\n"); Aws::GameLift::Server::InitSDKOutcome initOutcome = m_gameLiftServerSDKWrapper->InitSDK(serverParameters); if (initOutcome.IsSuccess()) { AZ_Trace(AWSGameLiftServerManagerName, "InitSDK request against Amazon GameLift service is complete.\n"); } else { AZ_Trace(AWSGameLiftServerManagerName, "InitSDK request against Amazon GameLift service failed.\n"); } m_serverSDKInitialized = initOutcome.IsSuccess(); AZ_Error(AWSGameLiftServerManagerName, m_serverSDKInitialized, AWSGameLiftServerInitSDKErrorMessage, initOutcome.GetError().GetErrorMessage().c_str()); } void AWSGameLiftServerManager::HandleDestroySession() { // No further request should be handled by GameLift server manager at this point if (AZ::Interface<Multiplayer::ISessionHandlingProviderRequests>::Get()) { AZ::Interface<Multiplayer::ISessionHandlingProviderRequests>::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<bool&, AZStd::logical_and<bool>> result(destroySessionResult); Multiplayer::SessionNotificationBus::BroadcastResult(result, &Multiplayer::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(); if (processEndingOutcome.IsSuccess()) { AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessEnding request against Amazon GameLift service succeeded."); Multiplayer::SessionNotificationBus::Broadcast(&Multiplayer::SessionNotifications::OnDestroySessionEnd); } else { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerProcessEndingErrorMessage, processEndingOutcome.GetError().GetErrorMessage().c_str()); } } void AWSGameLiftServerManager::HandlePlayerLeaveSession(const Multiplayer::PlayerConnectionConfig& playerConnectionConfig) { AZStd::string playerSessionId = ""; RemoveConnectedPlayer(playerConnectionConfig.m_playerConnectionId, playerSessionId); if (playerSessionId.empty()) { return; } AZ_TracePrintf(AWSGameLiftServerManagerName, "Removing player session %s from Amazon GameLift service ...", playerSessionId.c_str()); Aws::GameLift::GenericOutcome disconnectOutcome = m_gameLiftServerSDKWrapper->RemovePlayerSession(playerSessionId); AZ_TracePrintf(AWSGameLiftServerManagerName, "RemovePlayerSession request for player session %s against Amazon GameLift service is complete.", playerSessionId.c_str()); AZ_Error(AWSGameLiftServerManagerName, disconnectOutcome.IsSuccess(), AWSGameLiftServerRemovePlayerSessionErrorMessage, playerSessionId.c_str(), disconnectOutcome.GetError().GetErrorMessage().c_str()); } bool AWSGameLiftServerManager::NotifyGameLiftProcessReady() { if (!m_serverSDKInitialized) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage); return false; } GameLiftServerProcessDesc desc = BuildGameLiftServerProcessDesc(); 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<std::string> 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::placeholders::_1), 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); AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessReady request against Amazon GameLift service is complete."); 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) { UpdateGameSessionData(gameSession); Multiplayer::SessionConfig sessionConfig = BuildSessionConfig(gameSession); if (!AZ::Interface<Multiplayer::ISessionHandlingProviderRequests>::Get()) { AZ::Interface<Multiplayer::ISessionHandlingProviderRequests>::Register(this); } bool createSessionResult = true; AZ::EBusReduceResult<bool&, AZStd::logical_and<bool>> result(createSessionResult); Multiplayer::SessionNotificationBus::BroadcastResult( result, &Multiplayer::SessionNotifications::OnCreateSessionBegin, sessionConfig); if (createSessionResult) { AZ_TracePrintf(AWSGameLiftServerManagerName, "Activating GameLift game session ..."); Aws::GameLift::GenericOutcome activationOutcome = m_gameLiftServerSDKWrapper->ActivateGameSession(); if (activationOutcome.IsSuccess()) { AZ_TracePrintf(AWSGameLiftServerManagerName, "ActivateGameSession request against Amazon GameLift service succeeded."); Multiplayer::SessionNotificationBus::Broadcast(&Multiplayer::SessionNotifications::OnCreateSessionEnd); } 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<bool&, AZStd::logical_and<bool>> result(healthCheckResult); Multiplayer::SessionNotificationBus::BroadcastResult(result, &Multiplayer::SessionNotifications::OnSessionHealthCheck); return m_serverSDKInitialized && healthCheckResult; } void AWSGameLiftServerManager::OnUpdateGameSession(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession) { Multiplayer::SessionConfig sessionConfig = BuildSessionConfig(updateGameSession.GetGameSession()); Aws::GameLift::Server::Model::UpdateReason updateReason = updateGameSession.GetUpdateReason(); Multiplayer::SessionNotificationBus::Broadcast(&Multiplayer::SessionNotifications::OnUpdateSessionBegin, sessionConfig, Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str()); // Update game session data locally if (updateReason == Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED) { UpdateGameSessionData(updateGameSession.GetGameSession()); } Multiplayer::SessionNotificationBus::Broadcast(&Multiplayer::SessionNotifications::OnUpdateSessionEnd); } bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId) { AZStd::lock_guard<AZStd::mutex> 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> gameLiftServerSDKWrapper) { m_gameLiftServerSDKWrapper.reset(); m_gameLiftServerSDKWrapper = AZStd::move(gameLiftServerSDKWrapper); } bool AWSGameLiftServerManager::StartMatchBackfill(const AZStd::string& ticketId, const AZStd::vector<AWSGameLiftPlayer>& players) { if (!m_serverSDKInitialized) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage); return false; } if (!IsMatchmakingDataValid()) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingDataMissingErrorMessage); return false; } Aws::GameLift::Server::Model::StartMatchBackfillRequest request; if (!BuildStartMatchBackfillRequest(ticketId, players, request)) { return false; } AZ_TracePrintf(AWSGameLiftServerManagerName, "Starting match backfill %s ...", ticketId.c_str()); auto outcome = m_gameLiftServerSDKWrapper->StartMatchBackfill(request); if (!outcome.IsSuccess()) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftStartMatchBackfillErrorMessage, outcome.GetError().GetErrorMessage().c_str()); return false; } else { AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service succeeded."); return true; } } bool AWSGameLiftServerManager::StopMatchBackfill(const AZStd::string& ticketId) { if (!m_serverSDKInitialized) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage); return false; } if (!IsMatchmakingDataValid()) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingDataMissingErrorMessage); return false; } Aws::GameLift::Server::Model::StopMatchBackfillRequest request; BuildStopMatchBackfillRequest(ticketId, request); AZ_TracePrintf(AWSGameLiftServerManagerName, "Stopping match backfill %s ...", ticketId.c_str()); auto outcome = m_gameLiftServerSDKWrapper->StopMatchBackfill(request); if (!outcome.IsSuccess()) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftStopMatchBackfillErrorMessage, outcome.GetError().GetErrorMessage().c_str()); return false; } else { AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service succeeded."); return true; } } void AWSGameLiftServerManager::UpdateGameSessionData(const Aws::GameLift::Server::Model::GameSession& gameSession) { AZ_TracePrintf(AWSGameLiftServerManagerName, "Lazy loading game session and matchmaking data from Amazon GameLift service ..."); m_gameSession = Aws::GameLift::Server::Model::GameSession(gameSession); if (m_gameSession.GetMatchmakerData().empty()) { m_matchmakingData.Parse("{}"); } else { rapidjson::ParseResult parseResult = m_matchmakingData.Parse(m_gameSession.GetMatchmakerData().c_str()); if (!parseResult) { AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftMatchmakingDataInvalidErrorMessage, rapidjson::GetParseError_En(parseResult.Code())); } } } bool AWSGameLiftServerManager::ValidatePlayerJoinSession(const Multiplayer::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 %s connection with Amazon GameLift service ...", playerSessionId.c_str()); auto acceptPlayerSessionOutcome = m_gameLiftServerSDKWrapper->AcceptPlayerSession(playerSessionId.c_str()); AZ_TracePrintf(AWSGameLiftServerManagerName, "AcceptPlayerSession request for player session %s against Amazon GameLift service is complete.", 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