Settle frame pacing after an inconsistent vsync gate (#2197)

This commit is contained in:
Matt Van Horn authored and GitHub committed 2026-10-03 11:51:30 -07:00
1 parent 75a0278138
commit 338d94197c
2 files changed
+61 -1

No files matched your search

@@ -63,6 +63,7 @@ final class PacerCore {
private boolean locked;
private boolean probing;
private boolean probeExhausted;
private boolean gateAbandoned;
private long probeLeadNanos;
private boolean haveCandidate;
private long candidateEndNanos;
@@ -336,7 +337,7 @@ final class PacerCore {
if (locked && framesSinceCandidate >= LOCK_FRAMES) dropLock();
return;
}
if (haveCandidate && framesSinceCandidate <= LOCK_WINDOW) {
if (!gateAbandoned && haveCandidate && framesSinceCandidate <= LOCK_WINDOW) {
final long delta = sampleEnd - candidateEndNanos;
final long k = (delta + refreshPeriodNanos / 2) / refreshPeriodNanos;
if (k >= 1L && Math.abs(delta - k * refreshPeriodNanos) <= refreshPeriodNanos / LOCK_TOL_DIVISOR) {
@@ -347,6 +348,7 @@ final class PacerCore {
if (++inconsistentPairs >= LOCK_WINDOW) {
dropLock();
probeExhausted = true;
gateAbandoned = true;
}
}
}
@@ -358,6 +360,7 @@ final class PacerCore {
private void armProbe() {
probeLeadNanos = 0L;
probeExhausted = false;
gateAbandoned = false;
}
private void dropLock() {
@@ -172,6 +172,51 @@ class PacerCoreTest {
assertFalse(rig.core.probing(), "a gate proven off the grid must not be probed again");
}
@Test
void anAbandonedGateStaysRetiredThroughAnotherOnGridRun() {
final Rig rig = new Rig(P60, true, 120);
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
assertTrue(rig.core.locked());
assertEquals(60, rig.core.effectiveCapHz());
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
end += P60 / 2;
rig.feed(end, 5 * MS, end);
}
assertFalse(rig.core.locked());
assertFalse(rig.core.probing());
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
end += P60;
rig.feed(end, 5 * MS, end);
}
assertFalse(rig.core.locked(), "a retired inconsistent gate must not lock again");
assertFalse(rig.core.probing());
}
@Test
void invalidateClearsAnAbandonedGate() {
final Rig rig = new Rig(P60, true, 120);
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
end += P60 / 2;
rig.feed(end, 5 * MS, end);
}
assertFalse(rig.core.locked());
assertFalse(rig.core.probing());
rig.core.invalidate();
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
end += P60;
rig.feed(end, 5 * MS, end);
}
assertTrue(rig.core.locked());
assertFalse(rig.core.probing());
}
@Test
void samplesFurtherApartThanTheLockWindowNeverPairUp() {
final Rig rig = new Rig(P60, true, 0);
@@ -372,6 +417,18 @@ class PacerCoreTest {
assertEquals(P60, rig.presents.get(300) - rig.presents.get(299), "an expired probe must fall back to the exact cadence");
}
@Test
void anExpiredProbeLeadStillLocksWhenTheGateArrives() {
final Rig rig = new Rig(P60, true, 0);
for (int i = 0; i < 20; i++) rig.frame(4 * MS);
assertFalse(rig.core.locked());
assertFalse(rig.core.probing());
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
assertTrue(rig.core.locked());
assertFalse(rig.core.probing());
}
@Test
void mailboxIsNeverProbedAndNeverLocks() {
final Rig rig = new Rig(P60, true, false, 0);