ProcessingBudget.java
package org.hammer.audio.experimental.acoustic.tracking;
import java.util.Objects;
import java.util.function.LongSupplier;
/**
* Lightweight per-frame deadline checker for the real-time tracking pipeline.
*
* <p>A {@code ProcessingBudget} is created with a {@link FrameSchedule}; callers invoke {@link
* #start()} at the beginning of a block and {@link #checkpoint(String)} after each pipeline stage.
* {@link #elapsedNanos()} reports the time spent so far and {@link #exceeded()} is {@code true}
* once the configured per-block deadline is past.
*
* <p>The budget intentionally does not abort processing; it is observational and lets the host
* decide whether to skip stages, downsample, or drop output. A {@link LongSupplier} time source is
* injectable so deterministic tests can advance virtual time.
*/
public final class ProcessingBudget {
private final FrameSchedule schedule;
private final LongSupplier nanoTimeSource;
private long startNanos;
private long lastCheckpointNanos;
private String lastStage;
/** Create a budget tied to {@code schedule} that uses {@link System#nanoTime()}. */
public ProcessingBudget(FrameSchedule schedule) {
this(schedule, System::nanoTime);
}
/** Create a budget with an injectable time source for deterministic testing. */
public ProcessingBudget(FrameSchedule schedule, LongSupplier nanoTimeSource) {
this.schedule = Objects.requireNonNull(schedule, "schedule");
this.nanoTimeSource = Objects.requireNonNull(nanoTimeSource, "nanoTimeSource");
}
/** Mark the beginning of a block. Must be called before {@link #checkpoint(String)}. */
public void start() {
long now = nanoTimeSource.getAsLong();
this.startNanos = now;
this.lastCheckpointNanos = now;
this.lastStage = null;
}
/** Record the completion of a pipeline stage and return the time it took. */
public long checkpoint(String stage) {
Objects.requireNonNull(stage, "stage");
long now = nanoTimeSource.getAsLong();
long delta = now - lastCheckpointNanos;
lastCheckpointNanos = now;
lastStage = stage;
return delta;
}
/** Total elapsed time since {@link #start()} in nanoseconds. */
public long elapsedNanos() {
return nanoTimeSource.getAsLong() - startNanos;
}
/** Whether the elapsed time has exceeded the per-block deadline. */
public boolean exceeded() {
return elapsedNanos() > schedule.maxProcessingNanos();
}
/** Underlying {@link FrameSchedule}. */
public FrameSchedule schedule() {
return schedule;
}
/** Name of the last stage that was checkpointed, or {@code null} if none. */
public String lastStage() {
return lastStage;
}
}