Files
o3de/Gems/GameLift/Code/Source/Session/GameLiftServerSession.cpp
T
2021-03-08 14:30:57 -08:00

634 lines
26 KiB
C++

/*
* 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 defined(BUILD_GAMELIFT_SERVER)
#include <GameLift/Session/GameLiftServerSDKWrapper.h>
#include <GameLift/Session/GameLiftServerSession.h>
#include <GameLift/Session/GameLiftServerService.h>
#include <GridMate/Online/UserServiceTypes.h>
#include <GridMate/Replica/Replica.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Carrier/SocketDriver.h>
#include <GridMate/Carrier/Utils.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/JSON/error/error.h>
#include <AzCore/JSON/error/en.h>
#include <aws/gamelift/server/GameLiftServerAPI.h>
#include <aws/gamelift/server/model/GameSession.h>
#include <GameLift/Session/GameLiftServerServiceEventsBus.h>
namespace GridMate
{
class GameLiftServerMember;
//-----------------------------------------------------------------------------
// GameLiftServerSessionReplica
//-----------------------------------------------------------------------------
class GameLiftServerSessionReplica
: public Internal::GridSessionReplica
{
public:
class GameLiftSessionReplicaDesc
: public ReplicaChunkDescriptor
{
public:
GameLiftSessionReplicaDesc()
: ReplicaChunkDescriptor(GameLiftServerSessionReplica::GetChunkName(), sizeof(GameLiftServerSessionReplica))
{
}
ReplicaChunkBase* CreateFromStream(UnmarshalContext&) override
{
AZ_Assert(false, "GameLiftServerSessionReplica should never be created from stream on the server!");
return nullptr;
}
void DiscardCtorStream(UnmarshalContext&) override {}
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override { delete chunkInstance; }
void MarshalCtorData(ReplicaChunkBase*, WriteBuffer&) override {}
};
GM_CLASS_ALLOCATOR(GameLiftServerSessionReplica);
static const char* GetChunkName() { return "GridMate::GameLiftSessionReplica"; }
GameLiftServerSessionReplica(GameLiftServerSession* session)
: GridSessionReplica(session)
{
}
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// GameLiftServerMemberID
//-----------------------------------------------------------------------------
class GameLiftServerMemberID
: public MemberID
{
public:
//-----------------------------------------------------------------------------
// Marshaler
//-----------------------------------------------------------------------------
class Marshaler
{
public:
void Marshal(WriteBuffer& wb, const GameLiftServerMemberID& id) const
{
wb.Write(id.m_id);
}
void Unmarshal(GameLiftServerMemberID& id, ReadBuffer& rb) const
{
rb.Read(id.m_id);
}
};
//-----------------------------------------------------------------------------
GameLiftServerMemberID()
: m_id(0)
{
}
GameLiftServerMemberID(const string& address, AZ::u32 memberId)
: m_address(address)
, m_id(memberId)
{
AZ_Assert(m_id != 0, "Invalid member id");
}
string ToString() const override { return string::format("%08X", m_id); }
string ToAddress() const override { return m_address; }
MemberIDCompact Compact() const override { return m_id; }
bool IsValid() const override { return !m_address.empty() && m_id != 0; }
private:
AZ::u32 m_id;
string m_address;
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// GameLiftMemberInfoCtorContext
//-----------------------------------------------------------------------------
struct GameLiftMemberInfoCtorContext
: public CtorContextBase
{
CtorDataSet<GameLiftServerMemberID, GameLiftServerMemberID::Marshaler> m_memberId;
CtorDataSet<RemotePeerMode> m_peerMode;
CtorDataSet<bool> m_isHost;
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// GameLiftServerMemberState
//-----------------------------------------------------------------------------
class GameLiftServerMemberState
: public Internal::GridMemberStateReplica
{
public:
GM_CLASS_ALLOCATOR(GameLiftServerMemberState);
static const char* GetChunkName() { return "GameLiftMemberState"; }
explicit GameLiftServerMemberState(GridMember* member = nullptr)
: GridMemberStateReplica(member)
{
}
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// GameLiftServerMember
//-----------------------------------------------------------------------------
class GameLiftServerMember
: public GridMember
{
friend class GameLiftServerSession;
public:
class GameLiftServerMemberDesc
: public ReplicaChunkDescriptor
{
public:
GameLiftServerMemberDesc()
: ReplicaChunkDescriptor(GameLiftServerMember::GetChunkName(), sizeof(GameLiftServerMember))
{
}
ReplicaChunkBase* CreateFromStream(UnmarshalContext&) override
{
AZ_Assert(false, "GameLiftServerMemberDesc should never be created from stream on the server!");
return nullptr;
}
void DiscardCtorStream(UnmarshalContext& mc) override
{
GameLiftMemberInfoCtorContext ctorContext;
ctorContext.Unmarshal(*mc.m_iBuf);
}
void DeleteReplicaChunk(ReplicaChunkBase* chunkInstance) override
{
if (!static_cast<GameLiftServerMember*>(chunkInstance)->IsLocal())
{
delete chunkInstance;
}
}
void MarshalCtorData(ReplicaChunkBase* chunkInstance, WriteBuffer& wb) override
{
GameLiftServerMember* member = static_cast<GameLiftServerMember*>(chunkInstance);
GameLiftMemberInfoCtorContext ctorContext;
ctorContext.m_memberId.Set(member->m_memberId);
ctorContext.m_peerMode.Set(member->m_peerMode.Get());
ctorContext.m_isHost.Set(member->IsHost());
ctorContext.Marshal(wb);
}
};
GM_CLASS_ALLOCATOR(GameLiftServerMember);
static const char* GetChunkName() { return "GridMate::GameLiftMember"; }
const PlayerId* GetPlayerId() const override
{
return nullptr;
}
const MemberID& GetId() const override
{
return m_memberId;
}
void SetPlayerSessionId(const char* playerSessionId) { AZ_Assert(playerSessionId, "Invalid player session id"); m_playerSessionId = playerSessionId; }
const char* GetPlayerSessionId() const { return m_playerSessionId.c_str(); }
/// Remote member ctor.
GameLiftServerMember(ConnectionID connId, const GameLiftServerMemberID& memberId, GameLiftServerSession* session)
: GridMember(memberId.Compact())
, m_memberId(memberId)
{
m_session = session;
m_connectionId = connId;
}
/// Local member ctor.
GameLiftServerMember(const GameLiftServerMemberID& memberId, GameLiftServerSession* session)
: GridMember(memberId.Compact())
, m_memberId(memberId)
{
m_session = session;
m_clientState = CreateReplicaChunk<GameLiftServerMemberState>(this);
m_clientState->m_name.Set(memberId.ToString());
m_clientStateReplica = Replica::CreateReplica(memberId.ToString().c_str());
m_clientStateReplica->AttachReplicaChunk(m_clientState);
}
void OnReplicaDeactivate(const ReplicaContext& rc) override
{
if (IsMaster() && !IsLocal())
{
GameLiftServerSession* serverSession = static_cast<GameLiftServerSession*>(m_session);
Aws::GameLift::GenericOutcome outcome = serverSession->GetGameLiftServerSDKWrapper().lock()->RemovePlayerSession(m_playerSessionId.c_str());
if (!outcome.IsSuccess())
{
AZ_TracePrintf("GameLift", "[SERVER SESSION] Failed to disconnect a master non-local GameLift player:'%s' with id=%s\n", outcome.GetError().GetErrorName().c_str(), GetId().ToString().c_str());
}
else
{
AZ_TracePrintf("GameLift", "Player removed current used public slots:%d and free public slots:%d", m_session->GetNumUsedPublicSlots(), m_session->GetNumFreePublicSlots());
AZ_TracePrintf("GameLift", "[SERVER SESSION] Sucessfully disconnected a master non-local GameLift player with id=%s\n", GetId().ToString().c_str());
}
}
else
{
AZ_TracePrintf("GameLift", "[SERVER SESSION] Deactivating a gridmember, memberid %d", rc.m_peer ? rc.m_peer->GetId() : 0);
}
GridMember::OnReplicaDeactivate(rc);
}
using GridMember::SetHost;
GameLiftServerMemberID m_memberId;
string m_playerSessionId;
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// GameLiftServerSession
//-----------------------------------------------------------------------------
GameLiftServerSession::GameLiftServerSession(GameLiftServerService* service)
: GridSession(service)
{
}
GameLiftServerSession::~GameLiftServerSession()
{
if(m_gameLiftSession)
{
delete m_gameLiftSession;
m_gameLiftSession = nullptr;
}
}
bool GameLiftServerSession::Initialize(const GameLiftSessionParams& params, const CarrierDesc& carrierDesc)
{
const Aws::GameLift::Server::Model::GameSession* gameSession = params.m_gameSession;
AZ_Assert(gameSession, "No game session instance specified.");
m_gameLiftSession = new Aws::GameLift::Server::Model::GameSession(*gameSession);
if (!GridSession::Initialize(carrierDesc))
{
return false;
}
m_myMember = CreateLocalMember(true, true, Mode_Peer);
m_sessionParams = params;
UpdateMatchmakerData();
const auto& properties = gameSession->GetGameProperties();
GameLiftServerSessionReplica::ParamContainer sessionProperties;
for (AZStd::size_t i = 0; i < properties.size(); ++i)
{
GridSessionParam param;
param.m_id = properties[i].GetKey().c_str();
param.m_value = properties[i].GetValue().c_str();
sessionProperties.push_back(param);
}
m_sessionParams.m_numPublicSlots = gameSession->GetMaximumPlayerSessionCount();
//////////////////////////////////////////////////////////////////////////
// start up the session state we will bind it later
AZ_Assert(m_sessionParams.m_numPublicSlots < 0xff && m_sessionParams.m_numPrivateSlots < 0xff, "Can't have more than 255 slots!");
AZ_Assert(m_sessionParams.m_numPublicSlots > 0 || m_sessionParams.m_numPrivateSlots > 0, "You don't have any slots open!");
GameLiftServerSessionReplica* state = CreateReplicaChunk<GameLiftServerSessionReplica>(this);
state->m_numFreePrivateSlots.Set(static_cast<unsigned char>(m_sessionParams.m_numPrivateSlots));
state->m_numFreePublicSlots.Set(static_cast<unsigned char>(m_sessionParams.m_numPublicSlots));
state->m_peerToPeerTimeout.Set(m_sessionParams.m_peerToPeerTimeout);
state->m_flags.Set(m_sessionParams.m_flags);
state->m_topology.Set(m_sessionParams.m_topology);
state->m_params.Set(sessionProperties);
m_state = state;
//////////////////////////////////////////////////////////////////////////
m_sessionId = gameSession->GetGameSessionId().c_str();
SetUpStateMachine();
RequestEvent(SE_HOST);
return true;
}
bool GameLiftServerSession::GameSessionUpdated(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession)
{
// Delete previous game session before updating.
if (m_gameLiftSession)
{
delete m_gameLiftSession;
m_gameLiftSession = nullptr;
}
m_gameLiftSession = new Aws::GameLift::Server::Model::GameSession(updateGameSession.GetGameSession());
Aws::GameLift::Server::Model::UpdateReason updateReason = updateGameSession.GetUpdateReason();
switch (updateReason)
{
case Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED:
return UpdateMatchmakerData();
case Aws::GameLift::Server::Model::UpdateReason::BACKFILL_CANCELLED:
case Aws::GameLift::Server::Model::UpdateReason::BACKFILL_FAILED:
case Aws::GameLift::Server::Model::UpdateReason::BACKFILL_TIMED_OUT:
case Aws::GameLift::Server::Model::UpdateReason::UNKNOWN:
default:
AZ_TracePrintf("GameLift", "GameSessionUpdate matchmaker error reasonname:%s GameSessionData:%s MatchmakerData:%s", Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str()
, updateGameSession.GetGameSession().GetGameSessionData().c_str(), updateGameSession.GetGameSession().GetMatchmakerData().c_str());
}
return false;
}
bool GameLiftServerSession::UpdateMatchmakerData()
{
const char* matchmakerData = m_gameLiftSession->GetMatchmakerData().c_str();
rapidjson::ParseResult parseResult = m_matchmakerDataDocument.Parse(matchmakerData);
if (!parseResult)
{
AZ_Error("GameLift", parseResult, "Error parsing matchmaker data Error:%s Offset:%u", rapidjson::GetParseError_En(parseResult.Code()), parseResult.Offset());
return false;
}
return true;
}
bool GameLiftServerSession::GetTeamForPlayerFromMatchmakerData(const char* playerId, AZStd::string& teamName)
{
rapidjson::Value& teams = m_matchmakerDataDocument["teams"];
AZ_Assert(teams.IsArray(), "Teams is not array");
for (rapidjson::SizeType teamIndex = 0; teamIndex < teams.Size(); ++teamIndex)
{
rapidjson::Value& players = teams[teamIndex]["players"];
AZ_Assert(players.IsArray(), "Players is not array");
for (rapidjson::SizeType playerIndex = 0; playerIndex < players.Size(); ++playerIndex)
{
if (std::strcmp(players[playerIndex]["playerId"].GetString(), playerId) == 0)
{
teamName = teams[teamIndex]["name"].GetString();
return true;
}
}
}
return false;
}
bool GameLiftServerSession::StartMatchmakingBackfill(AZStd::string& matchmakingTicketId, bool checkForAutoBackfill = true)
{
if (checkForAutoBackfill && m_matchmakerDataDocument.HasMember("autoBackfillTicketId") && m_matchmakerDataDocument["autoBackfillTicketId"].IsString())
{
const char* autoBackfillTicketId = m_matchmakerDataDocument["autoBackfillTicketId"].GetString();
AZ_TracePrintf("GameLift", "Ignoring backfill request when AUTOMATIC backfill is active %s", autoBackfillTicketId);
return false;
}
if (!m_matchmakerDataDocument.HasMember("matchmakingConfigurationArn"))
{
AZ_TracePrintf("GameLift", "Ignoring backfill request when no matchmaking config arn found");
return false;
}
const char* gameSessionId = m_gameLiftSession->GetGameSessionId().c_str();
rapidjson::Value& teams = m_matchmakerDataDocument["teams"];
AZ_Assert(teams.IsArray(), "Teams is not array");
const char* matchmakingConfigurationArn = m_matchmakerDataDocument["matchmakingConfigurationArn"].GetString();
Aws::GameLift::Server::Model::StartMatchBackfillRequest startBackfillRequest;
startBackfillRequest.SetMatchmakingConfigurationArn(matchmakingConfigurationArn);
startBackfillRequest.SetGameSessionArn(gameSessionId);
// Set matchmaking ticket id if provided.
if (!matchmakingTicketId.empty())
{
startBackfillRequest.SetTicketId(matchmakingTicketId.c_str());
}
const std::vector<Aws::GameLift::Server::Model::PlayerSession> &gameLiftPlayerSessions = GetGameLiftPlayerSessions(gameSessionId, Aws::GameLift::Server::Model::PlayerSessionStatus::ACTIVE);
for (const Aws::GameLift::Server::Model::PlayerSession& playerSession : gameLiftPlayerSessions)
{
Aws::GameLift::Server::Model::Player player;
AZ_TracePrintf("GameLift", "Active member found playerId:%s", playerSession.GetPlayerId().c_str());
player.SetPlayerId(playerSession.GetPlayerId());
AZStd::string teamName;
if (GetTeamForPlayerFromMatchmakerData(playerSession.GetPlayerId().c_str(), teamName))
{
player.SetTeam(teamName.c_str());
}
startBackfillRequest.AddPlayer(player);
}
Aws::GameLift::StartMatchBackfillOutcome backfillOutcome = GetGameLiftServerSDKWrapper().lock()->StartMatchBackfill(startBackfillRequest);
if (backfillOutcome.IsSuccess())
{
matchmakingTicketId = backfillOutcome.GetResult().GetTicketId().c_str();
AZ_TracePrintf("GameLift", "Matchmaking Backfill request success ticketId:%s", matchmakingTicketId.c_str());
return true;
}
else
{
AZ_TracePrintf("GameLift", "Matchmaking Backfill request error:%s gamesession:%s config:%s", backfillOutcome.GetError().GetErrorMessage().c_str(), gameSessionId, matchmakingConfigurationArn);
return false;
}
}
bool GameLiftServerSession::StopMatchmakingBackfill(const AZStd::string& matchmakingTicketId)
{
const char* gameSessionId = m_gameLiftSession->GetGameSessionId().c_str();
rapidjson::Value& teams = m_matchmakerDataDocument["teams"];
AZ_Assert(teams.IsArray(), "Teams is not array");
const char* matchmakingConfigurationArn = m_matchmakerDataDocument["matchmakingConfigurationArn"].GetString();
Aws::GameLift::Server::Model::StopMatchBackfillRequest stopBackfillRequest;
stopBackfillRequest.SetTicketId(matchmakingTicketId.c_str());
stopBackfillRequest.SetMatchmakingConfigurationArn(matchmakingConfigurationArn);
stopBackfillRequest.SetGameSessionArn(gameSessionId);
Aws::GameLift::GenericOutcome backfillOutcome = GetGameLiftServerSDKWrapper().lock()->StopMatchBackfill(stopBackfillRequest);
if (backfillOutcome.IsSuccess())
{
AZ_TracePrintf("GameLift", "Matchmaking Backfill stop success matchmakingTicketId:%s", matchmakingTicketId.c_str());
return true;
}
else
{
AZ_TracePrintf("GameLift", "Matchmaking Backfill stop error:%s gamesession:%s config:%s matchmakingTicketId:%s", backfillOutcome.GetError().GetErrorMessage().c_str()
, gameSessionId, matchmakingConfigurationArn, matchmakingTicketId.c_str());
return false;
}
}
GridMember* GameLiftServerSession::CreateLocalMember(bool isHost, bool isInvited, RemotePeerMode peerMode)
{
AZ_Assert(isHost, "GameLiftServerSession can only run as host!");
AZ_Assert(!m_myMember, "We already have added a local member!");
string ip = Utils::GetMachineAddress(m_carrierDesc.m_familyType);
string address = SocketDriverCommon::IPPortToAddressString(ip.c_str(), m_carrierDesc.m_port);
GameLiftServerMemberID myId(address, AZ::Crc32("GameLiftServer"));
GameLiftServerMember* member = CreateReplicaChunk<GameLiftServerMember>(myId, this);
member->SetHost(isHost);
member->SetInvited(isInvited);
member->m_peerMode.Set(peerMode);
return member;
}
void GameLiftServerSession::Shutdown()
{
Aws::GameLift::GenericOutcome outcome = GetGameLiftServerSDKWrapper().lock()->TerminateGameSession();
if (!outcome.IsSuccess())
{
AZ_Warning("GridMate", outcome.IsSuccess(), "GameLift session failed to terminate:%s:%s\n",
outcome.GetError().GetErrorName().c_str(),
outcome.GetError().GetErrorMessage().c_str());
return;
}
if (m_gameLiftSession)
{
delete m_gameLiftSession;
m_gameLiftSession = nullptr;
}
GridSession::Shutdown();
}
GridMember* GameLiftServerSession::CreateRemoteMember(const string& address, ReadBuffer& data, RemotePeerMode peerMode, ConnectionID connId)
{
string playerSessionId;
data.Read(playerSessionId);
Aws::GameLift::GenericOutcome outcome = GetGameLiftServerSDKWrapper().lock()->AcceptPlayerSession(playerSessionId.c_str());
if (!outcome.IsSuccess())
{
AZ_TracePrintf("GameLift", "Failed to connect GameLift player:'%s' with id=%s\n", outcome.GetError().GetErrorName().c_str(), playerSessionId.c_str());
m_carrier->Disconnect(connId);
return nullptr;
}
GameLiftServerMemberID memberId(address, AZ::Crc32(playerSessionId.c_str()));
GameLiftServerMember* member = CreateReplicaChunk<GameLiftServerMember>(connId, memberId, this);
member->m_peerMode.Set(peerMode);
member->SetPlayerSessionId(playerSessionId.c_str());
return member;
}
bool GameLiftServerSession::OnStateCreate(AZ::HSM& sm, const AZ::HSM::Event& e)
{
bool isProcessed = GridSession::OnStateCreate(sm, e);
switch (e.id)
{
case AZ::HSM::EnterEventId:
{
Aws::GameLift::GenericOutcome activationOutcome = GetGameLiftServerSDKWrapper().lock()->ActivateGameSession();
if (!activationOutcome.IsSuccess())
{
AZ_TracePrintf("GridMate", "GameLift session activation failed: %s\n", activationOutcome.GetError().GetErrorMessage().c_str());
RequestEvent(SE_DELETE);
}
else
{
RequestEvent(SE_CREATED);
}
}
return true;
}
return isProcessed;
}
bool GameLiftServerSession::OnStateDelete(AZ::HSM& sm, const AZ::HSM::Event& e)
{
bool isProcessed = GridSession::OnStateDelete(sm, e);
switch (e.id)
{
case AZ::HSM::EnterEventId:
{
RequestEvent(SE_DELETED);
return true;
}
}
return isProcessed;
}
bool GameLiftServerSession::OnStateHostMigrateSession(AZ::HSM& sm, const AZ::HSM::Event& e)
{
(void)sm;
(void)e;
AZ_Assert(false, "Host migration is not supported for GameLift sessions.");
return false;
}
void GameLiftServerSession::RegisterReplicaChunks()
{
ReplicaChunkDescriptorTable::Get().RegisterChunkType<GameLiftServerSessionReplica, GameLiftServerSessionReplica::GameLiftSessionReplicaDesc>();
ReplicaChunkDescriptorTable::Get().RegisterChunkType<GameLiftServerMember, GameLiftServerMember::GameLiftServerMemberDesc>();
ReplicaChunkDescriptorTable::Get().RegisterChunkType<GameLiftServerMemberState>();
}
AZStd::weak_ptr<GameLiftServerSDKWrapper> GameLiftServerSession::GetGameLiftServerSDKWrapper()
{
GameLiftServerService* gmService = static_cast<GameLiftServerService*>(m_service);
return gmService->GetGameLiftServerSDKWrapper();
}
const std::vector<Aws::GameLift::Server::Model::PlayerSession> GameLiftServerSession::GetGameLiftPlayerSessions(const char* gameSessionId, Aws::GameLift::Server::Model::PlayerSessionStatus playerSessionStaus)
{
Aws::GameLift::Server::Model::DescribePlayerSessionsRequest request;
request.SetPlayerSessionStatusFilter(Aws::GameLift::Server::Model::PlayerSessionStatusMapper::GetNameForPlayerSessionStatus(playerSessionStaus));
request.SetLimit(m_gameLiftSession->GetMaximumPlayerSessionCount());
request.SetGameSessionId(gameSessionId);
Aws::GameLift::DescribePlayerSessionsOutcome playerSessionsOutcome = GetGameLiftServerSDKWrapper().lock()->DescribePlayerSessions(request);
if (!playerSessionsOutcome.IsSuccess())
{
AZ_TracePrintf("GameLift", "describe Player Sessions failed error:%s", playerSessionsOutcome.GetError().GetErrorMessage().c_str());
}
return playerSessionsOutcome.GetResult().GetPlayerSessions();
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
} // namespace GridMate
#endif // BUILD_GAMELIFT_SERVER