Render Caching
Relevant source files
The following files were used as context for generating this wiki page:
Render Caching in SwiftDitherKit is a performance-oriented system designed to generate, store, and reuse dithered chart frames. By offloading the computationally intensive process of ordered dithering from the Main Actor to a background actor, the library ensures that UI responsiveness is maintained even during complex chart updates or animations.
The system utilizes an asynchronous workflow where render requests are hashed and checked against a thread-safe cache. This prevents redundant pixel calculations for identical data sets and allows for smooth transitions between chart states.
Sources: SwiftDitherKit.md , DitherRenderCache.swift:63-73
Architecture and Components
The caching system is built around the DitherRenderCache actor, which manages the lifecycle of DitherRenderedRaster objects. It interfaces with the DitherRenderer to generate new frames when a cache miss occurs.
Key Data Structures
| Structure | Description |
|---|---|
DitherRenderRequest | A Hashable enum representing a specific chart configuration (Cartesian, Pie, or Radar) used as a cache key. |
DitherRenderedRaster | A Sendable container for the immutable CGImage, its checksum, byte count, and associated DitherSparkleField. |
DitherRenderCache | A global actor that enforces memory limits and manages Least Recently Used (LRU) eviction. |
Entry | A private wrapper within the cache that holds the rendered frame. |
Sources: DitherRenderCache.swift:5-61, DitherRenderCache.swift:80-82
Component Relationship Diagram
This diagram illustrates how a render request flows through the cache to either retrieve an existing frame or generate a new one via the renderer.
Sources: DitherRenderCache.swift:85-103
Cache Management and Eviction Logic
The DitherRenderCache implementation uses a byte-based memory limit rather than a simple count of entries. The default limit is approximately 16 MiB of rendered RGBA frames.
LRU Eviction Process
When the cache exceeds its maximumByteCount, it performs the following steps:
- It identifies the oldest request in the
recencyarray. - It removes the corresponding
Entryfrom theentriesdictionary. - It decrements the internal
byteCountby the size of the evicted frame. - This continues until there is sufficient space for the new frame.
Sources: DitherRenderCache.swift:75-78, DitherRenderCache.swift:115-126
Statistics Tracking
The cache maintains internal counters for monitoring performance, accessible via the statistics() function.
struct Statistics: Equatable, Sendable {
let entryCount: Int
let byteCount: Int
let hitCount: Int
let missCount: Int
}
Sources: DitherRenderCache.swift:64-69, DitherRenderCache.swift:105-112
Data Flow: From Request to Raster
The rendering process is triggered by DitherAsyncRaster. It determines whether to perform rendering immediately or asynchronously based on the DitherRenderingMode.
Asynchronous Rendering Sequence
The sequence below shows how the UI layer interacts with the cache to obtain pixels without blocking the main thread.
Implementation Details
Rendering Modes
SwiftDitherKit supports two rendering modes which affect how the cache is accessed:
- Asynchronous (Default): The view calls the cache actor inside a
.task. If a newer request arrives before the first finishes, the first task is cancelled. - Immediate: Used for offscreen snapshots (like
ImageRenderer). It bypasses the actor-based asynchronous flow and renders the frame directly during view evaluation.
Sources: DitherRasterView.swift:156-163, DitherRasterView.swift:179-182
Checksum and Integrity
Frames are validated using a 64-bit checksum. The DitherRenderedRaster checksum is calculated by combining the pixel data's checksum with the DitherSparkleField checksum, ensuring that even if pixel data is identical, changes in sparkles result in a unique cache entry.
checksum = resolvedSparkleField.combiningChecksum(raster.checksum)
Sources: DitherRenderedRaster.swift:57, DitherRaster.swift:51-55
Memory Efficiency
The cache utilizes CGImage, which is inherently immutable and Sendable. This allows frames to safely cross the actor boundary to the main UI thread without copying large pixel buffers. Large frames that exceed the maximumByteCount on their own are rendered and returned but never stored in the cache.
Sources: DitherRenderCache.swift:45-48, DitherRenderCache.swift:116
Summary
Render Caching serves as the performance backbone of SwiftDitherKit. By combining an LRU eviction strategy with asynchronous background rendering, it allows for the display of complex dithered textures at high frame rates. The system's reliance on unique request hashing ensures that the visual state of the chart—including specific selections and highlight states—is consistently and efficiently retrieved.
Sources: DitherRenderCacheTests.swift:6-20, DitherRasterView.swift:165-177