Skip to content
InputBooster
Technical ReferenceSemantics

The action queue

The bounded queue between the poller and the tick: its capacity, its locking, its drop rules and its interaction with replay.

3 min readInputBooster 4.1.2-alphaMinecraft 26.2 / 26.3

The queue is the boundary between two threads. It is bounded at 128 entries, lock-guarded, and every rejection is counted rather than hidden.

The contract

java
private static final int MAX_QUEUED = 128;
private static final ConcurrentLinkedQueue<InputAction.Stamped> QUEUE = new ConcurrentLinkedQueue<>();
private static final AtomicInteger COUNT = new AtomicInteger(0);
private static final Object LOCK = new Object();
PropertyValue
Capacity128 entries
StructureConcurrentLinkedQueue with an explicit counter
GuardOne lock around every counter and content mutation
Full behaviourThe action is rejected and counted as dropped
ClearingUsed when a screen opens, when the world changes and on shutdown

Why the counter exists

ConcurrentLinkedQueue.size() is O(n). The counter makes the size a constant time read, which matters because the overlay and the settings screen read it while the poller is running.

Why the lock exists

An earlier version incremented the counter outside the lock. The polling thread could then be between its increment and its offer() when clear() ran: the polled event was lost, but the count still said "one less", so the queue could grow past 128 and defeat the bounded-backlog guarantee the architecture depends on.

java
public static boolean queue(InputAction action, InputAction.Origin origin) {
    synchronized (LOCK) {
        if (COUNT.get() >= MAX_QUEUED) return false;
        QUEUE.offer(InputAction.Stamped.of(action, origin));
        COUNT.incrementAndGet();
        return true;
    }
}

The stamped record

java
public record Stamped(InputAction action, long capturedAt, Origin origin) {
    public static Stamped of(InputAction action, Origin origin) {
        return new Stamped(action, System.nanoTime(), origin);
    }
}

The capture timestamp is taken when the action is queued, which is as close to the physical press as the mod can measure. LatencyProfiler compares it with the drain time.

Pending physical events

java
public static int pendingPhysical() {
    synchronized (LOCK) {
        int physical = 0;
        for (InputAction.Stamped stamped : QUEUE) {
            if (!stamped.isReplay()) physical++;
        }
        return physical;
    }
}

The replay recorder calls this. When the number is above zero, playback holds back and rewinds its clock slightly, so a recording can never be injected on top of a live click.

When the queue is cleared

MomentWhy
A screen opensThe keys belong to the screen
The drainer finds a screen openSame, checked again on the game thread
The world is left or reloadedTransient state must not carry into the next session
The mod is not active or not initialisedNothing should execute
Safe Mode disabled the input moduleThe execution layer is off
ShutdownThe poller is stopped

What happens when it is full

The action is rejected. InputEventDispatcher.submit counts it as a dropped action and returns false. The caller does not retry — the physical press is lost.

In practice, 128 entries is far more than a tick can consume unless the client is severely behind: at 1000 Hz, a tick that takes 128 ms would be needed to fill it.

The suppression tokens

Two tick-scoped tokens live next to the queue:

java
private static final AtomicInteger TICK = new AtomicInteger(1);
private static volatile int attackHandledTick = -1;
private static volatile int useHandledTick = -1;

beginTick() expires the tokens from earlier ticks. A token carries the tick id it was issued in, so a missed or reordered call can never suppress a legitimate attack on a later tick.

Reading the queue

SourceShows
The overlayQueue <size>/128 on the dispatch line
The Overview tabThe queue depth
InputActionQueue.size()The current count
InputActionQueue.capacity()Always 128