ciorent 0.2.0 → 0.2.1

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 CHANGED
@@ -7,127 +7,141 @@ A lightweight, low-overhead concurrency library.
7
7
  - Fully type-safe.
8
8
 
9
9
  ## Examples
10
- ### Semaphore
11
- Semaphore is a concurrency primitive used to control access to a common resource by multiple processes.
12
-
10
+ ### Utilities
11
+ #### Spawning tasks
12
+ Utilities to create and run tasks.
13
13
  ```ts
14
- import * as semaphore from 'ciorent/semaphore';
15
14
  import * as co from 'ciorent';
16
15
 
17
- // Only allow 2 task to run concurrently
18
- const sem = semaphore.init(2);
19
-
20
16
  const task = async (id: number) => {
21
- // Acquire the semaphore or wait for the semaphore to be available
22
- await semaphore.pause(sem);
17
+ await co.sleep((10 - id) * 20 + 50);
18
+ console.log('Task', id, 'done');
19
+ }
23
20
 
24
- console.log('Task', id, 'started');
21
+ // Spawn and run 5 tasks sequentially
22
+ console.log('Running 5 tasks sequentially:');
23
+ await co.sequential(5, task);
25
24
 
26
- // Let the main thread schedules other tasks
27
- for (let i = 1; i <= 5; i++) await co.pause;
25
+ // Spawn and run 5 tasks concurrently
26
+ console.log('Running 5 tasks concurrently:');
27
+ await Promise.all(co.spawn(5, task));
28
+ ```
28
29
 
29
- console.log('Task', id, 'end');
30
+ #### Sleep
31
+ Cross-runtime synchronous and asynchronous sleep functions.
32
+ ```ts
33
+ import * as co from 'ciorent';
30
34
 
31
- // Release the semaphore
32
- semaphore.signal(sem);
33
- }
35
+ const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
34
36
 
35
- // Try to run 5 tasks concurrently
36
- co.spawn(5, task);
37
- ```
37
+ logTime('Start');
38
38
 
39
- ### Fibers
40
- Virtual threads with more controlled execution.
39
+ // Non-blocking
40
+ await co.sleep(500);
41
+ logTime('After about 0.5s');
42
+
43
+ // This blocks the event loop
44
+ // On the browser this only works in workers and blocks the worker thread
45
+ co.sleepSync(500);
46
+ logTime('After another 0.5s');
47
+ ```
41
48
 
49
+ #### Debounce
50
+ Postpones execution until after an idle period.
42
51
  ```ts
43
52
  import * as co from 'ciorent';
44
- import * as fiber from 'ciorent/fiber';
45
-
46
- const f1 = fiber.fn(function* () {
47
- // Wait for a promise
48
- yield co.sleep(1000);
49
53
 
50
- // Wait for a promise and return its result
51
- const res = yield* fiber.unwrap(Promise.resolve(1));
52
- console.log('Fiber 1 recieved:', res);
54
+ const fn = co.debounce((id: number) => {
55
+ console.log('ID:', id);
56
+ }, 500);
53
57
 
54
- return Math.random();
55
- });
58
+ fn(1); // fn(1) gets skipped
59
+ await co.sleep(100);
60
+ fn(2); // fn(2) gets executed
61
+ ```
56
62
 
57
- {
58
- const main = fiber.spawn(function* (proc) {
59
- // Start f1, wait for it to finish and get the result
60
- const res = yield* fiber.join(fiber.spawn(f1));
61
- console.log('Fiber 2 recieved:', res);
63
+ #### Throttle
64
+ Executes a function at a regular interval.
65
+ ```ts
66
+ import * as co from 'ciorent';
62
67
 
63
- // Start f1 and make its lifetime depends on current fiber
64
- const childProc = fiber.spawn(f1);
65
- fiber.mount(childProc, proc);
66
- });
68
+ // Allow 2 calls in 500ms
69
+ const throttle = co.throttle(500, 2);
67
70
 
