text/cache/shaping.go
Functions
func Capacity
func (c *ShapingCache) Capacity() int {
return c.capacity
}
func Clear
Clear removes all entries from the cache.
func (c *ShapingCache) Clear() {
for _, shard := range c.shards {
shard.mu.Lock()
shard.entries = make(map[ShapingKey]*cacheEntry)
shard.lru.Clear()
shard.mu.Unlock()
}
}
func DefaultShapingCache
DefaultShapingCache creates a shaping cache with default configuration.
Total capacity: 16 shards * 256 entries = 4096 entries.
func DefaultShapingCache() *ShapingCache {
return NewShapingCache(DefaultCapacity)
}
func Delete
Delete removes an entry from the cache.
Returns true if the entry was found and removed.
func (c *ShapingCache) Delete(key ShapingKey) bool {
shard := c.getShard(&key)
shard.mu.Lock()
defer shard.mu.Unlock()
entry, ok := shard.entries[key]
if !ok {
return false
}
shard.lru.Remove(entry.node)
delete(shard.entries, key)
return true
}
func Get
Get retrieves a cached ShapedRun by key.
Returns (value, true) if found, (nil, false) otherwise.
On cache hit, the entry is moved to the front of the LRU list.
This operation is thread-safe and optimized for minimal lock contention.
func (c *ShapingCache) Get(key ShapingKey) (*text.ShapedRun, bool) {
shard := c.getShard(&key)
// Fast path: read lock to check existence
shard.mu.RLock()
_, exists := shard.entries[key]
shard.mu.RUnlock()
if !exists {
c.misses.Add(1)
return nil, false
}
// Slow path: write lock for LRU update
shard.mu.Lock()
// Re-check after acquiring write lock (entry may have been evicted)
entry, ok := shard.entries[key]
if !ok {
shard.mu.Unlock()
c.misses.Add(1)
return nil, false
}
shard.lru.MoveToFront(entry.node)
value := entry.value
shard.mu.Unlock()
c.hits.Add(1)
return value, true
}
func GetOrCreate
GetOrCreate returns a cached ShapedRun or creates it using the provided function.
This is the preferred method for cache access as it prevents duplicate computation.
The create function is called with the shard lock held to prevent thundering herd.
Keep the create function fast to minimize lock contention.
func (c *ShapingCache) GetOrCreate(key ShapingKey, create func() *text.ShapedRun) *text.ShapedRun {
shard := c.getShard(&key)
// Fast path: read lock to check existence
shard.mu.RLock()
_, exists := shard.entries[key]
shard.mu.RUnlock()
if exists {
// Update LRU (requires write lock)
shard.mu.Lock()
if entry, ok := shard.entries[key]; ok {
shard.lru.MoveToFront(entry.node)
value := entry.value
shard.mu.Unlock()
c.hits.Add(1)
return value
}
shard.mu.Unlock()
}
// Slow path: create new entry
shard.mu.Lock()
defer shard.mu.Unlock()
// Re-check after acquiring write lock
if entry, ok := shard.entries[key]; ok {
shard.lru.MoveToFront(entry.node)
c.hits.Add(1)
return entry.value
}
c.misses.Add(1)
// Create new value (under lock)
value := create()
if value == nil {
return nil
}
// Evict if at capacity
for shard.lru.Len() >= c.capacity {
if oldest, ok := shard.lru.RemoveOldest(); ok {
delete(shard.entries, oldest)
c.evictions.Add(1)
} else {
break
}
}
// Add new entry
node := shard.lru.PushFront(key)
shard.entries[key] = &cacheEntry{
value: value,
node: node,
}
return value
}
func HashFeatures
HashFeatures computes a hash of OpenType feature settings.
This is a helper function for creating ShapingKey.
Features should be passed as tag/value pairs, e.g.:
HashFeatures(map[string]int{"liga": 1, "kern": 1})
func HashFeatures(features map[string]int) uint64 {
if len(features) == 0 {
return 0
}
h := fnv.New64a()
// Sort-independent hashing: XOR individual feature hashes
var result uint64
for tag, val := range features {
h.Reset()
_, _ = h.Write([]byte(tag))
tagHash := h.Sum64()
// Combine tag hash with value (val is always small for feature values)
result ^= tagHash ^ uint64(val) //nolint:gosec // feature values are small integers
}
return result
}
func Len
Len returns the total number of entries across all shards.
func (c *ShapingCache) Len() int {
total := 0
for _, shard := range c.shards {
shard.mu.RLock()
total += len(shard.entries)
shard.mu.RUnlock()
}
return total
}
func NewShapingCache
NewShapingCache creates a new shaping cache with the specified capacity per shard.
Total capacity is approximately capacity * DefaultShardCount (16).
If capacity <= 0, DefaultCapacity (256) is used.
func NewShapingCache(capacity int) *ShapingCache {
if capacity <= 0 {
capacity = DefaultCapacity
}
c := &ShapingCache{
capacity: capacity,
}
for i := range c.shards {
c.shards[i] = &cacheShard{
entries: make(map[ShapingKey]*cacheEntry),
lru: newLRUList[ShapingKey](),
}
}
return c
}
func NewShapingKey
NewShapingKey creates a ShapingKey from shaping parameters.
This is the canonical way to create cache keys.
func NewShapingKey(textStr string, fontID uint64, size float32, direction text.Direction, features uint64) ShapingKey {
return ShapingKey{
TextHash: hashString(textStr),
FontID: fontID,
SizeBits: math.Float32bits(size),
Direction: uint8(direction & 0xFF), //nolint:gosec // Direction enum is < 4
Features: features,
}
}
func ResetStats
ResetStats resets all statistics counters to zero.
func (c *ShapingCache) ResetStats() {
c.hits.Store(0)
c.misses.Store(0)
c.evictions.Store(0)
}
func Set
Set stores a ShapedRun in the cache.
If the shard exceeds capacity after insertion, oldest entries are evicted.
The value is stored as-is (not copied). Callers should not modify it
after caching.
func (c *ShapingCache) Set(key ShapingKey, value *text.ShapedRun) {
if value == nil {
return
}
shard := c.getShard(&key)
shard.mu.Lock()
defer shard.mu.Unlock()
// Check if entry already exists
if existing, ok := shard.entries[key]; ok {
// Update existing entry
existing.value = value
shard.lru.MoveToFront(existing.node)
return
}
// Evict if at capacity
for shard.lru.Len() >= c.capacity {
if oldest, ok := shard.lru.RemoveOldest(); ok {
delete(shard.entries, oldest)
c.evictions.Add(1)
} else {
break
}
}
// Add new entry
node := shard.lru.PushFront(key)
shard.entries[key] = &cacheEntry{
value: value,
node: node,
}
}
func ShardLen
ShardLen returns the number of entries in each shard.
Useful for debugging load distribution.
func (c *ShapingCache) ShardLen() [DefaultShardCount]int {
var lens [DefaultShardCount]int
for i, shard := range c.shards {
shard.mu.RLock()
lens[i] = len(shard.entries)
shard.mu.RUnlock()
}
return lens
}
func Stats
Stats returns current cache statistics.
This operation is mostly lock-free (atomic counters).
func (c *ShapingCache) Stats() CacheStats {
hits := c.hits.Load()
misses := c.misses.Load()
evictions := c.evictions.Load()
var hitRate float64
total := hits + misses
if total > 0 {
hitRate = float64(hits) / float64(total)
}
return CacheStats{
Len: c.Len(),
Capacity: c.capacity,
TotalCapacity: c.capacity * DefaultShardCount,
Hits: hits,
Misses: misses,
HitRate: hitRate,
Evictions: evictions,
}
}
func TotalCapacity
TotalCapacity returns the total capacity across all shards.
func (c *ShapingCache) TotalCapacity() int {
return c.capacity * DefaultShardCount
}
Structs
type ShapingKey struct
ShapingKey identifies shaped text in the shaping cache.
All shaping parameters that affect the result must be included.
type ShapingKey struct {
// TextHash is FNV-1a hash of the text string.
TextHash uint64
// FontID is the font source identifier.
FontID uint64
// SizeBits is the IEEE 754 bit pattern of the font size (float32).
// Using bit pattern ensures exact matching without floating-point issues.
SizeBits uint32
// Direction is the text direction (LTR, RTL, TTB, BTT).
Direction uint8
// Features is a hash of OpenType feature settings.
Features uint64
}
type ShapingCache struct
ShapingCache is a thread-safe, sharded LRU cache for shaped text runs.
Features:
- 16 shards for reduced lock contention
- LRU eviction with configurable capacity per shard
- Thread-safe for concurrent access
- Atomic statistics for monitoring
- Zero allocations on cache hit
Performance (Intel i7-1255U):
- Cache hit: ~75ns
- Cache miss: ~35ns
- Parallel: ~170ns/op
type ShapingCache struct {
shards [DefaultShardCount]*cacheShard
capacity int // Per-shard capacity
// Statistics (atomic for zero-allocation reads)
hits atomic.Uint64
misses atomic.Uint64
evictions atomic.Uint64
}
type CacheStats struct
CacheStats contains cache statistics for monitoring.
type CacheStats struct {
// Len is the current number of entries.
Len int
// Capacity is the per-shard capacity.
Capacity int
// TotalCapacity is the total capacity across all shards.
TotalCapacity int
// Hits is the number of cache hits.
Hits uint64
// Misses is the number of cache misses.
Misses uint64
// HitRate is the cache hit rate (0.0 to 1.0).
HitRate float64
// Evictions is the number of entries evicted.
Evictions uint64
}
Capacity returns the per-shard capacity.