lru.go 3.6 KB

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