---
title: Multithreading
description: Generate packet-ready noise chunks concurrently without managing worker threads, caches, or lighting margins yourself.
---

`helpers.createNoiseChunkPool` moves procedural chunk generation and lighting to worker threads. The main server thread remains available for sockets and packet handling while the pool distributes missing chunks, deduplicates concurrent requests, and retains recent packet payloads in an LRU cache.

```ts
import { helpers } from "mc-protocol";

const chunks = helpers.createNoiseChunkPool(
  {
    seed: "mc-protocol",
    seaLevel: 63,
    baseHeight: 70,
    amplitude: 24,
    terrain: {
      ridgeStrength: 0.25,
      warpAmplitude: 24,
    },
    caves: true,
    ores: true,
    trees: { density: 0.02 },
  },
  {
    workers: "auto",
    maxCachedChunks: 2048,
  },
);
```

Create one pool for a world and reuse it for every player. Do not create a pool per connection.

## Generate and send a batch

`getMany` accepts chunk coordinates, generates only cache misses, and returns one `LevelChunkWithLight` payload per input coordinate in the same order. A request already being generated for another player shares the same work.

```ts
const missing = [
  [0, 0],
  [0, 1],
  [1, 0],
] as const;

const generated = await chunks.getMany(missing);

await client.sendPacket("ChunkBatchStart");

for (const chunk of generated) {
  await client.sendPacket("LevelChunkWithLight", chunk);
}

await client.sendPacket("ChunkBatchFinished", {
  batchSize: generated.length,
});
```

For one coordinate, use `get`:

```ts
const chunk = await chunks.get(chunkX, chunkZ);
await client.sendPacket("LevelChunkWithLight", chunk);
```

The pool automatically generates the one-chunk halo required by [`getLevelChunkWithLight`](/docs/chunks#lighting). Applications do not need to create a `ChunkHelper`, generate neighboring chunks, or convert data returned by the workers back into `Buffer` objects.

## Complete example

[View the complete multithreaded procedural-world server.](https://github.com/GDLev/mc-protocol/blob/master/examples/multithreaded-world.ts)

## Pool options

| Option | Default | Meaning |
| --- | --- | --- |
| `workers` | `"auto"` | Positive worker count, or automatic selection based on available parallelism. Automatic mode leaves one logical CPU for the main thread when possible and is capped at 8 workers. |
| `maxCachedChunks` | `512` | Maximum packet payloads retained by the main-thread LRU cache. `0` disables caching. |

The selected count is available as `chunks.workerCount`. Workers process spatially sorted groups so each temporary world can share neighboring terrain while calculating lighting.

The cache stores ready-to-send packet payloads, not mutable `ChunkHelper` worlds. Treat returned payloads as immutable. `clearCache()` removes retained payloads; it does not cancel generation already in progress.

## Lightweight generator queries

Every pool exposes the equivalent generator on the main thread. Use it for inexpensive queries such as choosing a spawn height:

```ts
const spawnY = Math.max(
  chunks.generator.heightAt(0, 0),
  chunks.generator.seaLevel,
) + 1;
```

Calling `chunks.generator.fillChunk` still runs synchronously. Use `get` or `getMany` for pooled generation.

## Worker-safe options

The pool accepts the same built-in noise settings described in [World generation](/docs/world-generation), with two exceptions:

- `radius` is omitted because the pool generates coordinates on demand.
- `terrain.heightAt` is not accepted because JavaScript functions cannot be copied into worker threads.

Seeds, block names, caves, ores, trees, and declarative structure templates can be copied safely. Invalid or otherwise non-cloneable options are rejected when the pool is created, before generation starts.

For a custom height callback or an arbitrary `ChunkGenerator`, use the synchronous [`generateInto`](/docs/world-generation#per-chunk-generation) API. The worker pool deliberately supports the built-in declarative noise generator rather than pretending that function closures can be transferred safely.

## Shutdown

Worker threads keep process resources alive. Close the pool when the server shuts down:

```ts
process.once("SIGINT", async () => {
  await chunks.close();
  process.exit(0);
});
```

`close()` stops new work, rejects unfinished requests, terminates every worker, and can safely be called more than once. Calls to `get` or `getMany` after closing reject with an error.

## Version binding

Like the other data-backed helpers, the pool respects version binding:

```ts
const h = helpers.forVersion(776);
const chunks = h.createNoiseChunkPool(
  { seed: "world" },
  { workers: 4 },
);
```

The root helper uses the default protocol version. Imports from `mc-protocol/26.2` and `mc-protocol/776` expose the same pool factory already bound to protocol 776. Dynamically registered versions are not supported by the pool because registrations made in the main JavaScript isolate are not automatically available inside worker isolates.
