mlua-isle 0.3.0

Thread-isolated Lua VM with cancellation and async bridge for mlua
Documentation
# mlua-isle

Thread-isolated Lua VM with cancellation and async bridge for [mlua](https://crates.io/crates/mlua).

## Problem

`mlua::Lua` is `!Send` — it cannot cross thread boundaries. This makes it
difficult to use from async runtimes, UI threads, or any multi-threaded context.

**mlua-isle** solves this by confining the Lua VM to a dedicated thread and
communicating via channels.

## Features

- **Thread isolation** — Lua VM runs on a dedicated thread; callers interact
  via a `Send + Sync` handle
- **Cancellation** — long-running Lua code can be interrupted via `CancelToken`
  using a Lua debug hook
- **Sync API** — blocking `Isle` handle with `Task` for non-blocking usage
- **Async API** (optional, `tokio` feature) — `AsyncIsle` handle with
  Handle/Driver separation, bounded channel backpressure, and `AsyncTask<T>`
  which implements `Future`
- **Zero unsafe in user code** — both `Isle` and `AsyncIsle` are safe to
  share across threads

## Architecture

### Sync (`Isle`)

```text
┌─────────────────┐  std mpsc  ┌──────────────────┐
│  caller thread   │──────────►│  Lua thread       │
│                  │           │  (mlua confined)   │
│  Isle handle     │◄──────────│                    │
│                  │  oneshot   │  Lua VM + hook    │
└─────────────────┘            └──────────────────┘
```

### Async (`AsyncIsle`, requires `tokio` feature)

```text
┌──────────────────┐                ┌──────────────────┐
│  tokio tasks      │  tokio mpsc   │  Lua thread       │
│                   │──────────────►│  (mlua confined)   │
│  AsyncIsle handle │  (bounded,    │                    │
│  (Clone, no Arc)  │  backpressure)│  Lua VM + hook    │
│                   │◄──────────────│                    │
│                   │   oneshot     │                    │
├──────────────────┤                │                    │
│  AsyncIsleDriver  │───done_tx────►│                    │
│  (lifecycle owner)│               └──────────────────┘
└──────────────────┘
```

- **Handle** (`AsyncIsle`) — lightweight, cloneable. Share across tasks
  without `Arc`.
- **Driver** (`AsyncIsleDriver`) — sole lifecycle owner. Call
  `shutdown().await` for clean thread join, or drop to let the channel-close
  mechanism terminate the thread naturally.

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
mlua-isle = "0.3"

# For async support:
# mlua-isle = { version = "0.3", features = ["tokio"] }
```

### Sync API

```rust
use mlua_isle::Isle;

let isle = Isle::spawn(|lua| {
    lua.globals().set("greeting", "hello")?;
    Ok(())
}).unwrap();

let result: String = isle.eval("return greeting").unwrap();
assert_eq!(result, "hello");

isle.shutdown().unwrap();
```

### Async API

```rust
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;

let (isle, driver) = AsyncIsle::spawn(|lua| {
    lua.globals().set("greeting", "hello")?;
    Ok(())
}).await?;

// Clone freely — no Arc needed.
let isle2 = isle.clone();

let result: String = isle.eval("return greeting").await?;
assert_eq!(result, "hello");

driver.shutdown().await?;
# Ok(())
# }
```

### Cancellation (sync)

```rust
use mlua_isle::Isle;
use std::time::Duration;
use std::thread;

let isle = Isle::spawn(|_| Ok(())).unwrap();

let task = isle.spawn_eval("while true do end");

thread::sleep(Duration::from_millis(50));
task.cancel();

let result = task.wait();
assert!(result.is_err()); // IsleError::Cancelled
```

### Cancellation (async)

```rust
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
use mlua_isle::AsyncIsle;
use std::time::Duration;

let (isle, driver) = AsyncIsle::spawn(|_lua| Ok(())).await?;
let task = isle.spawn_eval("while true do end");

let token = task.cancel_token().clone();
tokio::spawn(async move {
    tokio::time::sleep(Duration::from_millis(100)).await;
    token.cancel();
});

let result = task.await; // Err(Cancelled)
assert!(result.is_err());
driver.shutdown().await?;
# Ok(())
# }
```

## API

### Sync (`Isle`)

| Method | Description |
|--------|-------------|
| `Isle::spawn(init)` | Create a Lua VM on a dedicated thread |
| `isle.eval(code)` | Evaluate a Lua chunk (blocking) |
| `isle.call(func, args)` | Call a global Lua function (blocking) |
| `isle.exec(closure)` | Run an arbitrary closure on the Lua thread |
| `isle.spawn_eval(code)` | Non-blocking eval, returns a `Task` |
| `isle.spawn_call(func, args)` | Non-blocking call, returns a `Task` |
| `isle.spawn_exec(closure)` | Non-blocking exec, returns a `Task` |
| `isle.shutdown()` | Graceful shutdown and thread join |
| `task.wait()` | Block until the task completes |
| `task.cancel()` | Cancel the running task |

### Async (`AsyncIsle`, `tokio` feature)

| Method | Description |
|--------|-------------|
| `AsyncIsle::spawn(init)` | Create a Lua VM, returns `(AsyncIsle, AsyncIsleDriver)` |
| `AsyncIsle::builder()` | Configure channel capacity / thread name |
| `isle.eval(code)` | Evaluate a Lua chunk (async) |
| `isle.call(func, args)` | Call a global Lua function (async) |
| `isle.exec(closure)` | Run a closure on the Lua thread (async) |
| `isle.spawn_eval(code)` | Returns a cancellable `AsyncTask` |
| `isle.spawn_call(func, args)` | Returns a cancellable `AsyncTask` |
| `isle.spawn_exec(closure)` | Returns a cancellable `AsyncTask` |
| `driver.shutdown().await` | Graceful shutdown and thread join |
| `task.cancel()` | Cancel the running task |
| `task.cancel_token()` | Access the `CancelToken` for sharing |

## Minimum Supported Rust Version

Rust 1.77 or later.

## License

Licensed under either of

- [MIT license]LICENSE-MIT
- [Apache License, Version 2.0]LICENSE-APACHE

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.