chore: simplify doTick (#31196)

This commit is contained in:
Pavel Feldman 2024-06-06 19:26:30 -07:00 committed by GitHub
parent 826343b8a0
commit dd3a41287e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 285 additions and 1125 deletions

View File

@ -39,22 +39,19 @@ export class Clock {
async runToNextTimer(): Promise<number> { async runToNextTimer(): Promise<number> {
this._assertInstalled(); this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.next()`); this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.next()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.nextAsync()`);
return this._now; return this._now;
} }
async runAllTimers(): Promise<number> { async runAllTimers(): Promise<number> {
this._assertInstalled(); this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.runAll()`); this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runAll()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runAllAsync()`);
return this._now; return this._now;
} }
async runToLastTimer(): Promise<number> { async runToLastTimer(): Promise<number> {
this._assertInstalled(); this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.runToLast()`); this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runToLast()`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.runToLastAsync()`);
return this._now; return this._now;
} }
@ -85,8 +82,8 @@ export class Clock {
async runFor(time: number | string): Promise<number> { async runFor(time: number | string): Promise<number> {
this._assertInstalled(); this._assertInstalled();
await this._browserContext.addInitScript(`globalThis.__pwClock.clock.tick(${JSON.stringify(time)})`); await this._browserContext.addInitScript(`globalThis.__pwClock.clock.recordTick(${JSON.stringify(time)})`);
this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.tickAsync(${JSON.stringify(time)})`); this._now = await this._evaluateInFrames(`globalThis.__pwClock.clock.tick(${JSON.stringify(time)})`);
return this._now; return this._now;
} }

View File

