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
|
package structr
import (
"strings"
)
// IndexConfig defines config variables
// for initializing a struct index.
type IndexConfig struct {
// Fields should contain a comma-separated
// list of struct fields used when generating
// keys for this index. Nested fields should
// be specified using periods. An example:
// "Username,Favorites.Color"
Fields string
// Multiple indicates whether to accept multiple
// possible values for any single index key. The
// default behaviour is to only accept one value
// and overwrite existing on any write operation.
Multiple bool
// AllowZero indicates whether to accept zero
// value fields in index keys. i.e. whether to
// index structs for this set of field values
// IF any one of those field values is the zero
// value for that type. The default behaviour
// is to skip indexing structs for this lookup
// when any of the indexing fields are zero.
AllowZero bool
}
// Index is an exposed Cache internal model, used to
// generate keys and store struct results by the init
// defined key generation configuration. This model is
// exposed to provide faster lookups in the case that
// you would like to manually provide the used index
// via the Cache.___By() series of functions, or access
// the underlying index key generator.
type Index[StructType any] struct {
// name is the actual name of this
// index, which is the unparsed
// string value of contained fields.
name string
// struct field key serializer.
keygen KeyGen[StructType]
// backing in-memory data store of
// generated index keys to result lists.
data map[string]*list[*result[StructType]]
// whether to allow
// multiple results
// per index key.
unique bool
}
// init initializes this index with the given configuration.
func (i *Index[T]) init(config IndexConfig) {
fields := strings.Split(config.Fields, ",")
i.name = config.Fields
i.keygen = NewKeyGen[T](fields, config.AllowZero)
i.unique = !config.Multiple
i.data = make(map[string]*list[*result[T]])
}
// KeyGen returns the key generator associated with this index.
func (i *Index[T]) KeyGen() *KeyGen[T] {
return &i.keygen
}
func index_append[T any](c *Cache[T], i *Index[T], key string, res *result[T]) {
// Acquire + setup indexkey.
ikey := indexkey_acquire(c)
ikey.entry.Value = res
ikey.key = key
ikey.index = i
// Append to result's indexkeys.
res.keys = append(res.keys, ikey)
// Get list at key.
l := i.data[key]
if l == nil {
// Allocate new list.
l = list_acquire(c)
i.data[key] = l
} else if i.unique {
// Remove currently
// indexed result.
old := l.head
l.remove(old)
// Get ptr to old
// result before we
// release to pool.
res := old.Value
// Drop this index's key from
// old res now not indexed here.
result_dropIndex(c, res, i)
if len(res.keys) == 0 {
// Old res now unused,
// release to mem pool.
result_release(c, res)
}
}
// Add result indexkey to
// front of results list.
l.pushFront(&ikey.entry)
}
func index_deleteOne[T any](c *Cache[T], i *Index[T], ikey *indexkey[T]) {
// Get list at key.
l := i.data[ikey.key]
if l == nil {
return
}
// Remove from list.
l.remove(&ikey.entry)
if l.len == 0 {
// Remove list from map.
delete(i.data, ikey.key)
// Release list to pool.
list_release(c, l)
}
}
func index_delete[T any](c *Cache[T], i *Index[T], key string, fn func(*result[T])) {
if fn == nil {
panic("nil fn")
}
// Get list at key.
l := i.data[key]
if l == nil {
return
}
// Delete data at key.
delete(i.data, key)
// Iterate results in list.
for x := 0; x < l.len; x++ {
// Pop current head.
res := l.head.Value
l.remove(l.head)
// Delete index's key
// from result tracking.
result_dropIndex(c, res, i)
// Call hook.
fn(res)
}
// Release list to pool.
list_release(c, l)
}
type indexkey[T any] struct {
// linked list entry the related
// result is stored under in the
// Index.data[key] linked list.
entry elem[*result[T]]
// key is the generated index key
// the related result is indexed
// under, in the below index.
key string
// index is the index that the
// related result is indexed in.
index *Index[T]
}
func indexkey_acquire[T any](c *Cache[T]) *indexkey[T] {
var ikey *indexkey[T]
if len(c.keyPool) == 0 {
// Allocate new key.
ikey = new(indexkey[T])
} else {
// Pop result from pool slice.
ikey = c.keyPool[len(c.keyPool)-1]
c.keyPool = c.keyPool[:len(c.keyPool)-1]
}
return ikey
}
func indexkey_release[T any](c *Cache[T], ikey *indexkey[T]) {
// Reset indexkey.
ikey.entry.Value = nil
ikey.key = ""
ikey.index = nil
// Release indexkey to memory pool.
c.keyPool = append(c.keyPool, ikey)
}
|