-
Notifications
You must be signed in to change notification settings - Fork 16
/
leaderboard.go
130 lines (118 loc) · 2.77 KB
/
leaderboard.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package main
import (
"context"
"fmt"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/jinzhu/copier"
)
type Leaderboard struct {
lock *sync.RWMutex
collector *StatsCollector
players Players
sorted []*Player
ready int32
cancel func()
C chan struct{}
}
func NewLeaderboard(eventDate, playerFile string) *Leaderboard {
lb := &Leaderboard{
lock: new(sync.RWMutex),
collector: NewStatsCollector(eventDate, playerFile),
players: Players{},
sorted: []*Player{},
ready: 0,
C: make(chan struct{}),
}
var ctx context.Context
ctx, lb.cancel = context.WithCancel(context.Background())
lb.start(ctx)
return lb
}
func (lb *Leaderboard) Shutdown() {
lb.cancel()
<-lb.C
}
func (lb *Leaderboard) Ready() bool {
return atomic.LoadInt32(&lb.ready) > 0
}
func (lb *Leaderboard) PlayersSorted() ([]*Player, error) {
lb.lock.RLock()
defer lb.lock.RUnlock()
pls := []*Player{}
return pls, copier.Copy(&pls, lb.sorted)
}
func (lb *Leaderboard) Player(username string) (*Player, error) {
lb.lock.RLock()
p, ok := lb.players[username]
if !ok {
lb.lock.RUnlock()
return nil, fmt.Errorf("User \"%s\" not found", username)
}
lb.lock.RUnlock()
res := new(Player)
return res, copier.Copy(res, p)
}
func (lb *Leaderboard) PlayerNames() []string {
names := make([]string, 0, len(lb.players))
lb.lock.RLock()
for k := range lb.players {
names = append(names, k)
}
lb.lock.RUnlock()
return names
}
func (lb *Leaderboard) start(ctx context.Context) {
fmt.Println("Starting background stats collector")
go func() {
defer close(lb.C)
for {
lb.update(ctx)
t := time.NewTimer(COLLECT_PERIOD)
select {
case <-ctx.Done():
fmt.Println("Background stats collector shutted down")
if !t.Stop() {
<-t.C
}
return
case <-t.C: // NOP
}
}
}()
}
func (lb *Leaderboard) update(ctx context.Context) {
fmt.Println("Collecting data")
start := time.Now()
players, err := lb.collector.Players(ctx)
if err != nil && err != context.Canceled {
fmt.Println("[ERROR]", err)
} else if err != context.Canceled {
duration := time.Since(start)
fmt.Println("Collection completed in", duration.String())
lb.lock.Lock()
lb.players = players
lb.sort()
lb.lock.Unlock()
atomic.StoreInt32(&lb.ready, 1)
}
}
func (lb *Leaderboard) sort() {
players := make([]*Player, 0, len(lb.players))
for _, p := range lb.players {
players = append(players, p)
}
sort.Slice(players, func(i, j int) bool {
ci := players[i].ContributionCount()
cj := players[j].ContributionCount()
if ci != cj {
return ci > cj
} else if len(players[i].Merged) == len(players[j].Merged) {
return players[i].LastMergeAt.Before(players[j].LastMergeAt)
}
return len(players[i].Merged) > len(players[j].Merged)
})
lb.sorted = players
}