@@ -2,11 +2,11 @@ package game
2
2
3
3
import (
4
4
"context"
5
- "sync"
6
5
"time"
7
6
8
7
"github.com/domino14/liwords/pkg/entity"
9
8
pb "github.com/domino14/liwords/rpc/api/proto/realtime"
9
+ lru "github.com/hashicorp/golang-lru"
10
10
"github.com/rs/zerolog/log"
11
11
)
12
12
@@ -21,12 +21,17 @@ type backingStore interface {
21
21
Disconnect ()
22
22
}
23
23
24
+ const (
25
+ // Assume every game takes up roughly 50KB in memory
26
+ // This is roughly 200 MB and allows for 4000 simultaneous games.
27
+ // We will want to increase this as we grow (as well as the size of our container)
28
+ CacheCap = 4000
29
+ )
30
+
24
31
// Cache will reside in-memory, and will be per-node. If we add more nodes
25
32
// we will need to make sure only the right nodes respond to game requests.
26
33
type Cache struct {
27
- sync.Mutex
28
-
29
- games map [string ]* entity.Game
34
+ cache * lru.Cache
30
35
activeGames []* pb.GameMeta
31
36
32
37
activeGamesTTL time.Duration
@@ -36,9 +41,17 @@ type Cache struct {
36
41
}
37
42
38
43
func NewCache (backing backingStore ) * Cache {
44
+
45
+ lrucache , err := lru .New (CacheCap )
46
+ if err != nil {
47
+ panic (err )
48
+ }
49
+
39
50
return & Cache {
40
51
backing : backing ,
41
- games : make (map [string ]* entity.Game ),
52
+ cache : lrucache ,
53
+
54
+ // games: make(map[string]*entity.Game),
42
55
// Have a non-trivial TTL for the cache of active games.
43
56
// XXX: This might act poorly if the following happens within the TTL:
44
57
// - active games gets cached
@@ -54,38 +67,29 @@ func NewCache(backing backingStore) *Cache {
54
67
}
55
68
}
56
69
57
- // Exists lets us know whether the game is in the cache
58
- func (c * Cache ) Exists (ctx context.Context , id string ) bool {
59
- _ , ok := c .games [id ]
60
- return ok
61
- }
62
-
63
70
// Unload unloads the game from the cache
64
71
func (c * Cache ) Unload (ctx context.Context , id string ) {
65
- delete ( c . games , id )
72
+ c . cache . Remove ( id )
66
73
}
67
74
68
75
// SetGameEventChan sets the game event channel to the passed in channel.
69
76
func (c * Cache ) SetGameEventChan (ch chan <- * entity.EventWrapper ) {
70
77
c .backing .SetGameEventChan (ch )
71
78
}
72
79
73
- // Get gets a game from the cache. It doesn't try to get it from the backing
74
- // store, if it can't find it in the cache.
80
+ // Get gets a game from the cache.. it loads it into the cache if it's not there.
75
81
func (c * Cache ) Get (ctx context.Context , id string ) (* entity.Game , error ) {
76
- g , ok := c .games [id ]
77
- if ! ok {
78
-
79
- g , err := c .backing .Get (ctx , id )
80
- if err != nil {
81
- return nil , err
82
- }
83
- c .Lock ()
84
- defer c .Unlock ()
85
- c .games [id ] = g
86
- return g , nil
82
+ g , ok := c .cache .Get (id )
83
+ if ok && g != nil {
84
+ return g .(* entity.Game ), nil
87
85
}
88
- return g , nil
86
+ log .Info ().Str ("gameid" , id ).Msg ("not-in-cache" )
87
+ uncachedGame , err := c .backing .Get (ctx , id )
88
+ if err == nil {
89
+ c .cache .Add (id , uncachedGame )
90
+ }
91
+ return uncachedGame , err
92
+
89
93
}
90
94
91
95
// Set sets a game in the cache, AND in the backing store. This ensures if the
@@ -113,9 +117,7 @@ func (c *Cache) setOrCreate(ctx context.Context, game *entity.Game, isNew bool)
113
117
if err != nil {
114
118
return err
115
119
}
116
- c .Lock ()
117
- defer c .Unlock ()
118
- c .games [gameID ] = game
120
+ c .cache .Add (gameID , game )
119
121
return nil
120
122
}
121
123
0 commit comments