sabcom 0.1.140 → 0.1.142
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 +108 -264
- package/build/index.cjs +526 -193
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +9 -176
- package/build/index.js +521 -161
- package/build/index.js.map +1 -1
- package/package.json +11 -7
- package/src/index.ts +614 -305
package/build/index.cjs
CHANGED
|
@@ -9,213 +9,546 @@ function _export(target, all) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
_export(exports, {
|
|
12
|
-
get
|
|
13
|
-
return
|
|
12
|
+
get createBuffer () {
|
|
13
|
+
return createBuffer;
|
|
14
14
|
},
|
|
15
|
-
get
|
|
16
|
-
return
|
|
15
|
+
get open () {
|
|
16
|
+
return open;
|
|
17
17
|
},
|
|
18
|
-
get
|
|
19
|
-
return
|
|
20
|
-
},
|
|
21
|
-
get Header () {
|
|
22
|
-
return Header;
|
|
23
|
-
},
|
|
24
|
-
get SEMAPHORE () {
|
|
25
|
-
return SEMAPHORE;
|
|
26
|
-
},
|
|
27
|
-
get Semaphore () {
|
|
28
|
-
return Semaphore;
|
|
29
|
-
},
|
|
30
|
-
get read () {
|
|
31
|
-
return read;
|
|
32
|
-
},
|
|
33
|
-
get readGenerator () {
|
|
34
|
-
return readGenerator;
|
|
35
|
-
},
|
|
36
|
-
get readSync () {
|
|
37
|
-
return readSync;
|
|
38
|
-
},
|
|
39
|
-
get write () {
|
|
40
|
-
return write;
|
|
41
|
-
},
|
|
42
|
-
get writeGenerator () {
|
|
43
|
-
return writeGenerator;
|
|
44
|
-
},
|
|
45
|
-
get writeSync () {
|
|
46
|
-
return writeSync;
|
|
18
|
+
get reset () {
|
|
19
|
+
return reset;
|
|
47
20
|
}
|
|
48
21
|
});
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Atomics.notify(header, SEMAPHORE);
|
|
105
|
-
const chunkResult = yield {
|
|
106
|
-
target: header,
|
|
107
|
-
index: SEMAPHORE,
|
|
108
|
-
value: 2,
|
|
109
|
-
timeout
|
|
22
|
+
const MAGIC = 0x53424332;
|
|
23
|
+
const INIT_SENTINEL = -1;
|
|
24
|
+
const MIN_BUFFER_BYTES = 4096;
|
|
25
|
+
const VERSION = 2;
|
|
26
|
+
const HEADER_WORDS = 5;
|
|
27
|
+
const RING_CONTROL_WORDS = 4;
|
|
28
|
+
const DESCRIPTOR_WORDS = 3;
|
|
29
|
+
const CONTINUATION = -1;
|
|
30
|
+
const SLOT_OPTIONS = [
|
|
31
|
+
256,
|
|
32
|
+
128,
|
|
33
|
+
64,
|
|
34
|
+
32,
|
|
35
|
+
16,
|
|
36
|
+
8
|
|
37
|
+
];
|
|
38
|
+
const MIN_DATA_BYTES = 256;
|
|
39
|
+
const FLAG_CLOSED_A = 1;
|
|
40
|
+
const FLAG_CLOSED_B = 2;
|
|
41
|
+
const IDX_MAGIC = 0;
|
|
42
|
+
const IDX_VERSION = 1;
|
|
43
|
+
const IDX_OWNER_A = 2;
|
|
44
|
+
const IDX_OWNER_B = 3;
|
|
45
|
+
const IDX_FLAGS = 4;
|
|
46
|
+
const IDX_WRITE_MSG = 0;
|
|
47
|
+
const IDX_READ_MSG = 1;
|
|
48
|
+
const IDX_READ_BYTE = 3;
|
|
49
|
+
const IDX_DESC_OFFSET = 0;
|
|
50
|
+
const IDX_DESC_SIZE = 1;
|
|
51
|
+
const IDX_DESC_TOTAL = 2;
|
|
52
|
+
const layoutCache = new Map();
|
|
53
|
+
const stateCache = new WeakMap();
|
|
54
|
+
const handleCache = new WeakMap();
|
|
55
|
+
const resolvedTail = Promise.resolve();
|
|
56
|
+
const roundDownToWord = (value)=>value & ~3;
|
|
57
|
+
const computeLayout = (byteLength)=>{
|
|
58
|
+
const cached = layoutCache.get(byteLength);
|
|
59
|
+
if (cached !== undefined) return cached;
|
|
60
|
+
if (byteLength % Int32Array.BYTES_PER_ELEMENT !== 0 || byteLength < MIN_BUFFER_BYTES) {
|
|
61
|
+
layoutCache.set(byteLength, null);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const headerBytes = HEADER_WORDS * Int32Array.BYTES_PER_ELEMENT;
|
|
65
|
+
const perDirectionBytes = roundDownToWord(byteLength - headerBytes >> 1);
|
|
66
|
+
const controlBytes = RING_CONTROL_WORDS * Int32Array.BYTES_PER_ELEMENT;
|
|
67
|
+
for (const ringSlots of SLOT_OPTIONS){
|
|
68
|
+
const descriptorBytes = ringSlots * DESCRIPTOR_WORDS * Int32Array.BYTES_PER_ELEMENT;
|
|
69
|
+
const dataBytes = perDirectionBytes - controlBytes - descriptorBytes;
|
|
70
|
+
if (dataBytes >= MIN_DATA_BYTES) {
|
|
71
|
+
const layout = {
|
|
72
|
+
controlBytes,
|
|
73
|
+
descriptorBytes,
|
|
74
|
+
dataBytes,
|
|
75
|
+
perDirectionBytes,
|
|
76
|
+
ringSlots
|
|
110
77
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
78
|
+
layoutCache.set(byteLength, layout);
|
|
79
|
+
return layout;
|
|
114
80
|
}
|
|
115
|
-
} finally{
|
|
116
|
-
Atomics.store(header, SEMAPHORE, 0);
|
|
117
|
-
Atomics.notify(header, SEMAPHORE);
|
|
118
81
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
value: 0,
|
|
133
|
-
timeout
|
|
82
|
+
layoutCache.set(byteLength, null);
|
|
83
|
+
return null;
|
|
84
|
+
};
|
|
85
|
+
const createRingViews = (buffer, byteOffset, layout)=>{
|
|
86
|
+
const descriptorOffset = byteOffset + layout.controlBytes;
|
|
87
|
+
const dataOffset = descriptorOffset + layout.descriptorBytes;
|
|
88
|
+
return {
|
|
89
|
+
control: new Int32Array(buffer, byteOffset, RING_CONTROL_WORDS),
|
|
90
|
+
descriptors: new Int32Array(buffer, descriptorOffset, layout.ringSlots * DESCRIPTOR_WORDS),
|
|
91
|
+
bytes: new Uint8Array(buffer, dataOffset, layout.dataBytes),
|
|
92
|
+
dataBytes: layout.dataBytes,
|
|
93
|
+
ringSlots: layout.ringSlots,
|
|
94
|
+
ringMask: layout.ringSlots - 1
|
|
134
95
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
96
|
+
};
|
|
97
|
+
const createEndpointToken = ()=>{
|
|
98
|
+
if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') {
|
|
99
|
+
const buf = new Int32Array(1);
|
|
100
|
+
do {
|
|
101
|
+
crypto.getRandomValues(buf);
|
|
102
|
+
}while (buf[0] === 0 || buf[0] === INIT_SENTINEL || buf[0] === MAGIC)
|
|
103
|
+
return buf[0];
|
|
104
|
+
}
|
|
105
|
+
let token = 0;
|
|
106
|
+
while(token === 0 || token === INIT_SENTINEL || token === MAGIC){
|
|
107
|
+
token = Math.random() * 0x7fffffff | 0;
|
|
108
|
+
}
|
|
109
|
+
return token;
|
|
110
|
+
};
|
|
111
|
+
const ENDPOINT_TOKEN = createEndpointToken();
|
|
112
|
+
const finalizeHeader = (buffer, header)=>{
|
|
113
|
+
const bodyOffset = HEADER_WORDS * Int32Array.BYTES_PER_ELEMENT;
|
|
114
|
+
if (bodyOffset < buffer.byteLength) {
|
|
115
|
+
new Int32Array(buffer, bodyOffset, (buffer.byteLength - bodyOffset) / Int32Array.BYTES_PER_ELEMENT).fill(0);
|
|
116
|
+
}
|
|
117
|
+
Atomics.store(header, IDX_VERSION, VERSION);
|
|
118
|
+
Atomics.store(header, IDX_OWNER_A, 0);
|
|
119
|
+
Atomics.store(header, IDX_OWNER_B, 0);
|
|
120
|
+
Atomics.store(header, IDX_FLAGS, 0);
|
|
121
|
+
Atomics.store(header, IDX_MAGIC, MAGIC);
|
|
122
|
+
Atomics.notify(header, IDX_MAGIC);
|
|
123
|
+
};
|
|
124
|
+
const initializeHeaderSync = (buffer, header)=>{
|
|
125
|
+
for(;;){
|
|
126
|
+
const magic = Atomics.load(header, IDX_MAGIC);
|
|
127
|
+
if (magic === MAGIC) {
|
|
128
|
+
const version = Atomics.load(header, IDX_VERSION);
|
|
129
|
+
if (version !== VERSION) throw new Error(`Unsupported channel version ${version}`);
|
|
130
|
+
return;
|
|
165
131
|
}
|
|
166
|
-
if (
|
|
167
|
-
|
|
132
|
+
if (magic === INIT_SENTINEL) {
|
|
133
|
+
Atomics.wait(header, IDX_MAGIC, INIT_SENTINEL);
|
|
134
|
+
continue;
|
|
168
135
|
}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
|
|
136
|
+
if (magic !== 0) throw new Error('SharedArrayBuffer does not contain a supported channel');
|
|
137
|
+
if (Atomics.compareExchange(header, IDX_MAGIC, 0, INIT_SENTINEL) !== 0) continue;
|
|
138
|
+
finalizeHeader(buffer, header);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const resetClosedHeaderSync = (buffer, header)=>{
|
|
143
|
+
for(;;){
|
|
144
|
+
const magic = Atomics.load(header, IDX_MAGIC);
|
|
145
|
+
if (magic === INIT_SENTINEL) {
|
|
146
|
+
Atomics.wait(header, IDX_MAGIC, INIT_SENTINEL);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (magic !== MAGIC) return;
|
|
150
|
+
const version = Atomics.load(header, IDX_VERSION);
|
|
151
|
+
if (version !== VERSION) throw new Error(`Unsupported channel version ${version}`);
|
|
152
|
+
if (Atomics.load(header, IDX_OWNER_A) !== 0 || Atomics.load(header, IDX_OWNER_B) !== 0 || Atomics.load(header, IDX_FLAGS) === 0) return;
|
|
153
|
+
if (Atomics.compareExchange(header, IDX_MAGIC, MAGIC, INIT_SENTINEL) !== MAGIC) continue;
|
|
154
|
+
finalizeHeader(buffer, header);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const resetHeaderSync = (buffer, header)=>{
|
|
159
|
+
initializeHeaderSync(buffer, header);
|
|
160
|
+
for(;;){
|
|
161
|
+
if (Atomics.load(header, IDX_OWNER_A) !== 0 || Atomics.load(header, IDX_OWNER_B) !== 0) {
|
|
162
|
+
throw new Error('Channel is still open');
|
|
172
163
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
throw new Error(`Invalid chunk metadata for chunk ${chunkIndex}`);
|
|
164
|
+
if (Atomics.compareExchange(header, IDX_MAGIC, MAGIC, INIT_SENTINEL) !== MAGIC) {
|
|
165
|
+
initializeHeaderSync(buffer, header);
|
|
166
|
+
continue;
|
|
177
167
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
finalizeHeader(buffer, header);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const claimEndpoint = (header)=>{
|
|
173
|
+
const ownerA = Atomics.load(header, IDX_OWNER_A);
|
|
174
|
+
if (ownerA === ENDPOINT_TOKEN) {
|
|
175
|
+
Atomics.and(header, IDX_FLAGS, ~FLAG_CLOSED_A);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
if (ownerA === 0 && Atomics.compareExchange(header, IDX_OWNER_A, 0, ENDPOINT_TOKEN) === 0) {
|
|
179
|
+
Atomics.and(header, IDX_FLAGS, ~FLAG_CLOSED_A);
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
const ownerB = Atomics.load(header, IDX_OWNER_B);
|
|
183
|
+
if (ownerB === ENDPOINT_TOKEN) {
|
|
184
|
+
Atomics.and(header, IDX_FLAGS, ~FLAG_CLOSED_B);
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
if (ownerB === 0 && Atomics.compareExchange(header, IDX_OWNER_B, 0, ENDPOINT_TOKEN) === 0) {
|
|
188
|
+
Atomics.and(header, IDX_FLAGS, ~FLAG_CLOSED_B);
|
|
189
|
+
return 1;
|
|
190
|
+
}
|
|
191
|
+
throw new Error('SharedArrayBuffer channel already claimed by two endpoints');
|
|
192
|
+
};
|
|
193
|
+
const createState = (buffer, layout)=>{
|
|
194
|
+
const cached = stateCache.get(buffer);
|
|
195
|
+
if (cached !== undefined) return cached;
|
|
196
|
+
const byteOffset = HEADER_WORDS * Int32Array.BYTES_PER_ELEMENT;
|
|
197
|
+
const directionA = createRingViews(buffer, byteOffset, layout);
|
|
198
|
+
const directionB = createRingViews(buffer, byteOffset + layout.perDirectionBytes, layout);
|
|
199
|
+
const header = new Int32Array(buffer, 0, HEADER_WORDS);
|
|
200
|
+
const endpoint = claimEndpoint(header);
|
|
201
|
+
const outbound = endpoint === 0 ? directionA : directionB;
|
|
202
|
+
const inbound = endpoint === 0 ? directionB : directionA;
|
|
203
|
+
const state = {
|
|
204
|
+
header,
|
|
205
|
+
inbound,
|
|
206
|
+
outbound,
|
|
207
|
+
ownerIndex: endpoint === 0 ? IDX_OWNER_A : IDX_OWNER_B,
|
|
208
|
+
localClosedMask: endpoint === 0 ? FLAG_CLOSED_A : FLAG_CLOSED_B,
|
|
209
|
+
peerClosedMask: endpoint === 0 ? FLAG_CLOSED_B : FLAG_CLOSED_A,
|
|
210
|
+
localClosed: false,
|
|
211
|
+
writeLocked: false,
|
|
212
|
+
readLocked: false,
|
|
213
|
+
readTail: resolvedTail,
|
|
214
|
+
writeTail: resolvedTail,
|
|
215
|
+
wMsg: Atomics.load(outbound.control, IDX_WRITE_MSG),
|
|
216
|
+
wByte: 0,
|
|
217
|
+
rMsg: Atomics.load(inbound.control, IDX_READ_MSG),
|
|
218
|
+
rByte: 0,
|
|
219
|
+
pendingAckMsg: 0,
|
|
220
|
+
pendingAckByte: 0,
|
|
221
|
+
pendingAckSize: 0
|
|
222
|
+
};
|
|
223
|
+
stateCache.set(buffer, state);
|
|
224
|
+
return state;
|
|
225
|
+
};
|
|
226
|
+
const getStateSync = (buffer)=>{
|
|
227
|
+
const cached = stateCache.get(buffer);
|
|
228
|
+
if (cached !== undefined) return cached;
|
|
229
|
+
const layout = computeLayout(buffer.byteLength);
|
|
230
|
+
if (layout === null) throw new Error(`SharedArrayBuffer too small for channel (minimum ${MIN_BUFFER_BYTES} bytes)`);
|
|
231
|
+
const header = new Int32Array(buffer, 0, HEADER_WORDS);
|
|
232
|
+
initializeHeaderSync(buffer, header);
|
|
233
|
+
resetClosedHeaderSync(buffer, header);
|
|
234
|
+
return createState(buffer, layout);
|
|
235
|
+
};
|
|
236
|
+
const copyToRing = (ring, ringOffset, source, sourceOffset, size)=>{
|
|
237
|
+
const first = Math.min(size, ring.byteLength - ringOffset);
|
|
238
|
+
ring.set(source.subarray(sourceOffset, sourceOffset + first), ringOffset);
|
|
239
|
+
if (first < size) ring.set(source.subarray(sourceOffset + first, sourceOffset + size), 0);
|
|
240
|
+
};
|
|
241
|
+
const copyFromRing = (target, targetOffset, ring, ringOffset, size)=>{
|
|
242
|
+
const first = Math.min(size, ring.byteLength - ringOffset);
|
|
243
|
+
target.set(ring.subarray(ringOffset, ringOffset + first), targetOffset);
|
|
244
|
+
if (first < size) target.set(ring.subarray(0, size - first), targetOffset + first);
|
|
245
|
+
};
|
|
246
|
+
const freeWriteSpace = (ring, writeMsg, writeByte)=>{
|
|
247
|
+
const readMsg = Atomics.load(ring.control, IDX_READ_MSG);
|
|
248
|
+
const pending = writeMsg - readMsg | 0;
|
|
249
|
+
if (pending >= ring.ringSlots) return 0;
|
|
250
|
+
if (pending === 0) return ring.dataBytes;
|
|
251
|
+
const readByte = Atomics.load(ring.control, IDX_READ_BYTE);
|
|
252
|
+
const free = ring.dataBytes - (writeByte - readByte | 0);
|
|
253
|
+
return free > 0 ? free : 0;
|
|
254
|
+
};
|
|
255
|
+
const publishSegment = (ring, offset, size, total, writeMsg)=>{
|
|
256
|
+
const di = (writeMsg & ring.ringMask) * DESCRIPTOR_WORDS;
|
|
257
|
+
ring.descriptors[di + IDX_DESC_OFFSET] = offset;
|
|
258
|
+
ring.descriptors[di + IDX_DESC_SIZE] = size;
|
|
259
|
+
ring.descriptors[di + IDX_DESC_TOTAL] = total;
|
|
260
|
+
Atomics.store(ring.control, IDX_WRITE_MSG, writeMsg + 1 | 0);
|
|
261
|
+
Atomics.notify(ring.control, IDX_WRITE_MSG, 1);
|
|
262
|
+
};
|
|
263
|
+
const ackSegment = (ring, readMsg, readByte, size)=>{
|
|
264
|
+
Atomics.store(ring.control, IDX_READ_BYTE, readByte + size | 0);
|
|
265
|
+
Atomics.store(ring.control, IDX_READ_MSG, readMsg + 1 | 0);
|
|
266
|
+
Atomics.notify(ring.control, IDX_READ_MSG, 1);
|
|
267
|
+
};
|
|
268
|
+
const flushPendingAck = (state)=>{
|
|
269
|
+
if (state.pendingAckSize > 0) {
|
|
270
|
+
ackSegment(state.inbound, state.pendingAckMsg, state.pendingAckByte, state.pendingAckSize);
|
|
271
|
+
state.pendingAckSize = 0;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
const isPeerClosed = (state)=>(Atomics.load(state.header, IDX_FLAGS) & state.peerClosedMask) !== 0;
|
|
275
|
+
const assertOpen = (state)=>{
|
|
276
|
+
if (state.localClosed) throw new Error('Channel is closed');
|
|
277
|
+
};
|
|
278
|
+
const assertWritable = (state)=>{
|
|
279
|
+
assertOpen(state);
|
|
280
|
+
if (isPeerClosed(state)) throw new Error('Peer channel is closed');
|
|
281
|
+
};
|
|
282
|
+
const assertReadable = (state, receiving)=>{
|
|
283
|
+
assertOpen(state);
|
|
284
|
+
if (isPeerClosed(state)) throw new Error(receiving ? 'Peer channel closed while receiving message' : 'Peer channel is closed');
|
|
285
|
+
};
|
|
286
|
+
const notifyWaiters = (state)=>{
|
|
287
|
+
Atomics.notify(state.inbound.control, IDX_WRITE_MSG);
|
|
288
|
+
Atomics.notify(state.inbound.control, IDX_READ_MSG);
|
|
289
|
+
Atomics.notify(state.outbound.control, IDX_WRITE_MSG);
|
|
290
|
+
Atomics.notify(state.outbound.control, IDX_READ_MSG);
|
|
291
|
+
};
|
|
292
|
+
const writeChannelSync = (data, state, timeout)=>{
|
|
293
|
+
if (state.writeLocked) throw new Error('Cannot writeSync while an async write is in progress');
|
|
294
|
+
flushPendingAck(state);
|
|
295
|
+
const ring = state.outbound;
|
|
296
|
+
assertWritable(state);
|
|
297
|
+
let writeMsg = state.wMsg;
|
|
298
|
+
let writeByte = state.wByte;
|
|
299
|
+
if (data.length === 0) {
|
|
300
|
+
while(freeWriteSpace(ring, writeMsg, writeByte) === 0){
|
|
301
|
+
assertWritable(state);
|
|
302
|
+
const readMsg = Atomics.load(ring.control, IDX_READ_MSG);
|
|
303
|
+
if (Atomics.wait(ring.control, IDX_READ_MSG, readMsg, timeout) === 'timed-out') throw new Error('Write timeout waiting for channel space');
|
|
304
|
+
}
|
|
305
|
+
publishSegment(ring, (writeByte >>> 0) % ring.dataBytes, 0, 0, writeMsg);
|
|
306
|
+
state.wMsg = writeMsg + 1 | 0;
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
let sourceOffset = 0;
|
|
310
|
+
let firstSegment = true;
|
|
311
|
+
while(sourceOffset < data.length){
|
|
312
|
+
let free = freeWriteSpace(ring, writeMsg, writeByte);
|
|
313
|
+
while(free === 0){
|
|
314
|
+
assertWritable(state);
|
|
315
|
+
const readMsg = Atomics.load(ring.control, IDX_READ_MSG);
|
|
316
|
+
if (Atomics.wait(ring.control, IDX_READ_MSG, readMsg, timeout) === 'timed-out') throw new Error('Write timeout waiting for channel space');
|
|
317
|
+
free = freeWriteSpace(ring, writeMsg, writeByte);
|
|
318
|
+
}
|
|
319
|
+
const size = Math.min(data.length - sourceOffset, free);
|
|
320
|
+
const ringOffset = (writeByte >>> 0) % ring.dataBytes;
|
|
321
|
+
copyToRing(ring.bytes, ringOffset, data, sourceOffset, size);
|
|
322
|
+
publishSegment(ring, ringOffset, size, firstSegment ? data.length : CONTINUATION, writeMsg);
|
|
323
|
+
writeMsg = writeMsg + 1 | 0;
|
|
324
|
+
writeByte = writeByte + size | 0;
|
|
325
|
+
state.wMsg = writeMsg;
|
|
326
|
+
state.wByte = writeByte;
|
|
327
|
+
sourceOffset += size;
|
|
328
|
+
firstSegment = false;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const writeChannel = async (data, state, timeout)=>{
|
|
332
|
+
flushPendingAck(state);
|
|
333
|
+
const ring = state.outbound;
|
|
334
|
+
assertWritable(state);
|
|
335
|
+
let writeMsg = state.wMsg;
|
|
336
|
+
let writeByte = state.wByte;
|
|
337
|
+
if (data.length === 0) {
|
|
338
|
+
while(freeWriteSpace(ring, writeMsg, writeByte) === 0){
|
|
339
|
+
assertWritable(state);
|
|
340
|
+
const readMsg = Atomics.load(ring.control, IDX_READ_MSG);
|
|
341
|
+
if (await Atomics.waitAsync(ring.control, IDX_READ_MSG, readMsg, timeout).value === 'timed-out') throw new Error('Write timeout waiting for channel space');
|
|
342
|
+
}
|
|
343
|
+
publishSegment(ring, (writeByte >>> 0) % ring.dataBytes, 0, 0, writeMsg);
|
|
344
|
+
state.wMsg = writeMsg + 1 | 0;
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
let sourceOffset = 0;
|
|
348
|
+
let firstSegment = true;
|
|
349
|
+
while(sourceOffset < data.length){
|
|
350
|
+
let free = freeWriteSpace(ring, writeMsg, writeByte);
|
|
351
|
+
while(free === 0){
|
|
352
|
+
assertWritable(state);
|
|
353
|
+
const readMsg = Atomics.load(ring.control, IDX_READ_MSG);
|
|
354
|
+
if (await Atomics.waitAsync(ring.control, IDX_READ_MSG, readMsg, timeout).value === 'timed-out') throw new Error('Write timeout waiting for channel space');
|
|
355
|
+
free = freeWriteSpace(ring, writeMsg, writeByte);
|
|
356
|
+
}
|
|
357
|
+
const size = Math.min(data.length - sourceOffset, free);
|
|
358
|
+
const ringOffset = (writeByte >>> 0) % ring.dataBytes;
|
|
359
|
+
copyToRing(ring.bytes, ringOffset, data, sourceOffset, size);
|
|
360
|
+
publishSegment(ring, ringOffset, size, firstSegment ? data.length : CONTINUATION, writeMsg);
|
|
361
|
+
writeMsg = writeMsg + 1 | 0;
|
|
362
|
+
writeByte = writeByte + size | 0;
|
|
363
|
+
state.wMsg = writeMsg;
|
|
364
|
+
state.wByte = writeByte;
|
|
365
|
+
sourceOffset += size;
|
|
366
|
+
firstSegment = false;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
const readChannelSync = (state, timeout)=>{
|
|
370
|
+
if (state.readLocked) throw new Error('Cannot readSync while an async read is in progress');
|
|
371
|
+
const ring = state.inbound;
|
|
372
|
+
flushPendingAck(state);
|
|
373
|
+
let readMsg = state.rMsg;
|
|
374
|
+
let readByte = state.rByte;
|
|
375
|
+
while(readMsg === Atomics.load(ring.control, IDX_WRITE_MSG)){
|
|
376
|
+
assertReadable(state, false);
|
|
377
|
+
if (Atomics.wait(ring.control, IDX_WRITE_MSG, readMsg, timeout) === 'timed-out') throw new Error('Read timeout');
|
|
378
|
+
}
|
|
379
|
+
const di = (readMsg & ring.ringMask) * DESCRIPTOR_WORDS;
|
|
380
|
+
const offset = ring.descriptors[di + IDX_DESC_OFFSET];
|
|
381
|
+
const size = ring.descriptors[di + IDX_DESC_SIZE];
|
|
382
|
+
const total = ring.descriptors[di + IDX_DESC_TOTAL];
|
|
383
|
+
if (offset < 0 || offset >= ring.dataBytes || size < 0 || size > ring.dataBytes) throw new Error('Invalid descriptor');
|
|
384
|
+
if (total < 0) throw new Error('Invalid descriptor');
|
|
385
|
+
if (size === total && offset + size <= ring.dataBytes) {
|
|
386
|
+
state.pendingAckMsg = readMsg;
|
|
387
|
+
state.pendingAckByte = readByte;
|
|
388
|
+
state.pendingAckSize = size;
|
|
389
|
+
state.rMsg = readMsg + 1 | 0;
|
|
390
|
+
state.rByte = readByte + size | 0;
|
|
391
|
+
if (size === 0) return new Uint8Array(0);
|
|
392
|
+
return new Uint8Array(ring.bytes.buffer, ring.bytes.byteOffset + offset, size);
|
|
393
|
+
}
|
|
394
|
+
const data = new Uint8Array(total);
|
|
395
|
+
const targetSize = total;
|
|
396
|
+
let targetOffset = 0;
|
|
397
|
+
if (targetOffset + size > targetSize) throw new Error('Invalid descriptor');
|
|
398
|
+
if (size > 0) copyFromRing(data, targetOffset, ring.bytes, offset, size);
|
|
399
|
+
targetOffset += size;
|
|
400
|
+
ackSegment(ring, readMsg, readByte, size);
|
|
401
|
+
readMsg = readMsg + 1 | 0;
|
|
402
|
+
readByte = readByte + size | 0;
|
|
403
|
+
state.rMsg = readMsg;
|
|
404
|
+
state.rByte = readByte;
|
|
405
|
+
while(targetOffset < targetSize){
|
|
406
|
+
while(readMsg === Atomics.load(ring.control, IDX_WRITE_MSG)){
|
|
407
|
+
assertReadable(state, true);
|
|
408
|
+
if (Atomics.wait(ring.control, IDX_WRITE_MSG, readMsg, timeout) === 'timed-out') throw new Error('Read timeout waiting for segment');
|
|
409
|
+
}
|
|
410
|
+
const di2 = (readMsg & ring.ringMask) * DESCRIPTOR_WORDS;
|
|
411
|
+
const off2 = ring.descriptors[di2 + IDX_DESC_OFFSET];
|
|
412
|
+
const sz2 = ring.descriptors[di2 + IDX_DESC_SIZE];
|
|
413
|
+
const tot2 = ring.descriptors[di2 + IDX_DESC_TOTAL];
|
|
414
|
+
if (off2 < 0 || off2 >= ring.dataBytes || sz2 < 0 || sz2 > ring.dataBytes) throw new Error('Invalid descriptor');
|
|
415
|
+
if (tot2 !== CONTINUATION) throw new Error('Invalid descriptor');
|
|
416
|
+
if (targetOffset + sz2 > targetSize) throw new Error('Invalid descriptor');
|
|
417
|
+
if (sz2 > 0) copyFromRing(data, targetOffset, ring.bytes, off2, sz2);
|
|
418
|
+
targetOffset += sz2;
|
|
419
|
+
ackSegment(ring, readMsg, readByte, sz2);
|
|
420
|
+
readMsg = readMsg + 1 | 0;
|
|
421
|
+
readByte = readByte + sz2 | 0;
|
|
422
|
+
state.rMsg = readMsg;
|
|
423
|
+
state.rByte = readByte;
|
|
181
424
|
}
|
|
182
425
|
return data;
|
|
183
|
-
}
|
|
184
|
-
const
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
426
|
+
};
|
|
427
|
+
const readChannel = async (state, timeout)=>{
|
|
428
|
+
const ring = state.inbound;
|
|
429
|
+
flushPendingAck(state);
|
|
430
|
+
let readMsg = state.rMsg;
|
|
431
|
+
let readByte = state.rByte;
|
|
432
|
+
while(readMsg === Atomics.load(ring.control, IDX_WRITE_MSG)){
|
|
433
|
+
assertReadable(state, false);
|
|
434
|
+
if (await Atomics.waitAsync(ring.control, IDX_WRITE_MSG, readMsg, timeout).value === 'timed-out') throw new Error('Read timeout');
|
|
435
|
+
}
|
|
436
|
+
const di = (readMsg & ring.ringMask) * DESCRIPTOR_WORDS;
|
|
437
|
+
const offset = ring.descriptors[di + IDX_DESC_OFFSET];
|
|
438
|
+
const size = ring.descriptors[di + IDX_DESC_SIZE];
|
|
439
|
+
const total = ring.descriptors[di + IDX_DESC_TOTAL];
|
|
440
|
+
if (offset < 0 || offset >= ring.dataBytes || size < 0 || size > ring.dataBytes) throw new Error('Invalid descriptor');
|
|
441
|
+
if (total < 0) throw new Error('Invalid descriptor');
|
|
442
|
+
if (size === total && offset + size <= ring.dataBytes) {
|
|
443
|
+
state.pendingAckMsg = readMsg;
|
|
444
|
+
state.pendingAckByte = readByte;
|
|
445
|
+
state.pendingAckSize = size;
|
|
446
|
+
state.rMsg = readMsg + 1 | 0;
|
|
447
|
+
state.rByte = readByte + size | 0;
|
|
448
|
+
if (size === 0) return new Uint8Array(0);
|
|
449
|
+
return new Uint8Array(ring.bytes.buffer, ring.bytes.byteOffset + offset, size);
|
|
450
|
+
}
|
|
451
|
+
const data = new Uint8Array(total);
|
|
452
|
+
const targetSize = total;
|
|
453
|
+
let targetOffset = 0;
|
|
454
|
+
if (targetOffset + size > targetSize) throw new Error('Invalid descriptor');
|
|
455
|
+
if (size > 0) copyFromRing(data, targetOffset, ring.bytes, offset, size);
|
|
456
|
+
targetOffset += size;
|
|
457
|
+
ackSegment(ring, readMsg, readByte, size);
|
|
458
|
+
readMsg = readMsg + 1 | 0;
|
|
459
|
+
readByte = readByte + size | 0;
|
|
460
|
+
state.rMsg = readMsg;
|
|
461
|
+
state.rByte = readByte;
|
|
462
|
+
while(targetOffset < targetSize){
|
|
463
|
+
while(readMsg === Atomics.load(ring.control, IDX_WRITE_MSG)){
|
|
464
|
+
assertReadable(state, true);
|
|
465
|
+
if (await Atomics.waitAsync(ring.control, IDX_WRITE_MSG, readMsg, timeout).value === 'timed-out') throw new Error('Read timeout waiting for segment');
|
|
466
|
+
}
|
|
467
|
+
const di2 = (readMsg & ring.ringMask) * DESCRIPTOR_WORDS;
|
|
468
|
+
const off2 = ring.descriptors[di2 + IDX_DESC_OFFSET];
|
|
469
|
+
const sz2 = ring.descriptors[di2 + IDX_DESC_SIZE];
|
|
470
|
+
const tot2 = ring.descriptors[di2 + IDX_DESC_TOTAL];
|
|
471
|
+
if (off2 < 0 || off2 >= ring.dataBytes || sz2 < 0 || sz2 > ring.dataBytes) throw new Error('Invalid descriptor');
|
|
472
|
+
if (tot2 !== CONTINUATION) throw new Error('Invalid descriptor');
|
|
473
|
+
if (targetOffset + sz2 > targetSize) throw new Error('Invalid descriptor');
|
|
474
|
+
if (sz2 > 0) copyFromRing(data, targetOffset, ring.bytes, off2, sz2);
|
|
475
|
+
targetOffset += sz2;
|
|
476
|
+
ackSegment(ring, readMsg, readByte, sz2);
|
|
477
|
+
readMsg = readMsg + 1 | 0;
|
|
478
|
+
readByte = readByte + sz2 | 0;
|
|
479
|
+
state.rMsg = readMsg;
|
|
480
|
+
state.rByte = readByte;
|
|
481
|
+
}
|
|
482
|
+
return data;
|
|
483
|
+
};
|
|
484
|
+
const runExclusive = async (state, key, task)=>{
|
|
485
|
+
const previous = state[key];
|
|
486
|
+
let release;
|
|
487
|
+
const next = new Promise((resolve)=>{
|
|
488
|
+
release = resolve;
|
|
489
|
+
});
|
|
490
|
+
state[key] = previous.then(()=>next, ()=>next);
|
|
491
|
+
await previous;
|
|
492
|
+
try {
|
|
493
|
+
return await task();
|
|
494
|
+
} finally{
|
|
495
|
+
release();
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const closeState = (buffer, state)=>{
|
|
499
|
+
if (state.localClosed) return;
|
|
500
|
+
flushPendingAck(state);
|
|
501
|
+
state.localClosed = true;
|
|
502
|
+
Atomics.or(state.header, IDX_FLAGS, state.localClosedMask);
|
|
503
|
+
Atomics.compareExchange(state.header, state.ownerIndex, ENDPOINT_TOKEN, 0);
|
|
504
|
+
notifyWaiters(state);
|
|
505
|
+
handleCache.delete(buffer);
|
|
506
|
+
stateCache.delete(buffer);
|
|
507
|
+
};
|
|
508
|
+
const createBuffer = (byteLength)=>{
|
|
509
|
+
if (byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
|
|
510
|
+
const buffer = new SharedArrayBuffer(byteLength);
|
|
511
|
+
if (computeLayout(buffer.byteLength) === null) throw new Error(`SharedArrayBuffer too small for channel (minimum ${MIN_BUFFER_BYTES} bytes)`);
|
|
512
|
+
initializeHeaderSync(buffer, new Int32Array(buffer, 0, HEADER_WORDS));
|
|
513
|
+
return buffer;
|
|
514
|
+
};
|
|
515
|
+
const open = (buffer)=>{
|
|
516
|
+
const cached = handleCache.get(buffer);
|
|
517
|
+
if (cached !== undefined) return cached;
|
|
518
|
+
if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
|
|
519
|
+
const state = getStateSync(buffer);
|
|
520
|
+
const channel = {
|
|
521
|
+
write: (data, options)=>runExclusive(state, 'writeTail', async ()=>{
|
|
522
|
+
state.writeLocked = true;
|
|
523
|
+
try {
|
|
524
|
+
await writeChannel(data, state, options?.timeout ?? 5000);
|
|
525
|
+
} finally{
|
|
526
|
+
state.writeLocked = false;
|
|
527
|
+
}
|
|
528
|
+
}),
|
|
529
|
+
read: (options)=>runExclusive(state, 'readTail', async ()=>{
|
|
530
|
+
state.readLocked = true;
|
|
531
|
+
try {
|
|
532
|
+
return await readChannel(state, options?.timeout ?? 5000);
|
|
533
|
+
} finally{
|
|
534
|
+
state.readLocked = false;
|
|
535
|
+
}
|
|
536
|
+
}),
|
|
537
|
+
writeSync: (data, options)=>writeChannelSync(data, state, options?.timeout ?? 5000),
|
|
538
|
+
readSync: (options)=>readChannelSync(state, options?.timeout ?? 5000),
|
|
539
|
+
close: ()=>closeState(buffer, state)
|
|
540
|
+
};
|
|
541
|
+
handleCache.set(buffer, channel);
|
|
542
|
+
return channel;
|
|
543
|
+
};
|
|
544
|
+
const reset = (buffer)=>{
|
|
545
|
+
if (buffer.byteLength % Int32Array.BYTES_PER_ELEMENT !== 0) throw new Error('SharedArrayBuffer byteLength must be a multiple of 4');
|
|
546
|
+
if (computeLayout(buffer.byteLength) === null) throw new Error(`SharedArrayBuffer too small for channel (minimum ${MIN_BUFFER_BYTES} bytes)`);
|
|
547
|
+
const cached = stateCache.get(buffer);
|
|
548
|
+
if (cached !== undefined && !cached.localClosed) throw new Error('Channel is still open in this runtime');
|
|
549
|
+
resetHeaderSync(buffer, new Int32Array(buffer, 0, HEADER_WORDS));
|
|
550
|
+
handleCache.delete(buffer);
|
|
551
|
+
stateCache.delete(buffer);
|
|
219
552
|
};
|
|
220
553
|
|
|
221
554
|
//# sourceMappingURL=index.cjs.map
|