@fncts/io 0.0.30 → 0.0.31

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. package/IO/api/blocking.d.ts +7 -0
  2. package/IO.d.ts +1 -0
  3. package/Ref/Synchronized/definition.d.ts +3 -3
  4. package/Semaphore.d.ts +30 -0
  5. package/SubscriptionRef.d.ts +3 -3
  6. package/_cjs/Channel/api/mapOutConcurrentIO.cjs +14 -16
  7. package/_cjs/Channel/api/mapOutConcurrentIO.cjs.map +1 -1
  8. package/_cjs/Channel/api/mergeAllWith.cjs +30 -32
  9. package/_cjs/Channel/api/mergeAllWith.cjs.map +1 -1
  10. package/_cjs/IO/api/blocking.cjs +20 -0
  11. package/_cjs/IO/api/blocking.cjs.map +1 -0
  12. package/_cjs/IO.cjs +11 -0
  13. package/_cjs/IO.cjs.map +1 -1
  14. package/_cjs/Ref/Synchronized/constructors.cjs +4 -5
  15. package/_cjs/Ref/Synchronized/constructors.cjs.map +1 -1
  16. package/_cjs/Ref/Synchronized/definition.cjs +1 -2
  17. package/_cjs/Ref/Synchronized/definition.cjs.map +1 -1
  18. package/_cjs/Semaphore.cjs +90 -0
  19. package/_cjs/Semaphore.cjs.map +1 -0
  20. package/_cjs/Stream/api.cjs +47 -49
  21. package/_cjs/Stream/api.cjs.map +1 -1
  22. package/_cjs/SubscriptionRef.cjs +2 -3
  23. package/_cjs/SubscriptionRef.cjs.map +1 -1
  24. package/_cjs/internal/BackgroundScheduler.cjs +199 -0
  25. package/_cjs/internal/BackgroundScheduler.cjs.map +1 -0
  26. package/_mjs/Channel/api/mapOutConcurrentIO.mjs +14 -16
  27. package/_mjs/Channel/api/mapOutConcurrentIO.mjs.map +1 -1
  28. package/_mjs/Channel/api/mergeAllWith.mjs +30 -32
  29. package/_mjs/Channel/api/mergeAllWith.mjs.map +1 -1
  30. package/_mjs/IO/api/blocking.mjs +12 -0
  31. package/_mjs/IO/api/blocking.mjs.map +1 -0
  32. package/_mjs/IO.mjs +1 -0
  33. package/_mjs/IO.mjs.map +1 -1
  34. package/_mjs/Ref/Synchronized/constructors.mjs +4 -5
  35. package/_mjs/Ref/Synchronized/constructors.mjs.map +1 -1
  36. package/_mjs/Ref/Synchronized/definition.mjs +1 -2
  37. package/_mjs/Ref/Synchronized/definition.mjs.map +1 -1
  38. package/_mjs/Semaphore.mjs +78 -0
  39. package/_mjs/Semaphore.mjs.map +1 -0
  40. package/_mjs/Stream/api.mjs +47 -49
  41. package/_mjs/Stream/api.mjs.map +1 -1
  42. package/_mjs/SubscriptionRef.mjs +2 -3
  43. package/_mjs/SubscriptionRef.mjs.map +1 -1
  44. package/_mjs/internal/BackgroundScheduler.mjs +191 -0
  45. package/_mjs/internal/BackgroundScheduler.mjs.map +1 -0
  46. package/_src/Channel/api/mapOutConcurrentIO.ts +1 -1
  47. package/_src/Channel/api/mergeAllWith.ts +1 -1
  48. package/_src/IO/api/blocking.ts +9 -0
  49. package/_src/IO.ts +1 -0
  50. package/_src/Ref/Synchronized/constructors.ts +2 -2
  51. package/_src/Ref/Synchronized/definition.ts +1 -1
  52. package/_src/Semaphore.ts +81 -0
  53. package/_src/Stream/api.ts +1 -1
  54. package/_src/SubscriptionRef.ts +2 -2
  55. package/_src/global.ts +4 -0
  56. package/_src/internal/BackgroundScheduler.ts +276 -0
  57. package/global.d.ts +4 -0
  58. package/internal/BackgroundScheduler.d.ts +47 -0
  59. 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 = Δ(TSemaphore.make(n).commit);
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";
@@ -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 = TSemaphore.unsafeMake(1);
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 = _(TSemaphore.make(1).commit);
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: TSemaphore,
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
+ }
@@ -1092,7 +1092,7 @@ export function distributedWithDynamic<E, A>(
1092
1092
  );
1093
1093
  const add = Δ(
1094
1094
  Do((Δ) => {
1095
- const queuesLock = Δ(TSemaphore.make(1).commit);
1095
+ const queuesLock = Δ(Semaphore(1));
1096
1096
  const newQueue = Δ(
1097
1097
  Ref.make<UIO<readonly [symbol, Queue<Exit<Maybe<E>, A>>]>>(
1098
1098
  Do((Δ) => {
@@ -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: TSemaphore, readonly hub: Hub<A>, readonly ref: Ref<A>) {
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 = Δ(TSemaphore.make(1).commit);
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 {};
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@fncts/io",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "dependencies": {
5
- "@fncts/base": "0.0.25",
5
+ "@fncts/base": "0.0.26",
6
6
  "@fncts/transformers": "0.0.4",
7
7
  "@fncts/typelevel": "0.0.15"
8
8
  },