コード例 #1
0
        public override async Task <GetInviteResponse> GetInvite(GetInviteRequest request, ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            if (string.IsNullOrEmpty(request.InviteId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument, "Expected non-empty invite id"));
            }

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var invite = await memClient.GetAsync <InviteDataModel>(request.InviteId) ??
                             throw new EntryNotFoundException(request.InviteId,
                                                              "No such invite with the given id found");

                if (!invite.PlayerInvolved(playerId))
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The player is not involved in this invite"));
                }

                return(new GetInviteResponse {
                    Invite = ConvertToProto(invite)
                });
            }
        }
コード例 #2
0
        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
            }));
        }
コード例 #3
0
        public override async Task <DeletePartyResponse> DeleteParty(DeletePartyRequest request,
                                                                     ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var party = await GetPartyByPlayerId(memClient, playerId) ??
                            throw new RpcException(new Status(StatusCode.NotFound,
                                                              "The player is not a member of any party"));

                if (playerId != party.LeaderPlayerId)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "Cannot delete party: player needs to be the leader of the party"));
                }

                // TODO(iuliaharasim/dom): Move logic specific to party deletion in a separate class.
                var entitiesToDelete = new List <Entry> {
                    party
                };
                entitiesToDelete.AddRange(party.GetMembers());

                try
                {
                    using (var transaction = memClient.CreateTransaction())
                    {
                        transaction.DeleteAll(entitiesToDelete);
                    }
                }
                catch (EntryNotFoundException exception)
                {
                    if (exception.Id.Contains(party.Id))
                    {
                        throw;
                    }

                    // If one of the members has left the party, it is safe to retry this RPC.
                    throw new TransactionAbortedException();
                }

                _analytics.Send("player_cancelled_party", new Dictionary <string, string> {
                    { "partyId", party.Id }
                }, playerId);
                _analytics.Send("party_cancelled", new Dictionary <string, string> {
                    { "partyId", party.Id }
                }, playerId);

                foreach (var m in party.GetMembers())
                {
                    _analytics.Send(
                        "player_left_cancelled_party", new Dictionary <string, string>
                    {
                        { "partyId", party.Id }
                    }, m.Id);
                }
            }

            return(new DeletePartyResponse());
        }
コード例 #4
0
        public override async Task <LeavePartyResponse> LeaveParty(LeavePartyRequest request, ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            await LeaveParty(playerId);

            return(new LeavePartyResponse());
        }
コード例 #5
0
        public override async Task <DeleteInviteResponse> DeleteInvite(DeleteInviteRequest request,
                                                                       ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            if (string.IsNullOrEmpty(request.InviteId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument, "Expected non-empty invite id"));
            }

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var invite = await memClient.GetAsync <InviteDataModel>(request.InviteId);

                if (invite == null)
                {
                    return(new DeleteInviteResponse());
                }

                if (!invite.PlayerInvolved(playerId))
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The player is not involved in this invite"));
                }

                var senderInvites = await memClient.GetAsync <PlayerInvites>(invite.SenderId) ??
                                    throw new EntryNotFoundException(playerId, "No invites found for the sender");

                senderInvites.OutboundInviteIds.Remove(invite.Id);

                var receiverInvites = await memClient.GetAsync <PlayerInvites>(invite.ReceiverId) ??
                                      throw new EntryNotFoundException(playerId, "No invites found for the receiver");

                receiverInvites.InboundInviteIds.Remove(invite.Id);

                using (var transaction = memClient.CreateTransaction())
                {
                    transaction.DeleteAll(new List <InviteDataModel> {
                        invite
                    });
                    transaction.UpdateAll(new List <PlayerInvites> {
                        senderInvites, receiverInvites
                    });
                }

                _analytics.Send("player_invite_to_party_revoked", new Dictionary <string, string>
                {
                    { "partyId", invite.PartyId },
                    { "playerIdInviter", playerId },
                    { "inviteId", invite.Id }
                }, invite.ReceiverId);
            }

            return(new DeleteInviteResponse());
        }
