@fncts/io 0.0.29 → 0.0.31
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/IO/api/blocking.d.ts +7 -0
- package/IO.d.ts +2 -0
- package/Ref/Synchronized/definition.d.ts +3 -3
- package/Semaphore.d.ts +30 -0
- package/SubscriptionRef.d.ts +3 -3
- package/_cjs/Channel/api/mapOutConcurrentIO.cjs +14 -16
- package/_cjs/Channel/api/mapOutConcurrentIO.cjs.map +1 -1
- package/_cjs/Channel/api/mergeAllWith.cjs +30 -32
- package/_cjs/Channel/api/mergeAllWith.cjs.map +1 -1
- package/_cjs/IO/api/blocking.cjs +20 -0
- package/_cjs/IO/api/blocking.cjs.map +1 -0
- package/_cjs/IO.cjs +22 -0
- package/_cjs/IO.cjs.map +1 -1
- package/_cjs/Ref/Synchronized/constructors.cjs +4 -5
- package/_cjs/Ref/Synchronized/constructors.cjs.map +1 -1
- package/_cjs/Ref/Synchronized/definition.cjs +1 -2
- package/_cjs/Ref/Synchronized/definition.cjs.map +1 -1
- package/_cjs/Semaphore.cjs +90 -0
- package/_cjs/Semaphore.cjs.map +1 -0
- package/_cjs/Stream/api.cjs +47 -49
- package/_cjs/Stream/api.cjs.map +1 -1
- package/_cjs/SubscriptionRef.cjs +2 -3
- package/_cjs/SubscriptionRef.cjs.map +1 -1
- package/_cjs/internal/BackgroundScheduler.cjs +199 -0
- package/_cjs/internal/BackgroundScheduler.cjs.map +1 -0
- package/_mjs/Channel/api/mapOutConcurrentIO.mjs +14 -16
- package/_mjs/Channel/api/mapOutConcurrentIO.mjs.map +1 -1
- package/_mjs/Channel/api/mergeAllWith.mjs +30 -32
- package/_mjs/Channel/api/mergeAllWith.mjs.map +1 -1
- package/_mjs/IO/api/blocking.mjs +12 -0
- package/_mjs/IO/api/blocking.mjs.map +1 -0
- package/_mjs/IO.mjs +2 -0
- package/_mjs/IO.mjs.map +1 -1
- package/_mjs/Ref/Synchronized/constructors.mjs +4 -5
- package/_mjs/Ref/Synchronized/constructors.mjs.map +1 -1
- package/_mjs/Ref/Synchronized/definition.mjs +1 -2
- package/_mjs/Ref/Synchronized/definition.mjs.map +1 -1
- package/_mjs/Semaphore.mjs +78 -0
- package/_mjs/Semaphore.mjs.map +1 -0
- package/_mjs/Stream/api.mjs +47 -49
- package/_mjs/Stream/api.mjs.map +1 -1
- package/_mjs/SubscriptionRef.mjs +2 -3
- package/_mjs/SubscriptionRef.mjs.map +1 -1
- package/_mjs/internal/BackgroundScheduler.mjs +191 -0
- package/_mjs/internal/BackgroundScheduler.mjs.map +1 -0
- package/_src/Channel/api/mapOutConcurrentIO.ts +1 -1
- package/_src/Channel/api/mergeAllWith.ts +1 -1
- package/_src/IO/api/blocking.ts +9 -0
- package/_src/IO.ts +2 -0
- package/_src/Ref/Synchronized/constructors.ts +2 -2
- package/_src/Ref/Synchronized/definition.ts +1 -1
- package/_src/Semaphore.ts +81 -0
- package/_src/Stream/api.ts +1 -1
- package/_src/SubscriptionRef.ts +2 -2
- package/_src/global.ts +4 -0
- package/_src/internal/BackgroundScheduler.ts +276 -0
- package/global.d.ts +4 -0
- package/internal/BackgroundScheduler.d.ts +47 -0
- package/package.json +2 -2
@@ -35,7 +35,7 @@ export function mergeAllWith<OutDone>(
|
|
35
35
|
const cancelers = Δ(IO.acquireRelease(Queue.makeUnbounded<Future<never, void>>(), (queue) => queue.shutdown));
|
36
36
|
const lastDone = Δ(Ref.make<Maybe<OutDone>>(Nothing()));
|
37
37
|
const errorSignal = Δ(Future.make<never, void>());
|
38
|
-
const permits = Δ(
|
38
|
+
const permits = Δ(Semaphore(n));
|
39
39
|
const pull = Δ(channels.toPull);
|
40
40
|
const evaluatePull = (pull: IO<Env | Env1, OutErr | OutErr1, Either<OutDone, OutElem>>) =>
|
41
41
|
pull
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { backgroundScheduler } from "@fncts/io/internal/BackgroundScheduler";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @tsplus static fncts.io.IOOps blocking
|
5
|
+
* @tsplus getter fncts.io.IO blocking
|
6
|
+
*/
|
7
|
+
export function blocking<R, E, A>(self: IO<R, E, A>, __tsplusTrace?: string): IO<R, E, A> {
|
8
|
+
return FiberRef.currentScheduler.locally(backgroundScheduler)(IO.yieldNow) > self;
|
9
|
+
}
|
package/_src/IO.ts
CHANGED
@@ -13,6 +13,7 @@ export * from "./IO/api/addFinalizer.js";
|
|
13
13
|
export * from "./IO/api/addFinalizerExit.js";
|
14
14
|
export * from "./IO/api/asyncInterrupt.js";
|
15
15
|
export * from "./IO/api/asyncIO.js";
|
16
|
+
export * from "./IO/api/blocking.js";
|
16
17
|
export * from "./IO/api/bracket.js";
|
17
18
|
export * from "./IO/api/bracketExit.js";
|
18
19
|
export * from "./IO/api/clockWith.js";
|
@@ -38,6 +39,7 @@ export * from "./IO/api/memoize.js";
|
|
38
39
|
export * from "./IO/api/once.js";
|
39
40
|
export * from "./IO/api/onTermination.js";
|
40
41
|
export * from "./IO/api/provideLayer.js";
|
42
|
+
export * from "./IO/api/provideScope.js";
|
41
43
|
export * from "./IO/api/provideSomeLayer.js";
|
42
44
|
export * from "./IO/api/race.js";
|
43
45
|
export * from "./IO/api/raceFirst.js";
|
@@ -5,7 +5,7 @@ import { PSynchronizedInternal } from "./definition.js";
|
|
5
5
|
*/
|
6
6
|
export function unsafeMakeSynchronized<A>(a: A, __tsplusTrace?: string): Ref.Synchronized<A> {
|
7
7
|
const ref = Ref.unsafeMake(a);
|
8
|
-
const semaphore =
|
8
|
+
const semaphore = Semaphore.unsafeMake(1);
|
9
9
|
return new PSynchronizedInternal(semaphore, ref.get, (a: A) => ref.set(a));
|
10
10
|
}
|
11
11
|
|
@@ -15,7 +15,7 @@ export function unsafeMakeSynchronized<A>(a: A, __tsplusTrace?: string): Ref.Syn
|
|
15
15
|
export function makeSynchronized<A>(a: Lazy<A>, __tsplusTrace?: string): UIO<Ref.Synchronized<A>> {
|
16
16
|
return Do((_) => {
|
17
17
|
const ref = _(Ref.make(a));
|
18
|
-
const semaphore = _(
|
18
|
+
const semaphore = _(Semaphore(1));
|
19
19
|
return new PSynchronizedInternal(semaphore, ref.get, (a: A) => ref.set(a));
|
20
20
|
});
|
21
21
|
}
|
@@ -30,7 +30,7 @@ export const Synchronized: PSynchronizedOps = {};
|
|
30
30
|
export class PSynchronizedInternal<RA, RB, EA, EB, A, B> extends RefInternal<RA, RB, EA, EB, A, B> {
|
31
31
|
readonly [SynchronizedTypeId]: SynchronizedTypeId = SynchronizedTypeId;
|
32
32
|
constructor(
|
33
|
-
readonly semaphore:
|
33
|
+
readonly semaphore: Semaphore,
|
34
34
|
readonly unsafeGet: IO<RB, EB, B>,
|
35
35
|
readonly unsafeSet: (a: A) => IO<RA, EA, void>,
|
36
36
|
) {
|
@@ -0,0 +1,81 @@
|
|
1
|
+
/**
|
2
|
+
* @tsplus type fncts.io.Semaphore
|
3
|
+
* @tsplus companion fncts.io.SemaphoreOps
|
4
|
+
*/
|
5
|
+
export class Semaphore {
|
6
|
+
constructor(readonly permits: number) {}
|
7
|
+
|
8
|
+
taken = 0;
|
9
|
+
waiters = new Set<() => void>();
|
10
|
+
|
11
|
+
get free(): number {
|
12
|
+
return this.permits - this.taken;
|
13
|
+
}
|
14
|
+
|
15
|
+
runNext() {
|
16
|
+
const next = this.waiters.values().next();
|
17
|
+
if (!next.done) {
|
18
|
+
this.waiters.delete(next.value);
|
19
|
+
next.value();
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
take(n: number) {
|
24
|
+
return IO.asyncInterrupt<never, never, number>((cb) => {
|
25
|
+
if (this.free < n) {
|
26
|
+
const observer = () => {
|
27
|
+
if (this.free >= n) {
|
28
|
+
this.waiters.delete(observer);
|
29
|
+
this.taken += n;
|
30
|
+
cb(IO.succeedNow(n));
|
31
|
+
}
|
32
|
+
};
|
33
|
+
this.waiters.add(observer);
|
34
|
+
return Either.left(
|
35
|
+
IO(() => {
|
36
|
+
this.waiters.delete(observer);
|
37
|
+
}),
|
38
|
+
);
|
39
|
+
}
|
40
|
+
this.taken += n;
|
41
|
+
return Either.right(IO.succeedNow(n));
|
42
|
+
});
|
43
|
+
}
|
44
|
+
|
45
|
+
release(n: number) {
|
46
|
+
return IO.withFiberRuntime<never, never, void>((fiber) => {
|
47
|
+
this.taken -= n;
|
48
|
+
fiber.getFiberRef(FiberRef.currentScheduler).scheduleTask(() => {
|
49
|
+
this.waiters.forEach((wake) => wake());
|
50
|
+
});
|
51
|
+
return IO.unit;
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
withPermits(permits: number) {
|
56
|
+
return <R, E, A>(io: IO<R, E, A>): IO<R, E, A> => {
|
57
|
+
return IO.uninterruptibleMask((restore) =>
|
58
|
+
restore(this.take(permits)).flatMap((permits) => restore(io).ensuring(this.release(permits))),
|
59
|
+
);
|
60
|
+
};
|
61
|
+
}
|
62
|
+
|
63
|
+
withPermit<R, E, A>(io: IO<R, E, A>): IO<R, E, A> {
|
64
|
+
return this.withPermits(1)(io);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
/**
|
69
|
+
* @tsplus static fncts.io.SemaphoreOps unsafeMake
|
70
|
+
*/
|
71
|
+
export function unsafeMakeSemaphore(permits: number): Semaphore {
|
72
|
+
return new Semaphore(permits);
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @tsplus static fncts.io.SemaphoreOps make
|
77
|
+
* @tsplus static fncts.io.SemaphoreOps __call
|
78
|
+
*/
|
79
|
+
export function makeSemaphore(permits: number, __tsplusTrace?: string): UIO<Semaphore> {
|
80
|
+
return IO(Semaphore.unsafeMake(permits));
|
81
|
+
}
|
package/_src/Stream/api.ts
CHANGED
@@ -1092,7 +1092,7 @@ export function distributedWithDynamic<E, A>(
|
|
1092
1092
|
);
|
1093
1093
|
const add = Δ(
|
1094
1094
|
Do((Δ) => {
|
1095
|
-
const queuesLock = Δ(
|
1095
|
+
const queuesLock = Δ(Semaphore(1));
|
1096
1096
|
const newQueue = Δ(
|
1097
1097
|
Ref.make<UIO<readonly [symbol, Queue<Exit<Maybe<E>, A>>]>>(
|
1098
1098
|
Do((Δ) => {
|
package/_src/SubscriptionRef.ts
CHANGED
@@ -6,7 +6,7 @@ export type SubscriptionRefTypeId = typeof SubscriptionRefTypeId;
|
|
6
6
|
|
7
7
|
export class SubscriptionRefInternal<A> extends PSynchronizedInternal<never, never, never, never, A, A> {
|
8
8
|
readonly [SubscriptionRefTypeId]: SubscriptionRefTypeId = SubscriptionRefTypeId;
|
9
|
-
constructor(readonly semaphore:
|
9
|
+
constructor(readonly semaphore: Semaphore, readonly hub: Hub<A>, readonly ref: Ref<A>) {
|
10
10
|
super(semaphore, ref.get, (a) => ref.set(a));
|
11
11
|
}
|
12
12
|
changes: Stream<never, never, A> = Stream.unwrapScoped(
|
@@ -50,7 +50,7 @@ export function concrete<A>(_: SubscriptionRef<A>): asserts _ is SubscriptionRef
|
|
50
50
|
*/
|
51
51
|
export function make<A>(value: Lazy<A>): UIO<SubscriptionRef<A>> {
|
52
52
|
return Do((Δ) => {
|
53
|
-
const semaphore = Δ(
|
53
|
+
const semaphore = Δ(Semaphore(1));
|
54
54
|
const hub = Δ(Hub.makeUnbounded<A>());
|
55
55
|
const ref = Δ(Ref.make(value));
|
56
56
|
return new SubscriptionRefInternal(semaphore, hub, ref);
|
package/_src/global.ts
CHANGED
@@ -119,6 +119,10 @@ import { Finalizer } from "@fncts/io/Scope/Finalizer";
|
|
119
119
|
* @tsplus global
|
120
120
|
*/
|
121
121
|
import { ScopedRef } from "@fncts/io/ScopedRef/definition";
|
122
|
+
/**
|
123
|
+
* @tsplus global
|
124
|
+
*/
|
125
|
+
import { Semaphore } from "@fncts/io/Semaphore";
|
122
126
|
/**
|
123
127
|
* @tsplus global
|
124
128
|
*/
|
@@ -0,0 +1,276 @@
|
|
1
|
+
import type { Scheduler } from "@fncts/io/internal/Scheduler";
|
2
|
+
|
3
|
+
interface IdleDeadline {
|
4
|
+
timeRemaining(): number;
|
5
|
+
readonly didTimeout: boolean;
|
6
|
+
}
|
7
|
+
|
8
|
+
declare const requestIdleCallback: ((callback: (deadline: IdleDeadline) => void) => number) | undefined;
|
9
|
+
declare const cancelIdleCallback: ((handle: number | undefined) => void) | undefined;
|
10
|
+
declare const requestAnimationFrame: ((callback: (time: number) => void) => void) | undefined;
|
11
|
+
interface Navigator {
|
12
|
+
scheduling:
|
13
|
+
| {
|
14
|
+
isInputPending: (() => boolean) | undefined;
|
15
|
+
}
|
16
|
+
| undefined;
|
17
|
+
}
|
18
|
+
declare const navigator: Navigator;
|
19
|
+
|
20
|
+
interface WhenReady<T> {
|
21
|
+
promise: () => Promise<T>;
|
22
|
+
resolve: (value: T) => void;
|
23
|
+
}
|
24
|
+
|
25
|
+
function whenReady<T>(): WhenReady<T> {
|
26
|
+
const observers: Array<(value: T) => void> = [];
|
27
|
+
|
28
|
+
const promise = () => new Promise<T>((resolve) => observers.push(resolve));
|
29
|
+
|
30
|
+
return {
|
31
|
+
promise,
|
32
|
+
resolve: (value) => observers.forEach((observer) => observer(value)),
|
33
|
+
};
|
34
|
+
}
|
35
|
+
|
36
|
+
interface Task {
|
37
|
+
ready: () => Promise<void>;
|
38
|
+
resolve: () => void;
|
39
|
+
}
|
40
|
+
|
41
|
+
interface State {
|
42
|
+
tasks: Array<Task>;
|
43
|
+
frameTimeElapsed: boolean;
|
44
|
+
onIdleCallback: WhenReady<void>;
|
45
|
+
onAnimationFrame: WhenReady<void>;
|
46
|
+
frameWorkStartTime: number | undefined;
|
47
|
+
idleDeadline: IdleDeadline | undefined;
|
48
|
+
}
|
49
|
+
|
50
|
+
export class BackgroundScheduler implements Scheduler {
|
51
|
+
state: State = {
|
52
|
+
tasks: [],
|
53
|
+
idleDeadline: undefined,
|
54
|
+
frameTimeElapsed: false,
|
55
|
+
onIdleCallback: whenReady(),
|
56
|
+
onAnimationFrame: whenReady(),
|
57
|
+
frameWorkStartTime: undefined,
|
58
|
+
};
|
59
|
+
|
60
|
+
isTracking = false;
|
61
|
+
idleCallbackId: number | undefined;
|
62
|
+
lastCallTime = 0;
|
63
|
+
lastResult = false;
|
64
|
+
globalId = 0;
|
65
|
+
running = new Set<number>();
|
66
|
+
callbacks: Array<() => void> = [];
|
67
|
+
promiseEscapeId: number | undefined;
|
68
|
+
|
69
|
+
scheduleTask(task: () => void) {
|
70
|
+
this.yieldBackgroundOrContinue().then(() => {
|
71
|
+
task();
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
createTask(): Task {
|
76
|
+
const wr = whenReady<void>();
|
77
|
+
const item = { ready: wr.promise, resolve: wr.resolve };
|
78
|
+
this.state.tasks.push(item);
|
79
|
+
if (this.state.tasks.length === 1) {
|
80
|
+
this.startTracking();
|
81
|
+
}
|
82
|
+
return item;
|
83
|
+
}
|
84
|
+
|
85
|
+
startTracking(): void {
|
86
|
+
if (this.isTracking) {
|
87
|
+
return;
|
88
|
+
}
|
89
|
+
|
90
|
+
this.isTracking = true;
|
91
|
+
|
92
|
+
const reset = () => {
|
93
|
+
this.state.idleDeadline = undefined;
|
94
|
+
this.state.frameTimeElapsed = false;
|
95
|
+
this.state.frameWorkStartTime = undefined;
|
96
|
+
};
|
97
|
+
|
98
|
+
const loop = () => {
|
99
|
+
if (typeof requestIdleCallback !== "undefined") {
|
100
|
+
this.idleCallbackId = requestIdleCallback((deadline) => {
|
101
|
+
reset();
|
102
|
+
this.state.idleDeadline = deadline;
|
103
|
+
this.state.onIdleCallback.resolve();
|
104
|
+
this.state.onIdleCallback = whenReady();
|
105
|
+
});
|
106
|
+
}
|
107
|
+
|
108
|
+
const cb = () => {
|
109
|
+
reset();
|
110
|
+
this.state.onAnimationFrame.resolve();
|
111
|
+
this.state.onAnimationFrame = whenReady();
|
112
|
+
if (this.state.tasks.length === 0) {
|
113
|
+
this.isTracking = false;
|
114
|
+
if (typeof cancelIdleCallback !== "undefined") {
|
115
|
+
cancelIdleCallback(this.idleCallbackId);
|
116
|
+
}
|
117
|
+
} else {
|
118
|
+
loop();
|
119
|
+
}
|
120
|
+
};
|
121
|
+
|
122
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
123
|
+
requestAnimationFrame(cb);
|
124
|
+
} else {
|
125
|
+
setTimeout(cb, 0);
|
126
|
+
}
|
127
|
+
};
|
128
|
+
|
129
|
+
loop();
|
130
|
+
}
|
131
|
+
|
132
|
+
removeTask(task: Task) {
|
133
|
+
const index = this.state.tasks.indexOf(task);
|
134
|
+
if (index !== -1) {
|
135
|
+
this.state.tasks.splice(index, 1);
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
nextTask() {
|
140
|
+
if (this.state.tasks.length > 0) {
|
141
|
+
this.state.tasks[0]!.resolve();
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
isTimeToYield(): boolean {
|
146
|
+
const now = Date.now();
|
147
|
+
|
148
|
+
if (!this.lastResult && now - this.lastCallTime === 0) {
|
149
|
+
return this.lastResult;
|
150
|
+
}
|
151
|
+
|
152
|
+
this.lastCallTime = now;
|
153
|
+
this.lastResult =
|
154
|
+
now >= this.calculateDeadline() ||
|
155
|
+
(typeof navigator !== "undefined" && navigator.scheduling?.isInputPending?.() === true);
|
156
|
+
|
157
|
+
if (this.lastResult) {
|
158
|
+
this.state.frameTimeElapsed = true;
|
159
|
+
}
|
160
|
+
|
161
|
+
return this.lastResult;
|
162
|
+
}
|
163
|
+
|
164
|
+
calculateDeadline(): number {
|
165
|
+
if (this.state.frameWorkStartTime === undefined) {
|
166
|
+
return -1;
|
167
|
+
}
|
168
|
+
|
169
|
+
const idleDeadline =
|
170
|
+
this.state.idleDeadline === undefined
|
171
|
+
? Number.MAX_SAFE_INTEGER
|
172
|
+
: Date.now() + this.state.idleDeadline.timeRemaining();
|
173
|
+
|
174
|
+
return Math.min(this.state.frameWorkStartTime + 5, idleDeadline);
|
175
|
+
}
|
176
|
+
|
177
|
+
requestPromiseEscape(callback: () => void): number {
|
178
|
+
const id = this.globalId;
|
179
|
+
|
180
|
+
this.running.add(id);
|
181
|
+
|
182
|
+
Promise.resolve().then(() => {
|
183
|
+
Promise.resolve().then(() => {
|
184
|
+
if (this.running.has(id)) {
|
185
|
+
callback();
|
186
|
+
this.running.delete(id);
|
187
|
+
}
|
188
|
+
});
|
189
|
+
});
|
190
|
+
|
191
|
+
this.globalId += 1;
|
192
|
+
|
193
|
+
return id;
|
194
|
+
}
|
195
|
+
|
196
|
+
cancelPromiseEscape(id: number | undefined): void {
|
197
|
+
if (id !== undefined) {
|
198
|
+
this.running.delete(id);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
requestNextTask(callback: () => void): void {
|
203
|
+
if (this.callbacks.length === 0) {
|
204
|
+
const channel = new MessageChannel();
|
205
|
+
channel.port2.postMessage(undefined);
|
206
|
+
// @ts-expect-error
|
207
|
+
channel.port1.onmessage = (): void => {
|
208
|
+
channel.port1.close();
|
209
|
+
channel.port2.close();
|
210
|
+
|
211
|
+
const callbacksCopy = this.callbacks;
|
212
|
+
this.callbacks = [];
|
213
|
+
for (const callback of callbacksCopy) {
|
214
|
+
callback();
|
215
|
+
}
|
216
|
+
};
|
217
|
+
}
|
218
|
+
|
219
|
+
this.callbacks.push(callback);
|
220
|
+
}
|
221
|
+
|
222
|
+
async yieldControl(): Promise<void> {
|
223
|
+
this.cancelPromiseEscape(this.promiseEscapeId);
|
224
|
+
|
225
|
+
const task = this.createTask();
|
226
|
+
|
227
|
+
await this.schedule();
|
228
|
+
|
229
|
+
if (this.state.tasks[0] !== task) {
|
230
|
+
await task.ready();
|
231
|
+
if (this.isTimeToYield()) {
|
232
|
+
await this.schedule();
|
233
|
+
}
|
234
|
+
}
|
235
|
+
|
236
|
+
this.removeTask(task);
|
237
|
+
|
238
|
+
this.cancelPromiseEscape(this.promiseEscapeId);
|
239
|
+
|
240
|
+
this.promiseEscapeId = this.requestPromiseEscape(() => {
|
241
|
+
this.nextTask();
|
242
|
+
});
|
243
|
+
}
|
244
|
+
|
245
|
+
async schedule(): Promise<void> {
|
246
|
+
if (this.state.frameTimeElapsed) {
|
247
|
+
await this.state.onAnimationFrame.promise();
|
248
|
+
}
|
249
|
+
|
250
|
+
if (typeof requestIdleCallback === "undefined") {
|
251
|
+
await new Promise<void>((resolve) => this.requestNextTask(resolve));
|
252
|
+
|
253
|
+
if (typeof navigator !== "undefined" && navigator.scheduling?.isInputPending?.() === true) {
|
254
|
+
await this.schedule();
|
255
|
+
} else if (this.state.frameWorkStartTime === undefined) {
|
256
|
+
this.state.frameWorkStartTime = Date.now();
|
257
|
+
}
|
258
|
+
} else {
|
259
|
+
await this.state.onIdleCallback.promise();
|
260
|
+
|
261
|
+
if (this.state.frameWorkStartTime === undefined) {
|
262
|
+
this.state.frameWorkStartTime = Date.now();
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
yieldBackgroundOrContinue(): Promise<void> {
|
268
|
+
if (this.isTimeToYield()) {
|
269
|
+
return this.yieldControl();
|
270
|
+
}
|
271
|
+
|
272
|
+
return Promise.resolve();
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
export const backgroundScheduler = new BackgroundScheduler();
|
package/global.d.ts
CHANGED
@@ -118,6 +118,10 @@ import { Finalizer } from "@fncts/io/Scope/Finalizer";
|
|
118
118
|
* @tsplus global
|
119
119
|
*/
|
120
120
|
import { ScopedRef } from "@fncts/io/ScopedRef/definition";
|
121
|
+
/**
|
122
|
+
* @tsplus global
|
123
|
+
*/
|
124
|
+
import { Semaphore } from "@fncts/io/Semaphore";
|
121
125
|
/**
|
122
126
|
* @tsplus global
|
123
127
|
*/
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import type { Scheduler } from "@fncts/io/internal/Scheduler";
|
2
|
+
interface IdleDeadline {
|
3
|
+
timeRemaining(): number;
|
4
|
+
readonly didTimeout: boolean;
|
5
|
+
}
|
6
|
+
interface WhenReady<T> {
|
7
|
+
promise: () => Promise<T>;
|
8
|
+
resolve: (value: T) => void;
|
9
|
+
}
|
10
|
+
interface Task {
|
11
|
+
ready: () => Promise<void>;
|
12
|
+
resolve: () => void;
|
13
|
+
}
|
14
|
+
interface State {
|
15
|
+
tasks: Array<Task>;
|
16
|
+
frameTimeElapsed: boolean;
|
17
|
+
onIdleCallback: WhenReady<void>;
|
18
|
+
onAnimationFrame: WhenReady<void>;
|
19
|
+
frameWorkStartTime: number | undefined;
|
20
|
+
idleDeadline: IdleDeadline | undefined;
|
21
|
+
}
|
22
|
+
export declare class BackgroundScheduler implements Scheduler {
|
23
|
+
state: State;
|
24
|
+
isTracking: boolean;
|
25
|
+
idleCallbackId: number | undefined;
|
26
|
+
lastCallTime: number;
|
27
|
+
lastResult: boolean;
|
28
|
+
globalId: number;
|
29
|
+
running: Set<number>;
|
30
|
+
callbacks: Array<() => void>;
|
31
|
+
promiseEscapeId: number | undefined;
|
32
|
+
scheduleTask(task: () => void): void;
|
33
|
+
createTask(): Task;
|
34
|
+
startTracking(): void;
|
35
|
+
removeTask(task: Task): void;
|
36
|
+
nextTask(): void;
|
37
|
+
isTimeToYield(): boolean;
|
38
|
+
calculateDeadline(): number;
|
39
|
+
requestPromiseEscape(callback: () => void): number;
|
40
|
+
cancelPromiseEscape(id: number | undefined): void;
|
41
|
+
requestNextTask(callback: () => void): void;
|
42
|
+
yieldControl(): Promise<void>;
|
43
|
+
schedule(): Promise<void>;
|
44
|
+
yieldBackgroundOrContinue(): Promise<void>;
|
45
|
+
}
|
46
|
+
export declare const backgroundScheduler: BackgroundScheduler;
|
47
|
+
export {};
|