Skip to content

Latest commit

 

History

History
221 lines (175 loc) · 7.01 KB

File metadata and controls

221 lines (175 loc) · 7.01 KB

Launch

The launch pipeline ties everything together: fetch loader metadata, ensure Java is present, install the 8 buckets in parallel, build the argv, spawn the JVM, register the PID, stream stdio.

.launch(profile, java).run()
    │
    ▼
1. Prepare metadata          (loader-specific HTTP fetch + parse)
2. Ensure Java installed     (download if missing; lighty-java)
3. Install dependencies      (8 parallel buckets; see installation.md)
4. Forge/NeoForge post-hook  (install_profile libs + processors)
5. Build argv                (placeholders + JVM + game args)
6. Spawn JVM                 (Command::spawn; register PID)
7. Stream stdio + lifecycle  (see instance-lifecycle.md)

The Launch trait

Adds .launch(...) to any installable instance:

pub trait Launch {
    fn launch<'a>(
        &'a mut self,
        profile: &'a UserProfile,
        java_distribution: JavaDistribution,
    ) -> LaunchBuilder<'a, Self>
    where Self: Sized;
}

Blanket-implemented for every type that satisfies the pipeline's bounds (VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer + WithMods) — that's VersionBuilder and LightyVersionBuilder out of the box. WithMods returns an empty slice on vanilla instances, so it's free.

LaunchBuilder API

pub struct LaunchBuilder<'a, T> { /* … */ }

impl<'a, T> LaunchBuilder<'a, T>
where T: VersionInfo<LoaderType = Loader> + LoaderExtensions + Arguments + Installer + WithMods
{
    pub fn with_jvm_options(self) -> JvmOptionsBuilder<'a, T>;
    pub fn with_arguments  (self) -> ArgumentsBuilder<'a, T>;

    #[cfg(feature = "events")]
    pub fn with_event_bus(self, bus: &'a EventBus) -> Self;

    pub async fn run(self) -> InstallerResult<()>;
}

with_jvm_options() / with_arguments() open sub-builders that take .set(key, value) / .remove(key) / .done() to return to the parent. See arguments.md for the placeholder catalogue.

Step-by-step

1. Prepare metadata

Dispatches to the loader's get_metadata() (Vanilla, Fabric, Quilt, Forge, NeoForge, …). With events enabled, emits LoaderEvent::FetchingData then LoaderEvent::DataFetched. Per-loader detail and the actual HTTP endpoints live in crates/loaders/docs/loaders/.

2. Ensure Java installed

Extracts version.java_version.major_version from the metadata, asks lighty-java for a matching JRE. If missing, downloads from the requested distribution (Temurin, Zulu, Graal, Liberica) and extracts to java_dirs()/jre/java-<v>/. Emits JavaEvent::{JavaAlreadyInstalled, JavaNotFound, …}.

3. Install dependencies

8-way parallel tokio::try_join! across libraries, natives, client JAR, assets, mods, resource packs, shader packs, datapacks. Optional modpack pre-step runs before user-mod resolution. Full pipeline + SHA1 verification + per-bucket layout in installation.md.

4. Forge / NeoForge post-hook

For Loader::Forge (modern, 1.13+) and Loader::NeoForge: download the install_profile.json libraries through the shared library installer, then run the install processors. Legacy Forge (1.7.10 – 1.12.2) skips processors — the universal JAR ships inside the installer and is extracted to its Maven path. Per-loader detail in crates/loaders/docs/loaders/forge.md and neoforge.md.

The forge Cargo feature covers both modern and legacy Forge in a single switch — there's no separate forge_legacy feature.

5. Build argv

Variable map → JVM args (defaults if metadata is empty) → critical JVM injections (-Djava.library.path=..., launcher brand / version, -cp ...) → game args. Detailed in arguments.md; the access-token routing (in particular how the keyring feature keeps the secret out of process memory) is documented there too.

6. Spawn JVM

Command::new(java_path)
    .args(arguments)
    .current_dir(builder.game_dirs())
    .stdout(Stdio::piped())
    .stderr(Stdio::piped())
    .spawn()?;

Emits LaunchEvent::Launched { version, pid } on success (or NotLaunched { error } on failure). InstallerError::NoPid is returned if child.id() is None.

7. Register + stream

The new GameInstance is registered in the global InstanceManager keyed by PID. A dedicated task takes ownership of the Child and:

  • streams stdout/stderr line-by-line as LaunchEvent::ProcessOutput { pid, stream, line },
  • waits on the exit code,
  • emits LaunchEvent::ProcessExited { pid, exit_code },
  • unregisters the instance from the manager.

Full state-machine + manager internals: instance-lifecycle.md.

Platform differences

Platform Java exec Classpath sep Process kill Natives
Windows java.exe ; taskkill /PID {pid} /F …-natives-windows.jar
Linux java : kill -SIGTERM {pid} …-natives-linux.jar
macOS java : kill -SIGTERM {pid} …-natives-macos.jar

-XstartOnFirstThread is auto-injected on macOS (LWJGL / GLFW requirement).

Complete example

use lighty_auth::{offline::OfflineAuth, Authenticator};
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;

#[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("Player123");
    let     profile = auth.authenticate(
        #[cfg(feature = "events")] None,
    ).await?;

    instance.launch(&profile, JavaDistribution::Temurin)
        .with_jvm_options()
            .set("Xmx", "4G")
            .set("XX:+UseG1GC", "")
            .done()
        .with_arguments()
            .set("width",  "1920")
            .set("height", "1080")
            .done()
        .run()
        .await?;

    if let Some(pid) = instance.get_pid() {
        println!("Running with PID {pid}");
    }
    Ok(())
}

Error handling

pub enum InstallerError {
    DownloadFailed(String),
    VerificationFailed(String),
    ExtractionFailed(String),
    InvalidMetadata,
    NoPid,
    IOError(std::io::Error),
    // …
}

InstanceError is separate (manager-level), documented in instance-control.md.

Related