mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 06:59:09 +00:00
Merge pull request #604 from cpvalente/roll-back
add skippedOutOfEvent to TimerService
This commit is contained in:
@@ -5,7 +5,7 @@ import { eventStore } from '../stores/EventStore.js';
|
|||||||
import { PlaybackService } from './PlaybackService.js';
|
import { PlaybackService } from './PlaybackService.js';
|
||||||
import { updateRoll } from './rollUtils.js';
|
import { updateRoll } from './rollUtils.js';
|
||||||
import { integrationService } from './integration-service/IntegrationService.js';
|
import { integrationService } from './integration-service/IntegrationService.js';
|
||||||
import { getCurrent, getExpectedFinish } from './timerUtils.js';
|
import { getCurrent, getExpectedFinish, skippedOutOfEvent } from './timerUtils.js';
|
||||||
import { clock } from './Clock.js';
|
import { clock } from './Clock.js';
|
||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
import type { RestorePoint } from './RestoreService.js';
|
import type { RestorePoint } from './RestoreService.js';
|
||||||
@@ -18,10 +18,13 @@ type initialLoadingData = {
|
|||||||
|
|
||||||
type RestoreCallback = (newState: RestorePoint) => Promise<void>;
|
type RestoreCallback = (newState: RestorePoint) => Promise<void>;
|
||||||
|
|
||||||
|
export const timeSkipLimit = 3 * 32;
|
||||||
|
|
||||||
export class TimerService {
|
export class TimerService {
|
||||||
private readonly _interval: NodeJS.Timer;
|
private readonly _interval: NodeJS.Timer;
|
||||||
private _updateInterval: number;
|
private _updateInterval: number;
|
||||||
private _lastUpdate: number | null;
|
private _lastUpdate: number | null;
|
||||||
|
private _skipThreshold: number;
|
||||||
|
|
||||||
playback: Playback;
|
playback: Playback;
|
||||||
timer: TimerState;
|
timer: TimerState;
|
||||||
@@ -40,11 +43,13 @@ export class TimerService {
|
|||||||
* @param {object} [timerConfig]
|
* @param {object} [timerConfig]
|
||||||
* @param {number} [timerConfig.refresh]
|
* @param {number} [timerConfig.refresh]
|
||||||
* @param {number} [timerConfig.updateInterval]
|
* @param {number} [timerConfig.updateInterval]
|
||||||
|
* @param {number} [timerConfig.skipThreshold]
|
||||||
*/
|
*/
|
||||||
constructor(timerConfig: { refresh?: number; updateInterval?: number } = {}) {
|
constructor(timerConfig: { refresh: number; updateInterval: number; skipThreshold: number }) {
|
||||||
this._clear();
|
this._clear();
|
||||||
this._interval = setInterval(() => this.update(), timerConfig?.refresh ?? 1000);
|
this._interval = setInterval(() => this.update(), timerConfig.refresh);
|
||||||
this._updateInterval = timerConfig?.updateInterval ?? 1000;
|
this._updateInterval = timerConfig.updateInterval;
|
||||||
|
this._skipThreshold = timerConfig.skipThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,7 +344,6 @@ export class TimerService {
|
|||||||
this.timer.expectedFinish >= this.timer.startedAt
|
this.timer.expectedFinish >= this.timer.startedAt
|
||||||
? this.timer.expectedFinish
|
? this.timer.expectedFinish
|
||||||
: this.timer.expectedFinish + dayInMs,
|
: this.timer.expectedFinish + dayInMs,
|
||||||
|
|
||||||
clock: this.timer.clock,
|
clock: this.timer.clock,
|
||||||
secondaryTimer: this.timer.secondaryTimer,
|
secondaryTimer: this.timer.secondaryTimer,
|
||||||
secondaryTarget: this.secondaryTarget,
|
secondaryTarget: this.secondaryTarget,
|
||||||
@@ -405,7 +409,19 @@ export class TimerService {
|
|||||||
let shouldNotify = false;
|
let shouldNotify = false;
|
||||||
if (this.playback === Playback.Roll) {
|
if (this.playback === Playback.Roll) {
|
||||||
shouldNotify = true;
|
shouldNotify = true;
|
||||||
this.updateRoll();
|
if (
|
||||||
|
skippedOutOfEvent(
|
||||||
|
previousTime,
|
||||||
|
this.timer.clock,
|
||||||
|
this.timer.startedAt,
|
||||||
|
this.timer.expectedFinish,
|
||||||
|
this._skipThreshold,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
PlaybackService.roll();
|
||||||
|
} else {
|
||||||
|
this.updateRoll();
|
||||||
|
}
|
||||||
} else if (this.timer.startedAt !== null) {
|
} else if (this.timer.startedAt !== null) {
|
||||||
// we only update timer if a timer has been started
|
// we only update timer if a timer has been started
|
||||||
shouldNotify = true;
|
shouldNotify = true;
|
||||||
@@ -505,4 +521,5 @@ export class TimerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate at 30fps, refresh at 1fps
|
// calculate at 30fps, refresh at 1fps
|
||||||
export const eventTimer = new TimerService({ refresh: 32, updateInterval: 1000 });
|
// we consider a skip at 3 lost updates
|
||||||
|
export const eventTimer = new TimerService({ refresh: 32, updateInterval: 1000, skipThreshold: 32 * 3 });
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { dayInMs } from 'ontime-utils';
|
import { dayInMs } from 'ontime-utils';
|
||||||
import { TimerType } from 'ontime-types';
|
import { TimerType } from 'ontime-types';
|
||||||
|
|
||||||
import { getCurrent, getExpectedFinish } from '../timerUtils.js';
|
import { getCurrent, getExpectedFinish, skippedOutOfEvent } from '../timerUtils.js';
|
||||||
|
|
||||||
describe('getExpectedFinish()', () => {
|
describe('getExpectedFinish()', () => {
|
||||||
it('is null if we havent started', () => {
|
it('is null if we havent started', () => {
|
||||||
@@ -354,3 +354,106 @@ describe('getExpectedFinish() and getCurrentTime() combined', () => {
|
|||||||
expect(current).toBe(8);
|
expect(current).toBe(8);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('skippedOutOfEvent()', () => {
|
||||||
|
const testSkipLimit = 32;
|
||||||
|
it('does not consider an event end as a skip', () => {
|
||||||
|
const startedAt = 1000;
|
||||||
|
const duration = 1000;
|
||||||
|
const expectedFinish = startedAt + duration;
|
||||||
|
const previousTime = expectedFinish - testSkipLimit / 2;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock += testSkipLimit;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows rolling backwards in an event', () => {
|
||||||
|
const startedAt = 1000;
|
||||||
|
const duration = 1000;
|
||||||
|
const expectedFinish = startedAt + duration;
|
||||||
|
const previousTime = startedAt + testSkipLimit / 2;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock -= testSkipLimit;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accounts for crossing midnight', () => {
|
||||||
|
const startedAt = dayInMs - testSkipLimit;
|
||||||
|
const expectedFinish = 10;
|
||||||
|
const previousTime = dayInMs - 1;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock = testSkipLimit - 2;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows rolling backwards in an event across midnight', () => {
|
||||||
|
const startedAt = dayInMs - testSkipLimit;
|
||||||
|
const expectedFinish = 10;
|
||||||
|
const previousTime = startedAt + 1;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock -= testSkipLimit;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds skip forwards out of event', () => {
|
||||||
|
const startedAt = 1000;
|
||||||
|
const duration = 1000;
|
||||||
|
const expectedFinish = startedAt + duration;
|
||||||
|
const previousTime = expectedFinish - testSkipLimit / 2;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock += testSkipLimit + 1;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds skip backwards out of event', () => {
|
||||||
|
const startedAt = 1000;
|
||||||
|
const duration = 1000;
|
||||||
|
const expectedFinish = startedAt + duration;
|
||||||
|
const previousTime = startedAt + testSkipLimit / 2;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock -= testSkipLimit + 1;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds skip forwards out of event across midnight', () => {
|
||||||
|
const startedAt = dayInMs - testSkipLimit;
|
||||||
|
const expectedFinish = 10;
|
||||||
|
const previousTime = dayInMs - 3;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock = testSkipLimit - 2;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('finds skip backwards out of event across midnight', () => {
|
||||||
|
const startedAt = dayInMs - testSkipLimit;
|
||||||
|
const expectedFinish = 10;
|
||||||
|
const previousTime = startedAt + 1;
|
||||||
|
|
||||||
|
let clock = previousTime;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(false);
|
||||||
|
|
||||||
|
clock -= testSkipLimit + 1;
|
||||||
|
expect(skippedOutOfEvent(previousTime, clock, startedAt, expectedFinish, testSkipLimit)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -64,3 +64,20 @@ export function getCurrent(
|
|||||||
}
|
}
|
||||||
return startedAt + duration + addedTime + pausedTime - clock;
|
return startedAt + duration + addedTime + pausedTime - clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function skippedOutOfEvent(
|
||||||
|
previousTime: number,
|
||||||
|
clock: number,
|
||||||
|
startedAt: number,
|
||||||
|
expectedFinish: number,
|
||||||
|
skipLimit: number,
|
||||||
|
): boolean {
|
||||||
|
const hasPassedMidnight = previousTime > dayInMs - skipLimit && clock < skipLimit;
|
||||||
|
const adjustedClock = hasPassedMidnight ? clock + dayInMs : clock;
|
||||||
|
|
||||||
|
const timeDifference = previousTime - adjustedClock;
|
||||||
|
const hasSkipped = Math.abs(timeDifference) > skipLimit;
|
||||||
|
const adjustedExpectedFinish = expectedFinish >= startedAt ? expectedFinish : expectedFinish + dayInMs;
|
||||||
|
|
||||||
|
return hasSkipped && (adjustedClock > adjustedExpectedFinish || adjustedClock < startedAt);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user