@ -89,149 +89,59 @@ export class ClockController {
return this._now - this._adjustedSystemTime - this.start; return this._now - this._adjustedSystemTime - this.start;
} }
private _doTick(tickValue: number | string, isAsync: boolean, resolve?: (time: number) => void, reject?: (error: Error) => void): number | undefined { private async _doTick(msFloat: number): Promise<number> {
const msFloat =
typeof tickValue === 'number'
? tickValue
: parseTime(tickValue);
const ms = Math.floor(msFloat);
let tickTo = this._now + ms;
if (msFloat < 0) if (msFloat < 0)
throw new TypeError('Negative ticks are not supported'); throw new TypeError('Negative ticks are not supported');
const ms = Math.floor(msFloat);
let tickTo = this._now + ms;
let tickFrom = this._now; let tickFrom = this._now;
let previous = this._now; let previous = this._now;
// ESLint fails to detect this correctly let firstException: Error | undefined;
/* eslint-disable prefer-const */
let timer;
let firstException: Error;
let oldNow: number;
let nextPromiseTick: (() => void) | null;
let compensationCheck: () => void;
let postTimerCall: () => void;
this._duringTick = true; this._duringTick = true;
// perform microtasks
oldNow = this._now;
if (oldNow !== this._now) {
// compensate for any setSystemTime() call during microtask callback
tickFrom += this._now - oldNow;
tickTo += this._now - oldNow;
}
const doTickInner = (): number | undefined => {
// perform each timer in the requested range // perform each timer in the requested range
timer = this._firstTimerInRange(tickFrom, tickTo); let timer = this._firstTimerInRange(tickFrom, tickTo);
while (timer && tickFrom <= tickTo) { while (timer && tickFrom <= tickTo) {
if (this._timers.has(timer.id)) {
tickFrom = timer.callAt; tickFrom = timer.callAt;
this._now = timer.callAt; this._now = timer.callAt;
oldNow = this._now; const oldNow = this._now;
try { try {
this._callTimer(timer); this._callTimer(timer);
await new Promise<void>(f => this._embedder.postTask(f));
} catch (e) { } catch (e) {
firstException = firstException || e; firstException = firstException || e;
} }
if (isAsync) {
// finish up after native setImmediate callback to allow
// all native es6 promises to process their callbacks after
// each timer fires.
this._embedder.postTask(nextPromiseTick!);
return;
}
compensationCheck();
}
postTimerCall();
}
// perform process.nextTick()s again
oldNow = this._now;
if (oldNow !== this._now) {
// compensate for any setSystemTime() call during process.nextTick() callback
tickFrom += this._now - oldNow;
tickTo += this._now - oldNow;
}
this._duringTick = false;
// corner case: during runJobs new timers were scheduled which could be in the range [clock.now, tickTo]
timer = this._firstTimerInRange(tickFrom, tickTo);
if (timer) {
try {
this.tick(tickTo - this._now); // do it all again - for the remainder of the requested range
} catch (e) {
firstException = firstException || e;
}
} else {
// no timers remaining in the requested range: move the clock all the way to the end
this._now = tickTo;
}
if (firstException)
throw firstException;
if (isAsync)
resolve!(this._now);
else
return this._now;
};
nextPromiseTick =
isAsync ?
() => {
try {
compensationCheck();
postTimerCall();
doTickInner();
} catch (e) {
reject!(e);
}
} : null;
compensationCheck = () => {
// compensate for any setSystemTime() call during timer callback // compensate for any setSystemTime() call during timer callback
if (oldNow !== this._now) { if (oldNow !== this._now) {
tickFrom += this._now - oldNow; tickFrom += this._now - oldNow;
tickTo += this._now - oldNow; tickTo += this._now - oldNow;
previous += this._now - oldNow; previous += this._now - oldNow;
} }
};
postTimerCall = () => {
timer = this._firstTimerInRange(previous, tickTo); timer = this._firstTimerInRange(previous, tickTo);
previous = tickFrom; previous = tickFrom;
};
return doTickInner();
} }
tick(tickValue: string | number): number {
return this._doTick(tickValue, false)!;
}
async tickAsync(tickValue: string | number): Promise<number> {
await new Promise<void>(f => this._embedder.postTask(f));
return new Promise((resolve, reject) => this._doTick(tickValue, true, resolve, reject));
}
next() {
const timer = this._firstTimer();
if (!timer)
return this._now;
this._duringTick = true;
try {
this._now = timer.callAt;
this._callTimer(timer);
return this._now;
} finally {
this._duringTick = false; this._duringTick = false;
} this._now = tickTo;
if (firstException)
throw firstException;
return this._now;
} }
async nextAsync() { async recordTick(tickValue: string | number) {
await new Promise<void>(f => this._embedder.postTask(f)); const msFloat = parseTime(tickValue);
this._now += msFloat;
}
async tick(tickValue: string | number): Promise<number> {
return await this._doTick(parseTime(tickValue));
}
async next() {
const timer = this._firstTimer(); const timer = this._firstTimer();
if (!timer) if (!timer)
return this._now; return this._now;
@ -241,45 +151,29 @@ export class ClockController {
this._now = timer.callAt; this._now = timer.callAt;
try { try {
this._callTimer(timer); this._callTimer(timer);
await new Promise<void>(f => this._embedder.postTask(f));
} catch (e) { } catch (e) {
err = e; err = e;
} }
this._duringTick = false; this._duringTick = false;
await new Promise<void>(f => this._embedder.postTask(f));
if (err) if (err)
throw err; throw err;
return this._now; return this._now;
} }
runAll() { async runToFrame() {
for (let i = 0; i < this._loopLimit; i++) {
const numTimers = this._timers.size;
if (numTimers === 0)
return this._now;
this.next();
}
const excessJob = this._firstTimer();
if (!excessJob)
return;
throw this._getInfiniteLoopError(excessJob);
}
runToFrame() {
return this.tick(this.getTimeToNextFrame()); return this.tick(this.getTimeToNextFrame());
} }
async runAllAsync() { async runAll() {
for (let i = 0; i < this._loopLimit; i++) { for (let i = 0; i < this._loopLimit; i++) {
await new Promise<void>(f => this._embedder.postTask(f));
const numTimers = this._timers.size; const numTimers = this._timers.size;
if (numTimers === 0) if (numTimers === 0)
return this._now; return this._now;
this.next(); await this.next();
} }
await new Promise<void>(f => this._embedder.postTask(f));
const excessJob = this._firstTimer(); const excessJob = this._firstTimer();
if (!excessJob) if (!excessJob)
@ -287,28 +181,11 @@ export class ClockController {
throw this._getInfiniteLoopError(excessJob); throw this._getInfiniteLoopError(excessJob);
} }
runToLast() { async runToLast() {
const timer = this._lastTimer(); const timer = this._lastTimer();
if (!timer) if (!timer)
return this._now; return this._now;
return this.tick(timer.callAt - this._now); return await this.tick(timer.callAt - this._now);
}
runToLastAsync() {
return new Promise<number>((resolve, reject) => {
this._embedder.postTask(() => {
try {
const timer = this._lastTimer();
if (!timer) {
resolve(this._now);
return;
}
this.tickAsync(timer.callAt - this._now).then(resolve);
} catch (e) {
reject(e);
}
});
});
} }
reset() { reset() {
@ -332,18 +209,15 @@ export class ClockController {
} }
} }
jump(tickValue: string | number): number { async jump(tickValue: string | number): Promise<number> {
const msFloat = const msFloat = parseTime(tickValue);
typeof tickValue === 'number'
? tickValue
: parseTime(tickValue);
const ms = Math.floor(msFloat); const ms = Math.floor(msFloat);
for (const timer of this._timers.values()) { for (const timer of this._timers.values()) {
if (this._now + ms > timer.callAt) if (this._now + ms > timer.callAt)
timer.callAt = this._now + ms; timer.callAt = this._now + ms;
} }
return this.tick(ms); return await this.tick(ms);
} }
addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: () => any[] }): number { addTimer(options: { func: TimerHandler, type: TimerType, delay?: number | string, args?: () => any[] }): number {
@ -524,9 +398,12 @@ function inRange(from: number, to: number, timer: Timer): boolean {
* number of milliseconds. This is used to support human-readable strings passed * number of milliseconds. This is used to support human-readable strings passed
* to clock.tick() * to clock.tick()
*/ */
function parseTime(str: string): number { function parseTime(value: number | string): number {
if (!str) if (typeof value === 'number')
return value;
if (!value)
return 0; return 0;
const str = value;
const strings = str.split(':'); const strings = str.split(':');
const l = strings.length; const l = strings.length;

File diff suppressed because it is too large Load Diff