-
Notifications
You must be signed in to change notification settings - Fork 134
/
lru.go
183 lines (157 loc) · 3.58 KB
/
lru.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
// SPDX-License-Identifier: Unlicense OR MIT
package text
import (
"image"
"sync/atomic"
giofont "gioui.org/font"
"gioui.org/io/system"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"golang.org/x/image/math/fixed"
)
// entry holds a single key-value pair for an LRU cache.
type entry[K comparable, V any] struct {
next, prev *entry[K, V]
key K
v V
}
// lru is a generic least-recently-used cache.
type lru[K comparable, V any] struct {
m map[K]*entry[K, V]
head, tail *entry[K, V]
}
// Get fetches the value associated with the given key, if any.
func (l *lru[K, V]) Get(k K) (V, bool) {
if lt, ok := l.m[k]; ok {
l.remove(lt)
l.insert(lt)
return lt.v, true
}
var v V
return v, false
}
// Put inserts the given value with the given key, evicting old
// cache entries if necessary.
func (l *lru[K, V]) Put(k K, v V) {
if l.m == nil {
l.m = make(map[K]*entry[K, V])
l.head = new(entry[K, V])
l.tail = new(entry[K, V])
l.head.prev = l.tail
l.tail.next = l.head
}
val := &entry[K, V]{key: k, v: v}
l.m[k] = val
l.insert(val)
if len(l.m) > maxSize {
oldest := l.tail.next
l.remove(oldest)
delete(l.m, oldest.key)
}
}
// remove cuts e out of the lru linked list.
func (l *lru[K, V]) remove(e *entry[K, V]) {
e.next.prev = e.prev
e.prev.next = e.next
}
// insert adds e to the lru linked list.
func (l *lru[K, V]) insert(e *entry[K, V]) {
e.next = l.head
e.prev = l.head.prev
e.prev.next = e
e.next.prev = e
}
type bitmapCache = lru[GlyphID, bitmap]
type bitmap struct {
img paint.ImageOp
size image.Point
}
type layoutCache = lru[layoutKey, document]
type glyphValue[V any] struct {
v V
glyphs []glyphInfo
}
type glyphLRU[V any] struct {
seed uint64
cache lru[uint64, glyphValue[V]]
}
var seed uint32
// hashGlyphs computes a hash key based on the ID and X offset of
// every glyph in the slice.
func (c *glyphLRU[V]) hashGlyphs(gs []Glyph) uint64 {
if c.seed == 0 {
c.seed = uint64(atomic.AddUint32(&seed, 3900798947))
}
if len(gs) == 0 {
return 0
}
h := c.seed
firstX := gs[0].X
for _, g := range gs {
h += uint64(g.X - firstX)
h *= 6585573582091643
h += uint64(g.ID)
h *= 3650802748644053
}
return h
}
func (c *glyphLRU[V]) Get(key uint64, gs []Glyph) (V, bool) {
if v, ok := c.cache.Get(key); ok && gidsEqual(v.glyphs, gs) {
return v.v, true
}
var v V
return v, false
}
func (c *glyphLRU[V]) Put(key uint64, glyphs []Glyph, v V) {
gids := make([]glyphInfo, len(glyphs))
firstX := fixed.I(0)
for i, glyph := range glyphs {
if i == 0 {
firstX = glyph.X
}
// Cache glyph X offsets relative to the first glyph.
gids[i] = glyphInfo{ID: glyph.ID, X: glyph.X - firstX}
}
val := glyphValue[V]{
glyphs: gids,
v: v,
}
c.cache.Put(key, val)
}
type pathCache = glyphLRU[clip.PathSpec]
type bitmapShapeCache = glyphLRU[op.CallOp]
type glyphInfo struct {
ID GlyphID
X fixed.Int26_6
}
type layoutKey struct {
ppem fixed.Int26_6
maxWidth, minWidth int
maxLines int
str string
truncator string
locale system.Locale
font giofont.Font
forceTruncate bool
wrapPolicy WrapPolicy
lineHeight fixed.Int26_6
lineHeightScale float32
}
const maxSize = 1000
func gidsEqual(a []glyphInfo, glyphs []Glyph) bool {
if len(a) != len(glyphs) {
return false
}
firstX := fixed.Int26_6(0)
for i := range a {
if i == 0 {
firstX = glyphs[i].X
}
// Cache glyph X offsets relative to the first glyph.
if a[i].ID != glyphs[i].ID || a[i].X != (glyphs[i].X-firstX) {
return false
}
}
return true
}