ciorent 0.1.3 → 0.1.4
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/README.md +87 -87
- package/fiber.d.ts +4 -4
- package/fiber.js +1 -1
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/latch.d.ts +2 -2
- package/latch.js +1 -1
- package/package.json +5 -5
- package/topic.d.ts +3 -3
- package/topic.js +1 -1
package/README.md
CHANGED
@@ -4,7 +4,7 @@ Semaphore is a concurrency primitive used to control access to a common resource
|
|
4
4
|
|
5
5
|
```ts
|
6
6
|
import * as semaphore from 'ciorent/semaphore';
|
7
|
-
import * as
|
7
|
+
import * as co from 'ciorent';
|
8
8
|
|
9
9
|
// Only allow 2 task to run concurrently
|
10
10
|
const sem = semaphore.init(2);
|
@@ -16,7 +16,7 @@ const task = async (id: number) => {
|
|
16
16
|
console.log('Task', id, 'started');
|
17
17
|
|
18
18
|
// Let the main thread schedules other tasks
|
19
|
-
for (let i = 1; i <= 5; i++) await
|
19
|
+
for (let i = 1; i <= 5; i++) await co.pause;
|
20
20
|
|
21
21
|
console.log('Task', id, 'end');
|
22
22
|
|
@@ -25,7 +25,66 @@ const task = async (id: number) => {
|
|
25
25
|
}
|
26
26
|
|
27
27
|
// Try to run 5 tasks concurrently
|
28
|
-
|
28
|
+
co.spawn(5, task);
|
29
|
+
```
|
30
|
+
|
31
|
+
## Fibers
|
32
|
+
Virtual threads with more controlled execution.
|
33
|
+
|
34
|
+
```ts
|
35
|
+
import * as co from 'ciorent';
|
36
|
+
import * as fiber from 'ciorent/fiber';
|
37
|
+
|
38
|
+
const f1 = fiber.fn(function* () {
|
39
|
+
console.log('Fiber 1 started');
|
40
|
+
|
41
|
+
// Wait for a promise
|
42
|
+
yield co.sleep(1000);
|
43
|
+
|
44
|
+
console.log('Fiber 1 done');
|
45
|
+
return Math.random();
|
46
|
+
});
|
47
|
+
|
48
|
+
fiber.spawn(function* (proc) {
|
49
|
+
console.log('Fiber 2 started');
|
50
|
+
|
51
|
+
// Start f1, wait for it to finish and get the result
|
52
|
+
const res = yield* fiber.join(fiber.spawn(f1));
|
53
|
+
console.log('Fiber 1 result:', res);
|
54
|
+
|
55
|
+
// Start f1 and make its lifetime depends on current fiber
|
56
|
+
fiber.mount(fiber.spawn(f1), proc);
|
57
|
+
|
58
|
+
// The runtime will interrupt f1
|
59
|
+
console.log('Fiber 2 done');
|
60
|
+
});
|
61
|
+
```
|
62
|
+
|
63
|
+
## Latch
|
64
|
+
Latch is a synchronization primitive that allows one process to wait until another completes an operation before continuing execution.
|
65
|
+
|
66
|
+
```ts
|
67
|
+
import * as latch from 'ciorent/latch';
|
68
|
+
|
69
|
+
const startFetch = latch.init();
|
70
|
+
|
71
|
+
const task = async () => {
|
72
|
+
// Blocks until the latch is open
|
73
|
+
await latch.pause(startFetch);
|
74
|
+
|
75
|
+
console.log('Start fetching...');
|
76
|
+
const res = await fetch('http://example.com');
|
77
|
+
console.log('Fetch status:', res.status);
|
78
|
+
}
|
79
|
+
|
80
|
+
const prepare = () => {
|
81
|
+
// This always run first
|
82
|
+
console.log('Run before fetch:', performance.now().toFixed(2));
|
83
|
+
latch.open(startFetch);
|
84
|
+
}
|
85
|
+
|
86
|
+
task();
|
87
|
+
prepare();
|
29
88
|
```
|
30
89
|
|
31
90
|
## Pubsub
|
@@ -33,15 +92,15 @@ A fast, simple publish-subscribe API.
|
|
33
92
|
|
34
93
|
```ts
|
35
94
|
import * as topic from 'ciorent/topic';
|
36
|
-
import * as
|
95
|
+
import * as co from 'ciorent';
|
37
96
|
|
38
97
|
const messages = topic.init<number>();
|
39
98
|
|
40
99
|
// A task that publish messages
|
41
100
|
const publisher = async () => {
|
42
101
|
for (let i = 0; i < 3; i++) {
|
43
|
-
await
|
44
|
-
topic.
|
102
|
+
await co.sleep(100);
|
103
|
+
topic.publish(messages, i);
|
45
104
|
}
|
46
105
|
|
47
106
|
// Resolve all waiting promises
|
@@ -50,8 +109,8 @@ const publisher = async () => {
|
|
50
109
|
}
|
51
110
|
|
52
111
|
// Spawn 3 tasks that recieve messages
|
53
|
-
|
54
|
-
const sub = topic.
|
112
|
+
co.spawn(3, async (id: number) => {
|
113
|
+
const sub = topic.subscribe(messages);
|
55
114
|
|
56
115
|
while (true) {
|
57
116
|
// Block until the value is sent
|
@@ -69,13 +128,13 @@ Channel is a synchronization primitive via message passing. A message may be sen
|
|
69
128
|
|
70
129
|
```ts
|
71
130
|
import * as channel from 'ciorent/channel';
|
72
|
-
import * as
|
131
|
+
import * as co from 'ciorent';
|
73
132
|
|
74
133
|
const c = channel.init<number>();
|
75
134
|
|
76
135
|
const run = async () => {
|
77
136
|
for (let i = 0; i < 5; i++) {
|
78
|
-
await
|
137
|
+
await co.sleep(100);
|
79
138
|
channel.send(c, i);
|
80
139
|
console.log('Sent', i);
|
81
140
|
}
|
@@ -102,70 +161,11 @@ run();
|
|
102
161
|
console.log('Starting...');
|
103
162
|
```
|
104
163
|
|
105
|
-
## Latch
|
106
|
-
Latch is a synchronization primitive that allows one process to wait until another completes an operation before continuing execution.
|
107
|
-
|
108
|
-
```ts
|
109
|
-
import * as latch from 'ciorent/latch';
|
110
|
-
|
111
|
-
const startFetch = latch.init();
|
112
|
-
|
113
|
-
const task = async () => {
|
114
|
-
// Blocks until the latch is open
|
115
|
-
await latch.pause(startFetch);
|
116
|
-
|
117
|
-
console.log('Start fetching...');
|
118
|
-
const res = await fetch('http://example.com');
|
119
|
-
console.log('Fetch status:', res.status);
|
120
|
-
}
|
121
|
-
|
122
|
-
const prepare = () => {
|
123
|
-
// This always run first
|
124
|
-
console.log('Run before fetch:', performance.now().toFixed(2));
|
125
|
-
latch.open(startFetch);
|
126
|
-
}
|
127
|
-
|
128
|
-
task();
|
129
|
-
prepare();
|
130
|
-
```
|
131
|
-
|
132
|
-
## Fibers
|
133
|
-
Virtual threads with more controlled execution.
|
134
|
-
|
135
|
-
```ts
|
136
|
-
import * as cio from 'ciorent';
|
137
|
-
import * as fiber from 'ciorent/fiber';
|
138
|
-
|
139
|
-
const f1 = fiber.fn(function* () {
|
140
|
-
console.log('Fiber 1 started');
|
141
|
-
|
142
|
-
// Wait for a promise
|
143
|
-
yield cio.sleep(1000);
|
144
|
-
|
145
|
-
console.log('Fiber 1 done');
|
146
|
-
return Math.random();
|
147
|
-
});
|
148
|
-
|
149
|
-
fiber.spawn(function* (proc) {
|
150
|
-
console.log('Fiber 2 started');
|
151
|
-
|
152
|
-
// Start f1, wait for it to finish and get the result
|
153
|
-
const res = yield* fiber.join(fiber.spawn(f1));
|
154
|
-
console.log('Fiber 1 result:', res);
|
155
|
-
|
156
|
-
// Start f1 and make its lifetime depends on current fiber
|
157
|
-
fiber.mount(fiber.spawn(f1), proc);
|
158
|
-
|
159
|
-
// The runtime will interrupt f1
|
160
|
-
console.log('Fiber 2 done');
|
161
|
-
});
|
162
|
-
```
|
163
|
-
|
164
164
|
## Utilities
|
165
165
|
### Pausing
|
166
166
|
Delay the execution of a function for other asynchronous tasks to run.
|
167
167
|
```ts
|
168
|
-
import * as
|
168
|
+
import * as co from 'ciorent';
|
169
169
|
|
170
170
|
// Expensive sync task
|
171
171
|
const task1 = async () => {
|
@@ -173,7 +173,7 @@ const task1 = async () => {
|
|
173
173
|
|
174
174
|
// Yield control back to the runtime, allowing it to
|
175
175
|
// schedule other tasks
|
176
|
-
await
|
176
|
+
await co.pause;
|
177
177
|
|
178
178
|
// Simulate heavy operation
|
179
179
|
for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
|
@@ -197,82 +197,82 @@ task2();
|
|
197
197
|
### Sleep
|
198
198
|
Cross-runtime synchronous and asynchronous sleep functions.
|
199
199
|
```ts
|
200
|
-
import * as
|
200
|
+
import * as co from 'ciorent';
|
201
201
|
|
202
202
|
const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
|
203
203
|
|
204
204
|
logTime('Start');
|
205
205
|
|
206
206
|
// Non-blocking
|
207
|
-
await
|
207
|
+
await co.sleep(500);
|
208
208
|
logTime('After about 0.5s');
|
209
209
|
|
210
210
|
// This blocks the event loop
|
211
211
|
// On the browser this only works in workers and blocks the worker thread
|
212
|
-
|
212
|
+
co.sleepSync(500);
|
213
213
|
logTime('After another 0.5s');
|
214
214
|
```
|
215
215
|
|
216
216
|
### Spawning tasks
|
217
217
|
Utilities to create and run tasks.
|
218
218
|
```ts
|
219
|
-
import * as
|
219
|
+
import * as co from 'ciorent';
|
220
220
|
|
221
221
|
const task = async (id: number) => {
|
222
|
-
await
|
222
|
+
await co.sleep((10 - id) * 20 + 50);
|
223
223
|
console.log('Task', id, 'done');
|
224
224
|
}
|
225
225
|
|
226
226
|
// Spawn and run 5 tasks sequentially
|
227
227
|
console.log('Running 5 tasks sequentially:');
|
228
|
-
await
|
228
|
+
await co.sequential(5, task);
|
229
229
|
|
230
230
|
// Spawn and run 5 tasks concurrently
|
231
231
|
console.log('Running 5 tasks concurrently:');
|
232
|
-
await
|
232
|
+
await Promise.all(co.spawn(5, task));
|
233
233
|
```
|
234
234
|
|
235
235
|
### Debounce
|
236
236
|
Postpones execution until after an idle period.
|
237
237
|
```ts
|
238
|
-
import * as
|
238
|
+
import * as co from 'ciorent';
|
239
239
|
|
240
|
-
const fn =
|
240
|
+
const fn = co.debounce((id: number) => {
|
241
241
|
console.log('ID:', id);
|
242
242
|
}, 500);
|
243
243
|
|
244
244
|
fn(1); // fn(1) gets skipped
|
245
|
-
await
|
245
|
+
await co.sleep(100);
|
246
246
|
fn(2); // fn(2) gets executed
|
247
247
|
```
|
248
248
|
|
249
249
|
### Rate Limit
|
250
250
|
Limits the number of calls within a time window.
|
251
251
|
```ts
|
252
|
-
import * as
|
252
|
+
import * as co from 'ciorent';
|
253
253
|
|
254
254
|
// Allow 2 calls in 500ms, other calls are dropped
|
255
|
-
const fn =
|
255
|
+
const fn = co.rateLimit((id: number) => {
|
256
256
|
console.log('Call ' + id + ':', Math.floor(performance.now()) + 'ms');
|
257
257
|
}, 500, 2);
|
258
258
|
|
259
259
|
// Some calls will be dropped
|
260
260
|
for (let i = 0; i < 8; i++) {
|
261
261
|
fn(i);
|
262
|
-
await
|
262
|
+
await co.sleep(400);
|
263
263
|
}
|
264
264
|
```
|
265
265
|
|
266
266
|
### Throttle
|
267
267
|
Executes a function at a regular interval.
|
268
268
|
```ts
|
269
|
-
import * as
|
269
|
+
import * as co from 'ciorent';
|
270
270
|
|
271
271
|
// Allow 2 calls in 500ms
|
272
|
-
const fn =
|
272
|
+
const fn = co.throttle((id: number) => {
|
273
273
|
console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
|
274
274
|
}, 500, 2);
|
275
275
|
|
276
|
-
|
276
|
+
co.spawn(8, fn);
|
277
277
|
```
|
278
278
|
|
package/fiber.d.ts
CHANGED
@@ -27,19 +27,19 @@ export interface Process<TReturn = unknown> {
|
|
27
27
|
*/
|
28
28
|
export type Runtime = <const TReturn, const Args extends any[]>(gen: (proc: Process<TReturn>, ...args: Args) => Generator<any, TReturn>, ...args: Args) => Process<TReturn>;
|
29
29
|
/**
|
30
|
-
* Check whether the fiber
|
30
|
+
* Check whether the fiber has been paused
|
31
31
|
*/
|
32
32
|
export declare const paused: (t: Process) => boolean;
|
33
33
|
/**
|
34
34
|
* Check whether the fiber is running
|
35
35
|
*/
|
36
|
-
export declare const
|
36
|
+
export declare const resumed: (t: Process) => boolean;
|
37
37
|
/**
|
38
|
-
* Check whether the fiber
|
38
|
+
* Check whether the fiber has finished
|
39
39
|
*/
|
40
40
|
export declare const done: (t: Process) => boolean;
|
41
41
|
/**
|
42
|
-
* Check whether the fiber
|
42
|
+
* Check whether the fiber has been interrupted
|
43
43
|
*/
|
44
44
|
export declare const stopped: (t: Process) => boolean;
|
45
45
|
/**
|
package/fiber.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export let paused=(t)=>t[1]===0;export let
|
1
|
+
export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let done=(t)=>t[1]===2;export let stopped=(t)=>t[1]===3;let invoke=async(g,thread)=>{try{let t=g.next();while(!t.done){let v=await t.value;if(thread[1]===0){let r;let p=new Promise((res)=>{r=res});thread[2]=r;await p}if(thread[1]===3)return;t=g.next(v)}thread[1]=2;return t.value}finally{thread[3].forEach(stop)}};export let fn=(f)=>f;export let spawn=(f,...args)=>{let thread=[null,1,null,[]];thread[0]=invoke(f(thread,...args),thread);return thread};export let pause=(t)=>{if(t[1]===1)t[1]=0};export let resume=(t)=>{if(t[1]===0){t[1]=1;t[2]?.()}};export let stop=(t)=>{if(t[1]===0)t[2]?.();t[1]=3};export function*join(t){return yield t[0]}export let finish=(t)=>t[3];export let mount=(child,parent)=>{parent[3].push(child)};export let control=(t,signal)=>{signal.addEventListener("abort",()=>{stop(t)})};export function*unwrap(t){return yield t}
|
package/index.d.ts
CHANGED
@@ -30,11 +30,11 @@ export declare const sleepSync: (ms: number) => void;
|
|
30
30
|
*/
|
31
31
|
export declare const sequential: <const T extends any[]>(n: number, task: (...args: [...T, id: number]) => Promise<any>, ...args: T) => Promise<void>;
|
32
32
|
/**
|
33
|
-
* Spawn n tasks
|
33
|
+
* Spawn n concurrent tasks
|
34
34
|
* @param n
|
35
35
|
* @param task - The function to run
|
36
36
|
*/
|
37
|
-
export declare const
|
37
|
+
export declare const spawn: <const T extends any[], const R>(n: number, task: (...args: [...T, id: number]) => Promise<R>, ...args: T) => Promise<R>[];
|
38
38
|
/**
|
39
39
|
* Drop function calls until it doesn't get called for a specific period.
|
40
40
|
* @param f - The target function to debounce (it must not throw errors)
|
package/index.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let
|
1
|
+
export let pause=Promise.resolve();export let sleep=globalThis.Bun?.sleep??globalThis.process?.getBuiltinModule?.("timers/promises").setTimeout??((ms)=>new Promise((res)=>{setTimeout(res,ms)}));let sharedBuf=new Int32Array(new SharedArrayBuffer(4));export let sleepSync=globalThis.Bun?.sleepSync??((ms)=>{Atomics.wait(sharedBuf,0,0,ms)});export let sequential=async(n,task,...args)=>{for(let i=0;i<n;i++)await task(...args,i)};export let spawn=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return arr};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let rateLimit=(f,ms,limit)=>{let cur=limit;let unlock=()=>{cur=limit};return(...a)=>{if(cur>0){if(cur===1)setTimeout(unlock,ms);cur--;f(...a)}}};export let throttle=(f,ms,limit)=>{let head=[null];let tail=head;let cur=limit;let unlock=()=>{cur=limit;while(cur>0){if(tail===head)return;cur--;tail=tail[0];tail[1](f(...tail[2]))}setTimeout(unlock,ms)};return(...a)=>{if(cur===1){setTimeout(unlock,ms)}else if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r,a];return p}cur--;return f(...a)}};
|
package/latch.d.ts
CHANGED
@@ -18,6 +18,6 @@ export declare const pause: (latch: Latch) => Promise<void>;
|
|
18
18
|
*/
|
19
19
|
export declare const open: (latch: Latch) => void;
|
20
20
|
/**
|
21
|
-
*
|
21
|
+
* Close a latch
|
22
22
|
*/
|
23
|
-
export declare const
|
23
|
+
export declare const close: (latch: Latch) => void;
|
package/latch.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
import{pause as endPromise}from"./index.js";export let init=()=>{let r;return[new Promise((res)=>{r=res}),r]};export let pause=(latch)=>latch[0];export let open=(latch)=>{latch[1]();latch[0]=endPromise};export let
|
1
|
+
import{pause as endPromise}from"./index.js";export let init=()=>{let r;return[new Promise((res)=>{r=res}),r]};export let pause=(latch)=>latch[0];export let open=(latch)=>{latch[1]();latch[0]=endPromise};export let close=(latch)=>{if(latch[0]===endPromise){let r;latch[0]=new Promise((res)=>{r=res});latch[1]=r}};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "ciorent",
|
3
|
-
"version": "0.1.
|
3
|
+
"version": "0.1.4",
|
4
4
|
"description": "A lightweight, low-overhead concurrency library",
|
5
5
|
"homepage": "https://ciorent.netlify.app",
|
6
6
|
"repository": {
|
@@ -18,14 +18,14 @@
|
|
18
18
|
"main": "./index.js",
|
19
19
|
"types": "./index.d.ts",
|
20
20
|
"exports": {
|
21
|
-
"./sliding-queue": "./sliding-queue.js",
|
22
21
|
"./fixed-queue": "./fixed-queue.js",
|
23
|
-
"./fiber": "./fiber.js",
|
24
|
-
".": "./index.js",
|
25
|
-
"./semaphore": "./semaphore.js",
|
26
22
|
"./dropping-queue": "./dropping-queue.js",
|
23
|
+
"./sliding-queue": "./sliding-queue.js",
|
24
|
+
".": "./index.js",
|
27
25
|
"./topic": "./topic.js",
|
26
|
+
"./semaphore": "./semaphore.js",
|
28
27
|
"./channel": "./channel.js",
|
28
|
+
"./fiber": "./fiber.js",
|
29
29
|
"./latch": "./latch.js"
|
30
30
|
}
|
31
31
|
}
|
package/topic.d.ts
CHANGED
@@ -34,12 +34,12 @@ export interface Subscriber<T extends {}> {
|
|
34
34
|
* Subscribe to a topic
|
35
35
|
* @param t
|
36
36
|
*/
|
37
|
-
export declare const
|
37
|
+
export declare const subscribe: <T extends {}>(t: Topic<T>) => Subscriber<T>;
|
38
38
|
/**
|
39
|
-
*
|
39
|
+
* Publish to a topic
|
40
40
|
* @param t
|
41
41
|
*/
|
42
|
-
export declare const
|
42
|
+
export declare const publish: <T extends {}>(t: Topic<T>, value: T) => void;
|
43
43
|
/**
|
44
44
|
* Resolve all waiting promises and clear all pending values
|
45
45
|
* @param t
|
package/topic.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export let init=()=>[[null],[],[]];export let
|
1
|
+
export let init=()=>[[null],[],[]];export let subscribe=(t)=>[t,t[0]];export let publish=(t,value)=>{let head=t[0]=t[0][0]=[null,value];for(let i=0,res=t[1],subs=t[2];i<res.length;i++){res[i](value);subs[i][1]=head}t[1]=[];t[2]=[]};export let flush=(t)=>{let head=t[0]=[null];for(let i=0,res=t[1],subs=t[2];i<res.length;i++){res[i]();subs[i][1]=head}t[1]=[];t[2]=[]};export let poll=(t)=>t[1][0]!==null?(t[1]=t[1][0])[1]:undefined;export let recieve=(t)=>{if(t[1][0]!==null)return Promise.resolve((t[1]=t[1][0])[1]);let topic=t[0];topic[2].push(t);return new Promise((res)=>{topic[1].push(res)})};
|