Class: Fontisan::Variation::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/fontisan/variation/cache.rb

Overview

Caches variation calculations for performance

This class implements a caching layer for expensive variation calculations to significantly improve instance generation performance. It caches:

  • Normalized scalars per coordinate set

  • Interpolated values

  • Instance generation results

  • Region matches

Cache strategies:

  1. LRU (Least Recently Used) for memory management

  2. Coordinate-based keys for scalar caching

  3. Invalidation on font modification

  4. Optional persistent caching across sessions

Examples:

Using the cache with instance generation

cache = Fontisan::Variation::Cache.new(max_size: 100)
scalars = cache.fetch_scalars(coordinates, axes) do
  calculate_scalars(coordinates, axes)
end

Cache statistics

cache.statistics
# => { hits: 150, misses: 50, hit_rate: 0.75 }

Direct Known Subclasses

ThreadSafeCache

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(max_size: 1000, ttl: nil) ⇒ Cache

Initialize cache

Parameters:

  • max_size (Integer) (defaults to: 1000)

    Maximum number of entries (default: 1000)

  • ttl (Integer, nil) (defaults to: nil)

    Time-to-live in seconds (nil for no expiration)



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/fontisan/variation/cache.rb', line 42

def initialize(max_size: 1000, ttl: nil)
  @max_size = max_size
  @ttl = ttl
  @cache = {}
  @access_times = {}
  @stats = {
    hits: 0,
    misses: 0,
    evictions: 0,
    invalidations: 0,
  }
end

Instance Attribute Details

#max_sizeInteger (readonly)

Returns Maximum cache size.

Returns:

  • (Integer)

    Maximum cache size



33
34
35
# File 'lib/fontisan/variation/cache.rb', line 33

def max_size
  @max_size
end

#statsHash (readonly)

Returns Cache statistics.

Returns:

  • (Hash)

    Cache statistics



36
37
38
# File 'lib/fontisan/variation/cache.rb', line 36

def stats
  @stats
end

Instance Method Details

#cached?(key) ⇒ Boolean

Check if key is cached and valid

Parameters:

  • key (String)

    Cache key

Returns:

  • (Boolean)

    True if cached and valid



122
123
124
125
126
127
# File 'lib/fontisan/variation/cache.rb', line 122

def cached?(key)
  return false unless @cache.key?(key)
  return false if expired?(key)

  true
end

#clearObject

Clear entire cache



144
145
146
147
148
# File 'lib/fontisan/variation/cache.rb', line 144

def clear
  @cache.clear
  @access_times.clear
  @stats[:invalidations] += 1
end

#empty?Boolean

Check if cache is empty

Returns:

  • (Boolean)

    True if empty



192
193
194
# File 'lib/fontisan/variation/cache.rb', line 192

def empty?
  @cache.empty?
end

#fetch(key) { ... } ⇒ Object

Generic fetch with caching

Parameters:

  • key (String)

    Cache key

Yields:

  • Block to compute value if not cached

Returns:

  • (Object)

    Cached or computed value



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/fontisan/variation/cache.rb', line 105

def fetch(key)
  if cached?(key)
    @stats[:hits] += 1
    touch(key)
    return @cache[key][:value]
  end

  @stats[:misses] += 1
  value = yield
  store(key, value)
  value
end

#fetch_instance(font_checksum, coordinates) { ... } ⇒ Hash

Fetch or compute instance generation result

Parameters:

  • font_checksum (String)

    Font identifier

  • coordinates (Hash<String, Float>)

    Instance coordinates

Yields:

  • Block to generate instance if not cached

Returns:

  • (Hash)

    Cached or generated instance tables



84
85
86
87
# File 'lib/fontisan/variation/cache.rb', line 84

def fetch_instance(font_checksum, coordinates, &)
  key = CacheKeyBuilder.instance_key(font_checksum, coordinates)
  fetch(key, &)
end

