docs: update documentation to match refactored codebase
- Delete obsolete plans/code-review.md (all suggestions implemented) - Rewrite docs/API.md to reflect modular architecture, DashMap, corrected AppError variants, accurate function signatures and module locations - Rewrite docs/ARCHITECTURE.md to reflect DashMap concurrency, remove completed future enhancements, fix AnalysisContext description - Update docs/USER_GUIDE.md with global CLI path options (--my-cars, --cars-db, --times-dir, --tracks-db) and updated troubleshooting
This commit is contained in:
parent
a9a46ee551
commit
d2c1418e5e
4 changed files with 284 additions and 539 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## System Overview
|
||||
|
||||
The Top Drives Records Analyzer consists of a main analysis binary and a shared library working together to provide comprehensive car performance analysis for the Top Drives racing game.
|
||||
The Top Drives Records Analyzer is a Rust CLI tool for analyzing car performance data in the Top Drives racing game. It consists of a shared library (`src/lib.rs`), a binary entry point (`src/records.rs`), and three internal module trees: `commands/`, `parsing/`, and `output/`.
|
||||
|
||||
## Component Architecture
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ The Top Drives Records Analyzer consists of a main analysis binary and a shared
|
|||
- **Data Types**: Defines `Car`, `BestCarRecord`, `AnalysisContext`, `CarCounts`, `MyCarsData`, `AppError`
|
||||
- **Track Name Translation**: Converts track codes to human-readable names via `translate_track_name()`
|
||||
- **Track Type Translation**: Converts condition codes to surface descriptions via `translate_track_type()`
|
||||
- **File Download**: Async file download utility via `get_file()`
|
||||
- **File Download**: Async file download utility with retry logic via `get_file()`
|
||||
- **User Data Loading**: Parses `my_cars.json` via `load_my_cars_data()`
|
||||
- **Car Comparison**: Sorting comparator via `compare_cars()`
|
||||
- **Count Aggregation**: Per-car win count computation via `compute_car_counts()`
|
||||
|
|
@ -26,18 +26,44 @@ All public functions return `Result<T, AppError>` where `AppError` is a unified
|
|||
- `AppError::Network` — HTTP/download errors
|
||||
- `AppError::Parse` — JSON parsing errors with file context
|
||||
- `AppError::MissingFile` — Required data file not found
|
||||
- `AppError::Poisoned` — Mutex poisoning from panicked threads
|
||||
- `AppError::Internal` — Runtime errors (semaphore closed, task join failures)
|
||||
|
||||
### 2. Records Binary (`src/records.rs`)
|
||||
|
||||
**Primary analysis engine** that processes pre-downloaded JSON timing data.
|
||||
**CLI entry point** that defines command-line arguments and dispatches to command handlers.
|
||||
|
||||
#### Key Responsibilities
|
||||
- **Data Fetching**: Downloads car and track databases from GitHub repository
|
||||
- **File Processing**: Parses JSON timing files for each car configuration
|
||||
- **Performance Analysis**: Compares timing data across RQ ranges and track conditions
|
||||
- **Collection Management**: Identifies owned cars and optimization opportunities
|
||||
- **CSV Output**: Writes analysis results to CSV files with proper escaping
|
||||
- **CLI Definition**: `clap` derive-based argument parsing with global path options
|
||||
- **Command Dispatch**: Routes to the appropriate command handler in `commands/`
|
||||
- **Error Reporting**: Catches all errors and prints to stderr with exit code 1
|
||||
|
||||
#### Global CLI Options
|
||||
- `--my-cars` — Path to user's car collection export (default: `my_cars.json`)
|
||||
- `--cars-db` — Path to car database file (default: `cars_repo.json`)
|
||||
- `--times-dir` — Directory for per-car timing JSON files (default: `times`)
|
||||
- `--tracks-db` — Path to tracks database file (default: `tracks_repo.json`)
|
||||
|
||||
### 3. Commands Module (`src/commands/`)
|
||||
|
||||
**Command handlers** that orchestrate analysis and output generation.
|
||||
|
||||
| Module | Command | Description |
|
||||
|--------|---------|-------------|
|
||||
| `fetch.rs` | `fetch` | Downloads car DB, tracks DB, and per-car timing files |
|
||||
| `best.rs` | `best` | Generates `best.csv` and `very_best.csv` |
|
||||
| `trash.rs` | `trash` | Generates `trash.csv` (owned cars not best on any track) |
|
||||
| `wishlist.rs` | `wishlist` | Generates `wishlist.csv`, `add.csv`, `remove.csv` |
|
||||
| `details.rs` | `details` | Generates `details.csv` with all timing data |
|
||||
| `track.rs` | `track` | Prints best car for a specific track/condition/RQ |
|
||||
| `all.rs` | `all` | Generates all output files in a single analysis pass |
|
||||
|
||||
#### Shared Helpers (`src/commands/mod.rs`)
|
||||
- `run_analysis(owned, cars_db_path, times_dir)` — Common analysis setup: loads car DB, parses timing files, computes counts
|
||||
- `load_my_cars_or_warn(path)` — Loads user data with graceful degradation (returns empty data if file missing)
|
||||
|
||||
### 4. Parsing Module (`src/parsing/`)
|
||||
|
||||
**Core parsing logic** for car timing data and car database loading.
|
||||
|
||||
#### Key Constants
|
||||
- `MAX_RQ: u8 = 113` — Maximum RQ level used for range analysis
|
||||
|
|
@ -46,19 +72,43 @@ All public functions return `Result<T, AppError>` where `AppError` is a unified
|
|||
- `TEST_BOWL_FACTOR: f64 = 2000.0` — Multiplier for Test Bowl time inversion
|
||||
- `CONCURRENCY_LIMIT: usize = 20` — Maximum concurrent tasks
|
||||
|
||||
#### Key Functions
|
||||
- `parse_car_times()` — Synchronous per-car timing file parser (runs inside `spawn_blocking`)
|
||||
- `update_best_car()` — Updates `DashMap` with new performance records across RQ range
|
||||
- `parse_all_times()` — Orchestrates concurrent parsing with semaphore + `spawn_blocking`
|
||||
- `fetch_times()` — Downloads per-car timing files with bounded concurrency
|
||||
- `get_car_list()` (`car_list.rs`) — Loads car database via serde deserialization
|
||||
|
||||
### 5. Output Module (`src/output/`)
|
||||
|
||||
**CSV output utilities** and sorting helpers.
|
||||
|
||||
- `write_sorted_csv()` — Generic sorted CSV writer (index-based sorting to avoid cloning)
|
||||
- `write_best_csv()`, `write_very_best_csv()` — Best car CSV output
|
||||
- `write_trash_csv()` — Trash car CSV output
|
||||
- `write_details_csv()` — Comprehensive timing data CSV output
|
||||
- `write_wishlist_files()` — Wishlist, add, and remove CSV output
|
||||
- `print_best_car()` — Stdout output for track-specific query
|
||||
- `compute_ideal_wishlist()` — Ideal wishlist computation
|
||||
- `compare_records()` — Sorting comparator for `BestCarRecord`
|
||||
|
||||
#### Data Flow
|
||||
|
||||
```
|
||||
GitHub Repository
|
||||
↓
|
||||
cars_repo.json + tracks_repo.json
|
||||
↓
|
||||
times/*.json (1000+ car files)
|
||||
↓
|
||||
JSON Parsing → BestCarRecord Analysis
|
||||
↓
|
||||
User Collection (my_cars.json)
|
||||
↓
|
||||
GitHub Repository / API
|
||||
↓
|
||||
cars_repo.json + tracks_repo.json ← fetch command
|
||||
↓
|
||||
times/*.json (1000+ car files) ← fetch command
|
||||
↓
|
||||
parse_all_times() → parse_car_times() × N cars (concurrent via spawn_blocking)
|
||||
↓
|
||||
AnalysisContext (DashMap<TripletKey, Vec<BestCarRecord>>)
|
||||
↓
|
||||
compute_car_counts() → CarCounts
|
||||
↓
|
||||
User Collection (my_cars.json) → owned/wishlist sets
|
||||
↓
|
||||
Output: CSV files / Terminal Results
|
||||
```
|
||||
|
||||
|
|
@ -74,7 +124,7 @@ type TripletKey = (String, u8, String);
|
|||
```
|
||||
|
||||
**Purpose**: Composite key for uniquely identifying track conditions
|
||||
**Usage**: HashMap key for storing best car records
|
||||
**Usage**: `DashMap` key for storing best car records
|
||||
|
||||
#### Car
|
||||
```rust
|
||||
|
|
@ -82,10 +132,10 @@ type TripletKey = (String, u8, String);
|
|||
struct Car {
|
||||
rid: String, // Repository ID
|
||||
brand: String, // Manufacturer
|
||||
only_name: String, // Model name
|
||||
only_name: String, // Model name (serde rename: "onlyName")
|
||||
year: u16, // Model year
|
||||
rq: u8, // Rarity level
|
||||
prize: bool, // Prize car status
|
||||
prize: bool, // Prize car status (serde default)
|
||||
guid: String, // Game GUID
|
||||
}
|
||||
```
|
||||
|
|
@ -111,13 +161,13 @@ struct BestCarRecord {
|
|||
### AnalysisContext
|
||||
```rust
|
||||
struct AnalysisContext {
|
||||
best_cars: Arc<Mutex<HashMap<TripletKey, Vec<BestCarRecord>>>>,
|
||||
best_cars: Arc<DashMap<TripletKey, Vec<BestCarRecord>>>,
|
||||
}
|
||||
```
|
||||
|
||||
**Thread Safety**: Protected by `Mutex` for concurrent access during parsing
|
||||
**Lifetime**: Created in `main()`, passed through the call chain
|
||||
**Access**: `lock_best_cars()` method returns `Result` with proper error handling
|
||||
**Thread Safety**: Uses `DashMap` for sharded internal locking, providing high-concurrency access without a global mutex.
|
||||
**Lifetime**: Created in command handlers, passed through the call chain.
|
||||
**Access**: Direct `DashMap` methods (`insert`, `get`, `iter`, `entry`).
|
||||
|
||||
### CarCounts
|
||||
```rust
|
||||
|
|
@ -152,17 +202,19 @@ Multiple validation layers ensure data quality:
|
|||
2. **Downvote Filter**: Community-downvoted times are excluded
|
||||
3. **Tie Detection**: Times within `TIME_TOLERANCE` (5ms) are considered equal
|
||||
4. **Special Cases**: Test Bowl track uses inverted time calculation (`TEST_BOWL_FACTOR / time`)
|
||||
5. **Track Name Splitting**: Uses `rsplit_once("_a")` to correctly handle track names containing `_a` (e.g., `airplaneHangars`)
|
||||
|
||||
### Concurrent Processing
|
||||
|
||||
#### Bounded Concurrency Pattern
|
||||
- **Task Spawning**: One blocking task per car file via `tokio::task::spawn_blocking`
|
||||
- **Semaphore**: Limits concurrent operations to `CONCURRENCY_LIMIT` (20)
|
||||
- **Error Isolation**: Individual car failures don't stop batch processing
|
||||
- **Semaphore First**: Semaphore permit acquired in async context (avoids deadlocking the blocking thread pool)
|
||||
- **Blocking Work**: Synchronous parsing runs inside `tokio::task::spawn_blocking` while holding the permit
|
||||
- **Concurrency Limit**: Maximum `CONCURRENCY_LIMIT` (20) simultaneous operations
|
||||
|
||||
#### Synchronization
|
||||
- **DashMap**: Sharded concurrent HashMap — no global lock needed for parallel parsing
|
||||
- **Join Handles**: Waits for all parsing tasks to complete
|
||||
- **Mutex Protection**: Safe concurrent access to shared `AnalysisContext`
|
||||
- **Error Collection**: Parse errors are counted and reported; all-cars-failure propagates as an error
|
||||
|
||||
## File Organization
|
||||
|
||||
|
|
@ -170,7 +222,7 @@ Multiple validation layers ensure data quality:
|
|||
```
|
||||
├── cars_repo.json # Car database from repository
|
||||
├── tracks_repo.json # Track definitions
|
||||
├── track_names.json # Track code → name mappings (compiled in)
|
||||
├── track_names.json # Track code → name mappings (compiled into binary)
|
||||
├── my_cars.json # User's car collection (game export)
|
||||
└── times/ # Individual car timing files
|
||||
├── Ferrari_F40_1987.json
|
||||
|
|
@ -191,12 +243,13 @@ Multiple validation layers ensure data quality:
|
|||
|
||||
## Performance Characteristics
|
||||
|
||||
### Records Module
|
||||
- **Startup Time**: Fast (uses cached JSON files)
|
||||
- **Memory Usage**: Moderate (loads all timing data into memory)
|
||||
- **Accuracy**: High (comprehensive dataset)
|
||||
- **Update Frequency**: Manual refresh required
|
||||
- **Concurrency**: Bounded at 20 simultaneous operations via semaphore
|
||||
- **Lock Contention**: Minimal (DashMap sharded locking replaces global Mutex)
|
||||
- **Allocation Optimization**: `update_best_car` avoids cloning on the last RQ iteration
|
||||
|
||||
## Error Handling Strategy
|
||||
|
||||
|
|
@ -206,12 +259,13 @@ All errors are represented as `AppError`:
|
|||
- **Network Errors**: Download failures with descriptive messages
|
||||
- **Parse Errors**: JSON parsing failures with file name context
|
||||
- **Missing Files**: Specific variant for absent data files
|
||||
- **Poisoned Mutex**: Thread panic detection
|
||||
- **Internal Errors**: Runtime failures (semaphore closed, task join errors)
|
||||
|
||||
### Graceful Degradation
|
||||
- **Missing User Data**: `load_my_cars_or_warn()` returns empty data with a warning
|
||||
- **Network Errors**: Fetch errors logged but don't crash
|
||||
- **Parse Failures**: Individual car file failures emit warnings and skip
|
||||
- **Missing Timing Files**: Individual car file failures emit warnings and skip
|
||||
- **Network Errors**: Fetch errors logged; first error propagated
|
||||
- **All Parse Failure**: If every car fails to parse, the error is propagated
|
||||
|
||||
### User Feedback
|
||||
- **Progress Indication**: Shows fetching/download progress
|
||||
|
|
@ -225,15 +279,15 @@ All errors are represented as `AppError`:
|
|||
2. Add track name mapping in `track_names.json`
|
||||
3. Add condition mappings in `translate_track_type()` if needed
|
||||
|
||||
### Adding New Analysis Types
|
||||
1. Create new output function(s) using `write_sorted_csv`
|
||||
2. Add command variant to the `Commands` enum
|
||||
3. Add command handler in `main()`
|
||||
4. Update help text and documentation
|
||||
### Adding New Commands
|
||||
1. Create new command handler in `src/commands/`
|
||||
2. Add command variant to the `Commands` enum in `src/records.rs`
|
||||
3. Add dispatch case in the `run()` function
|
||||
4. Update documentation
|
||||
|
||||
### Adding New Output Formats
|
||||
1. Create new output function(s) alongside existing CSV writers
|
||||
2. Reuse `CarCounts` for data access
|
||||
1. Create new output function(s) in `src/output/mod.rs`
|
||||
2. Reuse `CarCounts` and `AnalysisContext` for data access
|
||||
3. Add CLI flag for format selection if needed
|
||||
|
||||
## Security Considerations
|
||||
|
|
@ -241,6 +295,7 @@ All errors are represented as `AppError`:
|
|||
### Network Access
|
||||
- **HTTPS Only**: All external requests use HTTPS
|
||||
- **Rate Limiting**: Respects server limits with controlled concurrency
|
||||
- **Atomic Writes**: Downloads write to `.tmp` files first, then rename on success
|
||||
|
||||
### Local File Access
|
||||
- **Read Only**: No modification of game files
|
||||
|
|
@ -255,8 +310,6 @@ All errors are represented as `AppError`:
|
|||
3. **Configuration Files**: External configuration for analysis parameters
|
||||
4. **GUI Interface**: Web-based interface for non-technical users
|
||||
5. **Export Formats**: Multiple output formats (JSON, XML, etc.)
|
||||
6. **Module Split**: Break `records.rs` into a module tree for better organization
|
||||
7. **Concurrent HashMap**: Use `DashMap` for finer-grained locking
|
||||
|
||||
### Scalability Considerations
|
||||
1. **Pagination**: Handle large datasets with pagination
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue