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:
- Whether
cache/main_database.json and cache/aux_database.json exist.
- 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
| Thread | Role |
|---|
| UI thread | eframe main loop; renders at ~60 fps |
| Database worker | Downloads and processes avatar databases; sends UiMessage::Database messages |
| Login worker | Authenticates 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
cache/avatar_entries.ndjson is opened and read line-by-line using BufReader, keeping memory usage low even for large databases.
- Search filters (name, description, author, and platform) are applied in-memory to the loaded entries. Tag search is not yet implemented.
- Results are paginated at
PAGE_SIZE = 200 avatars per page.
- 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:
| Constant | Value | Description |
|---|
PAGE_SIZE | 200 | Avatars displayed per page |
MAX_TEXTURE_CACHE | 256 | Maximum number of cached thumbnail textures |
MAX_PENDING | 48 | Maximum in-flight image fetch requests |
WORKER_COUNT | 4 | Number of image fetch worker threads |
CARD_WIDTH | 240 px | Width of each avatar card in the grid |
CARD_IMAGE_WIDTH | 140 px | Width of the thumbnail image on a card |
CARD_IMAGE_HEIGHT | 105 px | Height of the thumbnail image on a card (4:3 ratio) |
Dependencies
The full dependency set is declared in Cargo.toml:
| Crate | Version | Purpose |
|---|
eframe | 0.26 | Native window and App trait |
egui_extras | 0.26 | Table builder and additional widgets |
flume | 0.11 | Multi-producer, multi-consumer channels |
reqwest | 0.11 | Blocking HTTP client (rustls TLS, cookies) |
serde / serde_json | 1.0 | Serialization for config and avatar data |
image | 0.25 | PNG/JPEG decoding for avatar thumbnails |
regex | 1.11 | Pattern matching for database parsing |
anyhow / thiserror | 1.0 | Error handling |
env_logger / log | 0.11 / 0.4 | Structured logging via RUST_LOG |
open | 5.1 | Open avatar URLs in the system browser |
chrono | 0.4 | Timestamps for cache metadata and login records |