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

View File

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

View File

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