68
- console.log('Fiber 2 started:', fiber.resumed(main));
71
+ co.spawn(8, async (id) => {
72
+ await throttle();
73
+ console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
74
+ });
75
+ ```
69
76
 
70
- // Pause the current fiber process
71
- fiber.pause(main);
72
- console.log('Fiber 2 is paused:', fiber.paused(main));
77
+ #### Pausing
78
+ Delay the execution of a function for other asynchronous tasks to run.
79
+ ```ts
80
+ import * as co from 'ciorent';
73
81
 
74
- // Resume the fiber
75
- fiber.resume(main);
76
- console.log('Fiber 2 is resumed:', fiber.resumed(main));
82
+ // Expensive sync task
83
+ const task1 = async () => {
84
+ let x = 0;
77
85
 
78
- // Wait for the fiber process to finish
79
- await fiber.done(main);
86
+ // Yield control back to the runtime, allowing it to
87
+ // schedule other tasks
88
+ await co.pause;
80
89
 
81
- // Check finish status
82
- console.log('Fiber 2 completed', fiber.completed(main));
83
- }
90
+ // Simulate heavy operation
91
+ for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
92
+ x += Math.random() * 32 + i * Math.round(Math.random() * 16);
84
93
 
85
- {
86
- console.log('------------------------');
94
+ console.log('Finish task 1:', x);
95
+ };
87
96
 
88
- const main = fiber.spawn(f1);
89
- console.log('Fiber 1 started:', fiber.resumed(main));
97
+ // Short async task
98
+ const task2 = async () => {
99
+ console.log('Start fetching...');
100
+ const txt = await fetch('http://example.com');
101
+ console.log('Fetch status', txt.status);
102
+ };
90
103
 
91
- // Stop a fiber
92
- fiber.stop(main);
93
- console.log('Fiber 1 stopped:', fiber.stopped(main));
94
- }
104
+ // Task 2 will not get blocked by task 1
105
+ task1();
106
+ task2();
95
107
  ```
96
108
 
97
- ### Pubsub
98
- A fast, simple publish-subscribe API.
109
+ ### Channel
110
+ Channel is a synchronization primitive via message passing. A message may be sent over a channel, and another process is able to receive messages sent over a channel it has a reference to.
99
111
 
100
112
  ```ts
101
- import * as topic from 'ciorent/topic';
113
+ import * as channel from 'ciorent/channel';
102
114
  import * as co from 'ciorent';
103
115
 
104
- const messages = topic.init<number>();
116
+ const c = channel.init<number>();
105
117
 
106
- // A task that publish messages
107
- const publisher = async () => {
108
- for (let i = 0; i < 3; i++) {
118
+ const run = async () => {
119
+ for (let i = 0; i < 5; i++) {
109
120
  await co.sleep(100);
110
- topic.publish(messages, i);
121
+ channel.send(c, i);
122
+ console.log('Sent', i);
111
123
  }
112
124
 
113
- // Resolve all waiting promises
114
- // And clear the value queue
115
- topic.flush(messages);
116
- }
117
-
118
- // Spawn 3 tasks that recieve messages
119
- co.spawn(3, async (id: number) => {
120
- const sub = topic.subscribe(messages);
125
+ // Resolve all waiting promises with `undefined`
126
+ // This is a way to tell the reciever to not listen to more data
127
+ channel.flush(c);
128
+ };
121
129
 
130
+ const log = async () => {
122
131
  while (true) {
123
- // Block until the value is sent
124
- const x = await topic.recieve(sub);
132
+ // Wait until a value is sent to the channel
133
+ const x = await channel.recieve(c);
125
134
  if (x == null) break;
126
- console.log(`Task ${id} recieved: ${x}`);
127
- }
128
- });
129
135
 
130
- publisher();
136
+ console.log('Recieved', x);
137
+ };
138
+ }
139
+
140
+ log();
141
+ run();
142
+
143
+ // This runs first
144
+ console.log('Starting...');
131
145
  ```
132
146
 
133
147
  ### Latch
@@ -157,140 +171,137 @@ task();
157
171
  prepare();
158
172
  ```
