nexus-async-rt 0.6.0

Single-threaded async executor with pre-allocated task storage
Documentation
# API Guide

How to use nexus-async-rt correctly. This covers the public API — what your
code looks like when you use this runtime.

## Quick Start

```rust
use nexus_async_rt::{Runtime, spawn_boxed};
use nexus_rt::WorldBuilder;

fn main() {
    let wb = WorldBuilder::new();
    let mut world = wb.build();
    let mut rt = Runtime::builder(&mut world).build();

    rt.block_on(async {
        let handle = spawn_boxed(async { 42 });
        let result = handle.await;
        assert_eq!(result, 42);
    });
}
```

## Building the Runtime

`RuntimeBuilder` configures the runtime before construction. All settings
have sensible defaults — you only need to set what you want to tune.

```rust
let mut rt = Runtime::builder(&mut world)
    .tasks_per_cycle(128)           // poll up to 128 tasks before checking IO
    .event_interval(31)             // check IO every 31 ticks (default: 61)
    .signal_handlers(true)          // install SIGTERM/SIGINT handlers
    .build();
```

### When to tune `tasks_per_cycle`

Higher values = more tasks processed per loop iteration = higher throughput.
Lower values = IO checked more frequently = lower IO latency. Default (64)
is a reasonable balance. For IO-heavy workloads (many sockets), lower it.
For compute-heavy workloads (many CPU tasks), raise it.

### When to tune `event_interval`

Controls how often `epoll(timeout=0)` is called between task batches. Lower
= more IO checks = lower IO tail latency but more syscalls. Default (61,
a prime to avoid resonance) is good for most workloads.

## Running the Event Loop

Two entry points. Choose based on your latency/CPU tradeoff:

```rust
// Parks thread when idle. CPU-friendly. Good for mixed workloads.
rt.block_on(async { ... });

// Spins when idle. Minimum wake latency. Burns a core.
rt.block_on_busy(async { ... });
```

Both return the root future's output. The runtime drives IO, timers, and
spawned tasks until the root future completes.

## Spawning Tasks

### Box-allocated (default)

```rust
use nexus_async_rt::spawn_boxed;

let handle = spawn_boxed(async {
    // your async work
    42
});
let result = handle.await; // JoinHandle<i32>
```

### Fire-and-forget

```rust
// Drop the handle — task runs to completion but output is discarded.
drop(spawn_boxed(async { do_work().await }));
```

### Slab-allocated (zero-alloc hot path)

Configure a slab at build time:

```rust
use nexus_slab::byte::unbounded::Slab;

let slab = unsafe { Slab::<256>::with_chunk_capacity(1024) };
let mut rt = Runtime::builder(&mut world)
    .slab_unbounded(slab)
    .build();
```

Then spawn into the slab:

```rust
use nexus_async_rt::spawn_slab;

let handle = spawn_slab(async { fast_path_work().await });
```

The `256` is the slot size in bytes. Must be >= 64 (task header size).
The future + output must fit in the remaining bytes.

### Two-phase slab allocation

For maximum control — reserve the slot first, spawn later:

```rust
use nexus_async_rt::{claim_slab, try_claim_slab};

// Guaranteed slot (panics if full):
let claim = claim_slab();
let handle = claim.spawn(async { ... });

// Non-blocking (returns None if full):
if let Some(claim) = try_claim_slab() {
    let handle = claim.spawn(async { ... });
} else {
    // fallback: spawn_boxed
}
```

Dropping a `SlabClaim` without calling `.spawn()` returns the slot to the
freelist. No leak.

## Timers

```rust
use nexus_async_rt::{sleep, timeout, interval};
use std::time::Duration;

// Sleep
sleep(Duration::from_millis(100)).await;

// Timeout a future
match timeout(some_future, Duration::from_secs(5)).await {
    Ok(result) => { /* completed in time */ }
    Err(_elapsed) => { /* timed out */ }
}

// Periodic interval
let mut ticker = interval(Duration::from_millis(10));
loop {
    ticker.next().await;
    do_periodic_work();
}
```

