Spice

Spice is a focus wallpaper manager for Windows and MacOS.


Project maintained by dixieflatline76 Hosted on GitHub Pages — Theme by mattgraham

Spice Architecture Documentation (Wallpaper Plugin)

Status: Current as of v1.1.1 Focus: Concurrency Model, Image Pipeline, and UI Synchronization

1. Executive Summary

Spice employs a Single-Writer, Multiple-Reader (SWMR) concurrency architecture to separate resource-intensive operations (image processing, I/O) from the user interface. This design eliminates UI main-thread blocking (“jank”) and lock contention, ensuring a buttery-smooth user experience even during heavy background downloads.

2. System Architecture (Plugin System)

Spice is built as a modular application where core functionality is delivered via plugins.

graph TD
    classDef core fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#000,font-size:16px;
    classDef plug fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000,font-size:16px;

    App[Spice Application]:::core
    PM[Plugin Manager]:::core
    
    subgraph Plugins ["Loaded Plugins"]
        WP[Wallpaper Plugin]:::plug
        Other[Other Plugins...]:::plug
    end

    App -->|Initializes| PM
    PM -->|Manages Lifecycle| Plugins
    
    WP -->|Injects| SettingsUI[Settings Tab]:::core
    WP -->|Injects| TrayUI[Tray Menu Items]:::core

3. Wallpaper Plugin Architecture

3.1 Core Concepts

3.1.1 The Single-Writer Principle

To prevent race conditions and lock contention, only one goroutine is allowed to mutate the global state (Image Store). All other components, including the UI, are Readers or Command Senders.

3.1.2 Decoupled UI Session

The User Interface maintains its own local session state (which image am I looking at right now?). It strictly reads from the shared store and sends asynchronous commands to request changes.

3.2 High-Level Architecture

The system is divided into two distinct execution contexts:

  1. The UI Context (Main Thread): Handles user input, rendering, and navigation. It is optimized for Read Speed.
  2. The Pipeline Context (Background): Handles downloading, processing, and state mutation. It is optimized for Throughput.
graph TD
    classDef ui fill:#e1f5fe,stroke:#01579b,stroke-width:2px,color:#000,font-size:16px;
    classDef pipe fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000,font-size:16px;
    classDef store fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px,color:#000,font-size:16px;

    subgraph UI_Context ["UI Context (Plugin)"]
        NextBtn[Next Button]:::ui
        PrevBtn[Delete Button]:::ui
        LocalState["Session State<br/>(Current Index, History)"]:::ui
    end

    subgraph Shared_Resource [Shared Resources]
        Store[(ImageStore)]:::store
    end

    subgraph Pipeline_Context ["Pipeline Context"]
        Workers[["Worker Pool<br/>(Download/Crop)"]]:::pipe
        LazyWorker(("Persistent Worker<br/>(Enrichment)")):::pipe
        Manager(("State Manager<br/>Loop")):::pipe
    end

    %% Reads
    NextBtn -- 1. RLock (Fast) --> Store
    LocalState -- Read Image Info --> Store

    %% Async Commands
    NextBtn -- 2. CmdMarkSeen (Async) --> Manager
    PrevBtn -- CmdDelete (Async) --> Manager
    Workers -- Result (New Image) --> Manager

    %% Writers
    Manager -- 3. Lock (Exclusive) --> Store

    %% Feedback
    Manager -- Yield (Gosched) --> Manager

3.3 Component Details

4.1 ImageStore (pkg/wallpaper/store.go)

A thread-safe, stateless container.

4.2 Pipeline State Manager (pkg/wallpaper/pipeline.go)

The “Brain” of the backend.

4.3 UI Plugin (pkg/wallpaper/wallpaper.go)

The “Controller”.

4.4 Lazy Enrichment Worker (startEnrichmentWorker)

A persistent, single-goroutine worker that “pivots” to the user’s current location.

3.4 Interaction Flows

3.4.1 “Next Wallpaper” Flow (Zero Contention)

This flow demonstrates how the UI updates instantly without waiting for a write lock.

sequenceDiagram
    participant User
    participant UI as UI (Plugin)
    participant Store as ImageStore
    participant Mgr as State Manager

    User->>UI: Click "Next"
    activate UI
    Note right of UI: 1. Calculate new index (Local)
    UI->>Store: Get(newIndex) [RLock]
    Store-->>UI: Image
    
    Note right of UI: 2. Optimistic Update
    UI->>UI: Update Tray Menu & currentImage
    
    Note right of UI: 3. Async Command
    UI--)Mgr: CmdMarkSeen(ImageID)
    
    Note right of UI: 4. Apply Wallpaper
    UI->>OS: setWallpaper(path)
    
    deactivate UI
    
    activate Mgr
    Note left of Mgr: Process CmdMarkSeen
    Mgr->>Store: MarkSeen() [Lock]
    
    deactivate Mgr

3.4.2 “Delete Wallpaper” Flow

Deleting requires modifying the store, handled asynchronously.

sequenceDiagram
    participant User
    participant UI as UI (Plugin)
    participant Mgr as State Manager
    participant Store as ImageStore

    User->>UI: Click "Delete"
    activate UI
    
    UI--)Mgr: CmdRemove(ImageID)
    UI->>OS: Remove File (Disk)
    
    Note right of UI: Move to Next
    UI->>UI: setNextWallpaper()
    
    deactivate UI
    
    activate Mgr
    Note left of Mgr: Process CmdRemove
    Mgr->>Store: Remove(ID) [Lock]
    deactivate Mgr

3.5 Directory Structure & Key Files

Component File Path Responsibility
Store pkg/wallpaper/store.go Data repository. RWMutex protected.
Pipeline pkg/wallpaper/pipeline.go Worker pool & State Manager Loop.
Controller pkg/wallpaper/wallpaper.go UI logic, Lifecycle, Optimistic Updates.
Processor pkg/wallpaper/smart_image_processor.go Face detection, cropping (Heavy CPU).

3.5 Resource Management

3.5.1 Deep Cache Cleaning (Recursive Deletion)

Spice implements a strict “Zero Orphans” policy for resource management. When a wallpaper collection (Query) is deleted:

  1. Configuration Callback: The Config triggers a registered callback (onQueryRemoved).
  2. Store Pruning: The callback invokes store.RemoveByQueryID(queryID).
  3. Deep Delete: The File Manager’s DeepDelete function is called for every image ID:
    • Deletes the Master Image.
    • recursively deletes all Derivatives (Smart Fit, Face Crop, Face Boost images) in their respective subdirectories.

3.5.2 Provider Strategies

Spice supports two distinct provider interaction models:

3.6 Future Considerations

3.7 Performance Strategies

To maintain responsiveness under load, the following optimizations are employed:

  1. O(1) Image Store: The store uses a secondary idSet map[string]bool to perform existence checks in constant time (45ns) rather than linear scans (470ns+), ensuring that the Writer Loop never lags even with thousands of images.
  2. Synchronous Race Prevention: The Controller synchronously anticipates background work (setting isDownloading = true under lock) before spawning goroutines. This prevents “job storms” and CPU saturation during rapid UI interactions.