AWSGameLiftServerManager.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*
  2. * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. * its licensors.
  4. *
  5. * For complete copyright and license terms please see the LICENSE at the root of this
  6. * distribution (the "License"). All use of this software is governed by the License,
  7. * or, if provided, by the license below or the license accompanying this file. Do not
  8. * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. *
  11. */
  12. #include <AWSGameLiftServerManager.h>
  13. #include <AWSGameLiftSessionConstants.h>
  14. #include <GameLiftServerSDKWrapper.h>
  15. #include <AzCore/Debug/Trace.h>
  16. #include <AzCore/Interface/Interface.h>
  17. #include <AzCore/Jobs/JobFunction.h>
  18. #include <AzCore/Jobs/JobManagerBus.h>
  19. #include <AzCore/std/bind/bind.h>
  20. #include <AzFramework/Session/SessionNotifications.h>
  21. namespace AWSGameLift
  22. {
  23. AWSGameLiftServerManager::AWSGameLiftServerManager()
  24. : m_serverSDKInitialized(false)
  25. , m_gameLiftServerSDKWrapper(AZStd::make_unique<GameLiftServerSDKWrapper>())
  26. , m_connectedPlayers()
  27. {
  28. }
  29. AWSGameLiftServerManager::~AWSGameLiftServerManager()
  30. {
  31. m_gameLiftServerSDKWrapper.reset();
  32. m_connectedPlayers.clear();
  33. }
  34. bool AWSGameLiftServerManager::AddConnectedPlayer(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
  35. {
  36. AZStd::lock_guard<AZStd::mutex> lock(m_gameliftMutex);
  37. if (m_connectedPlayers.contains(playerConnectionConfig.m_playerConnectionId))
  38. {
  39. if (m_connectedPlayers[playerConnectionConfig.m_playerConnectionId] != playerConnectionConfig.m_playerSessionId)
  40. {
  41. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerPlayerConnectionRegisteredErrorMessage,
  42. playerConnectionConfig.m_playerConnectionId, playerConnectionConfig.m_playerSessionId.c_str());
  43. }
  44. return false;
  45. }
  46. else
  47. {
  48. m_connectedPlayers.emplace(playerConnectionConfig.m_playerConnectionId, playerConnectionConfig.m_playerSessionId);
  49. return true;
  50. }
  51. }
  52. AzFramework::SessionConfig AWSGameLiftServerManager::BuildSessionConfig(const Aws::GameLift::Server::Model::GameSession& gameSession)
  53. {
  54. AzFramework::SessionConfig sessionConfig;
  55. sessionConfig.m_dnsName = gameSession.GetDnsName().c_str();
  56. AZStd::string propertiesOutput = "";
  57. for (const auto& gameProperty : gameSession.GetGameProperties())
  58. {
  59. sessionConfig.m_sessionProperties.emplace(gameProperty.GetKey().c_str(), gameProperty.GetValue().c_str());
  60. propertiesOutput += AZStd::string::format("{Key=%s,Value=%s},", gameProperty.GetKey().c_str(), gameProperty.GetValue().c_str());
  61. }
  62. if (!propertiesOutput.empty())
  63. {
  64. propertiesOutput = propertiesOutput.substr(0, propertiesOutput.size() - 1); // Trim last comma to fit array format
  65. }
  66. sessionConfig.m_sessionId = gameSession.GetGameSessionId().c_str();
  67. sessionConfig.m_ipAddress = gameSession.GetIpAddress().c_str();
  68. sessionConfig.m_maxPlayer = gameSession.GetMaximumPlayerSessionCount();
  69. sessionConfig.m_sessionName = gameSession.GetName().c_str();
  70. sessionConfig.m_port = gameSession.GetPort();
  71. sessionConfig.m_status = AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()];
  72. AZ_TracePrintf(AWSGameLiftServerManagerName,
  73. "Built SessionConfig with Name=%s, Id=%s, Status=%s, DnsName=%s, IpAddress=%s, Port=%d, MaxPlayer=%d and Properties=%s",
  74. sessionConfig.m_sessionName.c_str(),
  75. sessionConfig.m_sessionId.c_str(),
  76. sessionConfig.m_status.c_str(),
  77. sessionConfig.m_dnsName.c_str(),
  78. sessionConfig.m_ipAddress.c_str(),
  79. sessionConfig.m_port,
  80. sessionConfig.m_maxPlayer,
  81. AZStd::string::format("[%s]", propertiesOutput.c_str()).c_str());
  82. return sessionConfig;
  83. }
  84. AZ::IO::Path AWSGameLiftServerManager::GetExternalSessionCertificate()
  85. {
  86. // TODO: Add support to get TLS cert file path
  87. return AZ::IO::Path();
  88. }
  89. AZ::IO::Path AWSGameLiftServerManager::GetInternalSessionCertificate()
  90. {
  91. // GameLift doesn't support it, return empty path
  92. return AZ::IO::Path();
  93. }
  94. bool AWSGameLiftServerManager::InitializeGameLiftServerSDK()
  95. {
  96. if (m_serverSDKInitialized)
  97. {
  98. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKAlreadyInitErrorMessage);
  99. return false;
  100. }
  101. AZ_TracePrintf(AWSGameLiftServerManagerName, "Initiating Amazon GameLift Server SDK...");
  102. Aws::GameLift::Server::InitSDKOutcome initOutcome = m_gameLiftServerSDKWrapper->InitSDK();
  103. m_serverSDKInitialized = initOutcome.IsSuccess();
  104. AZ_Error(AWSGameLiftServerManagerName, m_serverSDKInitialized,
  105. AWSGameLiftServerInitSDKErrorMessage, initOutcome.GetError().GetErrorMessage().c_str());
  106. return m_serverSDKInitialized;
  107. }
  108. void AWSGameLiftServerManager::HandleDestroySession()
  109. {
  110. // No further request should be handled by GameLift server manager at this point
  111. if (AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
  112. {
  113. AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Unregister(this);
  114. }
  115. AZ_TracePrintf(AWSGameLiftServerManagerName, "Server process is scheduled to be shut down at %s",
  116. m_gameLiftServerSDKWrapper->GetTerminationTime().c_str());
  117. // Send notifications to handler(s) to gracefully shut down the server process.
  118. bool destroySessionResult = true;
  119. AZ::EBusReduceResult<bool&, AZStd::logical_and<bool>> result(destroySessionResult);
  120. AzFramework::SessionNotificationBus::BroadcastResult(result, &AzFramework::SessionNotifications::OnDestroySessionBegin);
  121. if (!destroySessionResult)
  122. {
  123. AZ_Error("AWSGameLift", destroySessionResult, AWSGameLiftServerGameSessionDestroyErrorMessage);
  124. return;
  125. }
  126. AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ending...");
  127. Aws::GameLift::GenericOutcome processEndingOutcome = m_gameLiftServerSDKWrapper->ProcessEnding();
  128. bool processEndingIsSuccess = processEndingOutcome.IsSuccess();
  129. AZ_Error(AWSGameLiftServerManagerName, processEndingIsSuccess, AWSGameLiftServerProcessEndingErrorMessage,
  130. processEndingOutcome.GetError().GetErrorMessage().c_str());
  131. }
  132. void AWSGameLiftServerManager::HandlePlayerLeaveSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
  133. {
  134. AZStd::string playerSessionId = "";
  135. RemoveConnectedPlayer(playerConnectionConfig.m_playerConnectionId, playerSessionId);
  136. if (playerSessionId.empty())
  137. {
  138. return;
  139. }
  140. Aws::GameLift::GenericOutcome disconnectOutcome = m_gameLiftServerSDKWrapper->RemovePlayerSession(playerSessionId);
  141. AZ_Error(AWSGameLiftServerManagerName, disconnectOutcome.IsSuccess(), AWSGameLiftServerRemovePlayerSessionErrorMessage,
  142. playerSessionId.c_str(), disconnectOutcome.GetError().GetErrorMessage().c_str());
  143. }
  144. bool AWSGameLiftServerManager::NotifyGameLiftProcessReady(const GameLiftServerProcessDesc& desc)
  145. {
  146. if (!m_serverSDKInitialized)
  147. {
  148. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerSDKNotInitErrorMessage);
  149. return false;
  150. }
  151. AZ_Warning(AWSGameLiftServerManagerName, desc.m_port != 0, AWSGameLiftServerTempPortErrorMessage);
  152. AZ::JobContext* jobContext = nullptr;
  153. AZ::JobManagerBus::BroadcastResult(jobContext, &AZ::JobManagerEvents::GetGlobalContext);
  154. AZ::Job* processReadyJob = AZ::CreateJobFunction(
  155. [this, desc]() {
  156. // The GameLift ProcessParameters object expects an vector (std::vector) of standard strings (std::string) as the log paths.
  157. std::vector<std::string> logPaths;
  158. for (const AZStd::string& path : desc.m_logPaths)
  159. {
  160. logPaths.push_back(path.c_str());
  161. }
  162. Aws::GameLift::Server::ProcessParameters processReadyParameter = Aws::GameLift::Server::ProcessParameters(
  163. AZStd::bind(&AWSGameLiftServerManager::OnStartGameSession, this, AZStd::placeholders::_1),
  164. AZStd::bind(&AWSGameLiftServerManager::OnUpdateGameSession, this),
  165. AZStd::bind(&AWSGameLiftServerManager::OnProcessTerminate, this),
  166. AZStd::bind(&AWSGameLiftServerManager::OnHealthCheck, this), desc.m_port,
  167. Aws::GameLift::Server::LogParameters(logPaths));
  168. AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ready...");
  169. auto processReadyOutcome = m_gameLiftServerSDKWrapper->ProcessReady(processReadyParameter);
  170. if (!processReadyOutcome.IsSuccess())
  171. {
  172. AZ_Error(AWSGameLiftServerManagerName, false,
  173. AWSGameLiftServerProcessReadyErrorMessage, processReadyOutcome.GetError().GetErrorMessage().c_str());
  174. this->HandleDestroySession();
  175. }
  176. }, true, jobContext);
  177. processReadyJob->Start();
  178. return true;
  179. }
  180. void AWSGameLiftServerManager::OnStartGameSession(const Aws::GameLift::Server::Model::GameSession& gameSession)
  181. {
  182. AzFramework::SessionConfig sessionConfig = BuildSessionConfig(gameSession);
  183. bool createSessionResult = true;
  184. AZ::EBusReduceResult<bool&, AZStd::logical_and<bool>> result(createSessionResult);
  185. AzFramework::SessionNotificationBus::BroadcastResult(
  186. result, &AzFramework::SessionNotifications::OnCreateSessionBegin, sessionConfig);
  187. if (createSessionResult)
  188. {
  189. AZ_TracePrintf(AWSGameLiftServerManagerName, "Activating GameLift game session...");
  190. Aws::GameLift::GenericOutcome activationOutcome = m_gameLiftServerSDKWrapper->ActivateGameSession();
  191. if (activationOutcome.IsSuccess())
  192. {
  193. // Register server manager as handler once game session has been activated
  194. if (!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
  195. {
  196. AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Register(this);
  197. }
  198. }
  199. else
  200. {
  201. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerActivateGameSessionErrorMessage,
  202. activationOutcome.GetError().GetErrorMessage().c_str());
  203. HandleDestroySession();
  204. }
  205. }
  206. else
  207. {
  208. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerGameInitErrorMessage);
  209. HandleDestroySession();
  210. }
  211. }
  212. void AWSGameLiftServerManager::OnProcessTerminate()
  213. {
  214. AZ_TracePrintf(AWSGameLiftServerManagerName, "GameLift is shutting down server process...");
  215. HandleDestroySession();
  216. }
  217. bool AWSGameLiftServerManager::OnHealthCheck()
  218. {
  219. bool healthCheckResult = true;
  220. AZ::EBusReduceResult<bool&, AZStd::logical_and<bool>> result(healthCheckResult);
  221. AzFramework::SessionNotificationBus::BroadcastResult(result, &AzFramework::SessionNotifications::OnSessionHealthCheck);
  222. return m_serverSDKInitialized && healthCheckResult;
  223. }
  224. void AWSGameLiftServerManager::OnUpdateGameSession()
  225. {
  226. // TODO: Perform game-specific tasks to prep for newly matched players
  227. return;
  228. }
  229. bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId)
  230. {
  231. AZStd::lock_guard<AZStd::mutex> lock(m_gameliftMutex);
  232. if (m_connectedPlayers.contains(playerConnectionId))
  233. {
  234. outPlayerSessionId = m_connectedPlayers[playerConnectionId];
  235. m_connectedPlayers.erase(playerConnectionId);
  236. return true;
  237. }
  238. else
  239. {
  240. outPlayerSessionId = "";
  241. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerPlayerConnectionMissingErrorMessage, playerConnectionId);
  242. return false;
  243. }
  244. }
  245. void AWSGameLiftServerManager::SetGameLiftServerSDKWrapper(AZStd::unique_ptr<GameLiftServerSDKWrapper> gameLiftServerSDKWrapper)
  246. {
  247. m_gameLiftServerSDKWrapper.reset();
  248. m_gameLiftServerSDKWrapper = AZStd::move(gameLiftServerSDKWrapper);
  249. }
  250. bool AWSGameLiftServerManager::ValidatePlayerJoinSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
  251. {
  252. uint32_t playerConnectionId = playerConnectionConfig.m_playerConnectionId;
  253. AZStd::string playerSessionId = playerConnectionConfig.m_playerSessionId;
  254. if (playerSessionId.empty())
  255. {
  256. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerInvalidConnectionConfigErrorMessage,
  257. playerConnectionId, playerSessionId.c_str());
  258. return false;
  259. }
  260. if (!AddConnectedPlayer(playerConnectionConfig))
  261. {
  262. return false;
  263. }
  264. AZ_TracePrintf(AWSGameLiftServerManagerName, "Attempting to accept player session connection with Amazon GameLift service...");
  265. auto acceptPlayerSessionOutcome = m_gameLiftServerSDKWrapper->AcceptPlayerSession(playerSessionId.c_str());
  266. if (!acceptPlayerSessionOutcome.IsSuccess())
  267. {
  268. AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerAcceptPlayerSessionErrorMessage,
  269. playerSessionId.c_str(), acceptPlayerSessionOutcome.GetError().GetErrorMessage().c_str());
  270. RemoveConnectedPlayer(playerConnectionId, playerSessionId);
  271. return false;
  272. }
  273. return true;
  274. }
  275. } // namespace AWSGameLift