Command Queue#

class CommandQueue#

Bounded multiple-producer, single-consumer queue carrying commands to the actor.

Any thread may push; only the actor thread pops. The ring is preallocated and its cells are reused, so unlike an intrusive linked-list MPSC there are no nodes to free, no ABA on node pointers, and no need for hazard pointers or epochs — the reclamation problem that makes most lock-free queues hard does not arise. The one heap object per command is allocated by the producer and normally destroyed by the actor, which a single consumer makes safe. Two paths destroy it elsewhere: a push onto a full ring destroys it on the producer, and cells own what is still pending when the ring itself is destroyed.

Bounded on purpose. A full queue makes push() fail rather than wait or grow, which hands the caller a backpressure signal instead of silently absorbing load it cannot serve.

Public Functions

explicit CommandQueue(int32_t capacity)#
Parameters:

capacity – Ring slots, rounded up to a power of two and to at least kMinCapacity. Must be positive.

~CommandQueue() = default#

Cells own their commands, so undrained ones are freed with the ring itself. As everywhere on this class, destruction requires that all use of the queue happens-before it — which the engine provides by owning its channel through a shared_ptr, whose final release synchronizes with every thread that used it.

CommandQueue(CommandQueue const&) = delete#
CommandQueue &operator=(CommandQueue const&) = delete#
CommandQueue(CommandQueue&&) = delete#
CommandQueue &operator=(CommandQueue&&) = delete#
bool push(std::unique_ptr<EngineCommand> command)#

Producer, any thread: hand a command to the actor.

Returns:

false when the ring is full, in which case the command is destroyed. Submission has no retry path — a full queue rejects the request outright — so nothing is salvaged.

std::unique_ptr<EngineCommand> pop()#

Consumer, actor thread only: take the next command, or nullptr when none is ready. Not the same as “empty”: a producer that has claimed the next position but not yet published it holds the line, so a later command that already succeeded stays invisible until that producer finishes. The gap is a few instructions wide, and the actor re-drains every boundary, so a command is delayed by at most one step.

inline bool hasPending() const noexcept#

Whether any command is waiting. The actor’s idle park asks this before sleeping.

Advisory: the counters may move between the two loads, and both wrong answers are cheap &#8212; a stale “no” is covered by the producer’s wake and the park’s timeout, a stale “yes” costs one empty pop.

inline int32_t capacity() const noexcept#

Public Static Attributes

static int32_t kMinCapacity = 2#

Smallest ring the sequence protocol can express. With a single cell, position 1 wraps onto the cell position 0 just published, whose sequence already reads as writable — a producer would overwrite an undelivered command and leave the consumer stuck a lap behind.