lighty-launch 26.5.10

Minecraft launch logic for Lighty Launcher
Documentation
# How to use `lighty-launch`

The minimal flow is four steps: init `AppState`, build an instance,
authenticate, call `.launch().run()`.

```rust
use lighty_auth::{offline::OfflineAuth, Authenticator};
use lighty_core::AppState;
use lighty_java::JavaDistribution;
use lighty_launch::launch::Launch;
use lighty_loaders::types::Loader;
use lighty_version::VersionBuilder;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    AppState::init("MyLauncher")?;

    let mut instance = VersionBuilder::new(
        "fabric-1.21",
        Loader::Fabric,
        "0.16.9",
        "1.21.1",
    );

    let mut auth    = OfflineAuth::new("Player");
    let     profile = auth.authenticate(
        #[cfg(feature = "events")] None,
    ).await?;

    instance
        .launch(&profile, JavaDistribution::Temurin)
        .run()
        .await?;
    Ok(())
}
```

The four parts have canonical docs elsewhere — this page just shows
how they fit together. For more `VersionBuilder` patterns see
[`crates/version/docs/how-to-use.md`](../../version/docs/how-to-use.md);
for `AppState` paths see
[`crates/core/docs/app_state.md`](../../core/docs/app_state.md);
for `OfflineAuth` / `MicrosoftAuth` / `AzuriomAuth` see
[`crates/auth/docs/how-to-use.md`](../../auth/docs/how-to-use.md).

## Tuning JVM and game args

