public async Task AllowOneThousandPlayersToMatchAtOnce() { const int playersPerParty = 1; const int parties = 1000; var startTime = DateTime.UtcNow; var tasks = new List <Task>(); for (var i = 0; i < playersPerParty * parties; i++) { var myId = i; var playerName = $"test_player_{startTime.ToLongTimeString()}_{myId}"; var playerPit = await CreatePlayerIdentityTokenForPlayer(playerName); var task = Task.Run(async() => { var playerMetadata = new Metadata { { PitRequestHeaderName, playerPit } }; if (myId % playersPerParty == 0) { // Lead player sets up the match and invites the others var party = false; while (!party) { try { GetPartyClient().CreateParty(new CreatePartyRequest(), playerMetadata); party = true; } catch (RpcException e) { Console.WriteLine($"CreateParty exception: {e}"); } } for (var j = 0; j < playersPerParty - 1; j++) { var invitedPlayerId = $"test_player_{startTime}_{myId + j + 1}"; GetInviteClient().CreateInvite(new CreateInviteRequest { ReceiverPlayerId = invitedPlayerId }, playerMetadata); } int members = 1; while (members < playersPerParty) { var partyData = GetPartyClient().GetPartyByPlayerId(new GetPartyByPlayerIdRequest(), playerMetadata); members = partyData.Party.MemberIds.Count; await Task.Delay(500); } Console.WriteLine($"Enough players joined: Continuing as master ({playerName})"); // Join matchmaking. var joined = false; while (!joined) { try { GetGatewayClient().Join(new JoinRequest { MatchmakingType = "match1" }, playerMetadata); joined = true; } catch (RpcException e) { if (e.StatusCode == StatusCode.AlreadyExists) { joined = true; } Console.WriteLine($"Exception in Join ({playerName}): {e}"); } } } else { // All other players wait for an invite Console.WriteLine("Beginning as player"); var joined = false; do { var invites = GetInviteClient().ListAllInvites(new ListAllInvitesRequest(), playerMetadata); if (invites.InboundInvites.Count > 0) { _ = GetPartyClient().JoinParty(new JoinPartyRequest { PartyId = invites.InboundInvites[0].PartyId }, playerMetadata); joined = true; } } while (!joined); } }).ContinueWith(async t => { // Non-leaders may not have started matchmaking yet so GetJoinStatus could fail a few times. GetJoinStatusResponse status = null; do { try { status = GetGatewayClient().GetJoinStatus(new GetJoinStatusRequest { PlayerId = playerName }, new Metadata { { PitRequestHeaderName, playerPit } }); } catch (Exception e) { Console.WriteLine($"Failed to poll: {e}"); } await Task.Delay(100); } while (status == null || !status.Complete); }).ContinueWith(t => { var playerMetadata = new Metadata { { PitRequestHeaderName, playerPit } }; GetPartyClient().DeleteParty(new DeletePartyRequest(), playerMetadata); }); tasks.Add(task); } Task.WaitAll(tasks.ToArray()); var seconds = (DateTime.UtcNow - startTime).TotalSeconds; Console.WriteLine($"Test completed in {seconds}s"); Assert.Less(seconds, TimeSpan.FromMinutes(2).TotalSeconds); }
static void Main(string[] args) { Parser.Default.ParseArguments <SampleClientArguments>(args) .WithParsed(parsedArgs => { var gatewayServiceUrl = parsedArgs.Local ? string.Format(LocalEndPointUrlFormat, "4040") : string.Format(CloudEndPointUrlFormat, "gateway", parsedArgs.GoogleProject); var partyServiceUrl = parsedArgs.Local ? string.Format(LocalEndPointUrlFormat, "4041") : string.Format(CloudEndPointUrlFormat, "party", parsedArgs.GoogleProject); var authServiceUrl = parsedArgs.Local ? string.Format(LocalEndPointUrlFormat, "4042") : string.Format(CloudEndPointUrlFormat, "playfab-auth", parsedArgs.GoogleProject); var playerId = RandomString(15); Console.WriteLine($"Using a randomly generated PlayFab player ID: {playerId}"); Console.WriteLine($"authServiceUrl: {authServiceUrl}"); //This is the type of code i would put into the game because it is responsible // for talking directly to steam for encrypted app ticket and then sending it // to my servers to authenticate and exchange got a player identity token // First, get a token from PlayFab. PlayFabSettings.staticSettings.TitleId = parsedArgs.PlayFabTitleId; var playFabLoginTask = PlayFabClientAPI.LoginWithCustomIDAsync(new LoginWithCustomIDRequest { TitleId = parsedArgs.PlayFabTitleId, CustomId = playerId, CreateAccount = true }); var playFabLoginResult = playFabLoginTask.GetAwaiter().GetResult(); if (playFabLoginResult.Error != null) { Console.WriteLine($"Got login error from PlayFab: {playFabLoginResult.Error.ErrorMessage}"); Environment.Exit(1); return; } var playFabId = playFabLoginResult.Result.PlayFabId; Console.WriteLine($"Got a token for PlayFab ID {playFabId}."); // Next, exchange the token with our auth service for a PIT. var playFabAuthClient = new AuthService.AuthServiceClient( new Channel(authServiceUrl, ChannelCredentials.Insecure)); var authResult = playFabAuthClient.ExchangePlayFabToken(new ExchangePlayFabTokenRequest { PlayfabToken = playFabLoginResult.Result.SessionTicket }); Console.WriteLine("Got a PIT."); var pitMetadata = new Metadata { { PitRequestHeaderName, authResult.PlayerIdentityToken } }; Console.WriteLine($"authResult.PlayerIdentityToken: {authResult.PlayerIdentityToken}"); // Create a single-player party for the player. var partyClient = new PartyService.PartyServiceClient( new Channel(partyServiceUrl, ChannelCredentials.Insecure)); var partyResponse = partyClient.CreateParty(new CreatePartyRequest { MinMembers = 1, MaxMembers = 1 }, pitMetadata); Console.WriteLine($"Created a new party with id {partyResponse.PartyId}."); var gatewayEndpoint = gatewayServiceUrl; var gatewayClient = new GatewayService.GatewayServiceClient(new Channel(gatewayEndpoint, ChannelCredentials.Insecure)); gatewayClient.Join(new JoinRequest { MatchmakingType = "match" }, pitMetadata); Console.WriteLine("Joined queue; waiting for match."); GetJoinStatusResponse resp = null; while (resp == null || !resp.Complete) { Thread.Sleep(1000); resp = gatewayClient.GetJoinStatus(new GetJoinStatusRequest { PlayerId = playFabId }, pitMetadata); } Console.WriteLine( $"Got deployment: {resp.DeploymentName}. Login token: [{resp.LoginToken}]."); }); }
public override async Task <GetJoinStatusResponse> GetJoinStatus(GetJoinStatusRequest request, ServerCallContext context) { var playerIdentity = AuthHeaders.ExtractPlayerId(context); if (!string.Equals(request.PlayerId, playerIdentity)) { throw new RpcException(new Status(StatusCode.PermissionDenied, "Fetching another player's join status is forbidden.")); } PlayerJoinRequest joinRequest; using (var memClient = _memoryStoreClientManager.GetClient()) { try { joinRequest = await memClient.GetAsync <PlayerJoinRequest>(request.PlayerId) ?? throw new EntryNotFoundException(request.PlayerId); if (joinRequest.IsComplete()) { using (var tx = memClient.CreateTransaction()) { tx.DeleteAll(joinRequest.Yield()); } } } catch (EntryNotFoundException e) { Reporter.JoinStatusNotFoundInc(); Log.Warning($"Join request for {e.Id} does not exist"); throw new RpcException(new Status(StatusCode.NotFound, "requested player does not exist")); } catch (TransactionAbortedException) { Reporter.TransactionAbortedInc("GetJoinStatus"); Log.Warning("Transaction for join request deletion was aborted"); throw new RpcException(new Status(StatusCode.Unavailable, "deletion aborted due to concurrent modification; safe to retry")); } } var resp = new GetJoinStatusResponse { Complete = joinRequest.IsComplete(), Status = MatchStateToJoinStatus(joinRequest.State) }; switch (resp.Status) { case GetJoinStatusResponse.Types.Status.Joined: resp.DeploymentName = joinRequest.DeploymentName; resp.LoginToken = await CreateLoginTokenForDeployment(joinRequest.DeploymentId, joinRequest.PlayerIdentityToken); break; case GetJoinStatusResponse.Types.Status.Error: resp.Error = "the join request encountered an error"; break; } Reporter.JoinStatusInc(joinRequest.State); if (resp.Complete) { Log.Information($"Join request for {request.PlayerId} done in state {joinRequest.State}."); } return(resp); }