@nxtedition/scheduler 1.0.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.
@@ -0,0 +1,8 @@
1
+ export class FixedQueue {
2
+ head: any;
3
+ tail: any;
4
+ size: number;
5
+ isEmpty(): any;
6
+ push(data: any): void;
7
+ shift(): any;
8
+ }
@@ -0,0 +1,123 @@
1
+ // Extracted from node/lib/internal/fixed_queue.js
2
+
3
+ // Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two.
4
+ const kSize = 2048
5
+ const kMask = kSize - 1
6
+
7
+ // The FixedQueue is implemented as a singly-linked list of fixed-size
8
+ // circular buffers. It looks something like this:
9
+ //
10
+ // head tail
11
+ // | |
12
+ // v v
13
+ // +-----------+ <-----\ +-----------+ <------\ +-----------+
14
+ // | [null] | \----- | next | \------- | next |
15
+ // +-----------+ +-----------+ +-----------+
16
+ // | item | <-- bottom | item | <-- bottom | [empty] |
17
+ // | item | | item | | [empty] |
18
+ // | item | | item | | [empty] |
19
+ // | item | | item | | [empty] |
20
+ // | item | | item | bottom --> | item |
21
+ // | item | | item | | item |
22
+ // | ... | | ... | | ... |
23
+ // | item | | item | | item |
24
+ // | item | | item | | item |
25
+ // | [empty] | <-- top | item | | item |
26
+ // | [empty] | | item | | item |
27
+ // | [empty] | | [empty] | <-- top top --> | [empty] |
28
+ // +-----------+ +-----------+ +-----------+
29
+ //
30
+ // Or, if there is only one circular buffer, it looks something
31
+ // like either of these:
32
+ //
33
+ // head tail head tail
34
+ // | | | |
35
+ // v v v v
36
+ // +-----------+ +-----------+
37
+ // | [null] | | [null] |
38
+ // +-----------+ +-----------+
39
+ // | [empty] | | item |
40
+ // | [empty] | | item |
41
+ // | item | <-- bottom top --> | [empty] |
42
+ // | item | | [empty] |
43
+ // | [empty] | <-- top bottom --> | item |
44
+ // | [empty] | | item |
45
+ // +-----------+ +-----------+
46
+ //
47
+ // Adding a value means moving `top` forward by one, removing means
48
+ // moving `bottom` forward by one. After reaching the end, the queue
49
+ // wraps around.
50
+ //
51
+ // When `top === bottom` the current queue is empty and when
52
+ // `top + 1 === bottom` it's full. This wastes a single space of storage
53
+ // but allows much quicker checks.
54
+
55
+ class FixedCircularBuffer {
56
+ constructor() {
57
+ this.bottom = 0
58
+ this.top = 0
59
+ this.list = new Array(kSize).fill(undefined)
60
+ this.next = null
61
+ }
62
+
63
+ isEmpty() {
64
+ return this.top === this.bottom
65
+ }
66
+
67
+ isFull() {
68
+ return ((this.top + 1) & kMask) === this.bottom
69
+ }
70
+
71
+ push(data) {
72
+ this.list[this.top] = data
73
+ this.top = (this.top + 1) & kMask
74
+ }
75
+
76
+ shift() {
77
+ const nextItem = this.list[this.bottom]
78
+ if (nextItem === undefined) {
79
+ return null
80
+ }
81
+ this.list[this.bottom] = undefined
82
+ this.bottom = (this.bottom + 1) & kMask
83
+ return nextItem
84
+ }
85
+ }
86
+
87
+ const POOL = []
88
+
89
+ export class FixedQueue {
90
+ constructor() {
91
+ this.head = this.tail = POOL.pop() ?? new FixedCircularBuffer()
92
+ this.size = 0
93
+ }
94
+
95
+ isEmpty() {
96
+ return this.head.isEmpty()
97
+ }
98
+
99
+ push(data) {
100
+ if (this.head.isFull()) {
101
+ // Head is full: Creates a new queue, sets the old queue's `.next` to it,
102
+ // and sets it as the new main queue.
103
+ this.head = this.head.next = POOL.pop() ?? new FixedCircularBuffer()
104
+ }
105
+ this.head.push(data)
106
+ }
107
+
108
+ shift() {
109
+ const tail = this.tail
110
+ const next = tail.shift()
111
+ if (tail.isEmpty() && tail.next !== null) {
112
+ // If there is another queue, it forms the new tail.
113
+ this.tail = tail.next
114
+ tail.next = null
115
+ tail.bottom = 0
116
+ tail.top = 0
117
+ if (POOL.length < 64) {
118
+ POOL.push(tail)
119
+ }
120
+ }
121
+ return next
122
+ }
123
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export declare class Scheduler {
2
+ #private;
3
+ static LOW: 0;
4
+ static NORMAL: 1;
5
+ static HIGH: 2;
6
+ constructor({ concurrency }?: {
7
+ concurrency?: number | undefined;
8
+ });
9
+ schedule(fn: ((next: Function) => any), priority?: 1): any;
10
+ }
package/lib/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import { FixedQueue } from './fixed-queue.js'
2
+
3
+ export class Scheduler {
4
+ static LOW = 0
5
+ static NORMAL = 1
6
+ static HIGH = 2
7
+
8
+ #concurrency
9
+ #running = 0
10
+ #counter = 0
11
+
12
+ #lowQueue = new FixedQueue()
13
+ #normalQueue = new FixedQueue()
14
+ #highQueue = new FixedQueue()
15
+
16
+ constructor({ concurrency = Infinity } = {}) {
17
+ this.#concurrency = concurrency
18
+ }
19
+
20
+ schedule(fn , priority = Scheduler.NORMAL) {
21
+ if (typeof fn !== 'function') {
22
+ throw new TypeError('First argument must be a function')
23
+ }
24
+
25
+ if (priority == null) {
26
+ priority = Scheduler.NORMAL
27
+ }
28
+
29
+ if (!Number.isInteger(priority)) {
30
+ throw new Error('Invalid priority')
31
+ }
32
+
33
+ if (this.#running < this.#concurrency) {
34
+ this.#running++
35
+ return fn(this.#next)
36
+ }
37
+
38
+ if (priority > Scheduler.NORMAL) {
39
+ this.#highQueue.push(fn)
40
+ } else if (priority < Scheduler.NORMAL) {
41
+ this.#lowQueue.push(fn)
42
+ } else {
43
+ this.#normalQueue.push(fn)
44
+ }
45
+ }
46
+
47
+ #next = () => {
48
+ this.#counter++
49
+ this.#running--
50
+
51
+ const fn =
52
+ ((this.#counter & 63) === 0 && this.#lowQueue.shift()) ??
53
+ ((this.#counter & 15) === 0 && this.#normalQueue.shift()) ??
54
+ this.#highQueue.shift() ??
55
+ this.#normalQueue.shift() ??
56
+ this.#lowQueue.shift()
57
+
58
+ if (fn) {
59
+ this.#running++
60
+ fn(this.#next)
61
+ }
62
+ }
63
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@nxtedition/scheduler",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "files": [
8
+ "lib"
9
+ ],
10
+ "license": "UNLICENSED",
11
+ "scripts": {
12
+ "build": "rimraf lib && tsc && amaroc ./src/index.ts && mv src/index.js lib/ && cp src/fixed-queue.js lib/",
13
+ "prepublishOnly": "yarn build",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "node --test",
16
+ "test:ci": "node --test"
17
+ },
18
+ "devDependencies": {
19
+ "amaroc": "^1.0.1",
20
+ "rimraf": "^6.1.2",
21
+ "typescript": "^5.9.3"
22
+ },
23
+ "gitHead": "5e9be5511fdd2e10aecbab7cc007bdaf990f728a"
24
+ }