2 releases
| 0.8.6 | Dec 14, 2025 |
|---|---|
| 0.8.2 | Dec 14, 2025 |
#694 in Rust patterns
Used in 6 crates
27KB
259 lines
lighty-event
Event system for LightyLauncher - A simple, efficient broadcast-based event system for tracking launcher operations.
Features
- Async-first - Built on tokio's broadcast channels
- Multiple subscribers - Broadcast events to multiple listeners simultaneously
- Typed events - Strongly typed event system with compile-time safety
- Comprehensive - Events for authentication, downloads, installations, and more
- Progress tracking - Real-time progress updates for long-running operations
- Zero-cost - No overhead when events feature is disabled
- Error handling - Custom error types with thiserror
- Extensible - Extend with traits for ergonomic APIs
Event Categories
Authentication Events (AuthEvent)
AuthenticationStarted- Authentication process beginsAuthenticationInProgress- Ongoing authentication with step infoAuthenticationSuccess- Authentication succeededAuthenticationFailed- Authentication failed with errorAlreadyAuthenticated- Valid session already exists
Java Events (JavaEvent)
JavaNotFound- JRE not installedJavaAlreadyInstalled- JRE already presentJavaDownloadStarted- JRE download beginsJavaDownloadProgress- Download progress updatesJavaDownloadCompleted- Download finishedJavaExtractionStarted- Extraction beginsJavaExtractionProgress- Extraction progressJavaExtractionCompleted- Extraction finished
Launch Events (LaunchEvent)
IsInstalled- Files already up-to-dateInstallStarted- Installation beginsInstallProgress- Installation progressInstallCompleted- Installation finishedLaunching- Game launch startingLaunched- Game process spawnedNotLaunched- Launch failedProcessOutput- Game process outputProcessExited- Game process exited
Loader Events (LoaderEvent)
FetchingData- Fetching loader manifestDataFetched- Manifest retrievedManifestNotFound- Version not foundManifestCached- Using cached manifestMergingLoaderData- Merging loader dataDataMerged- Merge completed
Core Events (CoreEvent)
ExtractionStarted- Archive extraction beginsExtractionProgress- Extraction progressExtractionCompleted- Extraction finished
Installation
Add this to your Cargo.toml:
[dependencies]
lighty-event = "0.6"
Usage
Basic Example
use lighty_event::{EventBus, Event, LaunchEvent, EventReceiveError};
#[tokio::main]
async fn main() {
// Create event bus with buffer capacity
let event_bus = EventBus::new(1000);
// Subscribe to events
let mut receiver = event_bus.subscribe();
// Spawn listener task
tokio::spawn(async move {
loop {
match receiver.next().await {
Ok(event) => {
handle_event(event);
}
Err(EventReceiveError::BusDropped) => {
println!("Event bus closed");
break;
}
Err(EventReceiveError::Lagged { skipped }) => {
eprintln!("Warning: Missed {} events", skipped);
}
}
}
});
// Use the event bus with launcher operations...
}
fn handle_event(event: Event) {
match event {
Event::Launch(LaunchEvent::InstallStarted { version, total_bytes }) => {
println!("Installing {} ({} MB)", version, total_bytes / 1_000_000);
}
Event::Launch(LaunchEvent::InstallProgress { bytes }) => {
println!("Downloaded {} bytes", bytes);
}
Event::Java(JavaEvent::JavaDownloadStarted { distribution, version, total_bytes }) => {
println!("Downloading {} {} ({} MB)", distribution, version, total_bytes / 1_000_000);
}
_ => {}
}
}
With LightyLauncher
use lighty_launcher::{JavaDistribution, Launch, Loader, VersionBuilder};
use lighty_auth::{offline::OfflineAuth, Authenticator};
use lighty_event::{EventBus, Event, LaunchEvent};
use directories::ProjectDirs;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let launcher_dir = ProjectDirs::from("fr", ".LightyLauncher", "")
.expect("Failed to get project directories");
// Create event bus
let event_bus = EventBus::new(1000);
let mut receiver = event_bus.subscribe();
// Spawn event listener
tokio::spawn(async move {
while let Ok(event) = receiver.next().await {
match event {
Event::Launch(LaunchEvent::InstallProgress { bytes }) => {
println!("Progress: {} bytes", bytes);
}
Event::Launch(LaunchEvent::InstallCompleted { version, .. }) => {
println!("Installation of {} completed!", version);
}
_ => {}
}
}
});
// Authenticate
let mut auth = OfflineAuth::new("Player");
let profile = auth.authenticate(Some(&event_bus)).await?;
// Build and launch
let mut version = VersionBuilder::new(
"my-instance",
Loader::Vanilla,
"",
"1.21.1",
&launcher_dir
);
version.launch(&profile, JavaDistribution::Temurin)
.with_event_bus(&event_bus)
.run()
.await?;
Ok(())
}
Progress Tracking Example
use lighty_event::{EventBus, Event, LaunchEvent, JavaEvent};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
#[tokio::main]
async fn main() {
let event_bus = EventBus::new(1000);
let mut receiver = event_bus.subscribe();
let total_bytes = Arc::new(AtomicU64::new(0));
let downloaded_bytes = Arc::new(AtomicU64::new(0));
tokio::spawn({
let total = Arc::clone(&total_bytes);
let downloaded = Arc::clone(&downloaded_bytes);
async move {
while let Ok(event) = receiver.next().await {
match event {
Event::Launch(LaunchEvent::InstallStarted { total_bytes: t, .. }) => {
total.store(t, Ordering::Relaxed);
downloaded.store(0, Ordering::Relaxed);
}
Event::Launch(LaunchEvent::InstallProgress { bytes }) => {
downloaded.fetch_add(bytes, Ordering::Relaxed);
let d = downloaded.load(Ordering::Relaxed);
let t = total.load(Ordering::Relaxed);
if t > 0 {
let percent = (d as f64 / t as f64) * 100.0;
print!("\rProgress: {:.1}%", percent);
std::io::Write::flush(&mut std::io::stdout()).ok();
}
}
Event::Launch(LaunchEvent::InstallCompleted { .. }) => {
println!("\nInstallation completed!");
}
_ => {}
}
}
}
});
// Use launcher...
}
Multi-Subscriber Example
use lighty_event::{EventBus, Event, LaunchEvent};
#[tokio::main]
async fn main() {
let event_bus = EventBus::new(1000);
// UI subscriber
let mut ui_receiver = event_bus.subscribe();
tokio::spawn(async move {
while let Ok(event) = ui_receiver.next().await {
// Update UI with event
update_ui(event);
}
});
// Logger subscriber
let mut log_receiver = event_bus.subscribe();
tokio::spawn(async move {
while let Ok(event) = log_receiver.next().await {
// Log event to file
log_event(&event);
}
});
// Analytics subscriber
let mut analytics_receiver = event_bus.subscribe();
tokio::spawn(async move {
while let Ok(event) = analytics_receiver.next().await {
// Send analytics
send_analytics(event);
}
});
// All subscribers receive the same events
// Use launcher with event_bus...
}
fn update_ui(event: Event) { /* ... */ }
fn log_event(event: &Event) { /* ... */ }
fn send_analytics(event: Event) { /* ... */ }
Error Handling
The event system provides custom error types:
EventReceiveError
BusDropped- Event bus has been closedLagged { skipped }- Receiver fell behind, some events were missed
EventTryReceiveError
Empty- No events available (non-blocking)BusDropped- Event bus has been closedLagged { skipped }- Receiver fell behind
EventSendError
NoReceivers- No active receivers (event not sent)
Example with error handling:
use lighty_event::{EventReceiver, EventReceiveError};
async fn listen_events(mut receiver: EventReceiver) {
loop {
match receiver.next().await {
Ok(event) => {
// Handle event
println!("Received: {:?}", event);
}
Err(EventReceiveError::BusDropped) => {
eprintln!("Event bus closed, exiting listener");
break;
}
Err(EventReceiveError::Lagged { skipped }) => {
eprintln!("Warning: Receiver lagged, missed {} events", skipped);
// Continue listening, but some events were lost
}
}
}
}
Buffer Size Recommendations
The buffer size determines how many events can be buffered before older events are dropped:
- Small applications: 100-500 events
- Medium applications: 500-2000 events
- Large applications: 2000-5000 events
- Very slow receivers: 5000+ events
// Small buffer for simple use cases
let event_bus = EventBus::new(100);
// Larger buffer for complex applications with slow receivers
let event_bus = EventBus::new(5000);
If receivers are too slow and the buffer fills up, the oldest events will be dropped and receivers will get a Lagged error.
Non-Blocking Receive
For non-blocking event checking, use try_next():
use lighty_event::{EventReceiver, EventTryReceiveError};
fn poll_events(receiver: &mut EventReceiver) {
match receiver.try_next() {
Ok(event) => {
println!("Got event: {:?}", event);
}
Err(EventTryReceiveError::Empty) => {
// No events available right now
}
Err(EventTryReceiveError::BusDropped) => {
eprintln!("Event bus closed");
}
Err(EventTryReceiveError::Lagged { skipped }) => {
eprintln!("Missed {} events", skipped);
}
}
}
Feature Flags
The event system is optional and can be disabled:
[dependencies]
lighty-launcher = { version = "0.6", features = ["events"] }
lighty-event = "0.6"
When the events feature is disabled, event-related code is compiled out with zero overhead.
Serialization
All events implement Serialize and Deserialize (via serde), making them easy to:
- Send over the network
- Store in files
- Log to JSON
- Integrate with web APIs (in Tauri)
use lighty_event::{Event, LaunchEvent};
use serde_json;
let event = Event::Launch(LaunchEvent::InstallStarted {
version: "1.21.1".to_string(),
total_bytes: 1000000,
});
// Serialize to JSON
let json = serde_json::to_string(&event).unwrap();
println!("{}", json);
// Deserialize from JSON
let deserialized: Event = serde_json::from_str(&json).unwrap();
Module Structure
lighty-event/
├── src/
│ ├── lib.rs # EventBus, EventReceiver
│ ├── errors.rs # Error types
│ └── module/ # Event definitions
│ ├── mod.rs # Module exports
│ ├── auth.rs # AuthEvent
│ ├── core.rs # CoreEvent
│ ├── java.rs # JavaEvent
│ ├── launch.rs # LaunchEvent
│ └── loader.rs # LoaderEvent
└── README.md
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
Licensed under the MIT License. See LICENSE for details.
Related Crates
lighty-launcher- Main launcher cratelighty-auth- Authentication systemlighty-java- Java runtime managementlighty-core- Core utilities
Dependencies
~2–3.5MB
~54K SLoC