lighty-event

Crates.iolighty-event
lib.rslighty-event
version0.8.6
created_at2025-12-14 03:13:02.066879+00
updated_at2025-12-14 07:25:25.807986+00
descriptionEvent system for LightyLauncher
homepage
repository
max_upload_size
id1983706
size29,695
Hamadi (Kalandi)

documentation

README

lighty-event

Crates.io Documentation License: MIT

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 begins
  • AuthenticationInProgress - Ongoing authentication with step info
  • AuthenticationSuccess - Authentication succeeded
  • AuthenticationFailed - Authentication failed with error
  • AlreadyAuthenticated - Valid session already exists

Java Events (JavaEvent)

  • JavaNotFound - JRE not installed
  • JavaAlreadyInstalled - JRE already present
  • JavaDownloadStarted - JRE download begins
  • JavaDownloadProgress - Download progress updates
  • JavaDownloadCompleted - Download finished
  • JavaExtractionStarted - Extraction begins
  • JavaExtractionProgress - Extraction progress
  • JavaExtractionCompleted - Extraction finished

Launch Events (LaunchEvent)

  • IsInstalled - Files already up-to-date
  • InstallStarted - Installation begins
  • InstallProgress - Installation progress
  • InstallCompleted - Installation finished
  • Launching - Game launch starting
  • Launched - Game process spawned
  • NotLaunched - Launch failed
  • ProcessOutput - Game process output
  • ProcessExited - Game process exited

Loader Events (LoaderEvent)

  • FetchingData - Fetching loader manifest
  • DataFetched - Manifest retrieved
  • ManifestNotFound - Version not found
  • ManifestCached - Using cached manifest
  • MergingLoaderData - Merging loader data
  • DataMerged - Merge completed

Core Events (CoreEvent)

  • ExtractionStarted - Archive extraction begins
  • ExtractionProgress - Extraction progress
  • ExtractionCompleted - 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 closed
  • Lagged { skipped } - Receiver fell behind, some events were missed

EventTryReceiveError

  • Empty - No events available (non-blocking)
  • BusDropped - Event bus has been closed
  • Lagged { 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

Commit count: 0

cargo fmt