
# tecs.workers


Worker threads and channels.

A worker is source text and two queues. It shares nothing with the state
that spawned it, so it reaches its own ends through `workers.current`, and
`spawn` pairs with `stop`:

```teal
local worker <const> = tecs.workers.spawn({
    source = [[
    local tecs <const> = require("tecs")
    local self <const> = tecs.workers.current()
    while true do
        local job = self:receive()
        if job == nil then break end
        self:send({name = job.name, hash = tecs.data.fnv1a64(job.bytes)})
    end
]],
})

worker:send({name = "level1", bytes = "..."})
local answer <const> = worker:receive(1000)
worker:stop()
```

The worker's source writes a require line because it is a separate state
with a separate global table, which is the one place in a game that does.

This is the only sanctioned way to run work off the main thread. Raw
thread creation is deliberately not exposed: a LuaJIT FFI callback invoked
from a thread the VM did not create is unsafe, and a thread entry point
written in Lua is exactly that mistake.

LuaJIT has no shared mutable heap across threads, so a worker cannot see
the spawning state's objects at all. Values cross as serialized bytes,
encoded here with `string.buffer`.

What can cross is therefore what `string.buffer` can encode: numbers,
strings, booleans, and tables of those. Not functions, not userdata, and
not cdata pointers into another state's heap.

## Polling and shutdown

The spawner's `receive` polls by default, which suits a frame. Inside the
worker it waits by default, so an idle worker does not spin. Closing the inbox
wakes the worker and returns nil after queued messages drain.

`stop` closes the inbox, joins the thread, and releases both channels. Worker
source must leave its receive loop when the inbox closes or shutdown can wait
indefinitely.

Set `TECS_TRACEPROF` to print a worker's trace aborts when its inbox closes.
Repeated `failed to allocate mcode memory` messages identify LuaJIT
machine-code allocation failures.

## Module contents

### Constructors

