Skip to content

API Pluck

kimja edited this page Sep 28, 2026 · 4 revisions

🧬 API β€Ί Pluck

🧬 Pluck - fire once publisher

πŸ“œ Gently plucks a single matter into rzeka.

rzeka.Pluck(this, new GameStarted());
  • Pluck has no automatic casuality tracking and it is usually OK for its usecases, which are mostly to just tick something once at the beginning of something greater.
  • When you know what other matter caused it (usually while calling it from inside a Loom or Weave), stamp the circumstances yourself using .WithCircumstances<T>():
rzeka.Pluck(this, new GamePaused().WithCircumstances(triggeringEvent));



Plucking from a worker thread

Eris will warn you that the Pluck was automatically scheduled onto the main thread when you pluck from a worker thread.

  • This is not an error, but an off-thread Pluck will still likely mean its matter would be better taken care of through a Shuttle response or a Strand, which carries its causality with it instead of needing a manual stamp.
  • The warning is there because your Pluck is no longer instantenous. It queues behind whatever is already on the main thread, rather than landing immediately. Eris considers that suspicious.

πŸ“œ Example of Strand replacing a recurring off-thread Plucking

Imagine a native/network/IO callback that fires repeatedly on its own thread – this is also the case when you will get much spam in Eris (one Hunch per emission).

  • As a Strand it becomes a registered spell.
  • Eris gets occurrences, mana lifecycle for downstream spell works, and disposing the Strand unsubscribes the event.
// ✘ Off-thread Pluck, once per packet
public override void _Ready()
{
    _socket.OnPacketReceived += packet =>
        rzeka.Pluck(this, new PacketReceived(packet));
}

public override void _Ready()
{
    // βœ” Strand – the source is registered once
    Q += rzeka.Strand(
        this,
        Observable
            .FromEvent<Packet>(
                h => _socket.OnPacketReceived += h,
                h => _socket.OnPacketReceived -= h)
            .Select(p => new PacketReceived(p))
    );
}

πŸ“œ Example of Shuttle replacing a recurring off-thread Plucking

An async operation kicked off imperatively, whose result gets yeeted into rzeka from the completion callback.

// ✘ Off-thread Pluck from a completion callback
void OnSaveButtonPressed(int slot)
{
    _saveSystem.SaveAsync(
        slot,
        wasSuccessful => rzeka.Pluck(this, new GameSaved(wasSuccessful)));
}

// βœ”  Shuttle + Ask combo
// Shuttle performs and reports
Q += rzeka.Shuttle<SaveGameRequest, SaveGameResponse>(
    this,
    spell => 
        spell
            .SelectMany(req => Observable
                .FromAsync(ct => _saveSystem.SaveAsync(req.Slot, ct))
                .Select(ok => new SaveGameResponse(req, ok)))
);

// The caller Asks, and the round trip carries its own causality
// Butten press gets its own matter emission somewhere upstream
Q += rzeka.Loom<SaveButtonPressed, GameSaved>(
    this,
    spell => 
        spell
            .SelectMany(evt =>
                rzeka
                    .Ask<SaveGameRequest, SaveGameResponse>(
                        this, 
                        new SaveGameRequest(evt.Slot).WithCircumstances(evt))
                    .Select(res => new GameSaved(res.WasSuccessful)
                        .WithCircumstances<GameSaved>(evt, res)))
);


See also: Strand · circumstance rules · 🧬 API overview

Clone this wiki locally