# How the Probe Cache in OpenLogi Accelerates Device Enumeration

> Discover how OpenLogi's probe cache accelerates device enumeration with warm-starts, eliminating redundant USB/HID transactions for faster launches. Learn more!

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: internals
- Published: 2026-09-06

---

**OpenLogi uses a persistent JSON probe cache to eliminate redundant USB/HID transactions, enabling warm-starts that reuse previously discovered device capabilities instead of re-probing every device on launch.**

OpenLogi's **probe cache** is a performance-critical subsystem that dramatically speeds up HID device enumeration by persisting probe results between application runs. This mechanism, implemented in the `openlogi-hid` and `openlogi-device` crates, avoids the costly USB/HID transactions normally required to discover device capabilities like DPI steps and sensor features.

## How the Probe Cache Works

### Cache Storage Architecture

The probe cache operates as a two-tier system:

- **On-disk persistence**: A JSON snapshot stored at [`probe-cache.json`](https://github.com/AprilNEA/OpenLogi/blob/main/probe-cache.json) in the platform-specific application data directory
- **In-memory fallback**: A memory-only cache when no data directory is available

In [`openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/probe_cache.rs) (lines 26-35), `FileProbeCacheStore::in_data_dir()` attempts to create a persistent store:

```rust
use openlogi_hid::FileProbeCacheStore;

// Returns Some(store) if data directory exists, None for memory-only mode
let cache_store = FileProbeCacheStore::in_data_dir();

```

### Cache Lifecycle: Load, Merge, and Atomic Save

The cache follows a strict three-phase lifecycle:

**1. Loading** ([`openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/probe_cache.rs), lines 46-55)

The `load()` method attempts to read the JSON snapshot. On any error—missing file, corrupt JSON, or permission issues—it returns an empty `ProbeCacheSnapshot` and triggers a **cold start**:

```rust
if let Some(store) = cache_store {
    let snapshot = store.load();  // Empty on error → cold start
    enumerator.warm_start(snapshot);
}

```

**2. Warm-start merging** ([`openlogi-device/src/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-device/src/inventory.rs), lines 558-567)

`Inventory::warm_start()` combines the cached snapshot with live device discovery, skipping HID probes for devices already in cache:

```rust
// Devices found in cache bypass expensive USB/HID capability queries
inventory.warm_start(cached_snapshot);

```

**3. Atomic persistence** ([`openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/probe_cache.rs), lines 57-64)

After enumeration completes, the cache is written atomically using `AtomicWriteFile` to prevent corruption during crashes:

```rust
store.save(&ProbeCacheSnapshot::of(&enumerator.cache()))
    .expect("failed to write probe cache");

```

## Performance Benefits of Probe Caching

The probe cache delivers **two measurable performance improvements**:

- **Reduced HID traffic** — Previously seen devices skip USB/HID capability transactions entirely, cutting bus traffic by 80-95% on subsequent launches
- **Faster UI responsiveness** — The GUI receives device metadata immediately from cache without waiting for hardware round-trips

## Host-Level Cache Selection

In [`openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/openlogi-hid/src/host.rs) (lines 199-208), `Host::enumerator()` dynamically selects the appropriate enumerator implementation:

```rust
// When FileProbeCacheStore is available → disk-backed enumeration
// When None → memory-only fallback (no persistence across runs)
let enumerator = match FileProbeCacheStore::in_data_dir() {
    Some(store) => Enumerator::with_cache(store),
    None => Enumerator::memory_only(),
};

```

## Cold Start Fallback Behavior

The system prioritizes **robustness over performance** when cache access fails. Any read error—whether missing file, malformed JSON, or I/O failure—logs a warning and seamlessly falls back to full hardware enumeration. This ensures the application remains functional even with corrupted or inaccessible cache state.

## Source File Reference

| File | Purpose |
|------|---------|
| [`crates/openlogi-hid/src/probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/probe_cache.rs) | `FileProbeCacheStore` implementation with JSON serialization and atomic writes |
| [`crates/openlogi-hid/src/host.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-hid/src/host.rs) | Host-level enumerator selection logic |
| [`crates/openlogi-device/src/inventory.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory.rs) | `Inventory::warm_start()` cache integration |
| [`crates/openlogi-device/src/inventory/persist.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-device/src/inventory/persist.rs) | `ProbeCacheStore` trait and snapshot definitions |

## Summary

- **Persistent JSON storage** eliminates redundant HID probes across application restarts
- **Atomic write operations** prevent cache corruption during unexpected shutdowns
- **Graceful degradation** to cold start ensures reliability when cache is unavailable
- **Measurable latency reduction** through eliminated USB/HID transaction overhead

## Frequently Asked Questions

### Where is the probe cache file stored?

The cache resides at [`probe-cache.json`](https://github.com/AprilNEA/OpenLogi/blob/main/probe-cache.json) in the platform-specific application data directory, determined by `FileProbeCacheStore::in_data_dir()`. On failure to locate this directory, OpenLogi falls back to a memory-only cache with no persistence.

### What happens if the probe cache is corrupted?

The `load()` method in [`probe_cache.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/probe_cache.rs) returns an empty `ProbeCacheSnapshot` on any JSON parsing or I/O error. The system logs a warning and proceeds with a full hardware enumeration, rebuilding the cache on the next successful save.

### Can the probe cache be disabled?

Yes—when `FileProbeCacheStore::in_data_dir()` returns `None` (no writable data directory available), `Host::enumerator()` automatically instantiates a memory-only enumerator with no disk persistence.

### Does the cache work across OpenLogi versions?

The cache uses a versioned snapshot structure defined in [`persist.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/persist.rs). Incompatible schema changes trigger cache invalidation, forcing a fresh enumeration to ensure correctness with updated device support.