| Constructor | Description |
| --- | --- |
| [`newChannel`](/modules/workers/#tecs.workers.newChannel) | Creates an independent channel. |

### Types

| Type | Kind | Description |
| --- | --- | --- |
| [`Channel`](/modules/workers/#tecs.workers.Channel) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Carries serialized messages between two states. |
| [`Self`](/modules/workers/#tecs.workers.Self) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Contains the worker's two channel endpoints returned by workers.current. |
| [`SpawnOptions`](/modules/workers/#tecs.workers.SpawnOptions) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Options for workers.spawn. |
| [`Worker`](/modules/workers/#tecs.workers.Worker) | <span class="tealdoc-kind-badge tealdoc-kind-record">record</span> | Represents a thread with its own Lua state and two message channels. |

### Functions

| Function | Kind | Description |
| --- | --- | --- |
| [`current`](/modules/workers/#tecs.workers.current) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Returns the channels for the current worker. |
| [`spawn`](/modules/workers/#tecs.workers.spawn) | <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span> | Starts a worker running options.source on its own thread and state. |

### Values

| Value | Type | Description |
| --- | --- | --- |
| [`path`](/modules/workers/#tecs.workers.path) | `string` | Read-only. Contains the absolute path of the loaded native library. |

## Constructors

<a id="tecs.workers.newChannel"></a>
### tecs.workers.newChannel <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Creates an independent channel.



```teal
function tecs.workers.newChannel(): Channel
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| [`Channel`](/modules/workers/#tecs.workers.Channel) | An owning handle. `destroy` releases the native queue, so exactly one state should hold it. Raises when the queue cannot be created. |

## Types

<a id="tecs.workers.Channel"></a>
### tecs.workers.Channel <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Carries serialized messages between two states.

The channel copies every value across and never shares it, so the receiver gets a separate
object and mutating either one is invisible to the other. A `nil` is encodable and does
cross, but `receive` also answers nil for "nothing here", so nil is not usable as a
message a reader can recognize.
Read-only. Exposes the `Channel` type.


```teal
record tecs.workers.Channel is Closeable
    handle: loader.CPtr

    wrap: function(handle: loader.CPtr): Channel
    count: function(self): integer
    destroy: function(self)
    receive: function(self, timeoutMs: number): any
    send: function(self, value: any)
end
```

#### Interfaces

| Interface |
| --- |
| `Closeable` |

<a id="tecs.workers.Channel.handle"></a>
#### tecs.workers.Channel.handle <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Engine-owned. Stores the native queue pointer until `destroy` runs.
Ordinary game code should ignore this field.


```teal
tecs.workers.Channel.handle: loader.CPtr
```

<a id="tecs.workers.Channel.wrap"></a>
#### tecs.workers.Channel.wrap <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Wraps a channel pointer handed over by the native side.



```teal
function tecs.workers.Channel.wrap(handle: loader.CPtr): Channel
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `handle` | `loader.CPtr` | The native side keeps this pointer alive for the wrapper's lifetime. |

##### Returns

| Type | Description |
| --- | --- |
| [`Channel`](/modules/workers/#tecs.workers.Channel) | A borrowing handle. `destroy` does nothing, so the state that created the queue remains responsible for releasing it. |

<a id="tecs.workers.Channel.count"></a>
#### tecs.workers.Channel:count <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

```teal
function tecs.workers.Channel.count(self): integer
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Channel` |  |

##### Returns

| Type | Description |
| --- | --- |
| `integer` |  |

<a id="tecs.workers.Channel.destroy"></a>
#### tecs.workers.Channel:destroy <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

```teal
function tecs.workers.Channel.destroy(self)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Channel` |  |

##### Returns

None.

<a id="tecs.workers.Channel.receive"></a>
#### tecs.workers.Channel:receive <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Takes the next value, or nil if none arrived.

A zero timeout polls, a negative timeout waits indefinitely, and any
other value waits that many milliseconds.



```teal
function tecs.workers.Channel.receive(self, timeoutMs: number): any
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Channel` |  |
| `timeoutMs` | `number` | The timeout uses milliseconds. Omit it to poll, which is the opposite of the worker-side `receive`, where omission waits. |

##### Returns

| Type | Description |
| --- | --- |
| `any` | The next value, or nil. Nil covers an empty queue, a timeout, and a closed and drained channel. A sent nil also decodes to nil, so callers cannot distinguish those cases and should not send nil. |

<a id="tecs.workers.Channel.send"></a>
#### tecs.workers.Channel:send <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Serializes `value` and queues it without blocking.

The queue has no bound, so a reader that stops taking messages grows it
instead of pushing back on the writer.



```teal
function tecs.workers.Channel.send(self, value: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Channel` |  |
| `value` | `any` | The channel copies this value before returning. A function, userdata, cdata, a table nested past 32 levels, or a table with a key of any of those raises and names the path to it. |

##### Returns

None.

<a id="tecs.workers.Self"></a>
### tecs.workers.Self <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Contains the worker's two channel endpoints returned by `workers.current`.


```teal
global record tecs.workers.Self
    receive: function(self, timeoutMs: number): any
    send: function(self, value: any)
end
```

<a id="tecs.workers.Self.receive"></a>
#### tecs.workers.Self:receive <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Takes the next task, or nil when the inbox closes.



```teal
function tecs.workers.Self.receive(self, timeoutMs: number): any
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Self` |  |
| `timeoutMs` | `number` | Milliseconds. Omitted waits indefinitely, which is the opposite of `Channel:receive`; zero polls. Only the waiting form's nil means the spawner has asked this worker to stop, since a poll answers nil for a merely empty queue. |

##### Returns

| Type | Description |
| --- | --- |
| `any` | The next task, decoded into this state's own tables, so it shares nothing with what the spawner sent and mutating it is safe. Nil is the stop signal or an empty queue, on the terms above. |

<a id="tecs.workers.Self.send"></a>
#### tecs.workers.Self:send <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Returns a result to the spawner.



```teal
function tecs.workers.Self.send(self, value: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Self` |  |
| `value` | `any` | Subject to the same limits as `Channel:send`, and raises the same way. Nothing bounds the queue, so a worker outrunning its spawner grows it. |

##### Returns

None.

<a id="tecs.workers.SpawnOptions"></a>
### tecs.workers.SpawnOptions <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Options for `workers.spawn`.


```teal
global record tecs.workers.SpawnOptions
    source: string
    luaPath: string
end
```

<a id="tecs.workers.SpawnOptions.source"></a>
#### tecs.workers.SpawnOptions.source <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets the Lua source the worker runs. It reaches its channels through
`workers.current()`.
Required; nil raises. Source text, not a path, and it runs in a state that shares
nothing with this one, so it cannot close over anything here.


```teal
tecs.workers.SpawnOptions.source: string
```

<a id="tecs.workers.SpawnOptions.luaPath"></a>
#### tecs.workers.SpawnOptions.luaPath <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Caller-writable. Sets `package.path` for the worker's state. Defaults to this state's, so a
worker resolves the same modules the spawner does.


```teal
tecs.workers.SpawnOptions.luaPath: string
```

<a id="tecs.workers.Worker"></a>
### tecs.workers.Worker <span class="tealdoc-kind-badge tealdoc-kind-record">record</span>

Represents a thread with its own Lua state and two message channels.

The worker shares no globals, upvalues or loaded modules with the spawning state.
The worker reruns its own requires against `luaPath`.
Read-only. Exposes the `Worker` type.


```teal
record tecs.workers.Worker
    pending: integer

    available: function(self): integer
    receive: function(self, timeoutMs: number): any
    send: function(self, value: any)
    stop: function(self): integer
end
```

<a id="tecs.workers.Worker.pending"></a>
#### tecs.workers.Worker.pending <span class="tealdoc-kind-badge tealdoc-kind-field">field</span>

Read-only. Reports the messages the worker had not taken after the last
`send`. The value does not refresh between sends.


```teal
tecs.workers.Worker.pending: integer
```

<a id="tecs.workers.Worker.available"></a>
#### tecs.workers.Worker:available <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

```teal
function tecs.workers.Worker.available(self): integer
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Worker` |  |

##### Returns

| Type | Description |
| --- | --- |
| `integer` |  |

<a id="tecs.workers.Worker.receive"></a>
#### tecs.workers.Worker:receive <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Takes a result, or nil if none is ready.



```teal
function tecs.workers.Worker.receive(self, timeoutMs: number): any
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Worker` |  |
| `timeoutMs` | `number` | The timeout uses milliseconds. Omit it to poll and return immediately; a negative value blocks until something arrives and stalls the frame when called from the main thread. |

##### Returns

| Type | Description |
| --- | --- |
| `any` | Nil means the queue is empty or the worker has exited. Call `stop` to learn whether the worker still runs. |

<a id="tecs.workers.Worker.send"></a>
#### tecs.workers.Worker:send <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

Queues a value for the worker and refreshes `pending`.



```teal
function tecs.workers.Worker.send(self, value: any)
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Worker` |  |
| `value` | `any` | The worker applies the same limits and errors as `Channel:send`. |

##### Returns

None.

<a id="tecs.workers.Worker.stop"></a>
#### tecs.workers.Worker:stop <span class="tealdoc-kind-badge tealdoc-kind-instance">Instance</span>

```teal
function tecs.workers.Worker.stop(self): integer
```

##### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `self` | `Worker` |  |

##### Returns

| Type | Description |
| --- | --- |
| `integer` |  |

## Functions

<a id="tecs.workers.current"></a>
### tecs.workers.current <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Returns the channels for the current worker.

Only valid in a worker's state, where the native entry point installed the
pointers as globals before running the source.



```teal
function tecs.workers.current(): Self
```

#### Arguments

None.

#### Returns

| Type | Description |
| --- | --- |
| [`Self`](/modules/workers/#tecs.workers.Self) | A fresh object each call, borrowing channels the spawner owns. Call it once and keep the result: a second call makes a second pair of wrappers over the same two queues, and under `TECS_TRACEPROF` a second trace session, which raises. |

<a id="tecs.workers.spawn"></a>
### tecs.workers.spawn <span class="tealdoc-kind-badge tealdoc-kind-static">Static</span>

Starts a worker running `options.source` on its own thread and state.

Returns as soon as the thread starts, not when the source has run, so a failure
inside the source surfaces as `stop`'s status rather than here.



```teal
function tecs.workers.spawn(options: SpawnOptions): Worker
```

#### Arguments

| Name | Type | Description |
| --- | --- | --- |
| `options` | [`SpawnOptions`](/modules/workers/#tecs.workers.SpawnOptions) |  |

#### Returns

| Type | Description |
| --- | --- |
| [`Worker`](/modules/workers/#tecs.workers.Worker) | A worker the caller must stop. Collection does not join the thread or release its channels. |

## Values

<a id="tecs.workers.path"></a>
### tecs.workers.path <span class="tealdoc-kind-badge tealdoc-kind-variable">variable</span>

Read-only. Contains the absolute path of the loaded native library.


```teal
tecs.workers.path: string
```