## IO (TCP/UDP)

IO uses mio under the hood. Register sockets, await readiness.

```rust
use nexus_async_rt::net::{TcpListener, TcpStream};

let listener = TcpListener::bind("0.0.0.0:8080".parse()?)?;
let (stream, addr) = listener.accept().await?;

let mut buf = [0u8; 1024];
let n = stream.read(&mut buf).await?;
stream.write_all(&buf[..n]).await?;
```

## World Access

Access nexus-rt ECS resources from async tasks:

```rust
use nexus_async_rt::with_world;

// Synchronous, inline during task poll — no await point.
with_world(|world| {
    let config = world.resource::<Config>();
    println!("setting: {}", config.value);
});
```

### Pre-resolved handlers (hot path)

For event dispatch where HashMap lookup cost matters:

```rust
// At setup (cold path) — resolve resource IDs once:
let mut on_quote = (|mut books: ResMut<Books>, q: Quote| {
    books.update(q);
}).into_handler(world.registry());

// Per-event (hot path) — single deref per resource:
with_world(|world| on_quote.run(world, quote));
```

## Channels

In-process async channels for task-to-task communication:

```rust
use nexus_async_rt::channel::local;

// Bounded, single-producer single-consumer
let (tx, rx) = local::channel::<Quote>(64);

spawn_boxed(async move {
    tx.send(quote).await.unwrap();
});

spawn_boxed(async move {
    let quote = rx.recv().await.unwrap();
});
```

Also available: `spsc`, `mpsc` (typed), `spsc_bytes`, `mpsc_bytes` (byte buffers).

## Cancellation

Cooperative cancellation with hierarchical propagation:

```rust
use nexus_async_rt::CancellationToken;

let token = CancellationToken::new();
let child = token.child(); // cancelled when parent is

spawn_boxed({
    let token = token.clone();
    async move {
        token.cancelled().await;
        println!("shutting down");
    }
});

token.cancel(); // cancels token + all children
```

## Shutdown

For signal-based shutdown (SIGTERM/SIGINT):

```rust
let mut rt = Runtime::builder(&mut world)
    .signal_handlers(true)
    .build();

rt.block_on(async {
    // Completes when SIGTERM or SIGINT received
    nexus_async_rt::shutdown_signal().await;
    cleanup().await;
});
```

Single-waiter design. For multi-waiter shutdown, use `CancellationToken`.

## Tokio Bridge

For cold-path work that needs the tokio ecosystem (reqwest, database
drivers, etc.):

```rust
use nexus_async_rt::tokio_compat::{with_tokio, spawn_on_tokio};

// Run tokio future on our executor (tokio provides reactor):
let stream = with_tokio(|| {
    tokio::net::TcpStream::connect("127.0.0.1:6379")
}).await?;

// Run on tokio's thread pool, get result back:
let response = spawn_on_tokio(async {
    reqwest::get("https://api.example.com/data").await
}).await?;
```

Requires the `tokio-compat` feature.

## Common Patterns

### Gateway event loop

```rust
rt.block_on(async {
    let listener = TcpListener::bind(addr)?;
    let token = CancellationToken::new();

    loop {
        tokio::select! {
            conn = listener.accept() => {
                let (stream, _) = conn?;
                let token = token.child();
                spawn_boxed(handle_connection(stream, token));
            }
            _ = shutdown_signal() => {
                token.cancel();
                break;
            }
        }
    }
});
```

### Market data processing

```rust
let mut on_quote = quote_handler.into_handler(world.registry());
let mut on_trade = trade_handler.into_handler(world.registry());

rt.block_on(async {
    loop {
        let msg = ws.recv().await?;
        match msg.msg_type() {
            MsgType::Quote => with_world(|w| on_quote.run(w, parse_quote(&msg))),
            MsgType::Trade => with_world(|w| on_trade.run(w, parse_trade(&msg))),
        }
    }
});
```