コード例 #6
0
        public override async Task <GetPartyByPlayerIdResponse> GetPartyByPlayerId(GetPartyByPlayerIdRequest request,
                                                                                   ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var party = await GetPartyByPlayerId(memClient, playerId) ??
                            throw new RpcException(new Status(StatusCode.NotFound,
                                                              "The player is not a member of any party"));

                return(new GetPartyByPlayerIdResponse {
                    Party = ConvertToProto(party)
                });
            }
        }
コード例 #7
0
        // Updates the Party's information, excluding its member list.
        // TODO: Move to FieldMasks.
        public override async Task <UpdatePartyResponse> UpdateParty(UpdatePartyRequest request,
                                                                     ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            ValidateUpdatePartyRequest(request);
            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var updatedParty = request.UpdatedParty;
                var party        = await memClient.GetAsync <PartyDataModel>(updatedParty.Id) ??
                                   throw new RpcException(new Status(StatusCode.NotFound,
                                                                     "There is no such party with the given id"));

                if (party.LeaderPlayerId != playerId)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The update operation can only be done by the leader of the party"));
                }

                if (!party.UpdatePartyLeader(updatedParty.LeaderPlayerId))
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition,
                                                      "The proposed new leader is not a member of the party"));
                }

                if (!party.UpdateMinMaxMembers(updatedParty.MinMembers, updatedParty.MaxMembers))
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition,
                                                      "Encountered error while updating the minimum and maximum amount of members"));
                }

                // TODO(iuliaharasim/dom): Move logic specific to updating a party into a separate class.
                party.CurrentPhase = ConvertToDataModel(updatedParty.CurrentPhase);
                party.UpdateMetadata(updatedParty.Metadata);

                using (var transaction = memClient.CreateTransaction())
                {
                    transaction.UpdateAll(new List <Entry> {
                        party
                    });
                }

                return(new UpdatePartyResponse {
                    Party = ConvertToProto(party)
                });
            }
        }
コード例 #8
0
        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
            }));
        }
コード例 #9
0
        // Updates the metadata and current status. Sender, receiver and party id are ignored.
        // TODO: consider moving to FieldMasks.
        public override async Task <UpdateInviteResponse> UpdateInvite(UpdateInviteRequest request,
                                                                       ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            var updatedInvite = request.UpdatedInvite ??
                                throw new RpcException(new Status(StatusCode.InvalidArgument,
                                                                  "Expected non-empty updated invite"));

            if (string.IsNullOrEmpty(updatedInvite.Id))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument,
                                                  "Expected updated invite with non-empty id"));
            }

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var invite = await memClient.GetAsync <InviteDataModel>(updatedInvite.Id) ??
                             throw new EntryNotFoundException(updatedInvite.Id,
                                                              "No such invite with the given id found");

                if (!invite.PlayerInvolved(playerId))
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The player is not involved in this invite"));
                }

                invite.CurrentStatus = ConvertToDataModel(updatedInvite.CurrentStatus);
                invite.UpdateMetadata(updatedInvite.Metadata);

                using (var transaction = memClient.CreateTransaction())
                {
                    transaction.UpdateAll(new List <Entry> {
                        invite
                    });
                }

                return(new UpdateInviteResponse {
                    Invite = ConvertToProto(invite)
                });
            }
        }
コード例 #10
0
        public override async Task <ListAllInvitesResponse> ListAllInvites(ListAllInvitesRequest request,
                                                                           ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var playerInvites = await memClient.GetAsync <PlayerInvites>(playerId);

                if (playerInvites == null)
                {
                    return(new ListAllInvitesResponse());
                }

                var response = new ListAllInvitesResponse();
                foreach (var id in playerInvites.OutboundInviteIds)
                {
                    var invite = await memClient.GetAsync <InviteDataModel>(id) ??
                                 throw new RpcException(new Status(StatusCode.Unavailable,
                                                                   "Concurrent modification. Safe to retry"));

                    response.OutboundInvites.Add(ConvertToProto(invite));
                }

                foreach (var id in playerInvites.InboundInviteIds)
                {
                    var invite = await memClient.GetAsync <InviteDataModel>(id) ??
                                 throw new RpcException(new Status(StatusCode.Unavailable,
                                                                   "Concurrent modification. Safe to retry"));

                    response.InboundInvites.Add(ConvertToProto(invite));
                }

                return(response);
            }
        }
