From c2621657d0c48f3e304933854503824d44a6e4cd Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Wed, 18 May 2022 20:02:38 +0300 Subject: [PATCH 1/8] feat(cdk): add rxScheduleTask function --- .../docs/rx-schedule-task.md | 99 +++++++++++++++++++ libs/cdk/render-strategies/src/index.ts | 1 + .../src/lib/rx-schedule-task.ts | 47 +++++++++ 3 files changed, 147 insertions(+) create mode 100644 libs/cdk/render-strategies/docs/rx-schedule-task.md create mode 100644 libs/cdk/render-strategies/src/lib/rx-schedule-task.ts diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md new file mode 100644 index 0000000000..9007cf2d83 --- /dev/null +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -0,0 +1,99 @@ +# rxScheduleTask + +`rxScheduleTask` provides a helper function to schedule function execution. It is a minimal building block for making performance optimizations in your code. + +## Motivation + +Chromium based browsers considers all tasks that taking more than 50ms as long tasks. If task runs more than 50ms, users will start noticing lags. Optimally all user interactions should happen at 30 fps framerate with 32ms budget per browser task. In ideal world it should be 60 fps and 16ms budget. + +> 💡 In reality browser has a reserved overhead of 4ms, try to stick to 28ms of work for 30 fps and 12ms for 60 fps. + +## Scheduling mechanisms in browser + +Most common ways of delaying task execution are: + +- `setTimeout` +- `requestAnimationFrame` +- `requestIdleCallback` + +`rxScheduleTask` provides similar API but comes with huge benefits of notion of frame budget and priority configuration. + +## Concurrent strategies + +> 💡 Under the hood all our concurrent strategies are based on MessageChannel technology. + +To address the problem of long tasks and help browser split the work @rx-angular/cdk provides concurrent strategies. This strategies will help browser to chunk the work into non-blocking tasks whenever it's possible. + +You can read detailed information about concurrent strategies [here](https://github.com/rx-angular/rx-angular/blob/main/libs/cdk/render-strategies/docs/concurrent-strategies.md). + +## Usage examples + +### Input params + +- Just as common delaying apis this method `accepts` a work function that should be scheduled. +- Second important parameter is `strategy` which will be used for scheduling (`normal` is default). +- Third argument is `options` that can hold `delay` for the task and `ngZone` where task should run. + +### Return type + +Function returns a callback that you can use to cancel already scheduled tasks. + +### Usage with default arguments + +```typescript +function updateStateAndBackup(data: T) { + this.stateService.set(data); + + rxScheduleTask(() => localStorage.setItem('state', JSON.stringify(state))); +} +``` + +### Usage with non-default strategy + +```typescript +function updateStateAndBackup(data: T) { + this.stateService.set(data); + + rxScheduleTask( + () => localStorage.setItem('state', JSON.stringify(state)), + 'idle' + ); +} +``` + +### Usage with options strategy + +```typescript +function updateStateAndBackup(data: T) { + this.stateService.set(data); + + rxScheduleTask( + () => localStorage.setItem('state', JSON.stringify(state)), + 'idle', + { delay: 200, zone: this.ngZone } + ); +} +``` + +### Cancel scheduled task + +```typescript +let saveToLocalStorageCallback; + +function updateStateAndBackup(data: T) { + this.stateService.set(data); + + if (saveToLocalStorageCallback) { + saveToLocalStorage(); + } + + saveToLocalStorageCallback = rxScheduleTask(() => + localStorage.setItem('state', JSON.stringify(state)) + ); +} +``` + +## Links + +- [Detailed information about strategies](https://github.com/rx-angular/rx-angular/tree/master/libs/cdk/render-strategies) +- [MessageChannel documentation](https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel) diff --git a/libs/cdk/render-strategies/src/index.ts b/libs/cdk/render-strategies/src/index.ts index 34b64934dc..0d66b4270f 100644 --- a/libs/cdk/render-strategies/src/index.ts +++ b/libs/cdk/render-strategies/src/index.ts @@ -17,3 +17,4 @@ export { RxStrategyCredentials } from './lib/model'; export { RxRenderBehavior } from './lib/model'; export { RxRenderWork } from './lib/model'; export { RX_RENDER_STRATEGIES_CONFIG, RxRenderStrategiesConfig } from './lib/config'; +export { rxScheduleTask } from './lib/rx-schedule-task'; \ No newline at end of file diff --git a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts new file mode 100644 index 0000000000..97c762a06c --- /dev/null +++ b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts @@ -0,0 +1,47 @@ +import { + cancelCallback, + scheduleCallback, +} from '../../../internals/scheduler/src/lib/scheduler'; +import { SchedulerTaskZone } from '../../../internals/scheduler/src/lib/schedulerMinHeap'; +import { PriorityLevel } from '../../../internals/scheduler/src/lib/schedulerPriorities'; +import { RxConcurrentStrategyNames } from './model'; + +type StrategiesPriorityRecord = Record< + RxConcurrentStrategyNames, + PriorityLevel +>; + +const strategiesPrio: StrategiesPriorityRecord = { + immediate: PriorityLevel.ImmediatePriority, + userBlocking: PriorityLevel.UserBlockingPriority, + normal: PriorityLevel.NormalPriority, + low: PriorityLevel.LowPriority, + idle: PriorityLevel.IdlePriority, +}; + +/** + * @description + * This function is used to schedule a task with a certain priority. + * It is useful for tasks that can be done asynchronously. + * + * ```ts + * const task = rxScheduleTask(() => localStorage.setItem(state, JSON.stringify(state)); + * ``` + */ +export const rxScheduleTask = ( + work: (...args: any[]) => void, + strategy: keyof StrategiesPriorityRecord = 'normal', + options?: { + delay?: number; + ngZone?: SchedulerTaskZone; + } +) => { + const task = scheduleCallback( + strategiesPrio[strategy], + () => work(), + options + ); + return () => { + cancelCallback(task); + }; +}; From 15762a43c56e1e526e4f23d79d3ff0b570b3feb4 Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Wed, 18 May 2022 20:08:18 +0300 Subject: [PATCH 2/8] fix(cdk): improve usage examples of rxShceduleTask --- .../cdk/render-strategies/docs/rx-schedule-task.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md index 9007cf2d83..d18234920d 100644 --- a/libs/cdk/render-strategies/docs/rx-schedule-task.md +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -38,9 +38,12 @@ You can read detailed information about concurrent strategies [here](https://git Function returns a callback that you can use to cancel already scheduled tasks. -### Usage with default arguments +### Default usage ```typescript +import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; +... + function updateStateAndBackup(data: T) { this.stateService.set(data); @@ -51,6 +54,9 @@ function updateStateAndBackup(data: T) { ### Usage with non-default strategy ```typescript +import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; +... + function updateStateAndBackup(data: T) { this.stateService.set(data); @@ -64,6 +70,9 @@ function updateStateAndBackup(data: T) { ### Usage with options strategy ```typescript +import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; +... + function updateStateAndBackup(data: T) { this.stateService.set(data); @@ -78,6 +87,9 @@ function updateStateAndBackup(data: T) { ### Cancel scheduled task ```typescript +import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; +... + let saveToLocalStorageCallback; function updateStateAndBackup(data: T) { From e1b8629703b07d6c021d59359062d6a183901441 Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Wed, 18 May 2022 20:12:46 +0300 Subject: [PATCH 3/8] fix(cdk): fix rxScheduleTask imports --- libs/cdk/render-strategies/src/lib/rx-schedule-task.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts index 97c762a06c..1cbee7e557 100644 --- a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts +++ b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts @@ -1,9 +1,9 @@ +import { NgZone } from '@angular/core'; import { cancelCallback, scheduleCallback, -} from '../../../internals/scheduler/src/lib/scheduler'; -import { SchedulerTaskZone } from '../../../internals/scheduler/src/lib/schedulerMinHeap'; -import { PriorityLevel } from '../../../internals/scheduler/src/lib/schedulerPriorities'; + PriorityLevel, +} from '@rx-angular/cdk/internals/scheduler'; import { RxConcurrentStrategyNames } from './model'; type StrategiesPriorityRecord = Record< @@ -33,7 +33,7 @@ export const rxScheduleTask = ( strategy: keyof StrategiesPriorityRecord = 'normal', options?: { delay?: number; - ngZone?: SchedulerTaskZone; + ngZone?: NgZone; } ) => { const task = scheduleCallback( From c6bc9fa9c72a62511d530434cd1ad84710fb9a6a Mon Sep 17 00:00:00 2001 From: Kirill Date: Wed, 18 May 2022 20:21:40 +0300 Subject: [PATCH 4/8] fix(cdk): fix rxScheduleTask readme --- libs/cdk/render-strategies/docs/rx-schedule-task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md index d18234920d..250e458e44 100644 --- a/libs/cdk/render-strategies/docs/rx-schedule-task.md +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -67,7 +67,7 @@ function updateStateAndBackup(data: T) { } ``` -### Usage with options strategy +### Usage with options ```typescript import { rxScheduleTask } from '@rx-angular/cdk/render-strategies'; From fcd71c2fca16dcd830cc6efb5d12c03e13564d39 Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Sat, 28 May 2022 08:52:17 +0300 Subject: [PATCH 5/8] fix(cdk): correct callback in rxScheduleTask docs --- libs/cdk/render-strategies/docs/rx-schedule-task.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md index 250e458e44..b7a1e19306 100644 --- a/libs/cdk/render-strategies/docs/rx-schedule-task.md +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -96,7 +96,7 @@ function updateStateAndBackup(data: T) { this.stateService.set(data); if (saveToLocalStorageCallback) { - saveToLocalStorage(); + saveToLocalStorageCallback(); } saveToLocalStorageCallback = rxScheduleTask(() => From ef416e630b62ab1655321b8c581a1a2ef3ca3dee Mon Sep 17 00:00:00 2001 From: Kirill Date: Sat, 28 May 2022 08:56:12 +0300 Subject: [PATCH 6/8] Update libs/cdk/render-strategies/docs/rx-schedule-task.md Co-authored-by: Edouard Bozon --- libs/cdk/render-strategies/docs/rx-schedule-task.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md index b7a1e19306..7d7dcd98b7 100644 --- a/libs/cdk/render-strategies/docs/rx-schedule-task.md +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -78,8 +78,7 @@ function updateStateAndBackup(data: T) { rxScheduleTask( () => localStorage.setItem('state', JSON.stringify(state)), - 'idle', - { delay: 200, zone: this.ngZone } + { delay: 200, zone: this.ngZone, strategy: 'idle' } ); } ``` From 604ff8ab3e5bb435ad103aff7a78d2dc98e6e284 Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Sat, 28 May 2022 09:01:57 +0300 Subject: [PATCH 7/8] refactor(cdk): provide strategy in options --- libs/cdk/render-strategies/docs/rx-schedule-task.md | 2 +- libs/cdk/render-strategies/src/lib/rx-schedule-task.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libs/cdk/render-strategies/docs/rx-schedule-task.md b/libs/cdk/render-strategies/docs/rx-schedule-task.md index 7d7dcd98b7..a1535ddc76 100644 --- a/libs/cdk/render-strategies/docs/rx-schedule-task.md +++ b/libs/cdk/render-strategies/docs/rx-schedule-task.md @@ -62,7 +62,7 @@ function updateStateAndBackup(data: T) { rxScheduleTask( () => localStorage.setItem('state', JSON.stringify(state)), - 'idle' + {strategy: 'idle'} ); } ``` diff --git a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts index 1cbee7e557..149195b93b 100644 --- a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts +++ b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts @@ -19,6 +19,8 @@ const strategiesPrio: StrategiesPriorityRecord = { idle: PriorityLevel.IdlePriority, }; +const defaultStrategy: keyof StrategiesPriorityRecord = 'normal'; + /** * @description * This function is used to schedule a task with a certain priority. @@ -30,14 +32,14 @@ const strategiesPrio: StrategiesPriorityRecord = { */ export const rxScheduleTask = ( work: (...args: any[]) => void, - strategy: keyof StrategiesPriorityRecord = 'normal', options?: { + strategy?: keyof StrategiesPriorityRecord; delay?: number; ngZone?: NgZone; } ) => { const task = scheduleCallback( - strategiesPrio[strategy], + strategiesPrio[options?.strategy || defaultStrategy], () => work(), options ); From 9ff6d9da55948a358aba1600afc96c1b673ec47e Mon Sep 17 00:00:00 2001 From: Kirill Karnaukhov Date: Sat, 28 May 2022 09:11:38 +0300 Subject: [PATCH 8/8] fix(cdk): proper options passing in rxScheduleTask --- libs/cdk/render-strategies/src/lib/rx-schedule-task.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts index 149195b93b..3b47e8f0c3 100644 --- a/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts +++ b/libs/cdk/render-strategies/src/lib/rx-schedule-task.ts @@ -41,7 +41,10 @@ export const rxScheduleTask = ( const task = scheduleCallback( strategiesPrio[options?.strategy || defaultStrategy], () => work(), - options + { + delay: options?.delay, + ngZone: options?.ngZone, + } ); return () => { cancelCallback(task);