github.com/utopiagio/gio@v0.0.8/text/lru.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package text 4 5 import ( 6 "image" 7 "sync/atomic" 8 9 giofont "github.com/utopiagio/gio/font" 10 "github.com/utopiagio/gio/io/system" 11 "github.com/utopiagio/gio/op" 12 "github.com/utopiagio/gio/op/clip" 13 "github.com/utopiagio/gio/op/paint" 14 "golang.org/x/image/math/fixed" 15 ) 16 17 // entry holds a single key-value pair for an LRU cache. 18 type entry[K comparable, V any] struct { 19 next, prev *entry[K, V] 20 key K 21 v V 22 } 23 24 // lru is a generic least-recently-used cache. 25 type lru[K comparable, V any] struct { 26 m map[K]*entry[K, V] 27 head, tail *entry[K, V] 28 } 29 30 // Get fetches the value associated with the given key, if any. 31 func (l *lru[K, V]) Get(k K) (V, bool) { 32 if lt, ok := l.m[k]; ok { 33 l.remove(lt) 34 l.insert(lt) 35 return lt.v, true 36 } 37 var v V 38 return v, false 39 } 40 41 // Put inserts the given value with the given key, evicting old 42 // cache entries if necessary. 43 func (l *lru[K, V]) Put(k K, v V) { 44 if l.m == nil { 45 l.m = make(map[K]*entry[K, V]) 46 l.head = new(entry[K, V]) 47 l.tail = new(entry[K, V]) 48 l.head.prev = l.tail 49 l.tail.next = l.head 50 } 51 val := &entry[K, V]{key: k, v: v} 52 l.m[k] = val 53 l.insert(val) 54 if len(l.m) > maxSize { 55 oldest := l.tail.next 56 l.remove(oldest) 57 delete(l.m, oldest.key) 58 } 59 } 60 61 // remove cuts e out of the lru linked list. 62 func (l *lru[K, V]) remove(e *entry[K, V]) { 63 e.next.prev = e.prev 64 e.prev.next = e.next 65 } 66 67 // insert adds e to the lru linked list. 68 func (l *lru[K, V]) insert(e *entry[K, V]) { 69 e.next = l.head 70 e.prev = l.head.prev 71 e.prev.next = e 72 e.next.prev = e 73 } 74 75 type bitmapCache = lru[GlyphID, bitmap] 76 77 type bitmap struct { 78 img paint.ImageOp 79 size image.Point 80 } 81 82 type layoutCache = lru[layoutKey, document] 83 84 type glyphValue[V any] struct { 85 v V 86 glyphs []glyphInfo 87 } 88 89 type glyphLRU[V any] struct { 90 seed uint64 91 cache lru[uint64, glyphValue[V]] 92 } 93 94 var seed uint32 95 96 // hashGlyphs computes a hash key based on the ID and X offset of 97 // every glyph in the slice. 98 func (c *glyphLRU[V]) hashGlyphs(gs []Glyph) uint64 { 99 if c.seed == 0 { 100 c.seed = uint64(atomic.AddUint32(&seed, 3900798947)) 101 } 102 if len(gs) == 0 { 103 return 0 104 } 105 106 h := c.seed 107 firstX := gs[0].X 108 for _, g := range gs { 109 h += uint64(g.X - firstX) 110 h *= 6585573582091643 111 h += uint64(g.ID) 112 h *= 3650802748644053 113 } 114 115 return h 116 } 117 118 func (c *glyphLRU[V]) Get(key uint64, gs []Glyph) (V, bool) { 119 if v, ok := c.cache.Get(key); ok && gidsEqual(v.glyphs, gs) { 120 return v.v, true 121 } 122 var v V 123 return v, false 124 } 125 126 func (c *glyphLRU[V]) Put(key uint64, glyphs []Glyph, v V) { 127 gids := make([]glyphInfo, len(glyphs)) 128 firstX := fixed.I(0) 129 for i, glyph := range glyphs { 130 if i == 0 { 131 firstX = glyph.X 132 } 133 // Cache glyph X offsets relative to the first glyph. 134 gids[i] = glyphInfo{ID: glyph.ID, X: glyph.X - firstX} 135 } 136 val := glyphValue[V]{ 137 glyphs: gids, 138 v: v, 139 } 140 c.cache.Put(key, val) 141 } 142 143 type pathCache = glyphLRU[clip.PathSpec] 144 145 type bitmapShapeCache = glyphLRU[op.CallOp] 146 147 type glyphInfo struct { 148 ID GlyphID 149 X fixed.Int26_6 150 } 151 152 type layoutKey struct { 153 ppem fixed.Int26_6 154 maxWidth, minWidth int 155 maxLines int 156 str string 157 truncator string 158 locale system.Locale 159 font giofont.Font 160 forceTruncate bool 161 wrapPolicy WrapPolicy 162 lineHeight fixed.Int26_6 163 lineHeightScale float32 164 } 165 166 const maxSize = 1000 167 168 func gidsEqual(a []glyphInfo, glyphs []Glyph) bool { 169 if len(a) != len(glyphs) { 170 return false 171 } 172 firstX := fixed.Int26_6(0) 173 for i := range a { 174 if i == 0 { 175 firstX = glyphs[i].X 176 } 177 // Cache glyph X offsets relative to the first glyph. 178 if a[i].ID != glyphs[i].ID || a[i].X != (glyphs[i].X-firstX) { 179 return false 180 } 181 } 182 return true 183 }