コード例 #11
0
        public override async Task <KickOutPlayerResponse> KickOutPlayer(KickOutPlayerRequest request,
                                                                         ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            if (string.IsNullOrEmpty(request.EvictedPlayerId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument,
                                                  "LeaveParty requires a non-empty evicted player id"));
            }

            if (playerId == request.EvictedPlayerId)
            {
                await LeaveParty(request.EvictedPlayerId);

                return(new KickOutPlayerResponse());
            }

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var initiatorTask = memClient.GetAsync <Member>(playerId);
                var evictedTask   = memClient.GetAsync <Member>(request.EvictedPlayerId);
                Task.WaitAll(initiatorTask, evictedTask);

                var initiator = initiatorTask.Result ?? throw new RpcException(new Status(StatusCode.NotFound,
                                                                                          "The initiator player is not a member of any party"));

                // If the evicted has already left the party, we should return early.
                var evicted = evictedTask.Result;
                if (evicted == null)
                {
                    return(new KickOutPlayerResponse());
                }

                var party = await memClient.GetAsync <PartyDataModel>(initiator.PartyId) ??
                            throw new RpcException(new Status(StatusCode.NotFound,
                                                              "The party no longer exists"));

                if (party.LeaderPlayerId != initiator.Id)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The initiator is not the leader of the party"));
                }

                if (initiator.PartyId != evicted.PartyId)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "The players are not members of the same party"));
                }

                // TODO(iuliaharasim/dom): Move logic specific to removing a player from a party into a separate class.
                // If false, the player has already been removed from the party so we should terminate early.
                if (!party.RemovePlayerFromParty(evicted.Id))
                {
                    return(new KickOutPlayerResponse());
                }

                using (var transaction = memClient.CreateTransaction())
                {
                    transaction.DeleteAll(new List <Entry> {
                        evicted
                    });
                    transaction.UpdateAll(new List <Entry> {
                        party
                    });
                }

                _analytics.Send("player_kicked_from_party", new Dictionary <string, string>
                {
                    { "partyId", party.Id },
                    { "playerIdKicker", playerId }
                }, evicted.Id);
            }

            return(new KickOutPlayerResponse());
        }
