-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(schedulers): improve performance of animationFrameScheduler and asapScheduler #7059
Changes from all commits
bde6ad8
4759a8c
fde8f5f
d415101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -2,13 +2,14 @@ import { AsyncAction } from './AsyncAction'; | |||
import { AnimationFrameScheduler } from './AnimationFrameScheduler'; | ||||
import { SchedulerAction } from '../types'; | ||||
import { animationFrameProvider } from './animationFrameProvider'; | ||||
import { TimerHandle } from './timerHandle'; | ||||
|
||||
export class AnimationFrameAction<T> extends AsyncAction<T> { | ||||
constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction<T>, state?: T) => void) { | ||||
super(scheduler, work); | ||||
} | ||||
|
||||
protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: any, delay: number = 0): any { | ||||
protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle { | ||||
// If delay is greater than 0, request as an async action. | ||||
if (delay !== null && delay > 0) { | ||||
return super.requestAsyncId(scheduler, id, delay); | ||||
|
@@ -20,18 +21,20 @@ export class AnimationFrameAction<T> extends AsyncAction<T> { | |||
// the current animation frame request id. | ||||
return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined))); | ||||
} | ||||
protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: any, delay: number = 0): any { | ||||
|
||||
protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined { | ||||
// If delay exists and is greater than 0, or if the delay is null (the | ||||
// action wasn't rescheduled) but was originally scheduled as an async | ||||
// action, then recycle as an async action. | ||||
if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) { | ||||
if (delay != null ? delay > 0 : this.delay > 0) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify conditional |
||||
return super.recycleAsyncId(scheduler, id, delay); | ||||
} | ||||
// If the scheduler queue has no remaining actions with the same async id, | ||||
// cancel the requested animation frame and set the scheduled flag to | ||||
// undefined so the next AnimationFrameAction will request its own. | ||||
if (!scheduler.actions.some((action) => action.id === id)) { | ||||
animationFrameProvider.cancelAnimationFrame(id); | ||||
const { actions } = scheduler; | ||||
if (id != null && actions[actions.length - 1]?.id !== id) { | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perf improvement here. |
||||
animationFrameProvider.cancelAnimationFrame(id as number); | ||||
scheduler._scheduled = undefined; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't relate to a change that's made in this PR, but I'm not at all sure that setting rxjs/src/internal/scheduler/AsyncAction.ts Line 140 in 4afbc16
But if we've come into this block under different circumstances, it might make no sense. E.g. if there's an action at the end of the array that was scheduled from within a flush - and has a different This stuff is so complicated. Will look again at this tomorrow, but I have a bad feeling it's wrong. (Again, not a change in this PR; it was already like this.) |
||||
} | ||||
// Return undefined so the action knows to request a new async id if it's rescheduled. | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,14 @@ import { AsyncAction } from './AsyncAction'; | |
import { AsapScheduler } from './AsapScheduler'; | ||
import { SchedulerAction } from '../types'; | ||
import { immediateProvider } from './immediateProvider'; | ||
import { TimerHandle } from './timerHandle'; | ||
|
||
export class AsapAction<T> extends AsyncAction<T> { | ||
constructor(protected scheduler: AsapScheduler, protected work: (this: SchedulerAction<T>, state?: T) => void) { | ||
super(scheduler, work); | ||
} | ||
|
||
protected requestAsyncId(scheduler: AsapScheduler, id?: any, delay: number = 0): any { | ||
protected requestAsyncId(scheduler: AsapScheduler, id?: TimerHandle, delay: number = 0): TimerHandle { | ||
// If delay is greater than 0, request as an async action. | ||
if (delay !== null && delay > 0) { | ||
return super.requestAsyncId(scheduler, id, delay); | ||
|
@@ -20,17 +21,19 @@ export class AsapAction<T> extends AsyncAction<T> { | |
// the current scheduled microtask id. | ||
return scheduler._scheduled || (scheduler._scheduled = immediateProvider.setImmediate(scheduler.flush.bind(scheduler, undefined))); | ||
} | ||
protected recycleAsyncId(scheduler: AsapScheduler, id?: any, delay: number = 0): any { | ||
|
||
protected recycleAsyncId(scheduler: AsapScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined { | ||
// If delay exists and is greater than 0, or if the delay is null (the | ||
// action wasn't rescheduled) but was originally scheduled as an async | ||
// action, then recycle as an async action. | ||
if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) { | ||
if (delay != null ? delay > 0 : this.delay > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify conditional |
||
return super.recycleAsyncId(scheduler, id, delay); | ||
} | ||
// If the scheduler queue has no remaining actions with the same async id, | ||
// cancel the requested microtask and set the scheduled flag to undefined | ||
// so the next AsapAction will request its own. | ||
if (!scheduler.actions.some((action) => action.id === id)) { | ||
const { actions } = scheduler; | ||
if (id != null && actions[actions.length - 1]?.id !== id) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perf improvement here. |
||
immediateProvider.clearImmediate(id); | ||
scheduler._scheduled = undefined; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,9 +4,10 @@ import { Subscription } from '../Subscription'; | |
import { AsyncScheduler } from './AsyncScheduler'; | ||
import { intervalProvider } from './intervalProvider'; | ||
import { arrRemove } from '../util/arrRemove'; | ||
import { TimerHandle } from './timerHandle'; | ||
|
||
export class AsyncAction<T> extends Action<T> { | ||
public id: any; | ||
public id: TimerHandle | undefined; | ||
public state?: T; | ||
// @ts-ignore: Property has no initializer and is not definitely assigned | ||
public delay: number; | ||
|
@@ -58,23 +59,26 @@ export class AsyncAction<T> extends Action<T> { | |
|
||
this.delay = delay; | ||
// If this action has already an async Id, don't request a new one. | ||
this.id = this.id || this.requestAsyncId(scheduler, this.id, delay); | ||
this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay); | ||
|
||
return this; | ||
} | ||
|
||
protected requestAsyncId(scheduler: AsyncScheduler, _id?: any, delay: number = 0): any { | ||
protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle { | ||
return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay); | ||
} | ||
|
||
protected recycleAsyncId(_scheduler: AsyncScheduler, id: any, delay: number | null = 0): any { | ||
protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined { | ||
// If this action is rescheduled with the same delay time, don't clear the interval id. | ||
if (delay != null && this.delay === delay && this.pending === false) { | ||
return id; | ||
} | ||
// Otherwise, if the action's delay time is different from the current delay, | ||
// or the action has been rescheduled before it's executed, clear the interval id | ||
intervalProvider.clearInterval(id); | ||
if (id != null) { | ||
intervalProvider.clearInterval(id); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was being called with |
||
} | ||
|
||
return undefined; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most changed lines are just around typing these ids appropriately.