Loading CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading server/core_tournament.go +120 −55 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "encoding/gob" "errors" "fmt" "github.com/golang/protobuf/ptypes/timestamp" "strconv" "strings" "time" Loading Loading @@ -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 { Loading @@ -181,6 +183,7 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING` // Tournament is full. return runtime.ErrTournamentMaxSizeReached } } return nil }); err != nil { Loading @@ -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: ×tamp.Timestamp{Seconds: tournament.CreateTime}, StartTime: ×tamp.Timestamp{Seconds: tournament.StartTime}, Duration: uint32(tournament.Duration), StartActive: uint32(startActive), } if endTime > 0 { tournamentRecord.EndTime = ×tamp.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)) } Loading @@ -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 { Loading @@ -234,6 +290,7 @@ WHERE id IN (` + strings.Join(statements, ",") + `)` records = append(records, tournament) } _ = rows.Close() } return records, nil } Loading @@ -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 { Loading @@ -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() { Loading @@ -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 { Loading @@ -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 } Loading Loading @@ -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)) Loading server/leaderboard_cache.go +7 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "fmt" "github.com/jackc/pgconn" "log" "math" "sort" "strconv" "sync" Loading Loading @@ -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 } Loading Loading @@ -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" Loading server/runtime_go_nakama.go +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading server/runtime_javascript_nakama.go +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
CHANGELOG.md +1 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
server/core_tournament.go +120 −55 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "encoding/gob" "errors" "fmt" "github.com/golang/protobuf/ptypes/timestamp" "strconv" "strings" "time" Loading Loading @@ -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 { Loading @@ -181,6 +183,7 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING` // Tournament is full. return runtime.ErrTournamentMaxSizeReached } } return nil }); err != nil { Loading @@ -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: ×tamp.Timestamp{Seconds: tournament.CreateTime}, StartTime: ×tamp.Timestamp{Seconds: tournament.StartTime}, Duration: uint32(tournament.Duration), StartActive: uint32(startActive), } if endTime > 0 { tournamentRecord.EndTime = ×tamp.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)) } Loading @@ -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 { Loading @@ -234,6 +290,7 @@ WHERE id IN (` + strings.Join(statements, ",") + `)` records = append(records, tournament) } _ = rows.Close() } return records, nil } Loading @@ -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 { Loading @@ -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() { Loading @@ -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 { Loading @@ -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 } Loading Loading @@ -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)) Loading
server/leaderboard_cache.go +7 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "fmt" "github.com/jackc/pgconn" "log" "math" "sort" "strconv" "sync" Loading Loading @@ -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 } Loading Loading @@ -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" Loading
server/runtime_go_nakama.go +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
server/runtime_javascript_nakama.go +1 −1 Original line number Diff line number Diff line Loading @@ -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