コード例 #12
0
        public override async Task <JoinPartyResponse> JoinParty(JoinPartyRequest request, ServerCallContext context)
        {
            var playerId = AuthHeaders.ExtractPlayerId(context);

            var pit = AuthHeaders.ExtractPit(context);

            if (string.IsNullOrEmpty(request.PartyId))
            {
                throw new RpcException(new Status(StatusCode.InvalidArgument,
                                                  "JoinParty requires a non-empty party id"));
            }

            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var partyToJoin = await memClient.GetAsync <PartyDataModel>(request.PartyId) ??
                                  throw new RpcException(new Status(StatusCode.NotFound, "The party doesn't exist"));

                var member = await memClient.GetAsync <Member>(playerId);

                if (member != null && member.PartyId != request.PartyId)
                {
                    throw new RpcException(new Status(StatusCode.AlreadyExists,
                                                      "The player is a member of another party"));
                }

                var playerInvites = await memClient.GetAsync <PlayerInvites>(playerId);

                if (playerInvites == null)
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition,
                                                      "The player is not invited to this party"));
                }

                var invites = (await Task.WhenAll(playerInvites.InboundInviteIds
                                                  .Select(invite => memClient.GetAsync <Invite>(invite))))
                              .Where(invite =>
                {
                    if (invite == null)
                    {
                        Log.Logger.Warning("Failed to fetch an invite for {player}", playerId);
                    }

                    return(invite != null);
                }).ToList();

                var invited = invites
                              .Any(invite => invite.CurrentStatus == Invite.Status.Pending && invite.ReceiverId == playerId);

                if (!invited)
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition,
                                                      "The player is not invited to this party"));
                }


                if (partyToJoin.CurrentPhase != PartyPhaseDataModel.Forming)
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition,
                                                      "The party is no longer in the Forming phase"));
                }

                // TODO(iuliaharasim/dom): Move logic specific to joining a party into a separate class.
                try
                {
                    var added = partyToJoin.AddPlayerToParty(playerId, pit);
                    // If false, the player already joined the party so we should terminate early.
                    if (!added)
                    {
                        return(new JoinPartyResponse {
                            Party = ConvertToProto(partyToJoin)
                        });
                    }
                }
                catch (Exception exception)
                {
                    throw new RpcException(new Status(StatusCode.FailedPrecondition, exception.Message));
                }

                using (var transaction = memClient.CreateTransaction())
                {
                    transaction.CreateAll(new List <Entry> {
                        partyToJoin.GetMember(playerId)
                    });
                    transaction.UpdateAll(new List <Entry> {
                        partyToJoin
                    });
                }

                _analytics.Send("player_joined_party", new Dictionary <string, object>
                {
                    { "partyId", partyToJoin.Id },
                    {
                        "invites", invites.Select(invite => new Dictionary <string, string>
                        {
                            { "inviteId", invite.Id },
                            { "playerIdInviter", invite.SenderId }
                        })
                    }
                }, playerId);

                return(new JoinPartyResponse {
                    Party = ConvertToProto(partyToJoin)
                });
            }
        }
コード例 #13
0
        public override async Task <CancelJoinResponse> CancelJoin(CancelJoinRequest request, ServerCallContext context)
        {
            var playerIdentity = AuthHeaders.ExtractPlayerId(context);

            if (!string.Equals(request.PlayerId, playerIdentity))
            {
                throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                  "Removing another player from the queue is forbidden."));
            }

            Log.Information($"Requested cancellation for the party of player identifier {request.PlayerId}.");
            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var party = await GetPartyOfMember(memClient, request.PlayerId);

                if (party == null)
                {
                    throw new RpcException(new Status(StatusCode.NotFound,
                                                      "The player making this call is not a member of any party"));
                }

                if (party.LeaderPlayerId != request.PlayerId)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "Only the leader can delete a matchmaking join request"));
                }

                try
                {
                    var partyJoinRequest = await memClient.GetAsync <PartyJoinRequest>(party.Id) ??
                                           throw new EntryNotFoundException(party.Id);

                    var toDelete = new List <Entry> {
                        partyJoinRequest
                    };
                    foreach (var(member, _) in partyJoinRequest.Party.MemberIdToPit)
                    {
                        toDelete.Add(await memClient.GetAsync <PlayerJoinRequest>(member) ??
                                     throw new EntryNotFoundException(member));
                    }

                    party.CurrentPhase = PartyDataModel.Phase.Forming;

                    using (var tx = memClient.CreateTransaction())
                    {
                        tx.UpdateAll(party.Yield());
                        tx.RemoveAllFromQueue(partyJoinRequest.Yield());
                        tx.DeleteAll(toDelete);
                    }

                    Reporter.CancelJoinInc();

                    var eventAttributes = new Dictionary <string, string>
                    {
                        { "partyId", partyJoinRequest.Id },
                        { "matchRequestId", partyJoinRequest.MatchRequestId },
                        { "queueType", partyJoinRequest.Type }
                    };
                    _analytics.Send("player_cancels_match_request", eventAttributes, partyJoinRequest.Party.LeaderPlayerId);
                    var eventAttributesParty = new Dictionary <string, string>(eventAttributes)
                    {
                        { "partyPhase", "Forming" }
                    };
                    _analytics.Send("party_match_request_cancelled", eventAttributesParty, partyJoinRequest.Party.LeaderPlayerId);

                    foreach (var playerJoinRequest in toDelete.OfType <PlayerJoinRequest>())
                    {
                        _analytics.Send("player_left_cancelled_match_request", new Dictionary <string, string>
                        {
                            { "partyId", playerJoinRequest.PartyId },
                            { "matchRequestId", playerJoinRequest.MatchRequestId },
                            { "queueType", playerJoinRequest.Type }
                        }, playerJoinRequest.Id);
                    }

                    return(new CancelJoinResponse());
                }
                catch (EntryNotFoundException exception)
                {
                    Log.Warning($"Delete for {request.PlayerId} failed.");
                    if (exception.Id.Contains(party.Id))
                    {
                        Reporter.CancelJoinNotFoundInc();
                        throw new RpcException(new Status(StatusCode.NotFound,
                                                          "requested party is not in matchmaking"));
                    }

                    throw new RpcException(new Status(StatusCode.Internal,
                                                      $"could not find join request for player {exception.Id}"));
                }
                catch (TransactionAbortedException)
                {
                    Reporter.TransactionAbortedInc("CancelJoin");
                    Log.Warning("Transaction for join cancellation was aborted");
                    throw new RpcException(new Status(StatusCode.Unavailable,
                                                      "deletion aborted due to concurrent modification; safe to retry"));
                }
            }
        }
