ciorent 0.0.26 → 0.1.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/README.md CHANGED
@@ -1,27 +1,74 @@
1
1
  A lightweight, low-overhead concurrency library.
2
- ## Semaphore
3
- Semaphore is a concurrency primitive used to control access to a common resource by multiple processes.
2
+ ## Channel
3
+ 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.
4
4
 
5
5
  ```ts
6
- import * as semaphore from 'ciorent/semaphore';
6
+ import * as channel from 'ciorent/channel';
7
7
  import * as cio from 'ciorent';
8
8
 
9
- const task = semaphore.wrap(
10
- async (task: number) => {
11
- for (let i = 1; i <= 5; i++) {
12
- console.log('Task', task, 'iteration', i);
13
- await cio.pause;
14
- }
9
+ const c = channel.init<number>();
15
10
 
16
- console.log('Task', task, 'end');
11
+ const run = async () => {
12
+ for (let i = 0; i < 5; i++) {
13
+ await cio.sleep(100);
14
+ channel.send(c, i);
15
+ console.log('Sent', i);
17
16
  }
18
- );
19
17
 
20
- // Only allow 2 task to run concurrently
21
- const sem = semaphore.init(2);
18
+ // Resolve all waiting promises with `undefined`
19
+ // This is a way to tell the reciever to not listen to more data
20
+ channel.flush(c);
21
+ };
22
22
 
23
- // Try to run 6 tasks concurrently
24
- cio.concurrent(6, (sem, id) => task(sem, id), sem);
23
+ const log = async () => {
24
+ while (true) {
25
+ // Wait until a value is sent to the channel
26
+ const x = await channel.recieve(c);
27
+ if (x == null) break;
28
+
29
+ console.log('Recieved', x);
30
+ };
31
+ }
32
+
33
+ log();
34
+ run();
35
+
36
+ // This runs first
37
+ console.log('Starting...');
38
+ ```
39
+
40
+ ## Fibers
41
+ Virtual threads with more controlled execution.
42
+
43
+ ```ts
44
+ import * as cio from 'ciorent';
45
+ import * as fiber from 'ciorent/fiber';
46
+
47
+ const thread1 = fiber.fn(function* () {
48
+ console.log('Fiber 1 started');
49
+
50
+ // Thread1 will be interrupted by thread2
51
+ // As thread2 will end first
52
+ yield cio.sleep(1000);
53
+
54
+ console.log('Fiber 1 done');
55
+ });
56
+
57
+ const thread2 = fiber.fn(function* (thread) {
58
+ console.log('Fiber 2 started');
59
+
60
+ yield;
61
+ console.log('Fiber 2 resumed');
62
+
63
+ // Start thread 1 and make thread1
64
+ // lifetime depends on thread2
65
+ fiber.mount(fiber.spawn(thread1), thread);
66
+
67
+ console.log('Fiber 2 done');
68
+ });
69
+
70
+ // Start running the thread
71
+ fiber.spawn(thread2);
25
72
  ```
26
73
 
27
74
  ## Latch
@@ -67,40 +114,6 @@ await main();
67
114
  await main();
68
115
  ```
69
116
 
70
- ## Fibers
71
- Virtual threads with more controlled execution.
72
-
73
- ```ts
74
- import * as cio from 'ciorent';
75
- import * as fiber from 'ciorent/fiber';
76
-
77
- const thread1 = fiber.fn(function* () {
78
- console.log('Fiber 1 started');
79
-
80
- // Thread1 will be interrupted by thread2
81
- // As thread2 will end first
82
- yield cio.sleep(1000);
83
-
84
- console.log('Fiber 1 done');
85
- });
86
-
87
- const thread2 = fiber.fn(function* (thread) {
88
- console.log('Fiber 2 started');
89
-
90
- yield;
91
- console.log('Fiber 2 resumed');
92
-
93
- // Start thread 1 and make thread1
94
- // lifetime depends on thread2
95
- fiber.mount(fiber.spawn(thread1), thread);
96
-
97
- console.log('Fiber 2 done');
98
- });
99
-
100
- // Start running the thread
101
- fiber.spawn(thread2);
102
- ```
103
-
104
117
  ## Pubsub
105
118
  A fast, simple publish-subscribe API.
106
119
 
@@ -112,7 +125,7 @@ const messages = topic.init<number>();
112
125
 
113
126
  // A task that publish messages
114
127
  const publisher = async () => {
115
- for (let i = 0; i < 5; i++) {
128
+ for (let i = 0; i < 3; i++) {
116
129
  await cio.sleep(100);
117
130
  topic.pub(messages, i);
118
131
  }
@@ -122,57 +135,44 @@ const publisher = async () => {
122
135
  topic.flush(messages);
123
136
  }
124
137
 
125
- // Spawn 5 tasks that recieve messages
126
- cio.concurrent(5, async (id: number) => {
138
+ // Spawn 3 tasks that recieve messages
139
+ cio.concurrent(3, async (id: number) => {
127
140
  const sub = topic.sub(messages);
128
141
 
129
142
  while (true) {
130
143
  // Block until the value is sent
131
144
  const x = await topic.recieve(sub);
132
145
  if (x == null) break;
133
- console.log(`Task ${id}: ${x}`);
146
+ console.log(`Task ${id} recieved: ${x}`);
134
147
  }
135
148
  });
136
149
 
137
150
  publisher();
138
151
  ```
