Commit bfe29cbd authored by Andrei Mihu's avatar Andrei Mihu
Browse files

Improve handling of large tournament max size values.

parent 6e5af38a
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr
- Improve log messages from failed social provider requests.
- Improve Lua runtime function registration handling.
- Ensure authoritative match loggers correctly include only their own match identifier.
- Improve handling of large tournament max size values.

### Fixed
- Fix data returned by StreamUserList in JS runtime.
+120 −55
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import (
	"encoding/gob"
	"errors"
	"fmt"
	"github.com/golang/protobuf/ptypes/timestamp"
	"strconv"
	"strings"
	"time"
@@ -169,6 +170,7 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING`
			return nil
		}

		if leaderboard.HasMaxSize() {
			query = "UPDATE leaderboard SET size = size+1 WHERE id = $1 AND size < max_size"
			result, err = tx.ExecContext(ctx, query, tournamentId)
			if err != nil {
@@ -181,6 +183,7 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING`
				// Tournament is full.
				return runtime.ErrTournamentMaxSizeReached
			}
		}

		return nil
	}); err != nil {
@@ -196,12 +199,66 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING`
	return nil
}

func TournamentsGet(ctx context.Context, logger *zap.Logger, db *sql.DB, tournamentIDs []string) ([]*api.Tournament, error) {
func TournamentsGet(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderboardCache LeaderboardCache, tournamentIDs []string) ([]*api.Tournament, error) {
	now := time.Now().UTC()

	params := make([]interface{}, 0, len(tournamentIDs))
	statements := make([]string, 0, len(tournamentIDs))
	for i, tournamentID := range tournamentIDs {
	records := make([]*api.Tournament, 0, len(tournamentIDs))
	uniqueTournamentIDs := make(map[string]struct{}, len(tournamentIDs))
	dbLookupTournamentIDs := make([]string, 0, 1)
	for _, tournamentID := range tournamentIDs {
		if _, found := uniqueTournamentIDs[tournamentID]; found {
			continue
		}
		uniqueTournamentIDs[tournamentID] = struct{}{}

		tournament := leaderboardCache.Get(tournamentID)
		if tournament == nil || !tournament.IsTournament() {
			continue
		}
		if tournament.HasMaxSize() {
			dbLookupTournamentIDs = append(dbLookupTournamentIDs, tournamentID)
			continue
		}

		canEnter := true
		endTime := tournament.EndTime

		startActive, endActiveUnix, expiryUnix := calculateTournamentDeadlines(tournament.StartTime, endTime, int64(tournament.Duration), tournament.ResetSchedule, now)

		if startActive > now.Unix() || (endActiveUnix != 0 && endActiveUnix < now.Unix()) {
			canEnter = false
		}

		tournamentRecord := &api.Tournament{
			Id:          tournament.Id,
			Title:       tournament.Title,
			Description: tournament.Description,
			Category:    uint32(tournament.Category),
			SortOrder:   uint32(tournament.SortOrder),
			Size:        0,
			MaxSize:     uint32(tournament.MaxSize),
			MaxNumScore: uint32(tournament.MaxNumScore),
			CanEnter:    canEnter,
			EndActive:   uint32(endActiveUnix),
			NextReset:   uint32(expiryUnix),
			Metadata:    tournament.Metadata,
			CreateTime:  &timestamp.Timestamp{Seconds: tournament.CreateTime},
			StartTime:   &timestamp.Timestamp{Seconds: tournament.StartTime},
			Duration:    uint32(tournament.Duration),
			StartActive: uint32(startActive),
		}

		if endTime > 0 {
			tournamentRecord.EndTime = &timestamp.Timestamp{Seconds: endTime}
		}

		records = append(records, tournamentRecord)
	}

	if len(dbLookupTournamentIDs) > 0 {
		params := make([]interface{}, 0, len(dbLookupTournamentIDs))
		statements := make([]string, 0, len(dbLookupTournamentIDs))
		for i, tournamentID := range dbLookupTournamentIDs {
			params = append(params, tournamentID)
			statements = append(statements, fmt.Sprintf("$%v", i+1))
		}
@@ -217,7 +274,6 @@ WHERE id IN (` + strings.Join(statements, ",") + `)`
			return nil, err
		}

	records := make([]*api.Tournament, 0, len(tournamentIDs))
		for rows.Next() {
			tournament, err := parseTournament(rows, now)
			if err != nil {
@@ -234,6 +290,7 @@ WHERE id IN (` + strings.Join(statements, ",") + `)`
			records = append(records, tournament)
		}
		_ = rows.Close()
	}

	return records, nil
}
@@ -257,10 +314,18 @@ func TournamentList(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderb
	// Read most up to date sizes from database.
	statements := make([]string, 0, len(list))
	params := make([]interface{}, 0, len(list))
	for i, leaderboard := range list {
	var count int
	for _, leaderboard := range list {
		if !leaderboard.HasMaxSize() {
			continue
		}
		params = append(params, leaderboard.Id)
		statements = append(statements, "$"+strconv.Itoa(i+1))
		statements = append(statements, "$"+strconv.Itoa(count+1))
		count++
	}

	sizes := make(map[string]int, len(list))
	if len(statements) > 0 {
		query := "SELECT id, size FROM leaderboard WHERE id IN (" + strings.Join(statements, ",") + ")"
		rows, err := db.QueryContext(ctx, query, params...)
		if err != nil {
@@ -268,7 +333,6 @@ func TournamentList(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderb
			return nil, err
		}

	sizes := make(map[string]int, len(list))
		var dbID string
		var dbSize int
		for rows.Next() {
@@ -280,6 +344,7 @@ func TournamentList(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderb
			sizes[dbID] = dbSize
		}
		_ = rows.Close()
	}

	records := make([]*api.Tournament, 0, len(list))
	for _, leaderboard := range list {
@@ -290,7 +355,7 @@ func TournamentList(ctx context.Context, logger *zap.Logger, db *sql.DB, leaderb
		if startActive > nowUnix || endActiveUnix < nowUnix {
			canEnter = false
		}
		if canEnter && size >= leaderboard.MaxSize {
		if canEnter && (!leaderboard.HasMaxSize() || size >= leaderboard.MaxSize) {
			canEnter = false
		}

@@ -527,7 +592,7 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB,
				}

				// Check if we need to increment the tournament score count by checking if this was a newly inserted record.
				if dbNumScore <= 1 {
				if leaderboard.HasMaxSize() && dbNumScore <= 1 {
					res, err := tx.ExecContext(ctx, "UPDATE leaderboard SET size = size + 1 WHERE id = $1 AND (max_size = 0 OR size < max_size)", leaderboard.Id)
					if err != nil {
						logger.Error("Error updating tournament size", zap.Error(err))
+7 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import (
	"fmt"
	"github.com/jackc/pgconn"
	"log"
	"math"
	"sort"
	"strconv"
	"sync"
@@ -67,6 +68,9 @@ type Leaderboard struct {
func (l *Leaderboard) IsTournament() bool {
	return l.Duration != 0
}
func (l *Leaderboard) HasMaxSize() bool {
	return l.MaxSize != math.MaxInt32
}
func (l *Leaderboard) GetId() string {
	return l.Id
}
@@ -505,6 +509,9 @@ func (l *LocalLeaderboardCache) CreateTournament(ctx context.Context, id string,
		values += ", $" + strconv.Itoa(len(params))
	}

	if maxSize == 0 {
		maxSize = math.MaxInt32
	}
	if maxSize > 0 {
		params = append(params, maxSize)
		columns += ", max_size"
+1 −1
Original line number Diff line number Diff line
@@ -2546,7 +2546,7 @@ func (n *RuntimeGoNakamaModule) TournamentsGetId(ctx context.Context, tournament
		return []*api.Tournament{}, nil
	}

	return TournamentsGet(ctx, n.logger, n.db, tournamentIDs)
	return TournamentsGet(ctx, n.logger, n.db, n.leaderboardCache, tournamentIDs)
}

// @group tournaments
+1 −1
Original line number Diff line number Diff line
@@ -5670,7 +5670,7 @@ func (n *runtimeJavascriptNakamaModule) tournamentsGetId(r *goja.Runtime) func(g
			return r.ToValue(make([]interface{}, 0))
		}

		list, err := TournamentsGet(context.Background(), n.logger, n.db, tournmentIDs)
		list, err := TournamentsGet(context.Background(), n.logger, n.db, n.leaderboardCache, tournmentIDs)
		if err != nil {
			panic(r.NewGoError(fmt.Errorf("failed to get tournaments: %s", err.Error())))
		}
Loading