Skip to main content

Overview

The application is composed of three self-contained modules unified under a single eframe window. The top-level struct UnifiedAvatarManager owns the active module as an AppState enum variant and delegates every frame’s update() call to it.
enum AppState {
    DatabaseDownloader(DatabaseState),
    Login(LoginState),
    AvatarBrowser(BrowserState),
}

State machine

On startup, determine_initial_state() inspects the filesystem and selects the correct starting module. Transitions happen automatically during initialization, and the user can switch modules freely once setup is complete.
┌─────────────────────┐     cache missing      ┌──────────────────────┐
│  DatabaseDownloader │ ──────────────────────► │  DatabaseDownloader  │
│  (if no cache)      │                         │  running...          │
└─────────────────────┘                         └──────────┬───────────┘
                                                           │ complete

                                                ┌──────────────────────┐
                                                │  Login               │
                                                │  (if no auth cookie) │
                                                └──────────┬───────────┘
                                                           │ logged in

                                                ┌──────────────────────┐
                                                │  AvatarBrowser       │
                                                │  (ready)             │
                                                └──────────────────────┘
determine_initial_state() checks:
  1. Whether cache/main_database.json and cache/aux_database.json exist.
  2. Whether config.json exists and contains a non-empty auth_cookie.
If both conditions are satisfied the app opens directly in the avatar browser, skipping the download and login steps entirely.

Module communication

Each module that performs background work (database downloading, VRChat login) spawns a dedicated worker thread. The worker communicates with the UI thread using flume unbounded channels:
Worker thread  ──── tx.send(UiMessage) ────►  UI thread
                                              rx.try_recv() each frame
The top-level UiMessage enum wraps the per-module message types:
enum UiMessage {
    Database(DatabaseMessage),
    Login(LoginMessage),
}
The avatar browser manages its own internal channels for image fetching workers and does not use the top-level UiMessage.

Threading model

ThreadRole
UI threadeframe main loop; renders at ~60 fps
Database workerDownloads and processes avatar databases; sends UiMessage::Database messages
Login workerAuthenticates with the VRChat API; sends UiMessage::Login messages
Image loaders (×4)Fetch avatar thumbnail images concurrently; controlled by WORKER_COUNT = 4
The app uses the blocking reqwest client throughout rather than async/await. This keeps the code straightforward — each worker thread performs its HTTP requests synchronously without an async runtime.

Avatar browser data flow

  1. cache/avatar_entries.ndjson is opened and read line-by-line using BufReader, keeping memory usage low even for large databases.
  2. Search filters (name, description, author, and platform) are applied in-memory to the loaded entries. Tag search is not yet implemented.
  3. Results are paginated at PAGE_SIZE = 200 avatars per page.
  4. Avatar thumbnail images are fetched asynchronously by the image loader pool and stored in a HashMap<String, TextureHandle> that is capped at MAX_TEXTURE_CACHE = 256 entries.

Key constants

These constants are defined in avatar_browser_gui.rs and control runtime behavior:
ConstantValueDescription
PAGE_SIZE200Avatars displayed per page
MAX_TEXTURE_CACHE256Maximum number of cached thumbnail textures
MAX_PENDING48Maximum in-flight image fetch requests
WORKER_COUNT4Number of image fetch worker threads
CARD_WIDTH240 pxWidth of each avatar card in the grid
CARD_IMAGE_WIDTH140 pxWidth of the thumbnail image on a card
CARD_IMAGE_HEIGHT105 pxHeight of the thumbnail image on a card (4:3 ratio)

Dependencies

The full dependency set is declared in Cargo.toml:
CrateVersionPurpose
eframe0.26Native window and App trait
egui_extras0.26Table builder and additional widgets
flume0.11Multi-producer, multi-consumer channels
reqwest0.11Blocking HTTP client (rustls TLS, cookies)
serde / serde_json1.0Serialization for config and avatar data
image0.25PNG/JPEG decoding for avatar thumbnails
regex1.11Pattern matching for database parsing
anyhow / thiserror1.0Error handling
env_logger / log0.11 / 0.4Structured logging via RUST_LOG
open5.1Open avatar URLs in the system browser
chrono0.4Timestamps for cache metadata and login records

Build docs developers (and LLMs) love