-
Notifications
You must be signed in to change notification settings - Fork 0
/
cacher.go
229 lines (198 loc) · 5.45 KB
/
cacher.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Copyright 2023 oyogames2023
//
// Licensed under the MIT License, you may not use this file except
// in compliance with the License. You may obtain a copy of the
// License at
//
// https://opensource.org/license/mit
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This code implementation is based on Google's Open Match framework
// with modifications inspired by the Zeus framework.
//
// reference to: https://github.com/googleforgames/open-match/interal/config
package zeus_config
import (
"sync"
"time"
)
// Cacher is used to cache the construction of an object, such as a connection.
// It will detect which config values are read when constructing the object.
// Then, when further requests are made, it will return the same object as long
// as the config values which were used haven't changed.
type Cacher struct {
cfg View
newInstance NewInstanceFunc
m sync.Mutex
r *viewChangeDetector
v interface{}
c func()
}
// NewInstanceFunc is used by the cacher to create a new value given the config.
// It may return an additional function to close or otherwise cleanup if
// ForceReset is called.
type NewInstanceFunc func(cfg View) (interface{}, func(), error)
// NewCacher returns a cacher which uses cfg to detect relevant changes, and
// newInstance to construct the object when nessisary. newInstance MUST use the
// provided View when constructing the object.
func NewCacher(cfg View, newInstance NewInstanceFunc) *Cacher {
return &Cacher{
cfg: cfg,
newInstance: newInstance,
}
}
// Get returns the cached object if possible, otherwise it calls newInstance to
// construct the new cached object. When Get is next called, it will detect if
// any of the configuration values which were used to construct the object have
// changed. If they have, the cache is invalidated, and a new object is
// constructed. If newInstance returns an error, Get returns that error and the
// object will not be cached or returned.
func (c *Cacher) Get() (interface{}, error) {
c.m.Lock()
defer c.m.Unlock()
if c.r == nil || c.r.hasChanges() {
c.locklessReset()
c.r = newViewChangeDetector(c.cfg)
var err error
c.v, c.c, err = c.newInstance(c.r)
if err != nil {
c.locklessReset()
return nil, err
}
}
return c.v, nil
}
// ForceReset causes Cacher to forget the cached object. The next call to Get
// will again use newInstance to create a new object.
func (c *Cacher) ForceReset() {
c.m.Lock()
defer c.m.Unlock()
c.locklessReset()
}
func (c *Cacher) locklessReset() {
if c.c != nil {
c.c()
}
c.c = nil
c.r = nil
c.v = nil
} // Remember each value as it is read, and can detect if a value has been changed
// since it was last read.
type viewChangeDetector struct {
cfg View
isSet map[string]bool
getString map[string]string
getInt map[string]int
getInt64 map[string]int64
getFloat64 map[string]float64
getStringSlice map[string][]string
getBool map[string]bool
getDuration map[string]time.Duration
}
func newViewChangeDetector(cfg View) *viewChangeDetector {
return &viewChangeDetector{
cfg: cfg,
isSet: make(map[string]bool),
getString: make(map[string]string),
getInt: make(map[string]int),
getInt64: make(map[string]int64),
getFloat64: make(map[string]float64),
getStringSlice: make(map[string][]string),
getBool: make(map[string]bool),
getDuration: make(map[string]time.Duration),
}
}
func (r *viewChangeDetector) IsSet(k string) bool {
v := r.cfg.IsSet(k)
r.isSet[k] = v
return v
}
func (r *viewChangeDetector) GetString(k string) string {
v := r.cfg.GetString(k)
r.getString[k] = v
return v
}
func (r *viewChangeDetector) GetInt(k string) int {
v := r.cfg.GetInt(k)
r.getInt[k] = v
return v
}
func (r *viewChangeDetector) GetInt64(k string) int64 {
v := r.cfg.GetInt64(k)
r.getInt64[k] = v
return v
}
func (r *viewChangeDetector) GetFloat64(k string) float64 {
v := r.cfg.GetFloat64(k)
r.getFloat64[k] = v
return v
}
func (r *viewChangeDetector) GetStringSlice(k string) []string {
v := r.cfg.GetStringSlice(k)
r.getStringSlice[k] = v
return v
}
func (r *viewChangeDetector) GetBool(k string) bool {
v := r.cfg.GetBool(k)
r.getBool[k] = v
return v
}
func (r *viewChangeDetector) GetDuration(k string) time.Duration {
v := r.cfg.GetDuration(k)
r.getDuration[k] = v
return v
}
func (r *viewChangeDetector) hasChanges() bool {
for k, v := range r.isSet {
if r.cfg.IsSet(k) != v {
return true
}
}
for k, v := range r.getString {
if r.cfg.GetString(k) != v {
return true
}
}
for k, v := range r.getInt {
if r.cfg.GetInt(k) != v {
return true
}
}
for k, v := range r.getInt64 {
if r.cfg.GetInt64(k) != v {
return true
}
}
for k, v := range r.getFloat64 {
if r.cfg.GetFloat64(k) != v {
return true
}
}
for k, v := range r.getStringSlice {
actual := r.cfg.GetStringSlice(k)
if len(actual) != len(v) {
return true
}
for i := range v {
if v[i] != actual[i] {
return true
}
}
}
for k, v := range r.getBool {
if r.cfg.GetBool(k) != v {
return true
}
}
for k, v := range r.getDuration {
if r.cfg.GetDuration(k) != v {
return true
}
}
return false
}