139
152
 
140
- ## Channel
141
- 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.
153
+ ## Semaphore
154
+ Semaphore is a concurrency primitive used to control access to a common resource by multiple processes.
142
155
 
143
156
  ```ts
144
- import * as channel from 'ciorent/channel';
157
+ import * as semaphore from 'ciorent/semaphore';
145
158
  import * as cio from 'ciorent';
146
159
 
147
- const c = channel.init<number>();
148
-
149
- const run = async () => {
150
- for (let i = 0; i < 10; i++) {
151
- await cio.sleep(10);
152
- channel.send(c, i);
153
- console.log('Sent', i);
154
- }
155
-
156
- // Resolve all waiting promises with `undefined`
157
- // This is a way to tell the reciever to not listen to more data
158
- channel.flush(c);
159
- };
160
+ const task = semaphore.wrap(
161
+ async (task: number) => {
162
+ console.log('Task', task, 'started');
160
163
 
161
- const log = async () => {
162
- while (true) {
163
- // Wait until a value is sent
164
- const x = await channel.recieve(c);
165
- if (x == null) break;
164
+ for (let i = 1; i <= 5; i++)
165
+ await cio.pause;
166
166
 
167
- console.log('Recieved', x);
168
- };
169
- }
167
+ console.log('Task', task, 'end');
168
+ }
169
+ );
170
170
 
171
- run();
172
- log();
171
+ // Only allow 2 task to run concurrently
172
+ const sem = semaphore.init(2);
173
173
 
174
- // This runs first
175
- console.log('Starting...');
174
+ // Try to run 6 tasks concurrently
175
+ cio.concurrent(6, (sem, id) => task(sem, id), sem);
176
176
  ```
177
177
 
178
178
  ## Utilities
@@ -212,14 +212,14 @@ task2();
212
212
  ### Sleep
213
213
  Cross-runtime synchronous and asynchronous sleep functions.
214
214
  ```ts
215
- import { sleep, sleepSync } from 'ciorent';
215
+ import * as cio from 'ciorent';
216
216
 
217
- await sleep(500);
217
+ await cio.sleep(500);
218
218
  console.log('Hi');
219
219
 
220
220
  // This blocks the current thread
221
221
  // On the browser this only works in workers
222
- sleepSync(500);
222
+ cio.sleepSync(500);
223
223
  console.log('Hi');
224
224
  ```
225
225
 
@@ -229,17 +229,17 @@ Utilities to create and run tasks.
229
229
  import * as cio from 'ciorent';
230
230
 
231
231
  const task = async (id: number) => {
232
- await cio.sleep(Math.random() * 20 + 50);
232
+ await cio.sleep((10 - id) * 20 + 50);
233
233
  console.log('Task', id, 'done');
234
234
  }
235
235
 
236
236
  // Spawn and run 5 tasks sequentially
237
237
  console.log('Running 5 tasks sequentially:');
238
- cio.sequential(5, task);
238
+ await cio.sequential(5, task);
239
239
 
240
240
  // Spawn and run 5 tasks concurrently
241
241
  console.log('Running 5 tasks concurrently:');
242
- cio.concurrent(5, task);
242
+ await cio.concurrent(5, task);
243
243
  ```
244
244
 
245
245
  ### Debounce
@@ -283,7 +283,6 @@ const fn = cio.throttle((id: number) => {
283
283
  console.log(id + ': ' + Math.floor(performance.now()) + 'ms');
284
284
  }, 500, 2);
285
285
 
286
- for (let i = 0; i < 8; i++)
287
- fn(i);
286
+ cio.concurrent(8, (id) => fn(id));
288
287
  ```
