| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- package cache
- import (
- "os"
- "sync/atomic"
- "time"
- "fyne.io/fyne/v2"
- )
- var (
- cacheDuration = 1 * time.Minute
- cleanTaskInterval = cacheDuration / 2
- expiredObjects = make([]fyne.CanvasObject, 0, 50)
- lastClean time.Time
- skippedCleanWithCanvasRefresh = false
- // testing purpose only
- timeNow func() time.Time = time.Now
- )
- func init() {
- if t, err := time.ParseDuration(os.Getenv("FYNE_CACHE")); err == nil {
- cacheDuration = t
- cleanTaskInterval = cacheDuration / 2
- }
- }
- // Clean run cache clean task, it should be called on paint events.
- func Clean(canvasRefreshed bool) {
- now := timeNow()
- // do not run clean task too fast
- if now.Sub(lastClean) < 10*time.Second {
- if canvasRefreshed {
- skippedCleanWithCanvasRefresh = true
- }
- return
- }
- if skippedCleanWithCanvasRefresh {
- skippedCleanWithCanvasRefresh = false
- canvasRefreshed = true
- }
- if !canvasRefreshed && now.Sub(lastClean) < cleanTaskInterval {
- return
- }
- destroyExpiredSvgs(now)
- destroyExpiredFontMetrics(now)
- if canvasRefreshed {
- // Destroy renderers on canvas refresh to avoid flickering screen.
- destroyExpiredRenderers(now)
- // canvases cache should be invalidated only on canvas refresh, otherwise there wouldn't
- // be a way to recover them later
- destroyExpiredCanvases(now)
- }
- lastClean = timeNow()
- }
- // CleanCanvas performs a complete remove of all the objects that belong to the specified
- // canvas. Usually used to free all objects from a closing windows.
- func CleanCanvas(canvas fyne.Canvas) {
- deletingObjs := make([]fyne.CanvasObject, 0, 50)
- canvasesLock.RLock()
- for obj, cinfo := range canvases {
- if cinfo.canvas == canvas {
- deletingObjs = append(deletingObjs, obj)
- }
- }
- canvasesLock.RUnlock()
- if len(deletingObjs) == 0 {
- return
- }
- canvasesLock.Lock()
- for _, dobj := range deletingObjs {
- delete(canvases, dobj)
- }
- canvasesLock.Unlock()
- renderersLock.Lock()
- for _, dobj := range deletingObjs {
- wid, ok := dobj.(fyne.Widget)
- if !ok {
- continue
- }
- winfo, ok := renderers[wid]
- if !ok {
- continue
- }
- winfo.renderer.Destroy()
- delete(renderers, wid)
- }
- renderersLock.Unlock()
- }
- // CleanCanvases runs cache clean tasks for canvases that are being refreshed. This is called on paint events.
- func CleanCanvases(refreshingCanvases []fyne.Canvas) {
- now := timeNow()
- // do not run clean task too fast
- if now.Sub(lastClean) < 10*time.Second {
- return
- }
- if now.Sub(lastClean) < cleanTaskInterval {
- return
- }
- destroyExpiredSvgs(now)
- deletingObjs := make([]fyne.CanvasObject, 0, 50)
- canvasesLock.RLock()
- for obj, cinfo := range canvases {
- if cinfo.isExpired(now) && matchesACanvas(cinfo, refreshingCanvases) {
- deletingObjs = append(deletingObjs, obj)
- }
- }
- canvasesLock.RUnlock()
- if len(deletingObjs) == 0 {
- return
- }
- canvasesLock.Lock()
- for _, dobj := range deletingObjs {
- delete(canvases, dobj)
- }
- canvasesLock.Unlock()
- renderersLock.Lock()
- for _, dobj := range deletingObjs {
- wid, ok := dobj.(fyne.Widget)
- if !ok {
- continue
- }
- rinfo, ok := renderers[wid]
- if !ok {
- continue
- }
- if rinfo.isExpired(now) {
- rinfo.renderer.Destroy()
- delete(renderers, wid)
- }
- }
- renderersLock.Unlock()
- lastClean = timeNow()
- }
- // ResetThemeCaches clears all the svg and text size cache maps
- func ResetThemeCaches() {
- svgs.Range(func(key, value interface{}) bool {
- svgs.Delete(key)
- return true
- })
- fontSizeLock.Lock()
- fontSizeCache = map[fontSizeEntry]fontMetric{}
- fontSizeLock.Unlock()
- }
- // destroyExpiredCanvases deletes objects from the canvases cache.
- func destroyExpiredCanvases(now time.Time) {
- expiredObjects = expiredObjects[:0]
- canvasesLock.RLock()
- for obj, cinfo := range canvases {
- if cinfo.isExpired(now) {
- expiredObjects = append(expiredObjects, obj)
- }
- }
- canvasesLock.RUnlock()
- if len(expiredObjects) > 0 {
- canvasesLock.Lock()
- for i, exp := range expiredObjects {
- delete(canvases, exp)
- expiredObjects[i] = nil
- }
- canvasesLock.Unlock()
- }
- }
- // destroyExpiredRenderers deletes the renderer from the cache and calls
- // renderer.Destroy()
- func destroyExpiredRenderers(now time.Time) {
- expiredObjects = expiredObjects[:0]
- renderersLock.RLock()
- for wid, rinfo := range renderers {
- if rinfo.isExpired(now) {
- rinfo.renderer.Destroy()
- expiredObjects = append(expiredObjects, wid)
- }
- }
- renderersLock.RUnlock()
- if len(expiredObjects) > 0 {
- renderersLock.Lock()
- for i, exp := range expiredObjects {
- delete(renderers, exp.(fyne.Widget))
- expiredObjects[i] = nil
- }
- renderersLock.Unlock()
- }
- }
- // matchesACanvas returns true if the canvas represented by the canvasInfo object matches one of
- // the canvases passed in in 'canvases', otherwise false is returned.
- func matchesACanvas(cinfo *canvasInfo, canvases []fyne.Canvas) bool {
- canvas := cinfo.canvas
- for _, obj := range canvases {
- if obj == canvas {
- return true
- }
- }
- return false
- }
- type expiringCache struct {
- expires atomic.Value // time.Time
- }
- // isExpired check if the cache data is expired.
- func (c *expiringCache) isExpired(now time.Time) bool {
- t := c.expires.Load()
- if t == nil {
- return (time.Time{}).Before(now)
- }
- return t.(time.Time).Before(now)
- }
- // setAlive updates expiration time.
- func (c *expiringCache) setAlive() {
- c.expires.Store(timeNow().Add(cacheDuration))
- }
- type expiringCacheNoLock struct {
- expires time.Time
- }
- // isExpired check if the cache data is expired.
- func (c *expiringCacheNoLock) isExpired(now time.Time) bool {
- return c.expires.Before(now)
- }
- // setAlive updates expiration time.
- func (c *expiringCacheNoLock) setAlive() {
- t := timeNow().Add(cacheDuration)
- c.expires = t
- }
|