159
173
 
160
- ### Channel
161
- Channel is a synchronization primitive via message passing. A message may be sent over a channel, and another process is able to receive messages sent over a channel it has a reference to.
174
+ ### Semaphore
175
+ Semaphore is a concurrency primitive used to control access to a common resource by multiple processes.
162
176
 
163
177
  ```ts
164
- import * as channel from 'ciorent/channel';
178
+ import * as semaphore from 'ciorent/semaphore';
165
179
  import * as co from 'ciorent';
166
180
 
167
- const c = channel.init<number>();
181
+ // Only allow 2 task to run concurrently
182
+ const sem = semaphore.init(2);
168
183
 
169
- const run = async () => {
170
- for (let i = 0; i < 5; i++) {
171
- await co.sleep(100);
172
- channel.send(c, i);
173
- console.log('Sent', i);
174
- }
184
+ const task = async (id: number) => {
185
+ // Acquire the semaphore or wait for the semaphore to be available
186
+ await semaphore.pause(sem);
175
187
 
176
- // Resolve all waiting promises with `undefined`
177
- // This is a way to tell the reciever to not listen to more data
178
- channel.flush(c);
179
- };
188
+ console.log('Task', id, 'started');
180
189
 
181
- const log = async () => {
182
- while (true) {
183
- // Wait until a value is sent to the channel
184
- const x = await channel.recieve(c);
185
- if (x == null) break;
190
+ // Let the main thread schedules other tasks
191
+ for (let i = 1; i <= 5; i++) await co.pause;
186
192
 
187
- console.log('Recieved', x);
188
- };
189
- }
193
+ console.log('Task', id, 'end');
190
194
 
191
- log();
192
- run();
195
+ // Release the semaphore
196
+ semaphore.signal(sem);
197
+ }
193
198
 
194
- // This runs first
195
- console.log('Starting...');
199
+ // Try to run 5 tasks concurrently
200
+ co.spawn(5, task);
196
201
  ```
197
202
 
198
- ### Utilities
199
- #### Pausing
200
- Delay the execution of a function for other asynchronous tasks to run.
203
+ ### Pubsub
204
+ A fast, simple publish-subscribe API.
205
+
201
206
  ```ts
207
+ import * as topic from 'ciorent/topic';
202
208
  import * as co from 'ciorent';
203
209
 
204
- // Expensive sync task
205
- const task1 = async () => {
206
- let x = 0;
210
+ const messages = topic.init<number>();
207
211
 
208
- // Yield control back to the runtime, allowing it to
209
- // schedule other tasks
210
- await co.pause;
212
+ // A task that publish messages
213
+ const publisher = async () => {
214
+ for (let i = 0; i < 3; i++) {
215
+ await co.sleep(100);
216
+ topic.publish(messages, i);
217
+ }
211
218
 
212
- // Simulate heavy operation
213
- for (let i = 0; i < (Math.random() + 15) * 1e6; i++)
214
- x += Math.random() * 32 + i * Math.round(Math.random() * 16);
219
+ // Resolve all waiting promises
220
+ // And clear the value queue
221
+ topic.flush(messages);
222
+ }
215
223
 
216
- console.log('Finish task 1:', x);
217
- };
224
+ // Spawn 3 tasks that recieve messages
225
+ co.spawn(3, async (id: number) => {
226
+ const sub = topic.subscribe(messages);
218
227
 
219
- // Short async task
220
- const task2 = async () => {
221
- console.log('Start fetching...');
222
- const txt = await fetch('http://example.com');
223
- console.log('Fetch status', txt.status);
224
- };
228
+ while (true) {
229
+ // Block until the value is sent
230
+ const x = await topic.recieve(sub);
231
+ if (x == null) break;
232
+ console.log(`Task ${id} recieved: ${x}`);
233
+ }
234
+ });
225
235
 
226
- // Task 2 will not get blocked by task 1
227
- task1();
228
- task2();
236
+ publisher();
229
237
  ```
230
238
 
