/// <summary>
        /// Obtains the corresponding matchmaker entity from database
        ///
        /// Handles entity creation and migration
        /// </summary>
        private BasicMatchmakerEntity GetEntity()
        {
            // try to load the entity
            var e = DB.TakeAll <BasicMatchmakerEntity>()
                    .Filter(entity => entity.MatchmakerName == GetMatchmakerName())
                    .First();

            // delete deprecated entity
            if (e != null && e.Version != BasicMatchmakerEntity.CurrentVersion)
            {
                e.Delete();
                e = null;
            }

            // create new entity
            if (e == null)
            {
                e = new BasicMatchmakerEntity {
                    MatchmakerName = GetMatchmakerName()
                };
                e.Save();
            }

            return(e);
        }
        /// <summary>
        /// Player wants to join this matchmaker and start waiting
        /// </summary>
        /// <param name="ticket">Ticket of the player</param>
        /// <exception cref="ArgumentException">
        /// Ticket owner and facet caller differ
        /// </exception>
        public void JoinMatchmaker(TMatchmakerTicket ticket)
        {
            // null ticket owner gets set to the caller
            if (ticket.PlayerId == null)
            {
                ticket.PlayerId = Caller.EntityId;
            }

            // ticket owner has to match the caller
            if (ticket.PlayerId != Caller.EntityId)
            {
                throw new ArgumentException(
                          "Ticket belongs to a different player " +
                          "than the one registering it.",
                          nameof(ticket)
                          );
            }

            PrepareNewTicket(ticket);

            entity = GetEntity();

            DB.RetryOnConflict(() => {
                entity.Refresh();
                var tickets = entity
                              .DeserializeTickets <TMatchmakerTicket>();

                // if already waiting, perform a re-insert
                tickets.RemoveAll(t => t.PlayerId == ticket.PlayerId);

                // if to be notified, remove from notifications
                entity.Notifications.RemoveAll(
                    n => n.playerId == Caller.EntityId
                    );

                // add ticket into the queue
                ticket.InsertedNow();
                tickets.Add(ticket);

                entity.SerializeTickets(
                    tickets
                    );
                entity.SaveCarefully();
            });
        }
        /// <summary>
        /// Player polls for new status on his/her matching
        /// </summary>
        /// <param name="leave">Player wants to leave the matchmaker</param>
        /// <returns>Null if not matched yet, match entity otherwise</returns>
        /// <exception cref="UnknownPlayerPollingException">
        /// When the matchmaker has no clue why is this player polling
        /// </exception>
        public TMatchEntity PollMatchmaker(bool leave)
        {
            entity = GetEntity();

            TMatchEntity returnedValue = null;

            // first perform cleanup
            DB.RetryOnConflict(() => {
                entity.Refresh();
                CleanUpExpiredItems();
                entity.SaveCarefully();
            });

            DB.RetryOnConflict(() => {
                entity.Refresh();

                // player not waiting -> throw
                // unless there's a notification for this player
                if (entity.Notifications.All(
                        n => n.playerId != Caller.EntityId
                        ))
                {
                    var tickets = entity
                                  .DeserializeTickets <TMatchmakerTicket>();

                    if (tickets.All(t => t.PlayerId != Caller.EntityId))
                    {
                        throw new UnknownPlayerPollingException(
                            "Polling, but not waiting in ticket queue, " +
                            "nor having a notification prepared."
                            );
                    }
                }

                // update poll time for this ticket
                {
                    var tickets = entity
                                  .DeserializeTickets <TMatchmakerTicket>();
                    var ticket = tickets.FirstOrDefault(
                        t => t.PlayerId == Caller.EntityId
                        );
                    ticket?.PolledNow();
                    entity.SerializeTickets(
                        tickets
                        );
                }

                // attempt to create matches
                CallCreateMatches();

                // find notification to return
                var notification = entity.Notifications
                                   .FirstOrDefault(n => n.playerId == Caller.EntityId);
                if (notification != null)
                {
                    returnedValue = DB.Find <TMatchEntity>(notification.matchId);

                    entity.Notifications.RemoveAll(
                        n => n.playerId == Caller.EntityId
                        );
                }

                // stop waiting no match, but leaving requested
                if (leave && returnedValue == null)
                {
                    var tickets = entity
                                  .DeserializeTickets <TMatchmakerTicket>();
                    tickets.RemoveAll(t => t.PlayerId == Caller.EntityId);
                    entity.SerializeTickets(
                        tickets
                        );
                }

                entity.SaveCarefully();
            });

            return(returnedValue);
        }