[LYN-7050] Add matchmaking ticket tracker component (#4485)

Signed-off-by: onecent1101 <liug@amazon.com>
This commit is contained in:
Vincent Liu
2021-10-06 14:01:52 -07:00
committed by GitHub
parent ae96d1ab86
commit 0657f6f7ec
15 changed files with 799 additions and 95 deletions
@@ -0,0 +1,169 @@
/*
* 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 <AzCore/Interface/Interface.h>
#include <AzCore/std/bind/bind.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Session/ISessionHandlingRequests.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <AWSGameLiftSessionConstants.h>
#include <Request/IAWSGameLiftInternalRequests.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/GameLiftClient.h>
#include <aws/gamelift/model/DescribeMatchmakingRequest.h>
namespace AWSGameLift
{
AWSGameLiftClientLocalTicketTracker::AWSGameLiftClientLocalTicketTracker()
: m_status(TicketTrackerStatus::Idle)
, m_pollingPeriodInMS(AWSGameLiftClientDefaultPollingPeriodInMS)
{
}
void AWSGameLiftClientLocalTicketTracker::ActivateTracker()
{
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Register(this);
}
void AWSGameLiftClientLocalTicketTracker::DeactivateTracker()
{
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Unregister(this);
StopPolling();
}
void AWSGameLiftClientLocalTicketTracker::StartPolling(
const AZStd::string& ticketId, const AZStd::string& playerId)
{
AZStd::lock_guard<AZStd::mutex> lock(m_trackerMutex);
if (m_status == TicketTrackerStatus::Running)
{
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket tracker is running.");
return;
}
m_status = TicketTrackerStatus::Running;
m_trackerThread = AZStd::thread(AZStd::bind(
&AWSGameLiftClientLocalTicketTracker::ProcessPolling, this, ticketId, playerId));
}
void AWSGameLiftClientLocalTicketTracker::StopPolling()
{
AZStd::lock_guard<AZStd::mutex> lock(m_trackerMutex);
m_status = TicketTrackerStatus::Idle;
if (m_trackerThread.joinable())
{
m_trackerThread.join();
}
}
void AWSGameLiftClientLocalTicketTracker::ProcessPolling(
const AZStd::string& ticketId, const AZStd::string& playerId)
{
while (m_status == TicketTrackerStatus::Running)
{
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
if (gameliftClient)
{
Aws::GameLift::Model::DescribeMatchmakingRequest request;
request.AddTicketIds(ticketId.c_str());
auto describeMatchmakingOutcome = gameliftClient->DescribeMatchmaking(request);
if (describeMatchmakingOutcome.IsSuccess())
{
if (describeMatchmakingOutcome.GetResult().GetTicketList().size() == 1)
{
auto ticket = describeMatchmakingOutcome.GetResult().GetTicketList().front();
if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED)
{
m_status = TicketTrackerStatus::Idle;
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName,
"Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str());
RequestPlayerJoinMatch(ticket, playerId);
return;
}
else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::TIMED_OUT ||
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED ||
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED)
{
m_status = TicketTrackerStatus::Idle;
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s",
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str());
return;
}
else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE)
{
// broadcast acceptance requires to player
}
else
{
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.",
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str());
}
}
else
{
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str());
}
}
else
{
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate,
describeMatchmakingOutcome.GetError().GetExceptionName().c_str(),
describeMatchmakingOutcome.GetError().GetMessage().c_str());
}
}
else
{
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage);
}
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS));
}
}
void AWSGameLiftClientLocalTicketTracker::RequestPlayerJoinMatch(
const Aws::GameLift::Model::MatchmakingTicket& ticket, const AZStd::string& playerId)
{
auto connectionInfo = ticket.GetGameSessionConnectionInfo();
AzFramework::SessionConnectionConfig sessionConnectionConfig;
sessionConnectionConfig.m_ipAddress = connectionInfo.GetIpAddress().c_str();
for (auto matchedPlayer : connectionInfo.GetMatchedPlayerSessions())
{
if (playerId.compare(matchedPlayer.GetPlayerId().c_str()) == 0)
{
sessionConnectionConfig.m_playerSessionId = matchedPlayer.GetPlayerSessionId().c_str();
break;
}
}
sessionConnectionConfig.m_port = static_cast<uint16_t>(connectionInfo.GetPort());
if (!sessionConnectionConfig.m_playerSessionId.empty())
{
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName,
"Requesting and validating player session %s to connect to the match ...",
sessionConnectionConfig.m_playerSessionId.c_str());
bool result =
AZ::Interface<AzFramework::ISessionHandlingClientRequests>::Get()->RequestPlayerJoinSession(sessionConnectionConfig);
if (result)
{
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName,
"Started connection process, and connection validation is in process.");
}
else
{
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false,
"Failed to start connection process.");
}
}
else
{
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false,
"Player session id is missing for player % to join the match.", playerId.c_str());
}
}
}
@@ -0,0 +1,62 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/std/parallel/mutex.h>
#include <AzCore/std/parallel/thread.h>
#include <Request/IAWSGameLiftMatchmakingInternalRequests.h>
#include <aws/gamelift/model/MatchmakingTicket.h>
namespace AWSGameLift
{
enum TicketTrackerStatus
{
Idle,
Running
};
//! AWSGameLiftClientLocalTicketTracker
//! GameLift client ticket tracker to describe submitted matchmaking ticket periodically,
//! and join player to the match once matchmaking ticket is complete.
//! For use in production, please see GameLifts guidance about matchmaking at volume.
//! The continuous polling approach here is only suitable for low volume matchmaking and is meant to aid with development only
class AWSGameLiftClientLocalTicketTracker
: public IAWSGameLiftMatchmakingInternalRequests
{
public:
static constexpr const char AWSGameLiftClientLocalTicketTrackerName[] = "AWSGameLiftClientLocalTicketTracker";
// Set ticket polling period to 10 seconds
// https://docs.aws.amazon.com/gamelift/latest/flexmatchguide/match-client.html#match-client-track
static constexpr const uint64_t AWSGameLiftClientDefaultPollingPeriodInMS = 10000;
AWSGameLiftClientLocalTicketTracker();
virtual ~AWSGameLiftClientLocalTicketTracker() = default;
virtual void ActivateTracker();
virtual void DeactivateTracker();
// IAWSGameLiftMatchmakingInternalRequests interface implementation
void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) override;
void StopPolling() override;
protected:
// For testing friendly access
uint64_t m_pollingPeriodInMS;
TicketTrackerStatus m_status;
private:
void ProcessPolling(const AZStd::string& ticketId, const AZStd::string& playerId);
void RequestPlayerJoinMatch(const Aws::GameLift::Model::MatchmakingTicket& ticket, const AZStd::string& playerId);
AZStd::mutex m_trackerMutex;
AZStd::thread m_trackerThread;
};
} // namespace AWSGameLift
@@ -17,11 +17,13 @@
#include <ResourceMapping/AWSResourceMappingBus.h>
#include <AWSGameLiftClientManager.h>
#include <AWSGameLiftSessionConstants.h>
#include <Activity/AWSGameLiftCreateSessionActivity.h>
#include <Activity/AWSGameLiftCreateSessionOnQueueActivity.h>
#include <Activity/AWSGameLiftJoinSessionActivity.h>
#include <Activity/AWSGameLiftLeaveSessionActivity.h>
#include <Activity/AWSGameLiftSearchSessionsActivity.h>
#include <Request/IAWSGameLiftInternalRequests.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
@@ -31,11 +33,6 @@ namespace AWSGameLift
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<IAWSGameLiftRequests>::Register(this);
@@ -62,8 +59,7 @@ namespace AWSGameLift
bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region)
{
m_gameliftClient.reset();
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
Aws::Client::ClientConfiguration clientConfig;
// Set up client endpoint or region
AZStd::string localEndpoint = "";
@@ -103,7 +99,8 @@ namespace AWSGameLift
AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientCredentialMissingErrorMessage);
return false;
}
m_gameliftClient = AZStd::make_shared<Aws::GameLift::GameLiftClient>(credentialResult.result, clientConfig);
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(
AZStd::make_shared<Aws::GameLift::GameLiftClient>(credentialResult.result, clientConfig));
return true;
}
@@ -194,15 +191,15 @@ namespace AWSGameLift
AZStd::string AWSGameLiftClientManager::CreateSessionHelper(
const AWSGameLiftCreateSessionRequest& createSessionRequest)
{
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameLiftClient = m_gameliftClient;
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
AZStd::string result = "";
if (!gameLiftClient)
if (!gameliftClient)
{
AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage);
}
else
{
result = CreateSessionActivity::CreateSession(*gameLiftClient, createSessionRequest);
result = CreateSessionActivity::CreateSession(*gameliftClient, createSessionRequest);
}
return result;
}
@@ -210,7 +207,7 @@ namespace AWSGameLift
AZStd::string AWSGameLiftClientManager::CreateSessionOnQueueHelper(
const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest)
{
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient = m_gameliftClient;
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
AZStd::string result;
if (!gameliftClient)
{
@@ -265,7 +262,7 @@ namespace AWSGameLift
bool AWSGameLiftClientManager::JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest)
{
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient = m_gameliftClient;
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
bool result = false;
if (!gameliftClient)
{
@@ -345,8 +342,7 @@ namespace AWSGameLift
AzFramework::SearchSessionsResponse AWSGameLiftClientManager::SearchSessionsHelper(
const AWSGameLiftSearchSessionsRequest& searchSessionsRequest) const
{
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient = m_gameliftClient;
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
AzFramework::SearchSessionsResponse response;
if (!gameliftClient)
{
@@ -380,9 +376,4 @@ namespace AWSGameLift
{
AZ_UNUSED(stopMatchmakingRequest);
}
void AWSGameLiftClientManager::SetGameLiftClient(AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient)
{
m_gameliftClient.swap(gameliftClient);
}
} // namespace AWSGameLift
@@ -13,14 +13,6 @@
#include <Request/IAWSGameLiftRequests.h>
namespace Aws
{
namespace GameLift
{
class GameLiftClient;
}
}
namespace AWSGameLift
{
struct AWSGameLiftCreateSessionRequest;
@@ -127,13 +119,11 @@ namespace AWSGameLift
"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();
AWSGameLiftClientManager() = default;
virtual ~AWSGameLiftClientManager() = default;
virtual void ActivateManager();
@@ -165,16 +155,10 @@ namespace AWSGameLift
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<Aws::GameLift::GameLiftClient> 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<Aws::GameLift::GameLiftClient> m_gameliftClient;
};
} // namespace AWSGameLift
@@ -6,6 +6,7 @@
*
*/
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
@@ -27,7 +28,8 @@ namespace AWSGameLift
{
AWSGameLiftClientSystemComponent::AWSGameLiftClientSystemComponent()
{
m_gameliftClientManager = AZStd::make_unique<AWSGameLiftClientManager>();
m_gameliftManager = AZStd::make_unique<AWSGameLiftClientManager>();
m_gameliftTicketTracker = AZStd::make_unique<AWSGameLiftClientLocalTicketTracker>();
}
void AWSGameLiftClientSystemComponent::Reflect(AZ::ReflectContext* context)
@@ -90,12 +92,20 @@ namespace AWSGameLift
void AWSGameLiftClientSystemComponent::Activate()
{
m_gameliftClientManager->ActivateManager();
AZ::Interface<IAWSGameLiftInternalRequests>::Register(this);
m_gameliftClient.reset();
m_gameliftManager->ActivateManager();
m_gameliftTicketTracker->ActivateTracker();
}
void AWSGameLiftClientSystemComponent::Deactivate()
{
m_gameliftClientManager->DeactivateManager();
m_gameliftTicketTracker->DeactivateTracker();
m_gameliftManager->DeactivateManager();
m_gameliftClient.reset();
AZ::Interface<IAWSGameLiftInternalRequests>::Unregister(this);
}
void AWSGameLiftClientSystemComponent::ReflectGameLiftMatchmaking(AZ::ReflectContext* context)
@@ -213,9 +223,25 @@ namespace AWSGameLift
}
}
void AWSGameLiftClientSystemComponent::SetGameLiftClientManager(AZStd::unique_ptr<AWSGameLiftClientManager> gameliftClientManager)
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> AWSGameLiftClientSystemComponent::GetGameLiftClient() const
{
m_gameliftClientManager.reset();
m_gameliftClientManager = AZStd::move(gameliftClientManager);
return m_gameliftClient;
}
void AWSGameLiftClientSystemComponent::SetGameLiftClient(AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient)
{
m_gameliftClient.swap(gameliftClient);
}
void AWSGameLiftClientSystemComponent::SetGameLiftClientManager(AZStd::unique_ptr<AWSGameLiftClientManager> gameliftManager)
{
m_gameliftManager.reset();
m_gameliftManager = AZStd::move(gameliftManager);
}
void AWSGameLiftClientSystemComponent::SetGameLiftClientTicketTracker(AZStd::unique_ptr<AWSGameLiftClientLocalTicketTracker> gameliftTicketTracker)
{
m_gameliftTicketTracker.reset();
m_gameliftTicketTracker = AZStd::move(gameliftTicketTracker);
}
} // namespace AWSGameLift
@@ -11,6 +11,9 @@
#include <AzCore/Component/Component.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <Request/IAWSGameLiftInternalRequests.h>
namespace AWSGameLift
{
class AWSGameLiftClientManager;
@@ -18,6 +21,7 @@ namespace AWSGameLift
//! Gem client system component. Responsible for creating the gamelift client manager.
class AWSGameLiftClientSystemComponent
: public AZ::Component
, public IAWSGameLiftInternalRequests
{
public:
AZ_COMPONENT(AWSGameLiftClientSystemComponent, "{d481c15c-732a-4eea-9853-4965ed1bc2be}");
@@ -32,6 +36,10 @@ namespace AWSGameLift
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
// IAWSGameLiftInternalRequests interface implementation
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> GetGameLiftClient() const override;
void SetGameLiftClient(AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient) override;
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
@@ -40,7 +48,8 @@ namespace AWSGameLift
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
void SetGameLiftClientManager(AZStd::unique_ptr<AWSGameLiftClientManager> gameliftClientManager);
void SetGameLiftClientManager(AZStd::unique_ptr<AWSGameLiftClientManager> gameliftManager);
void SetGameLiftClientTicketTracker(AZStd::unique_ptr<AWSGameLiftClientLocalTicketTracker> gameliftTicketTracker);
private:
static void ReflectGameLiftMatchmaking(AZ::ReflectContext* context);
@@ -49,7 +58,9 @@ namespace AWSGameLift
static void ReflectCreateSessionRequest(AZ::ReflectContext* context);
static void ReflectSearchSessionsResponse(AZ::ReflectContext* context);
AZStd::unique_ptr<AWSGameLiftClientManager> m_gameliftClientManager;
AZStd::shared_ptr<Aws::GameLift::GameLiftClient> m_gameliftClient;
AZStd::unique_ptr<AWSGameLiftClientManager> m_gameliftManager;
AZStd::unique_ptr<AWSGameLiftClientLocalTicketTracker> m_gameliftTicketTracker;
};
} // namespace AWSGameLift
@@ -0,0 +1,43 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
namespace Aws
{
namespace GameLift
{
class GameLiftClient;
}
} // namespace Aws
namespace AWSGameLift
{
//! IAWSGameLiftRequests
//! GameLift Gem internal interface which is used to fetch gem global GameLift client
class IAWSGameLiftInternalRequests
{
public:
AZ_RTTI(IAWSGameLiftInternalRequests, "{DC0CC1C4-21EE-4A41-B428-D12D697F88A2}");
IAWSGameLiftInternalRequests() = default;
virtual ~IAWSGameLiftInternalRequests() = default;
//! GetGameLiftClient
//! Get GameLift client to interact with Amazon GameLift service
//! @return Shared pointer to the GameLift client
virtual AZStd::shared_ptr<Aws::GameLift::GameLiftClient> GetGameLiftClient() const = 0;
//! SetGameLiftClient
//! Set GameLift client to provided GameLift client
//! @param gameliftClient Input new GameLift client
virtual void SetGameLiftClient(AZStd::shared_ptr<Aws::GameLift::GameLiftClient> gameliftClient) = 0;
};
} // namespace AWSGameLift
@@ -0,0 +1,39 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
namespace AWSGameLift
{
//! IAWSGameLiftMatchmakingInternalRequests
//! GameLift Gem matchmaking internal interfaces which is used to communicate
//! with client side ticket tracker to sync matchmaking ticket data and join
//! player to the match
class IAWSGameLiftMatchmakingInternalRequests
{
public:
AZ_RTTI(IAWSGameLiftMatchmakingInternalRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}");
IAWSGameLiftMatchmakingInternalRequests() = default;
virtual ~IAWSGameLiftMatchmakingInternalRequests() = default;
//! StartPolling
//! Request to start process for polling matchmaking ticket based on given ticket id and player id
//! @param ticketId The requested matchmaking ticket id
//! @param playerId The requested matchmaking player id
virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0;
//! StopPolling
//! Request to stop process for polling matchmaking ticket
virtual void StopPolling() = 0;
};
} // namespace AWSGameLift