231
- #### Sleep
232
- Cross-runtime synchronous and asynchronous sleep functions.
239
+ ### Fibers
240
+ Virtual threads with more controlled execution.
241
+
233
242
  ```ts
234
243
  import * as co from 'ciorent';
244
+ import * as fiber from 'ciorent/fiber';
235
245
 
236
- const logTime = (label: string) => console.log(label + ':', Math.floor(performance.now()) + 'ms');
246
+ const f1 = fiber.fn(function* () {
247
+ // Wait for a promise
248
+ yield co.sleep(1000);
237
249
 
238
- logTime('Start');
250
+ // Wait for a promise and return its result
251
+ const res = yield* fiber.unwrap(Promise.resolve(1));
252
+ console.log('Fiber 1 recieved:', res);
239
253
 
240
- // Non-blocking
241
- await co.sleep(500);
242
- logTime('After about 0.5s');
254
+ return Math.random();
255
+ });
243
256
 
244
- // This blocks the event loop
245
- // On the browser this only works in workers and blocks the worker thread
246
- co.sleepSync(500);
247
- logTime('After another 0.5s');
248
- ```
257
+ {
258
+ const main = fiber.spawn(function* (proc) {
259
+ // Start f1, wait for the process to complete and get the result
260
+ const res = yield* fiber.join(fiber.spawn(f1));
261
+ console.log('Fiber 2 recieved:', res);
249
262
 
250
- #### Spawning tasks
251
- Utilities to create and run tasks.
252
- ```ts
253
- import * as co from 'ciorent';
263
+ // Start f1 and make its lifetime depends on current fiber
264
+ const childProc = fiber.spawn(f1);
265
+ fiber.mount(childProc, proc);
266
+ });
254
267
 
255
- const task = async (id: number) => {
256
- await co.sleep((10 - id) * 20 + 50);
257
- console.log('Task', id, 'done');
258
- }
268
+ console.log('Fiber 2 started:', fiber.resumed(main));
259
269
 
260
- // Spawn and run 5 tasks sequentially
261
- console.log('Running 5 tasks sequentially:');
262
- await co.sequential(5, task);
270
+ // Pause the current fiber process
271
+ fiber.pause(main);
272
+ console.log('Fiber 2 is paused:', fiber.paused(main));
263
273
 
264
- // Spawn and run 5 tasks concurrently
265
- console.log('Running 5 tasks concurrently:');
266
- await Promise.all(co.spawn(5, task));
267
- ```
274
+ // Resume the fiber
275
+ fiber.resume(main);
276
+ console.log('Fiber 2 is resumed:', fiber.resumed(main));
268
277
 
269
- #### Debounce
270
- Postpones execution until after an idle period.
271
- ```ts
272
- import * as co from 'ciorent';
278
+ // Wait for the fiber process to finish
279
+ await fiber.done(main);
273
280
 
274
- const fn = co.debounce((id: number) => {
275
- console.log('ID:', id);
276
- }, 500);
281
+ // Check finish status
282
+ console.log('Fiber 2 completed:', fiber.completed(main));
283
+ }
277
284
 
278
- fn(1); // fn(1) gets skipped
279
- await co.sleep(100);
280
- fn(2); // fn(2) gets executed
281
- ```
285
+ {
286
+ console.log('------------------------');
282
287
 
283
- #### Throttle
284
- Executes a function at a regular interval.
285
- ```ts
286
- import * as co from 'ciorent';
288
+ const main = fiber.spawn(f1);
289
+ console.log('Fiber 1 started:', fiber.resumed(main));
287
290
 
288
- // Allow 2 calls in 500ms
289
- const throttle = co.throttle(500, 2);
291
+ // Stop a fiber
292
+ fiber.interrupt(main);
293
+ console.log('Fiber 1 interrupted:', fiber.interrupted(main));
294
+ }
290
295
 
291
- co.spawn(8, async (id) => {
292
- await throttle();
293
- console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
294
- });
296
+ {
297
+ console.log('------------------------');
298
+
299
+ const main = fiber.spawn(f1);
300
+ console.log('Fiber 1 started:', fiber.resumed(main));
301
+
302
+ // Timeout a fiber
303
+ await fiber.timeout(main, 500);
304
+ console.log('Fiber 1 stopped:', fiber.interrupted(main));
305
+ }
295
306
  ```
296
307
 
package/fiber.d.ts CHANGED
@@ -41,11 +41,7 @@ export declare const completed: (t: Process) => boolean;
41
41
  /**
42
42
  * Check whether the fiber has been interrupted
43
43
  */
44
- export declare const stopped: (t: Process) => boolean;
45
- /**
46
- * Create a fiber function
47
- * @param f
48
- */
44
+ export declare const interrupted: (t: Process) => boolean;
49
45
  export declare const fn: <const Fn extends (thread: Process, ...args: any[]) => Generator>(f: Fn) => Fn;
50
46
  /**
51
47
  * A basic fiber runtime
@@ -63,10 +59,16 @@ export declare const pause: (t: Process) => void;
63
59
  */
64
60
  export declare const resume: (t: Process) => void;
65
61
  /**
66
- * Stop the execution of a fiber
62
+ * Interrupt the execution of a fiber
63
+ * @param t
64
+ */
65
+ export declare const interrupt: (t: Process) => void;
66
+ /**
67
+ * Timeout a fiber
67
68
  * @param t
69
+ * @param ms
68
70
  */
69
- export declare const stop: (t: Process) => void;
71
+ export declare const timeout: (t: Process, ms: number) => Promise<void>;
70
72
  /**
71
73
  * Wait for a fiber and retrieve its result
72
74
  * @param t
package/fiber.js CHANGED
@@ -1 +1 @@
1
- export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(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{if(thread[1]!==2)thread[1]=3;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]!==2){if(t[1]===0)t[2]?.();t[1]=3}};export function*join(t){return yield t[0]}export let done=(t)=>t[0];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}
1
+ import{sleep}from"./index.js";export let paused=(t)=>t[1]===0;export let resumed=(t)=>t[1]===1;export let completed=(t)=>t[1]===2;export let interrupted=(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{if(thread[1]!==2)thread[1]=3;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 interrupt=(t)=>{if(t[1]!==2){if(t[1]===0)t[2]?.();t[1]=3}};export let timeout=async(t,ms)=>{await sleep(ms);interrupt(t)};export function*join(t){return yield t[0]}export let done=(t)=>t[0];export let mount=(child,parent)=>{parent[3].push(child)};export let control=(t,signal)=>{signal.addEventListener("abort",()=>{interrupt(t)})};export function*unwrap(t){return yield t}
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 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 throttle=(ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;while(cur>0){if(tail===head){scheduled=false;return}cur--;(tail=tail[0])[1]()}setTimeout(unlock,ms)};return()=>{if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r];return p}if(!scheduled){scheduled=true;setTimeout(unlock,ms)}cur--;return pause}};
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 throttle=(ms,limit)=>{let head=[null];let tail=head;let cur=limit;let scheduled=false;let unlock=()=>{cur=limit;if(tail===head){scheduled=false;return}do{cur--;(tail=tail[0])[1]()}while(cur>0&&tail!==head);setTimeout(unlock,ms)};return()=>{if(cur===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r];return p}if(!scheduled){scheduled=true;setTimeout(unlock,ms)}cur--;return pause}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ciorent",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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",
21
22
  "./fixed-queue": "./fixed-queue.js",
22
23
  "./fiber": "./fiber.js",
23
- "./sliding-queue": "./sliding-queue.js",
24
- "./semaphore": "./semaphore.js",
24
+ ".": "./index.js",
25
25
  "./dropping-queue": "./dropping-queue.js",
26
26
  "./channel": "./channel.js",
27
- ".": "./index.js",
28
27
  "./topic": "./topic.js",
28
+ "./semaphore": "./semaphore.js",
29
29
  "./latch": "./latch.js"
30
30
  }
31
31
  }