`.launch(...)` returns a [`LaunchBuilder`](./launch.md#launchbuilder-api)
exposing two sub-builders. The full placeholder list and option
catalogue live in [arguments.md](./arguments.md); the common knobs:

```rust
# use lighty_auth::UserProfile;
# use lighty_core::AppState;
# use lighty_java::JavaDistribution;
# use lighty_launch::errors::InstallerResult;
# use lighty_launch::launch::Launch;
# use lighty_loaders::types::Loader;
# use lighty_version::VersionBuilder;
# async fn run() -> InstallerResult<()> {
# AppState::init("MyLauncher").ok();
# let profile = UserProfile::offline("Player", "");
# let mut instance = VersionBuilder::new("inst", Loader::Vanilla, "", "1.21.1");
instance
    .launch(&profile, JavaDistribution::Temurin)
    .with_jvm_options()
        .set("Xmx", "4G")               // -Xmx4G
        .set("Xms", "2G")               // -Xms2G
        .set("XX:+UseG1GC", "")         // -XX:+UseG1GC
        .done()
    .with_arguments()
        .set("width",  "1920")          // --width 1920
        .set("height", "1080")          // --height 1080
        .done()
    .run()
    .await?;
# Ok(()) }
```

`set(key, "")` for value-less flags. `.remove(key)` strips an option
even if the loader metadata injected it. Custom keys default to
`--key value` form unless they're a known launch placeholder constant
(see [arguments.md](./arguments.md#standard-placeholders)).

## Tracking progress with events

Enable the `events` feature and pass an `EventBus` via
`.with_event_bus(&bus)`. The launch crate emits two families
(`LaunchEvent` for install / spawn / process I/O,
`ModloaderEvent` for mod resolution / modpack / resource-shader-datapack
buckets) — full catalogue and example listener in
[events.md](./events.md).

```rust
# #[cfg(feature = "events")]
# {
use lighty_event::{Event, EventBus, LaunchEvent};
use lighty_launch::launch::Launch;
# use lighty_auth::UserProfile;
# use lighty_core::AppState;
# use lighty_java::JavaDistribution;
# use lighty_launch::errors::InstallerResult;
# use lighty_loaders::types::Loader;
# use lighty_version::VersionBuilder;
# async fn run() -> InstallerResult<()> {
# AppState::init("MyLauncher").ok();
# let profile = UserProfile::offline("Player", "");
# let mut instance = VersionBuilder::new("inst", Loader::Vanilla, "", "1.21.1");
let bus = EventBus::new(1000);
let mut rx = bus.subscribe();

tokio::spawn(async move {
    while let Ok(event) = rx.next().await {
        if let Event::Launch(LaunchEvent::ProcessOutput { pid, line, .. }) = event {
            print!("[{pid}] {line}");
        }
    }
});

instance.launch(&profile, JavaDistribution::Temurin)
    .with_event_bus(&bus)
    .run()
    .await?;
# Ok(()) }
# }
```

## Inspect or close the running game

`InstanceControl` is auto-implemented for every `VersionInfo`. **You
must import the trait** to use its methods:

```rust
# use lighty_auth::UserProfile;
# use lighty_core::AppState;
# use lighty_java::JavaDistribution;
# use lighty_launch::errors::InstallerResult;
# use lighty_launch::launch::Launch;
# use lighty_loaders::types::Loader;
# use lighty_version::VersionBuilder;
use lighty_launch::InstanceControl;

# async fn run() -> InstallerResult<()> {
# AppState::init("MyLauncher").ok();
# let profile = UserProfile::offline("Player", "");
# let mut instance = VersionBuilder::new("inst", Loader::Vanilla, "", "1.21.1");
# instance.launch(&profile, JavaDistribution::Temurin).run().await?;
if let Some(pid) = instance.get_pid() {
    println!("running with PID {pid}");
    // SIGTERM on Unix, taskkill /F on Windows. JVM runs its shutdown hooks.
    instance.close_instance(pid).await?;
}

// Delete the instance from disk. Errors if any PID is still tracked.
// instance.delete_instance().await?;
# Ok(()) }
```

Disk-size breakdown (libraries / mods / assets / client / natives) is
exposed by `instance.size_of_instance(&version)` — see
[instance-control.md](./instance-control.md) for the full API.

## Installation without launch

`Installer` is also a standalone trait. The runner calls it for you,
but you can drive it directly for "download now, play later" flows:

```rust
# use lighty_core::AppState;
# use lighty_launch::errors::InstallerResult;
# use lighty_loaders::types::version_metadata::VersionMetaData;
# use lighty_loaders::types::Loader;
# use lighty_version::VersionBuilder;
use lighty_launch::installer::Installer;

# async fn run() -> InstallerResult<()> {
# AppState::init("MyLauncher").ok();
# let mut instance = VersionBuilder::new("inst", Loader::Vanilla, "", "1.21.1");
let metadata = instance.get_metadata().await?;
if let VersionMetaData::Version(v) = metadata.as_ref() {
    instance.install(v, #[cfg(feature = "events")] None).await?;
}
# Ok(()) }
```

See [installation.md](./installation.md) for the pipeline detail.

## Error handling

Two error types — `InstallerError` for everything install / launch
pipeline related, `InstanceError` for manager-level operations
(`close`, `delete`):

```rust
use lighty_launch::errors::{InstallerError, InstanceError};
# use lighty_auth::UserProfile;
# use lighty_core::AppState;
# use lighty_java::JavaDistribution;
# use lighty_launch::launch::Launch;
# use lighty_launch::InstanceControl;
# use lighty_loaders::types::Loader;
# use lighty_version::VersionBuilder;
# async fn run() -> anyhow::Result<()> {
# AppState::init("MyLauncher").ok();
# let profile = UserProfile::offline("Player", "");
# let mut instance = VersionBuilder::new("inst", Loader::Vanilla, "", "1.21.1");

match instance.launch(&profile, JavaDistribution::Temurin).run().await {
    Ok(_)                                       => println!("launched"),
    Err(InstallerError::DownloadFailed(msg))    => eprintln!("download: {msg}"),
    Err(InstallerError::NoPid)                  => eprintln!("spawn succeeded but no PID"),
    Err(e)                                      => eprintln!("{e}"),
}

match instance.delete_instance().await {
    Ok(_)                                                     => println!("deleted"),
    Err(InstanceError::StillRunning { instance_name, pids })  => {
        eprintln!("close PIDs first: {instance_name} {pids:?}");
    }
    Err(e)                                                    => eprintln!("{e}"),
}
# Ok(()) }
```

## Related

- [Overview]./overview.md, [Launch]./launch.md
- [Installation]./installation.md, [Instance lifecycle]./instance-lifecycle.md
- [Instance control]./instance-control.md, [Arguments]./arguments.md
- [Events]./events.md, [Exports]./exports.md
- Auth: [`crates/auth/docs/how-to-use.md`]../../auth/docs/how-to-use.md
- `VersionBuilder`: [`crates/version/docs/how-to-use.md`]../../version/docs/how-to-use.md
- Loaders: [`crates/loaders/docs/overview.md`]../../loaders/docs/overview.md