text/v2: bug fix: StdFace was never released

StdFace was used as a cache key, then these are never released.

This change fixes this issue by adding faceCacheKey and use it as
a cache key.

Updates #498
This commit is contained in:
Hajime Hoshi 2023-11-12 18:46:22 +09:00
parent 77f3b57d1f
commit 3f180b2165
3 changed files with 30 additions and 10 deletions

View File

@ -52,7 +52,7 @@ type glyphImageCacheEntry struct {
}
type glyphImageCache struct {
cache map[Face]map[glyphImageCacheKey]*glyphImageCacheEntry
cache map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry
m sync.Mutex
}
@ -62,17 +62,18 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
g.m.Lock()
defer g.m.Unlock()
e, ok := g.cache[face][key]
e, ok := g.cache[face.faceCacheKey()][key]
if ok {
e.atime = now()
return e.image
}
if g.cache == nil {
g.cache = map[Face]map[glyphImageCacheKey]*glyphImageCacheEntry{}
g.cache = map[faceCacheKey]map[glyphImageCacheKey]*glyphImageCacheEntry{}
}
if g.cache[face] == nil {
g.cache[face] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
faceCacheKey := face.faceCacheKey()
if g.cache[faceCacheKey] == nil {
g.cache[faceCacheKey] = map[glyphImageCacheKey]*glyphImageCacheEntry{}
}
img := create()
@ -86,7 +87,7 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
// Keep this until the face is GCed.
e.atime = infTime
}
g.cache[face][key] = e
g.cache[faceCacheKey][key] = e
// Clean up old entries.
@ -95,13 +96,13 @@ func (g *glyphImageCache) getOrCreate(face Face, key glyphImageCacheKey, create
// Even after cleaning up the cache, the number of glyphs might still exceed the soft limit, but
// this is fine.
const cacheSoftLimit = 512
if len(g.cache[face]) > cacheSoftLimit {
for key, e := range g.cache[face] {
if len(g.cache[faceCacheKey]) > cacheSoftLimit {
for key, e := range g.cache[faceCacheKey] {
// 60 is an arbitrary number.
if e.atime >= now()-60 {
continue
}
delete(g.cache[face], key)
delete(g.cache[faceCacheKey], key)
}
}
@ -113,5 +114,5 @@ func (g *glyphImageCache) clear(face Face) {
g.m.Lock()
defer g.m.Unlock()
delete(g.cache, face)
delete(g.cache, face.faceCacheKey())
}

View File

@ -17,6 +17,7 @@ package text
import (
"image"
"runtime"
"sync/atomic"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
@ -24,6 +25,12 @@ import (
"github.com/hajimehoshi/ebiten/v2"
)
var currentStdFaceID uint64
func nextStdFaceID() uint64 {
return atomic.AddUint64(&currentStdFaceID, 1)
}
var _ Face = (*StdFace)(nil)
// StdFace is a Face implementation for a semi-standard font.Face (golang.org/x/image/font).
@ -32,6 +39,8 @@ var _ Face = (*StdFace)(nil)
type StdFace struct {
f *faceWithCache
id uint64
addr *StdFace
}
@ -41,6 +50,7 @@ func NewStdFace(face font.Face) *StdFace {
f: &faceWithCache{
f: face,
},
id: nextStdFaceID(),
}
s.addr = s
runtime.SetFinalizer(s, theGlyphImageCache.clear)
@ -71,6 +81,11 @@ func (s *StdFace) UnsafeInternal() any {
return s.f.f
}
// faceCacheKey implements Face.
func (s *StdFace) faceCacheKey() faceCacheKey {
return faceCacheKey(s.id)
}
// advance implements Face.
func (s *StdFace) advance(text string) float64 {
return fixed26_6ToFloat64(font.MeasureString(s.f, text))

View File

@ -26,6 +26,8 @@ import (
"github.com/hajimehoshi/ebiten/v2"
)
type faceCacheKey uint64
// Face is an interface representing a font face. The implementations are only GoTextFace and StdFace.
type Face interface {
// Metrics returns the metrics for this Face.
@ -36,6 +38,8 @@ type Face interface {
// This is unsafe since this might make internal cache states out of sync.
UnsafeInternal() any
faceCacheKey() faceCacheKey
advance(text string) float64
appendGlyphs(glyphs []Glyph, text string, originX, originY float64) []Glyph