gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/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 "gioui.org/font"
    10  	"gioui.org/io/system"
    11  	"gioui.org/op"
    12  	"gioui.org/op/clip"
    13  	"gioui.org/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  }