lighty-java 26.5.7

Java runtime management for Lighty Launcher
Documentation
# Runtime — spawn + stream a `java` process

`JavaRuntime` is a thin wrapper around `tokio::process::Command`. It
spawns the binary, returns the `Child`, and offers `handle_io` to
stream stdout / stderr until the process exits (or a one-shot
terminator fires).

## API

```rust,ignore
pub struct JavaRuntime(pub PathBuf);

impl JavaRuntime {
    pub fn new(path: PathBuf) -> Self;

    pub async fn execute(
        &self,
        arguments: Vec<String>,
        game_dir: &Path,
    ) -> JavaRuntimeResult<tokio::process::Child>;

    pub async fn handle_io<D: Send + Sync>(
        &self,
        process:    &mut tokio::process::Child,
        on_stdout:  fn(&D, &[u8]) -> JavaRuntimeResult<()>,
        on_stderr:  fn(&D, &[u8]) -> JavaRuntimeResult<()>,
        terminator: tokio::sync::oneshot::Receiver<()>,
        data:       &D,
    ) -> JavaRuntimeResult<()>;
}
```

`game_dir` becomes the process's working directory (this is what gets
passed to Minecraft via `${game_directory}`).

`handle_io` takes **function pointers** (not closures) so they can
cross the Tokio `select!` cleanly. The `data` argument is an opaque
context bag — pass whatever the callbacks need to share (a logger
handle, an `EventBus`, …).

Windows-only detail: `execute` sets `CREATE_NO_WINDOW` so spawned
processes don't pop up a console window.

## Examples

### Print `java -version`

```rust,no_run
use lighty_java::runtime::JavaRuntime;
use tokio::sync::oneshot;
use std::path::Path;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let rt = JavaRuntime::new("/usr/bin/java".into());
    let mut child = rt.execute(vec!["-version".into()], Path::new(".")).await?;

    let (_tx, rx) = oneshot::channel();
    rt.handle_io::<()>(
        &mut child,
        |_, b| { print!("{}",  String::from_utf8_lossy(b)); Ok(()) },
        |_, b| { eprint!("{}", String::from_utf8_lossy(b)); Ok(()) },
        rx,
        &(),
    ).await?;
    Ok(())
}
```

`java -version` writes to stderr — both callbacks get hit.

### Launch a JAR with memory tuning

```rust,no_run
use lighty_java::runtime::JavaRuntime;
use tokio::sync::oneshot;
use std::path::Path;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let rt = JavaRuntime::new("/path/to/java".into());

    let args = vec![
        "-Xmx4G".into(),
        "-Xms1G".into(),
        "-XX:+UseG1GC".into(),
        "-jar".into(),
        "minecraft.jar".into(),
    ];
    let mut child = rt.execute(args, Path::new("/games/minecraft")).await?;

    let (term_tx, term_rx) = oneshot::channel();
    let io = rt.handle_io::<()>(
        &mut child,
        |_, b| { print!("{}",  String::from_utf8_lossy(b)); Ok(()) },
        |_, b| { eprint!("{}", String::from_utf8_lossy(b)); Ok(()) },
        term_rx,
        &(),
    );

    // Fire `term_tx` from elsewhere to bail out early
    let _ = term_tx;
    io.await?;
    Ok(())
}
```

### Wait for the exit code

`handle_io` returns once the process exits or the terminator fires.
The exit code is available via `child.wait().await?`:

```rust,no_run
# use lighty_java::runtime::JavaRuntime;
# use tokio::sync::oneshot;
# use std::path::Path;
# async fn run() -> anyhow::Result<()> {
let rt = JavaRuntime::new("/usr/bin/java".into());
let mut child = rt.execute(vec!["-version".into()], Path::new(".")).await?;
let (_tx, rx) = oneshot::channel();
rt.handle_io::<()>(&mut child,
    |_, _| Ok(()), |_, _| Ok(()), rx, &()).await?;

let status = child.wait().await?;
println!("exit code: {:?}", status.code());
# Ok(()) }
```

## Errors

```rust,ignore
pub enum JavaRuntimeError {
    NotFound { path: PathBuf },
    NonZeroExit { code: i32 },
    IoCaptureFailure,                    // stdout/stderr couldn't be captured
    Spawn(std::io::Error),
    SignalTerminated,
}
```

The Windows forceful-termination code `-1073740791` (0xC0000409) is
treated as a normal exit, not an error.

## How `lighty-launch` uses it

`lighty-launch::launcher::Launcher` calls `execute` with the full
launch argv (built from `Arguments`) and pipes `handle_io` into the
event bus — every line becomes a `ConsoleOutputEvent` and the final
exit triggers `InstanceExited`. The same `oneshot` terminator is
hooked to the cancel button in the host UI.

## See also

- [`overview.md`]./overview.md — crate scope
- [`installation.md`]./installation.md — get a binary path first
- [`../../launch/docs/launch.md`]../../launch/docs/launch.md — how
  `Launcher` builds the argv
- [`../../launch/docs/arguments.md`]../../launch/docs/arguments.md  JVM arg construction