# @tiptap/projectiles@0.1.0

<!-- Generated by scripts/gen-package-reference.mts from the package source.
     Do not edit: §15.4 — package docs are generated, nobody writes them. -->

**Spec §7.1: `@tiptap/projectiles`** — a fixed-capacity pool with time to
live.

Extracted from `arena2d`, where the pooling discipline was spread across
`spawn_projectile`, `step_projectiles` and the compaction inside them. What
comes out is the part with no arena in it: **slot allocation, expiry, and the
index stability that everything else depends on.**

## Why a pool and not a `Vec`

`Arena2dState` is cloned into the lag-compensation ring 64 times and cloned
again every tick. A `Vec` in there is 64 allocations being copied sixty times
a second on a player's phone. So the table is fixed, `Copy`, and a slot is
free when its `ttl` is zero — which also means the wire format has a fixed
size and a decoder has nothing to be told about a length.

## The rule that makes it usable: indices are stable while alive

A projectile keeps its slot from spawn to expiry. Nothing compacts, nothing
swaps, nothing is reordered. That is what lets an event refer to a projectile
by index and still mean the same one next tick, and it is why
[`Pool::spawn`] takes **the lowest free slot** rather than pushing at the end
— lowest-free is a deterministic function of the current occupancy, so two
clients that spawned the same projectile put it in the same place without
having to agree on anything else.

Swap-remove would be faster and is the usual advice. It is wrong here: it
moves a live projectile into a dead one's slot, so an index recorded last tick
now names something else, on one client and not the other.

## Expiry order is part of the contract

[`Pool::advance`] walks slots in index order and reports expiries in that
order. A caller that fires an event per expiry needs that, because two clients
whose events arrive in different orders have two different games.

## Types

```rust
pub struct Shot
```

One pooled projectile. Alive exactly when `ttl > 0`.

```rust
pub struct Pool<'a>
```

A fixed-capacity pool over a caller-owned slice.

Borrowed rather than owning, so the table can live inside a game's own state
struct and be cloned with it — which is the whole reason it is fixed-capacity.

## Functions

```rust
pub fn free_slot(&self) -> Option<usize>
```

The lowest free slot, or `None` when full.

Lowest-free rather than round-robin or push-at-end: it is a pure function
of the current occupancy, so two clients reconstructing the same tick
choose the same slot without exchanging anything.

```rust
pub fn spawn(&mut self, shot: Shot) -> Option<usize>
```

Put `shot` in the lowest free slot.

Returns the slot, or `None` if the pool is full. **Full is not an error**
— a game that fires faster than its projectiles expire has chosen a rate
its own config allows, and the alternative is either allocating (banned)
or silently killing somebody else's shot (a desync waiting for two clients
to disagree about whose).

```rust
pub fn advance(&mut self, dt: Fixed, mut sink: impl FnMut(usize, &Shot))
```

Integrate every live projectile and expire the ones that run out.

`sink` is called with the slot index of each expiry, **in index order**,
before the slot is cleared — so a caller can read the shot that died.

```rust
pub fn kill(&mut self, slot: usize) -> bool
```

End one projectile early — it hit something.

Returns whether the slot held a live shot, so a caller cannot double-count
a hit against a projectile two bodies claim on the same tick.

```rust
pub fn iter(&self) -> impl Iterator<Item = (usize, &Shot)>
```

Every live projectile, ascending by slot.