289
288
 
package/channel.js CHANGED
@@ -1 +1 @@
1
- export let init=()=>{let qu=[null,null];let resolveQu=[null,null];return[qu,qu,resolveQu,resolveQu]};export let send=(c,t)=>{if(c[3][1]!==null)(c[3]=c[3][1])[0](t);else c[0]=c[0][1]=[t,null]};export let recieve=(c)=>c[1][1]!==null?Promise.resolve((c[1]=c[1][1])[0]):new Promise((res)=>{c[2]=c[2][1]=[res,null]});export let poll=(c)=>c[1][1]!==null?(c[1]=c[1][1])[0]:undefined;export let flush=(c)=>{while(c[3][1]!==null)(c[3]=c[3][1])[0]()};
1
+ export let init=()=>{let qu=[null];let resolveQu=[null,null];return[qu,qu,resolveQu,resolveQu]};export let send=(c,t)=>{if(c[3][0]!==null)(c[3]=c[3][0])[1](t);else c[0]=c[0][0]=[null,t]};export let recieve=(c)=>c[1][0]!==null?Promise.resolve((c[1]=c[1][0])[1]):new Promise((res)=>{c[2]=c[2][0]=[null,res]});export let poll=(c)=>c[1][0]!==null?(c[0]=c[1][0])[1]:undefined;export let flush=(c)=>{while(c[3][0]!==null)(c[3]=c[3][0])[1]()};
package/fiber.js CHANGED
@@ -1 +1 @@
1
- export let paused=(t)=>t[1]===0;export let running=(t)=>t[1]===1;export let done=(t)=>t[1]===2;let invoke=async(g,thread)=>{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]===2){thread[3].forEach(stop);return v}t=g.next(v)}thread[1]=2;thread[3].forEach(stop);return t.value};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[1]=2;t[2]?.()}else t[1]=2};export function*join(t){return yield t[1]}export let finish=(t)=>t[1];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
+ export let paused=(t)=>t[1]===0;export let running=(t)=>t[1]===1;export let done=(t)=>t[1]===2;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]===2){thread[3].forEach(stop);return v}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[1]=2;t[2]?.()}else t[1]=2};export function*join(t){return yield t[1]}export let finish=(t)=>t[1];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/fixed-queue.d.ts CHANGED
@@ -26,7 +26,7 @@ export interface FixedQueue<T extends {}> {
26
26
  /**
27
27
  * Describe a queue node (singly linked list node)
28
28
  */
29
- export type QueueNode<T> = [value: T, next: QueueNode<T> | null];
29
+ export type QueueNode<T> = [next: QueueNode<T> | null, value: T];
30
30
  /**
31
31
  * Create a fixed queue.
32
32
  * @param n - The queue size
package/index.d.ts CHANGED
@@ -35,20 +35,20 @@ export declare const sequential: <const T extends any[]>(n: number, task: (...ar
35
35
  export declare const concurrent: <const T extends any[], const R>(n: number, task: (...args: [...T, id: number]) => Promise<R>, ...args: T) => Promise<R[]>;
36
36
  /**
37
37
  * Drop function calls until it doesn't get called for a specific period.
38
- * @param f - The target function to debounce
38
+ * @param f - The target function to debounce (it must not throw errors)
39
39
  * @param ms - The time period in milliseconds
40
40
  */
41
41
  export declare const debounce: <const Args extends any[]>(f: (...args: Args) => any, ms: number) => ((...args: Args) => void);
42
42
  /**
43
43
  * Drop function calls for a specific period
44
- * @param f - The target function to rate limit
44
+ * @param f - The target function to rate limit (it must not throw errors)
45
45
  * @param ms - The time period in milliseconds
46
46
  * @param limit - The call limit in the time period
47
47
  */
48
48
  export declare const rateLimit: <const Args extends any[]>(f: (...args: Args) => any, ms: number, limit: number) => ((...args: Args) => void);
49
49
  /**
50
50
  * Throttle function execution for a time period
51
- * @param f - The function to throttle
51
+ * @param f - The function to throttle (it must not throw errors)
52
52
  * @param ms - The time in milliseconds
53
53
  * @param limit - The call limit in the time period
54
54
  */
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 concurrent=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return Promise.all(arr)};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let rateLimit=(f,ms,limit)=>{let call=()=>{limit++};return(...a)=>{if(limit>0){limit--;try{f(...a)}finally{setTimeout(call,ms)}}}};export let throttle=(f,ms,limit)=>{let head=[null,null,null];let tail=head;let unlock=()=>{if(tail!==head){tail=tail[2];tail[0](f(...tail[1]));setTimeout(unlock,ms)}else limit++};return(...a)=>{if(limit===0){let r;let p=new Promise((res)=>{r=res});head=head[2]=[r,a,null];return p}limit--;setTimeout(unlock,ms);return f(...a)}};
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 concurrent=(n,task,...args)=>{let arr=new Array(n);for(let i=0;i<n;i++)arr[i]=task(...args,i);return Promise.all(arr)};export let debounce=(f,ms)=>{let id;return(...a)=>{clearTimeout(id);id=setTimeout(f,ms,...a)}};export let rateLimit=(f,ms,limit)=>{let call=()=>{limit++};return(...a)=>{if(limit>0){limit--;f(...a);setTimeout(call,ms)}}};export let throttle=(f,ms,limit)=>{let head=[null];let tail=head;let unlock=()=>{if(tail!==head){tail=tail[0];tail[1](f(...tail[2]));setTimeout(unlock,ms)}else limit++};return(...a)=>{if(limit===0){let r;let p=new Promise((res)=>{r=res});head=head[0]=[null,r,a];return p}limit--;setTimeout(unlock,ms);return f(...a)}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ciorent",
3
- "version": "0.0.26",
3
+ "version": "0.1.0",
4
4
  "description": "A lightweight, low-overhead concurrency library",
5
5
  "homepage": "https://ciorent.netlify.app",
6
6
  "repository": {
@@ -19,13 +19,13 @@
19
19
  "types": "./index.d.ts",
20
20
  "exports": {
21
21
  "./fixed-queue": "./fixed-queue.js",
22
- "./latch": "./latch.js",
23
22
  "./sliding-queue": "./sliding-queue.js",
24
- ".": "./index.js",
25
- "./semaphore": "./semaphore.js",
26
- "./topic": "./topic.js",
27
- "./channel": "./channel.js",
23
+ "./fiber": "./fiber.js",
24
+ "./latch": "./latch.js",
28
25
  "./dropping-queue": "./dropping-queue.js",
29
- "./fiber": "./fiber.js"
26
+ "./topic": "./topic.js",
27
+ "./semaphore": "./semaphore.js",
28
+ ".": "./index.js",
29
+ "./channel": "./channel.js"
30
30
  }
31
31
  }
package/semaphore.js CHANGED
@@ -1 +1 @@
1
- import{pause as resolvedPromise}from"./index.js";export let init=(n)=>{let root=[null,null];return[n,root,root]};export let pause=(s)=>{s[0]--;if(s[0]<0){let r;let p=new Promise((res)=>{r=res});s[1]=s[1][1]=[r,null];return p}return resolvedPromise};export let signal=(s)=>{if(s[0]<0)(s[2]=s[2][1])[0]();s[0]++};export let wrap=(f)=>async(s,...a)=>{s[0]--;if(s[0]<0){let r;let p=new Promise((res)=>{r=res});s[1]=s[1][1]=[r,null];await p}try{return await f(...a)}finally{signal(s)}};
1
+ import{pause as resolvedPromise}from"./index.js";export let init=(n)=>{let root=[null];return[n,root,root]};export let pause=(s)=>{s[0]--;if(s[0]<0){let r;let p=new Promise((res)=>{r=res});s[1]=s[1][0]=[null,r];return p}return resolvedPromise};export let signal=(s)=>{if(s[0]<0)(s[2]=s[2][0])[1]();s[0]++};export let wrap=(f)=>async(s,...a)=>{s[0]--;if(s[0]<0){let r;let p=new Promise((res)=>{r=res});s[1]=s[1][0]=[null,r];await p}try{return await f(...a)}finally{signal(s)}};
package/topic.js CHANGED
@@ -1 +1 @@
1
- export let init=()=>[[null,null],[],[]];export let sub=(t)=>[t,t[0]];export let pub=(t,value)=>{let head=t[0]=t[0][1]=[value,null];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,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][1]!==null?(t[1]=t[1][1])[0]:undefined;export let recieve=(t)=>{if(t[1][1]!==null)return Promise.resolve((t[1]=t[1][1])[0]);let topic=t[0];topic[2].push(t);return new Promise((res)=>{topic[1].push(res)})};
1
+ export let init=()=>[[null],[],[]];export let sub=(t)=>[t,t[0]];export let pub=(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)})};