# `Lockstep.Generator`
[🔗](https://github.com/b-erdem/lockstep/blob/v0.1.0/lib/lockstep/generator.ex#L1)

Deterministic, seeded streams of operations for Jepsen-style
workloads.

A generator emits a sequence of opaque "ops" — tuples or atoms
describing what a worker should do next. Workers pull from
generators in a loop; the operations themselves are recorded via
`Lockstep.History`.

## Built-in generators

  * `random/2` — uniform random over a pool of ops, seeded so
    iterations are reproducible.
  * `cycle/1` — fixed sequence repeated forever.
  * `weighted/2` — biased random sampling.
  * `mix/2` — interleave outputs from multiple generators
    round-robin.
  * `take/2` — limit any generator to N ops total.

## Quick example

    gen =
      Lockstep.Generator.random(
        [:read, {:write, 1}, {:write, 2}, {:cas, 1, 2}],
        seed: 42
      )
      |> Lockstep.Generator.take(50)

    Lockstep.Generator.each(gen, fn op ->
      Lockstep.History.op(history, op_kind(op), op_args(op), fn ->
        apply_op(reg, op)
      end)
    end)

Generators are immutable; `next/1` returns `{op, new_gen}` or
`:done`. They DON'T allocate processes — pass a generator into a
closure and the worker advances it locally with no controller
involvement.

## Seeding

When a generator is created without an explicit `:seed`, it
derives one from `:erlang.unique_integer/1` — fine for one-off
use, but iterations may diverge. For Jepsen-style reproducibility,
pass an explicit seed (typically derived from the iteration's seed
via `phash2({iter_seed, worker_id})`).

# `t`

```elixir
@type t() :: %Lockstep.Generator{
  children: [t()] | nil,
  kind: :random | :cycle | :weighted | :mix,
  next_child: non_neg_integer() | nil,
  ops: [any()] | nil,
  remaining: non_neg_integer() | :infinity,
  seed: any(),
  total: non_neg_integer() | nil,
  weights: [non_neg_integer()] | nil
}
```

# `cycle`

```elixir
@spec cycle([any()]) :: t()
```

Fixed sequence repeated forever (until `take/2`-limited).

# `each`

```elixir
@spec each(t(), (any() -&gt; any())) :: non_neg_integer()
```

Iterate `gen` to exhaustion, calling `fun.(op)` for each op.
Returns the count of ops produced.

# `mix`

```elixir
@spec mix([t()]) :: t()
```

Round-robin interleave outputs from `gens`. When any child is
exhausted, this generator becomes done.

# `next`

```elixir
@spec next(t()) :: {any(), t()} | :done
```

Pull the next op. Returns `{op, new_gen}` or `:done` when the
generator has been exhausted (only happens after `take/2` or when
`mix/1`'s last child finishes).

# `random`

```elixir
@spec random(
  [any()],
  keyword()
) :: t()
```

Uniform random sampling over `ops` with replacement. Pass `:seed`
to make the stream reproducible across runs.

# `reduce`

```elixir
@spec reduce(t(), acc, (any(), acc -&gt; acc)) :: acc when acc: var
```

Reduce over `gen`'s ops to exhaustion. `fun.(op, acc)` returns
the new acc. Useful for accumulating results without an external
side effect.

# `take`

```elixir
@spec take(t(), non_neg_integer()) :: t()
```

Cap any generator at `n` total ops. After `n` calls to `next/1`,
the generator returns `:done`.

# `weighted`

```elixir
@spec weighted(
  [{any(), pos_integer()}],
  keyword()
) :: t()
```

Weighted random sampling. `weighted_ops` is a list of `{op, weight}`
tuples. Higher weight = more likely to be picked.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