#fetch_interpolated(base_value, deltas, scalars) { ... } ⇒ Float

Fetch or compute interpolated value

Parameters:

  • base_value (Numeric)

    Base value

  • deltas (Array<Numeric>)

    Delta values

  • scalars (Array<Float>)

    Region scalars

Yields:

  • Block to calculate value if not cached

Returns:

  • (Float)

    Cached or computed value



73
74
75
76
# File 'lib/fontisan/variation/cache.rb', line 73

def fetch_interpolated(base_value, deltas, scalars, &)
  key = CacheKeyBuilder.interpolation_key(base_value, deltas, scalars)
  fetch(key, &)
end

#fetch_region_matches(coordinates, regions) { ... } ⇒ Array

Fetch or compute region matches

Parameters:

  • coordinates (Hash<String, Float>)

    Design space coordinates

  • regions (Array)

    Variation regions

Yields:

  • Block to calculate matches if not cached

Returns:

  • (Array)

    Cached or computed region matches



95
96
97
98
# File 'lib/fontisan/variation/cache.rb', line 95

def fetch_region_matches(coordinates, regions, &)
  key = CacheKeyBuilder.region_matches_key(coordinates, regions)
  fetch(key, &)
end

#fetch_scalars(coordinates, axes) { ... } ⇒ Array<Float>

Fetch or compute scalars for coordinates

Parameters:

  • coordinates (Hash<String, Float>)

    Design space coordinates

  • axes (Array)

    Variation axes

Yields:

  • Block to calculate scalars if not cached

Returns:

  • (Array<Float>)

    Cached or computed scalars



61
62
63
64
# File 'lib/fontisan/variation/cache.rb', line 61

def fetch_scalars(coordinates, axes, &)
  key = CacheKeyBuilder.scalars_key(coordinates, axes)
  fetch(key, &)
end

#full?Boolean

Check if cache is full

Returns:

  • (Boolean)

    True if at capacity



199
200
201
# File 'lib/fontisan/variation/cache.rb', line 199

def full?
  @cache.size >= @max_size
end

#invalidate(key) ⇒ Object

Invalidate specific key

Parameters:

  • key (String)

    Cache key to invalidate



153
154
155
156
157
# File 'lib/fontisan/variation/cache.rb', line 153

def invalidate(key)
  @cache.delete(key)
  @access_times.delete(key)
  @stats[:invalidations] += 1
end

#invalidate_matching(pattern) ⇒ Object

Invalidate keys matching pattern

Parameters:

  • pattern (Regexp)

    Pattern to match keys



162
163
164
165
# File 'lib/fontisan/variation/cache.rb', line 162

def invalidate_matching(pattern)
  keys = @cache.keys.select { |k| k.match?(pattern) }
  keys.each { |k| invalidate(k) }
end

#sizeInteger

Get cache size

Returns:

  • (Integer)

    Number of cached entries



185
186
187
# File 'lib/fontisan/variation/cache.rb', line 185

def size
  @cache.size
end

#statisticsHash

Get cache statistics

Returns:

  • (Hash)

    Statistics including hit rate



170
171
172
173
174
175
176
177
178
179
180
# File 'lib/fontisan/variation/cache.rb', line 170

def statistics
  total = @stats[:hits] + @stats[:misses]
  hit_rate = total.zero? ? 0.0 : @stats[:hits].to_f / total

  @stats.merge(
    total_requests: total,
    hit_rate: hit_rate,
    size: @cache.size,
    max_size: @max_size,
  )
end

#store(key, value) ⇒ Object

Store value in cache

Parameters:

  • key (String)

    Cache key

  • value (Object)

    Value to store



133
134
135
136
137
138
139
140
141
# File 'lib/fontisan/variation/cache.rb', line 133

def store(key, value)
  evict_if_needed

  @cache[key] = {
    value: value,
    created_at: Time.now,
  }
  touch(key)
end