public override Task <CreatePartyResponse> CreateParty(CreatePartyRequest request, ServerCallContext context) { var playerId = AuthHeaders.ExtractPlayerId(context); var pit = AuthHeaders.ExtractPit(context); // TODO(iuliaharasim/dom): Move logic specific to party creation in a separate class. PartyDataModel party; try { party = new PartyDataModel(playerId, pit, request.MinMembers, request.MaxMembers, request.Metadata); } catch (ArgumentException exception) { throw new RpcException(new Status(StatusCode.InvalidArgument, exception.Message)); } var leader = party.GetLeader(); using (var memClient = _memoryStoreClientManager.GetClient()) using (var transaction = memClient.CreateTransaction()) { transaction.CreateAll(new List <Entry> { party, leader }); } return(Task.FromResult(new CreatePartyResponse { PartyId = party.Id })); }
public override Task <CreatePartyResponse> CreateParty(CreatePartyRequest request, ServerCallContext context) { var playerId = AuthHeaders.ExtractPlayerId(context); var pit = AuthHeaders.ExtractPit(context); // TODO(iuliaharasim/dom): Move logic specific to party creation in a separate class. PartyDataModel party; try { party = new PartyDataModel(playerId, pit, request.MinMembers, request.MaxMembers, request.Metadata); } catch (ArgumentException exception) { throw new RpcException(new Status(StatusCode.InvalidArgument, exception.Message)); } var leader = party.GetLeader(); using (var memClient = _memoryStoreClientManager.GetClient()) using (var transaction = memClient.CreateTransaction()) { transaction.CreateAll(new List <Entry> { party, leader }); } var eventAttributes = new Dictionary <string, string> { { "partyId", party.Id } }; string[] eventTypes = { "player_created_party", "player_joined_party", "party_created" }; foreach (string eventType in eventTypes) { if (eventType == "party_created") { eventAttributes.Add("partyPhase", party.CurrentPhase.ToString()); } _analytics.Send(eventType, eventAttributes, playerId); } return(Task.FromResult(new CreatePartyResponse { PartyId = party.Id })); }
public override async Task <AssignDeploymentsResponse> AssignDeployments(AssignDeploymentsRequest request, ServerCallContext context) { try { using (var memClient = _matchmakingMemoryStoreClientManager.GetClient()) { var toUpdate = new List <Entry>(); foreach (var assignment in request.Assignments) { Reporter.AssignDeploymentInc(assignment.DeploymentId, assignment.Result); foreach (var memberId in assignment.Party.MemberIds) { var playerJoinRequest = await memClient.GetAsync <PlayerJoinRequest>(memberId); if (playerJoinRequest == null) { continue; } switch (assignment.Result) { case Assignment.Types.Result.Error: playerJoinRequest.State = MatchState.Error; break; case Assignment.Types.Result.Matched: playerJoinRequest.AssignMatch(assignment.DeploymentId, assignment.DeploymentName); break; case Assignment.Types.Result.Requeued: playerJoinRequest.State = MatchState.Requested; break; } toUpdate.Add(playerJoinRequest); } } var toRequeue = new List <PartyJoinRequest>(); var toDelete = new List <PartyJoinRequest>(); foreach (var assignment in request.Assignments) { var party = assignment.Party; var partyJoinRequest = await memClient.GetAsync <PartyJoinRequest>(party.Id); if (partyJoinRequest == null) { // Party join request has been cancelled. continue; } if (assignment.Result == Assignment.Types.Result.Requeued) { partyJoinRequest.RefreshQueueData(); toRequeue.Add(partyJoinRequest); toUpdate.Add(partyJoinRequest); } else { // If the matchmaking process for this party has reached a final state, we should delete the // PartyJoinRequest associated to it. toDelete.Add(partyJoinRequest); } } using (var tx = memClient.CreateTransaction()) { tx.UpdateAll(toUpdate); tx.EnqueueAll(toRequeue); tx.DeleteAll(toDelete); } } } catch (EntryNotFoundException e) { Reporter.AssignDeploymentNotFoundInc(e.Id); Log.Warning($"Attempted to assign deployment to nonexistent join request {e.Id}."); throw new RpcException(new Status(StatusCode.NotFound, "Join request does not exist")); } catch (TransactionAbortedException) { Reporter.TransactionAbortedInc("AssignDeployments"); Log.Warning("Transaction aborted during deployment assignment."); throw new RpcException(new Status(StatusCode.Unavailable, "assignment aborted due to concurrent modification; safe to retry")); } return(new AssignDeploymentsResponse()); }
public override async Task <AssignDeploymentsResponse> AssignDeployments(AssignDeploymentsRequest request, ServerCallContext context) { try { using (var memClient = _matchmakingMemoryStoreClientManager.GetClient()) { var toUpdate = new List <Entry>(); foreach (var assignment in request.Assignments) { Reporter.AssignDeploymentInc(assignment.DeploymentId, assignment.Result); foreach (var memberId in assignment.Party.MemberIds) { var playerJoinRequest = await memClient.GetAsync <PlayerJoinRequest>(memberId); if (playerJoinRequest == null) { continue; } switch (assignment.Result) { case Assignment.Types.Result.Error: playerJoinRequest.State = MatchState.Error; break; case Assignment.Types.Result.Matched: playerJoinRequest.AssignMatch(assignment.DeploymentId, assignment.DeploymentName); break; case Assignment.Types.Result.Requeued: playerJoinRequest.State = MatchState.Requested; break; } toUpdate.Add(playerJoinRequest); } } var toRequeue = new List <PartyJoinRequest>(); var toDelete = new List <PartyJoinRequest>(); foreach (var assignment in request.Assignments) { var party = assignment.Party; var partyJoinRequest = await memClient.GetAsync <PartyJoinRequest>(party.Id); if (partyJoinRequest == null) { // Party join request has been cancelled. continue; } var eventAttributes = new Dictionary <string, string> { { "partyId", partyJoinRequest.Id }, { "matchRequestId", partyJoinRequest.MatchRequestId }, { "queueType", partyJoinRequest.Type }, { "partyPhase", partyJoinRequest.Party.CurrentPhase.ToString() } }; if (assignment.Result == Assignment.Types.Result.Matched) { toDelete.Add(partyJoinRequest); eventAttributes.Add("spatialProjectId", _project); eventAttributes.Add("deploymentName", assignment.DeploymentName); eventAttributes.Add("deploymentId", assignment.DeploymentId); _analytics.Send("party_matched", eventAttributes, partyJoinRequest.Party.LeaderPlayerId); } else if (assignment.Result == Assignment.Types.Result.Requeued) { partyJoinRequest.RefreshQueueData(); toRequeue.Add(partyJoinRequest); toUpdate.Add(partyJoinRequest); _analytics.Send("party_requeued", eventAttributes, partyJoinRequest.Party.LeaderPlayerId); } else if (assignment.Result == Assignment.Types.Result.Error) { toDelete.Add(partyJoinRequest); _analytics.Send("party_error", eventAttributes, partyJoinRequest.Party.LeaderPlayerId); } else { toDelete.Add(partyJoinRequest); } } using (var tx = memClient.CreateTransaction()) { tx.UpdateAll(toUpdate); tx.EnqueueAll(toRequeue); tx.DeleteAll(toDelete); } foreach (var playerJoinRequest in toUpdate.OfType <PlayerJoinRequest>()) { var eventAttributes = new Dictionary <string, string> { { "partyId", playerJoinRequest.PartyId }, { "matchRequestId", playerJoinRequest.MatchRequestId }, { "queueType", playerJoinRequest.Type }, { "playerJoinRequestState", playerJoinRequest.State.ToString() } }; switch (playerJoinRequest.State) { case MatchState.Matched: eventAttributes.Add("spatialProjectId", _project); eventAttributes.Add("deploymentName", playerJoinRequest.DeploymentName); eventAttributes.Add("deploymentId", playerJoinRequest.DeploymentId); _analytics.Send("player_matched", eventAttributes, playerJoinRequest.Id); break; case MatchState.Requested: _analytics.Send("player_requeued", eventAttributes, playerJoinRequest.Id); break; case MatchState.Error: _analytics.Send("player_error", eventAttributes, playerJoinRequest.Id); break; } } } } catch (EntryNotFoundException e) { Reporter.AssignDeploymentNotFoundInc(e.Id); Log.Warning($"Attempted to assign deployment to nonexistent join request {e.Id}."); throw new RpcException(new Status(StatusCode.NotFound, "Join request does not exist")); } catch (TransactionAbortedException) { Reporter.TransactionAbortedInc("AssignDeployments"); Log.Warning("Transaction aborted during deployment assignment."); throw new RpcException(new Status(StatusCode.Unavailable, "assignment aborted due to concurrent modification; safe to retry")); } return(new AssignDeploymentsResponse()); }
public override async Task <CreateInviteResponse> CreateInvite(CreateInviteRequest request, ServerCallContext context) { var playerId = AuthHeaders.ExtractPlayerId(context); if (string.IsNullOrEmpty(request.ReceiverPlayerId)) { throw new RpcException( new Status(StatusCode.InvalidArgument, "Expected a non-empty receiver player id")); } using (var memClient = _memoryStoreClientManager.GetClient()) { var party = await GetPartyByPlayerId(memClient, playerId); // This extra check is necessary because the player might have meanwhile left the party (between the // Get<Member> and Get<PartyDataModel> calls). if (party?.GetMember(playerId) == null) { throw new RpcException(new Status(StatusCode.FailedPrecondition, "The player creating this invite is not a member of any party")); } if (party.GetMember(request.ReceiverPlayerId) != null) { throw new RpcException(new Status(StatusCode.FailedPrecondition, "The receiving player is already a member of the party")); } var entitiesToCreate = new List <Entry>(); var entitiesToUpdate = new List <Entry>(); var invite = new InviteDataModel(playerId, request.ReceiverPlayerId, party.Id, request.Metadata); entitiesToCreate.Add(invite); var senderPlayerInvites = await memClient.GetAsync <PlayerInvites>(playerId); if (senderPlayerInvites == null) { senderPlayerInvites = new PlayerInvites(playerId); entitiesToCreate.Add(senderPlayerInvites); } else { entitiesToUpdate.Add(senderPlayerInvites); } senderPlayerInvites.OutboundInviteIds.Add(invite.Id); var receiverPlayerInvites = await memClient.GetAsync <PlayerInvites>(request.ReceiverPlayerId); if (receiverPlayerInvites == null) { receiverPlayerInvites = new PlayerInvites(request.ReceiverPlayerId); entitiesToCreate.Add(receiverPlayerInvites); } else { entitiesToUpdate.Add(receiverPlayerInvites); } receiverPlayerInvites.InboundInviteIds.Add(invite.Id); using (var transaction = memClient.CreateTransaction()) { transaction.CreateAll(entitiesToCreate); transaction.UpdateAll(entitiesToUpdate); } return(new CreateInviteResponse { InviteId = invite.Id }); } }
public override async Task <Operation> GetOperation(GetOperationRequest request, ServerCallContext context) { var playerIdentity = AuthHeaders.ExtractPlayerId(context); if (!string.Equals(request.Name, playerIdentity)) { throw new RpcException(new Status(StatusCode.PermissionDenied, "Fetching another player's operation is forbidden.")); } PlayerJoinRequest joinRequest; using (var memClient = _memoryStoreClientManager.GetClient()) { try { joinRequest = await memClient.GetAsync <PlayerJoinRequest>(request.Name) ?? throw new EntryNotFoundException(request.Name); if (joinRequest.IsComplete()) { using (var tx = memClient.CreateTransaction()) { tx.DeleteAll(joinRequest.Yield()); } } } catch (EntryNotFoundException e) { Reporter.OperationStateNotFoundInc(); 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("GetOperation"); Log.Warning("Transaction for operation deletion was aborted"); throw new RpcException(new Status(StatusCode.Unavailable, "deletion aborted due to concurrent modification; safe to retry")); } } var op = new Operation { Name = joinRequest.PlayerIdentity, Done = joinRequest.IsComplete() }; if (!op.Done) { Reporter.OperationStateInc(MatchState.Requested); return(op); } switch (joinRequest.State) { case MatchState.Matched: op.Response = CreateJoinResponse(joinRequest); break; case MatchState.Error: op.Error = new Google.Rpc.Status { Code = (int)Google.Rpc.Code.Unknown, Message = "the join request encountered an error" }; break; } Reporter.OperationStateInc(joinRequest.State); Log.Information($"Join request for {op.Name} done in state {joinRequest.State}."); return(op); }
public override async Task <Operation> Join(JoinRequestProto request, ServerCallContext context) { // TODO(dom): refactor this later. var playerIdentifier = AuthHeaders.ExtractPlayerId(context); using (var memClient = _memoryStoreClientManager.GetClient()) { try { var party = await GetPartyForMember(memClient, playerIdentifier) ?? throw new EntryNotFoundException(playerIdentifier); if (party.LeaderPlayerId != playerIdentifier) { throw new RpcException(new Status(StatusCode.PermissionDenied, "Only the leader can start matchmaking")); } if (!party.SufficientMembers()) { throw new RpcException(new Status(StatusCode.FailedPrecondition, "There are not enough members in the party to start matchmaking")); } party.CurrentPhase = PartyDataModel.Phase.Matchmaking; var matchmakingMetadata = new Dictionary <string, string>(request.Metadata); var partyJoinRequest = new PartyJoinRequest(party, request.MatchmakingType, matchmakingMetadata); var entriesToCreate = new List <Entry> { partyJoinRequest }; entriesToCreate.AddRange(CreateJoinRequestsForEachMember(party, request.MatchmakingType, matchmakingMetadata)); using (var tx = memClient.CreateTransaction()) { tx.UpdateAll(party.Yield()); tx.CreateAll(entriesToCreate); tx.EnqueueAll(partyJoinRequest.Yield()); } } catch (EntryNotFoundException) { Log.Information("Player is not a member of any party"); throw new RpcException(new Status(StatusCode.NotFound, "player is not a member of any party")); } catch (EntryAlreadyExistsException e) { Reporter.JoinRequestQueuedInc(); Log.Information($"Party already queued: {e.Id}."); throw new RpcException(new Status(StatusCode.AlreadyExists, "party is already queued for matchmaking")); } catch (TransactionAbortedException) { Reporter.TransactionAbortedInc("Join"); Log.Warning("Transaction for join was aborted"); throw new RpcException(new Status(StatusCode.Unavailable, "join aborted due to concurrent modification; safe to retry")); } } Reporter.JoinRequestInc(); Log.Information($"Created party join request for the party associated to player ${playerIdentifier}."); return(new Operation { Name = playerIdentifier }); }