@oscarpalmer/timer 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/constants.ts CHANGED
@@ -1,30 +1,35 @@
1
1
  import type {WorkHandlerType} from './models';
2
2
  import type {Timer} from './timer';
3
3
 
4
- /**
5
- * Stepper to calculate the average refresh rate of the display
6
- */
7
- function stepper(now: DOMHighResTimeStamp): void {
8
- if (values.length === 7) {
9
- milliseconds = Math.floor(
10
- (values.slice(2).reduce((first, second) => first + second) / 5) * 2,
11
- ) / 2;
12
-
13
- last = undefined;
14
- values.length = 0;
15
- } else {
16
- last ??= now;
17
-
18
- const difference = now - last;
19
-
20
- if (difference > 0) {
21
- values.push(difference);
4
+ function calculate(): Promise<number> {
5
+ return new Promise(resolve => {
6
+ const values: number[] = [];
7
+
8
+ let last: DOMHighResTimeStamp | undefined;
9
+
10
+ function step(now: DOMHighResTimeStamp): void {
11
+ if (last != null) {
12
+ values.push(now - last);
13
+ }
14
+
15
+ last = now;
16
+
17
+ if (values.length >= 10) {
18
+ const median =
19
+ values
20
+ .sort()
21
+ .slice(2, -2)
22
+ .reduce((first, second) => first + second, 0) /
23
+ (values.length - 4);
24
+
25
+ resolve(median);
26
+ } else {
27
+ requestAnimationFrame(step);
28
+ }
22
29
  }
23
30
 
24
- last = now;
25
-
26
- requestAnimationFrame(stepper);
27
- }
31
+ requestAnimationFrame(step);
32
+ });
28
33
  }
29
34
 
30
35
  /**
@@ -37,6 +42,11 @@ export const activeTimers = new Set<Timer>();
37
42
  */
38
43
  export const beginTypes = new Set<WorkHandlerType>(['continue', 'start']);
39
44
 
45
+ /**
46
+ * Buffer value to use when evaluating if a specific time is within a certain range
47
+ */
48
+ export const intervalBuffer = 5;
49
+
40
50
  /**
41
51
  * Message to show when a when-timer is destroyed
42
52
  */
@@ -70,12 +80,11 @@ export const startedMessage = 'Timer has already been started';
70
80
 
71
81
  //
72
82
 
73
- const values: number[] = [];
74
- let last: DOMHighResTimeStamp | undefined;
75
-
76
83
  /**
77
84
  * A calculated average of the refresh rate of the display _(in milliseconds)_
78
85
  */
79
- export let milliseconds = Math.floor((1000 / 60) * 2) / 2;
86
+ export let milliseconds = 1000 / 60;
80
87
 
81
- requestAnimationFrame(stepper);
88
+ calculate().then(value => {
89
+ milliseconds = value;
90
+ });
package/src/delay.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {milliseconds} from './constants';
1
+ import {intervalBuffer, milliseconds} from './constants';
2
2
  import {getValidNumber} from './get';
3
3
 
4
4
  /**
@@ -13,7 +13,10 @@ export function delay(time?: number): Promise<void> {
13
13
  function step(now: DOMHighResTimeStamp) {
14
14
  start ??= now;
15
15
 
16
- if (interval === milliseconds || now - start >= interval - 5) {
16
+ if (
17
+ interval === milliseconds ||
18
+ now - start >= interval - intervalBuffer
19
+ ) {
17
20
  resolve();
18
21
  } else {
19
22
  requestAnimationFrame(step);
package/src/repeat.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {milliseconds} from './constants';
2
2
  import {getCallback, getValidNumber} from './get';
3
+ import './global';
3
4
  import {type RepeatOptions, TimerTrace} from './models';
4
5
  import {Timer} from './timer';
5
- import {work} from './work';
6
6
 
7
7
  /**
8
8
  * Create a repeating timer
@@ -13,7 +13,6 @@ export function repeat(
13
13
  ): Timer {
14
14
  return new Timer(
15
15
  'repeat',
16
- work,
17
16
  {
18
17
  callback: getCallback(callback),
19
18
  trace: new TimerTrace().stack,
package/src/timer.ts CHANGED
@@ -3,9 +3,9 @@ import type {
3
3
  TimerOptions,
4
4
  TimerState,
5
5
  TimerType,
6
- WorkHandler,
7
6
  WorkHandlerType,
8
7
  } from './models';
8
+ import {stop, work} from './work';
9
9
 
10
10
  export class Timer {
11
11
  private declare readonly $timer: TimerType;
@@ -44,7 +44,6 @@ export class Timer {
44
44
 
45
45
  constructor(
46
46
  type: TimerType,
47
- protected readonly worker: WorkHandler,
48
47
  state: Pick<TimerState, 'callback' | 'trace'>,
49
48
  protected readonly options: TimerOptions,
50
49
  start: boolean,
@@ -82,11 +81,20 @@ export class Timer {
82
81
 
83
82
  this.options.onAfter = noop;
84
83
  this.options.onError = noop;
85
-
86
84
  this.state.callback = noop;
87
- this.state.trace = undefined;
88
85
 
89
- this.#work('stop');
86
+ if (!globalThis._oscarpalmer_timer_debug) {
87
+ this.state.trace = undefined;
88
+ }
89
+
90
+ stop(
91
+ {
92
+ instance: this,
93
+ type: this.$timer,
94
+ },
95
+ this.state,
96
+ this.options,
97
+ );
90
98
  }
91
99
 
92
100
  /**
@@ -118,7 +126,7 @@ export class Timer {
118
126
  }
119
127
 
120
128
  #work(type: WorkHandlerType): Timer {
121
- return this.worker(
129
+ return work(
122
130
  type,
123
131
  {
124
132
  instance: this,
package/src/wait.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import {milliseconds} from './constants';
2
2
  import {getCallback, getValidNumber} from './get';
3
+ import './global';
3
4
  import {TimerTrace} from './models';
4
5
  import {Timer} from './timer';
5
- import {work} from './work';
6
6
 
7
7
  /**
8
8
  * Create a waiting timer
@@ -12,7 +12,6 @@ import {work} from './work';
12
12
  export function wait(callback: () => void, time?: number): Timer {
13
13
  return new Timer(
14
14
  'wait',
15
- work,
16
15
  {
17
16
  callback: getCallback(callback),
18
17
  trace: new TimerTrace().stack,
package/src/when.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  import {noop} from '@oscarpalmer/atoms/function';
2
2
  import {destroyedMessage, milliseconds, startedMessage} from './constants';
3
3
  import {getValidNumber} from './get';
4
+ import './global';
4
5
  import {TimerTrace, type WhenOptions, type WhenState} from './models';
5
6
  import {Timer} from './timer';
6
- import {work} from './work';
7
7
 
8
8
  class When {
9
9
  private readonly $timer = 'when';
10
- private readonly state: WhenState;
10
+ private readonly state: WhenState = {
11
+ promise: undefined as never,
12
+ rejecter: undefined as never,
13
+ resolver: undefined as never,
14
+ started: false,
15
+ timer: undefined as never,
16
+ };
11
17
 
12
18
  /**
13
19
  * Is the timer active?
@@ -39,8 +45,53 @@ class When {
39
45
  : undefined;
40
46
  }
41
47
 
42
- constructor(state: WhenState) {
43
- this.state = state;
48
+ constructor(condition: () => boolean, options?: Partial<WhenOptions>) {
49
+ const {state} = this;
50
+
51
+ state.promise = new Promise<void>((resolve, reject) => {
52
+ state.resolver = resolve;
53
+ state.rejecter = reject;
54
+ });
55
+
56
+ let result = false;
57
+
58
+ this.state.timer = new Timer(
59
+ 'when',
60
+ {
61
+ callback(): void {
62
+ try {
63
+ if (condition()) {
64
+ result = true;
65
+
66
+ state.timer.stop();
67
+ }
68
+ } catch {
69
+ state.timer.stop();
70
+ }
71
+ },
72
+ trace: new TimerTrace().stack,
73
+ },
74
+ {
75
+ onAfter: () => {
76
+ if (result) {
77
+ state.resolver?.();
78
+ } else {
79
+ state.rejecter?.();
80
+ }
81
+
82
+ this.destroy();
83
+ },
84
+ onError: () => {
85
+ state.rejecter?.();
86
+
87
+ this.destroy();
88
+ },
89
+ count: getValidNumber(options?.count),
90
+ interval: getValidNumber(options?.interval, milliseconds),
91
+ timeout: getValidNumber(options?.timeout),
92
+ },
93
+ false,
94
+ );
44
95
  }
45
96
 
46
97
  /**
@@ -56,12 +107,14 @@ class When {
56
107
  * Destroys the timer _(and stops it,if it was running)_
57
108
  */
58
109
  destroy(): void {
59
- this.state.timer?.destroy();
110
+ const {state} = this;
60
111
 
61
- this.state.promise = undefined as never;
62
- this.state.resolver = noop;
63
- this.state.rejecter = noop;
64
- this.state.timer = undefined as never;
112
+ state.timer?.destroy();
113
+
114
+ state.promise = undefined as never;
115
+ state.resolver = noop;
116
+ state.rejecter = noop;
117
+ state.timer = undefined as never;
65
118
  }
66
119
 
67
120
  /**
@@ -90,17 +143,21 @@ class When {
90
143
  resolve?: (() => void) | null,
91
144
  reject?: (() => void) | null,
92
145
  ): Promise<void> {
93
- if (this.state.timer == null || this.state?.started) {
94
- throw new Error(
95
- this.state.timer == null ? destroyedMessage : startedMessage,
96
- );
146
+ const {state} = this;
147
+
148
+ if (state.timer == null) {
149
+ throw new Error(destroyedMessage);
150
+ }
151
+
152
+ if (state.started) {
153
+ throw new Error(startedMessage);
97
154
  }
98
155
 
99
- this.state.started = true;
156
+ state.started = true;
100
157
 
101
- this.state.timer.start();
158
+ state.timer.start();
102
159
 
103
- return this.state.promise.then(resolve ?? noop, reject ?? noop);
160
+ return state.promise.then(resolve ?? noop, reject ?? noop);
104
161
  }
105
162
  }
106
163
 
@@ -111,61 +168,7 @@ export function when(
111
168
  condition: () => boolean,
112
169
  options?: Partial<WhenOptions>,
113
170
  ): When {
114
- let called = false;
115
- let result = false;
116
-
117
- const state: WhenState = {
118
- started: false,
119
- timer: new Timer(
120
- 'when',
121
- work,
122
- {
123
- callback() {
124
- if (condition()) {
125
- result = true;
126
-
127
- state.timer.stop();
128
- }
129
- },
130
- trace: new TimerTrace().stack,
131
- },
132
- {
133
- onAfter() {
134
- if (!(state.timer?.paused ?? false) && !called) {
135
- called = true;
136
-
137
- if (result) {
138
- state.resolver?.();
139
- } else {
140
- state.rejecter?.();
141
- }
142
-
143
- instance.destroy();
144
- }
145
- },
146
- onError() {
147
- state.rejecter?.();
148
-
149
- instance.destroy();
150
- },
151
- count: getValidNumber(options?.count),
152
- interval: getValidNumber(options?.interval, milliseconds),
153
- timeout: getValidNumber(options?.timeout),
154
- },
155
- false,
156
- ),
157
- } as never;
158
-
159
- const promise = new Promise<void>((resolve, reject) => {
160
- state.resolver = resolve;
161
- state.rejecter = reject;
162
- });
163
-
164
- state.promise = promise;
165
-
166
- const instance = new When(state);
167
-
168
- return instance;
171
+ return new When(condition, options);
169
172
  }
170
173
 
171
174
  export type {When};
package/src/work.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  beginTypes,
4
4
  endOrRestartTypes,
5
5
  endTypes,
6
+ intervalBuffer,
6
7
  milliseconds,
7
8
  pauseTypes,
8
9
  } from './constants';
@@ -14,6 +15,27 @@ import type {
14
15
  } from './models';
15
16
  import type {Timer} from './timer';
16
17
 
18
+ function endOrRestart(
19
+ type: WorkHandlerType,
20
+ timer: WorkHandlerTimer,
21
+ state: TimerState,
22
+ options: TimerOptions,
23
+ ): Timer {
24
+ cancelAnimationFrame(state.frame as never);
25
+
26
+ if (type === 'stop') {
27
+ options.onAfter?.(false);
28
+ }
29
+
30
+ state.active = false;
31
+ state.frame = undefined;
32
+ state.paused = type === 'pause';
33
+
34
+ return type === 'restart'
35
+ ? work('start', timer, state, options)
36
+ : timer.instance;
37
+ }
38
+
17
39
  function finish(
18
40
  timer: WorkHandlerTimer,
19
41
  state: TimerState,
@@ -33,60 +55,36 @@ function finish(
33
55
  }
34
56
  }
35
57
 
36
- export function work(
37
- type: WorkHandlerType,
38
- timer: WorkHandlerTimer,
39
- state: TimerState,
40
- options: TimerOptions,
41
- ): Timer {
42
- if (
58
+ function ignore(type: WorkHandlerType, state: TimerState): boolean {
59
+ return (
43
60
  (state.destroyed && type !== 'stop') ||
44
61
  (state.active ? beginTypes.has(type) : endTypes.has(type))
45
- ) {
46
- return timer.instance;
47
- }
48
-
49
- const pausable = pauseTypes.has(type);
50
-
51
- state.elapsed = pausable ? state.elapsed : 0;
52
- state.index = pausable ? state.index : 0;
53
- state.total = pausable ? state.total : 0;
54
-
55
- if (endOrRestartTypes.has(type)) {
56
- activeTimers.delete(timer.instance);
57
-
58
- cancelAnimationFrame(state.frame as never);
59
-
60
- if (type === 'stop') {
61
- options.onAfter?.(false);
62
- }
63
-
64
- state.active = false;
65
- state.frame = undefined;
66
- state.paused = type === 'pause';
67
-
68
- return type === 'restart'
69
- ? work('start', timer, state, options)
70
- : timer.instance;
71
- }
72
-
73
- state.active = true;
74
- state.paused = false;
62
+ );
63
+ }
75
64
 
65
+ function run(
66
+ timer: WorkHandlerTimer,
67
+ state: TimerState,
68
+ options: TimerOptions,
69
+ ): (now: DOMHighResTimeStamp) => void {
70
+ let last: DOMHighResTimeStamp | undefined;
76
71
  let start: DOMHighResTimeStamp | undefined;
77
72
 
78
- function step(now: DOMHighResTimeStamp): void {
73
+ return function step(now: DOMHighResTimeStamp): void {
79
74
  if (!state.active) {
80
75
  return;
81
76
  }
82
77
 
78
+ last ??= now;
83
79
  start ??= now;
84
80
 
85
- const difference = now - start;
81
+ const difference = now - last;
86
82
 
87
83
  state.elapsed += difference;
88
84
  state.total += difference;
89
85
 
86
+ last = now;
87
+
90
88
  if (options.timeout > 0 && state.total >= options.timeout) {
91
89
  options.onError?.();
92
90
 
@@ -97,7 +95,7 @@ export function work(
97
95
 
98
96
  if (
99
97
  options.interval === milliseconds ||
100
- state.elapsed >= options.interval - 5
98
+ state.elapsed >= options.interval - intervalBuffer
101
99
  ) {
102
100
  if (options.count > -1) {
103
101
  (state.callback as (index: number) => void)(state.index);
@@ -119,11 +117,49 @@ export function work(
119
117
  }
120
118
 
121
119
  state.frame = requestAnimationFrame(step);
120
+ };
121
+ }
122
+
123
+ function setState(type: WorkHandlerType, state: TimerState): void {
124
+ const pausable = pauseTypes.has(type);
125
+
126
+ state.elapsed = pausable ? state.elapsed : 0;
127
+ state.index = pausable ? state.index : 0;
128
+ state.total = pausable ? state.total : 0;
129
+ }
130
+
131
+ export function stop(
132
+ timer: WorkHandlerTimer,
133
+ state: TimerState,
134
+ options: TimerOptions,
135
+ ): void {
136
+ endOrRestart('stop', timer, state, options);
137
+ }
138
+
139
+ export function work(
140
+ type: WorkHandlerType,
141
+ timer: WorkHandlerTimer,
142
+ state: TimerState,
143
+ options: TimerOptions,
144
+ ): Timer {
145
+ if (ignore(type, state)) {
146
+ return timer.instance;
147
+ }
148
+
149
+ setState(type, state);
150
+
151
+ if (endOrRestartTypes.has(type)) {
152
+ return endOrRestart(type, timer, state, options);
122
153
  }
123
154
 
155
+ state.active = true;
156
+ state.paused = false;
157
+
124
158
  activeTimers.add(timer.instance);
125
159
 
126
- state.frame = requestAnimationFrame(step);
160
+ const runner = run(timer, state, options);
161
+
162
+ state.frame = requestAnimationFrame(runner);
127
163
 
128
164
  return timer.instance;
129
165
  }
@@ -2,7 +2,6 @@
2
2
 
3
3
  declare class Timer {
4
4
  #private;
5
- protected readonly worker: WorkHandler;
6
5
  protected readonly options: TimerOptions;
7
6
  private readonly $timer;
8
7
  protected readonly state: TimerState;
@@ -22,7 +21,7 @@ declare class Timer {
22
21
  * Get the timer's origin _(if debugging is enabled)_
23
22
  */
24
23
  get trace(): string | undefined;
25
- constructor(type: TimerType, worker: WorkHandler, state: Pick<TimerState, "callback" | "trace">, options: TimerOptions, start: boolean);
24
+ constructor(type: TimerType, state: Pick<TimerState, "callback" | "trace">, options: TimerOptions, start: boolean);
26
25
  /**
27
26
  * Continue running the timer _(if it's paused)_
28
27
  */
@@ -67,11 +66,6 @@ export type TimerState = {
67
66
  trace: string | undefined;
68
67
  };
69
68
  export type TimerType = "repeat" | "wait" | "when";
70
- export type WorkHandler = (type: WorkHandlerType, timer: WorkHandlerTimer, state: TimerState, options: TimerOptions) => Timer;
71
- export type WorkHandlerTimer = {
72
- instance: Timer;
73
- type: TimerType;
74
- };
75
69
  export type WorkHandlerType = "continue" | "pause" | "restart" | "start" | "stop";
76
70
  /**
77
71
  * A set of all active timers
@@ -81,6 +75,10 @@ export declare const activeTimers: Set<Timer>;
81
75
  * A set of types that allow work to begin
82
76
  */
83
77
  export declare const beginTypes: Set<WorkHandlerType>;
78
+ /**
79
+ * Buffer value to use when evaluating if a specific time is within a certain range
80
+ */
81
+ export declare const intervalBuffer = 5;
84
82
  /**
85
83
  * Message to show when a when-timer is destroyed
86
84
  */
@@ -8,6 +8,10 @@ export declare const activeTimers: Set<Timer>;
8
8
  * A set of types that allow work to begin
9
9
  */
10
10
  export declare const beginTypes: Set<WorkHandlerType>;
11
+ /**
12
+ * Buffer value to use when evaluating if a specific time is within a certain range
13
+ */
14
+ export declare const intervalBuffer = 5;
11
15
  /**
12
16
  * Message to show when a when-timer is destroyed
13
17
  */
package/types/index.d.cts CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  export declare class Timer {
4
4
  #private;
5
- protected readonly worker: WorkHandler;
6
5
  protected readonly options: TimerOptions;
7
6
  private readonly $timer;
8
7
  protected readonly state: TimerState;
@@ -22,7 +21,7 @@ export declare class Timer {
22
21
  * Get the timer's origin _(if debugging is enabled)_
23
22
  */
24
23
  get trace(): string | undefined;
25
- constructor(type: TimerType, worker: WorkHandler, state: Pick<TimerState, "callback" | "trace">, options: TimerOptions, start: boolean);
24
+ constructor(type: TimerType, state: Pick<TimerState, "callback" | "trace">, options: TimerOptions, start: boolean);
26
25
  /**
27
26
  * Continue running the timer _(if it's paused)_
28
27
  */
@@ -101,19 +100,6 @@ export type WhenOptions = {
101
100
  */
102
101
  timeout: number;
103
102
  };
104
- export type WhenState = {
105
- promise: Promise<void>;
106
- rejecter?: () => void;
107
- resolver?: () => void;
108
- started: boolean;
109
- timer: Timer;
110
- };
111
- export type WorkHandler = (type: WorkHandlerType, timer: WorkHandlerTimer, state: TimerState, options: TimerOptions) => Timer;
112
- export type WorkHandlerTimer = {
113
- instance: Timer;
114
- type: TimerType;
115
- };
116
- export type WorkHandlerType = "continue" | "pause" | "restart" | "start" | "stop";
117
103
  export declare class When {
118
104
  private readonly $timer;
119
105
  private readonly state;
@@ -133,7 +119,7 @@ export declare class When {
133
119
  * Get the timer's origin _(if debugging is enabled)_
134
120
  */
135
121
  get trace(): string | undefined;
136
- constructor(state: WhenState);
122
+ constructor(condition: () => boolean, options?: Partial<WhenOptions>);
137
123
  /**
138
124
  * Continues the timer _(if it was paused)_
139
125
  */