From 3de8cf2e0d31a4c78b68dcefbc3d0aed3d46d0d7 Mon Sep 17 00:00:00 2001 From: Andrei Mihu Date: Mon, 2 Oct 2017 20:31:36 +0100 Subject: [PATCH] Improve how haystack leaderboard record list operations resolve the required page --- CHANGELOG.md | 4 ++++ server/core_leaderboard.go | 44 ++++++++++++++++++++++++++------------ server/session.go | 4 ++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459d2731f..07983b1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [keep a changelog](http://keepachangelog.com/) and this p ### Changed - Script runtime RPC and HTTP hook errors now return more detail when verbose logging is enabled. +### Fixed +- Haystack leaderboard record listings now return correct results around both sides of the pivot record. +- Haystack leaderboard record listings now return a complete page even when the pivot record is at the end of the leaderboard. + ## [1.0.2] - 2017-09-29 ### Added - New code runtime function to list leaderboard records for a given set of users. diff --git a/server/core_leaderboard.go b/server/core_leaderboard.go index 3763d91b0..d0d833582 100644 --- a/server/core_leaderboard.go +++ b/server/core_leaderboard.go @@ -283,16 +283,16 @@ func leaderboardRecordsList(logger *zap.Logger, db *sql.DB, caller uuid.UUID, li func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid.UUID, list *TLeaderboardRecordsList, leaderboardId, findOwnerId []byte, currentExpiresAt, limit, sortOrder int64, query string, params []interface{}) ([]*LeaderboardRecord, []byte, Error_Code, error) { // Find the owner's record. - var id []byte - var score int64 - var updatedAt int64 + var pivotID []byte + var pivotScore int64 + var pivotUpdatedAt int64 findQuery := `SELECT id, score, updated_at FROM leaderboard_record WHERE leaderboard_id = $1 AND expires_at = $2 AND owner_id = $3` logger.Debug("Leaderboard record find", zap.String("query", findQuery)) - err := db.QueryRow(findQuery, leaderboardId, currentExpiresAt, findOwnerId).Scan(&id, &score, &updatedAt) + err := db.QueryRow(findQuery, leaderboardId, currentExpiresAt, findOwnerId).Scan(&pivotID, &pivotScore, &pivotUpdatedAt) if err == sql.ErrNoRows { return []*LeaderboardRecord{}, nil, 0, nil } else if err != nil { @@ -303,22 +303,22 @@ func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid. // First half. count := len(params) firstQuery := query - firstParams := make([]interface{}, len(params)) + firstParams := make([]interface{}, count) copy(firstParams, params) if sortOrder == 0 { // Lower score is better, but get in reverse order from current user to get those immediately above. firstQuery += " AND (score, updated_at_inverse, id) <= ($" + strconv.Itoa(count+1) + ", $" + strconv.Itoa(count+2) + ", $" + strconv.Itoa(count+3) + ") ORDER BY score DESC, updated_at_inverse DESC" - firstParams = append(firstParams, score, invertMs(updatedAt), id) + firstParams = append(firstParams, pivotScore, invertMs(pivotUpdatedAt), pivotID) } else { // Higher score is better. firstQuery += " AND (score, updated_at, id) >= ($" + strconv.Itoa(count+1) + ", $" + strconv.Itoa(count+2) + ", $" + strconv.Itoa(count+3) + ") ORDER BY score ASC, updated_at ASC" - firstParams = append(firstParams, score, updatedAt, id) + firstParams = append(firstParams, pivotScore, pivotUpdatedAt, pivotID) } - firstParams = append(firstParams, int64(limit/2)) + firstParams = append(firstParams, limit) firstQuery += " LIMIT $" + strconv.Itoa(len(firstParams)) logger.Debug("Leaderboard records list", zap.String("query", firstQuery)) @@ -331,15 +331,18 @@ func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid. leaderboardRecords := []*LeaderboardRecord{} + var id []byte var ownerId []byte var handle string var lang string var location sql.NullString var timezone sql.NullString var rankValue int64 + var score int64 var numScore int64 var metadata []byte var rankedAt int64 + var updatedAt int64 var expiresAt int64 var bannedAt int64 for firstRows.Next() { @@ -378,22 +381,26 @@ func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid. // Second half. secondQuery := query - secondParams := make([]interface{}, len(params)) + secondParams := make([]interface{}, count) copy(secondParams, params) if sortOrder == 0 { // Lower score is better. secondQuery += " AND (score, updated_at, id) > ($" + strconv.Itoa(count+1) + ", $" + strconv.Itoa(count+2) + ", $" + strconv.Itoa(count+3) + ") ORDER BY score ASC, updated_at ASC" - secondParams = append(secondParams, score, updatedAt, id) + secondParams = append(secondParams, pivotScore, pivotUpdatedAt, pivotID) } else { // Higher score is better. secondQuery += " AND (score, updated_at_inverse, id) < ($" + strconv.Itoa(count+1) + ", $" + strconv.Itoa(count+2) + ", $" + strconv.Itoa(count+3) + ") ORDER BY score DESC, updated_at DESC" - secondParams = append(secondParams, score, invertMs(updatedAt), id) + secondParams = append(secondParams, pivotScore, invertMs(pivotUpdatedAt), pivotID) + } + secondLimit := limit/2 + 2 + if l := int64(len(leaderboardRecords)); l < limit/2 { + secondLimit = limit - l + 2 } - secondParams = append(secondParams, limit-int64(len(leaderboardRecords))+2) + secondParams = append(secondParams, secondLimit) secondQuery += " LIMIT $" + strconv.Itoa(len(secondParams)) logger.Debug("Leaderboard records list", zap.String("query", secondQuery)) @@ -406,8 +413,12 @@ func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid. var outgoingCursor []byte + need := limit / 2 + if l := int64(len(leaderboardRecords)); l < limit/2 { + need += limit/2 - l + } for secondRows.Next() { - if int64(len(leaderboardRecords)) >= limit { + if need <= 0 { cursorBuf := new(bytes.Buffer) newCursor := &leaderboardRecordListCursor{ Score: score, @@ -444,13 +455,18 @@ func loadLeaderboardRecordsHaystack(logger *zap.Logger, db *sql.DB, caller uuid. UpdatedAt: updatedAt, ExpiresAt: expiresAt, }) + need-- } if err = secondRows.Err(); err != nil { logger.Error("Could not process leaderboard records list query results", zap.Error(err)) return nil, nil, RUNTIME_EXCEPTION, errors.New("Error loading leaderboard records") } - return normalizeLeaderboardRecords(leaderboardRecords), outgoingCursor, 0, nil + start := int64(len(leaderboardRecords)) - limit + if start < 0 { + start = 0 + } + return normalizeLeaderboardRecords(leaderboardRecords[start:]), outgoingCursor, 0, nil } func normalizeLeaderboardRecords(records []*LeaderboardRecord) []*LeaderboardRecord { diff --git a/server/session.go b/server/session.go index bd6cc257c..05bd9e58a 100644 --- a/server/session.go +++ b/server/session.go @@ -183,7 +183,7 @@ func (s *session) cleanupClosedConnection() { s.logger.Info("Cleaning up closed client connection", zap.String("remoteAddress", s.conn.RemoteAddr().String())) s.unregister(s) s.pingTicker.Stop() - s.pingTickerStopCh <- true + close(s.pingTickerStopCh) s.conn.Close() s.logger.Info("Closed client connection") } @@ -198,7 +198,7 @@ func (s *session) close() { s.Unlock() s.pingTicker.Stop() - s.pingTickerStopCh <- true + close(s.pingTickerStopCh) err := s.conn.WriteControl(websocket.CloseMessage, []byte{}, time.Now().Add(time.Duration(s.config.GetSocket().WriteWaitMs)*time.Millisecond)) if err != nil { s.logger.Warn("Could not send close message. Closing prematurely.", zap.String("remoteAddress", s.conn.RemoteAddr().String()), zap.Error(err)) -- GitLab