コード例 #14
0
        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);
        }
コード例 #15
0
        public override async Task <Empty> DeleteOperation(DeleteOperationRequest request, ServerCallContext context)
        {
            var playerIdentity = AuthHeaders.ExtractPlayerId(context);

            if (!string.Equals(request.Name, playerIdentity))
            {
                throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                  "Deleting another player's operation is forbidden."));
            }

            Log.Information($"Requested cancellation for the party of player identifier {request.Name}.");
            using (var memClient = _memoryStoreClientManager.GetClient())
            {
                var party = await GetPartyOfMember(memClient, request.Name);

                if (party == null)
                {
                    throw new RpcException(new Status(StatusCode.NotFound,
                                                      "The player making this call is not a member of any party"));
                }

                if (party.LeaderPlayerId != request.Name)
                {
                    throw new RpcException(new Status(StatusCode.PermissionDenied,
                                                      "Only the leader can delete a matchmaking join request"));
                }

                try
                {
                    var partyJoinRequest = await memClient.GetAsync <PartyJoinRequest>(party.Id) ??
                                           throw new EntryNotFoundException(party.Id);

                    var toDelete = new List <Entry> {
                        partyJoinRequest
                    };
                    foreach (var(member, _) in partyJoinRequest.Party.MemberIdToPit)
                    {
                        toDelete.Add(await memClient.GetAsync <PlayerJoinRequest>(member) ??
                                     throw new EntryNotFoundException(member));
                    }

                    party.CurrentPhase = PartyDataModel.Phase.Forming;

                    using (var tx = memClient.CreateTransaction())
                    {
                        tx.UpdateAll(party.Yield());
                        tx.RemoveAllFromQueue(partyJoinRequest.Yield());
                        tx.DeleteAll(toDelete);
                    }

                    Reporter.CancelOperationInc();
                    return(new Empty());
                }
                catch (EntryNotFoundException exception)
                {
                    Log.Warning($"Delete for {request.Name} failed.");
                    if (exception.Id.Contains(party.Id))
                    {
                        Reporter.CancelOperationNotFoundInc();
                        throw new RpcException(new Status(StatusCode.NotFound,
                                                          "requested party is not in matchmaking"));
                    }

                    throw new RpcException(new Status(StatusCode.Internal,
                                                      $"could not find join request for player {exception.Id}"));
                }
                catch (TransactionAbortedException)
                {
                    Reporter.TransactionAbortedInc("DeleteOperation");
                    Log.Warning("Transaction for operation deletion was aborted");
                    throw new RpcException(new Status(StatusCode.Unavailable,
                                                      "deletion aborted due to concurrent modification; safe to retry"));
                }
            }
        }
コード例 #16
0
        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
            });
        }
コード例 #17
0
        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
                });
            }
        }
コード例 #18
0
        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);
        }