njs-modbus 3.2.0 → 3.3.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/dist/utils.mjs CHANGED
@@ -22,40 +22,75 @@ function bitsToMs(baudRate, bits) {
22
22
  * open / close / destroy callbacks.
23
23
  */
24
24
  function drainCbs(cbs, err) {
25
- if (!cbs)
25
+ if (!cbs) {
26
26
  return;
27
+ }
27
28
  for (const cb of cbs) {
28
29
  cb?.(err);
29
30
  }
30
31
  }
31
32
 
32
- function inRange(n, [min, max]) {
33
- return n >= min && n <= max;
34
- }
35
- function isRangeArray(range) {
36
- return Array.isArray(range[0]);
37
- }
38
33
  function checkRange(value, range) {
39
34
  if (!range || range.length === 0) {
40
35
  return true;
41
36
  }
42
- const values = Array.isArray(value) ? value : [value];
43
- if (isRangeArray(range)) {
44
- for (const r of range) {
45
- const [min, max] = r;
46
- const [lo, hi] = min <= max ? [min, max] : [max, min];
47
- if (values.every((n) => inRange(n, [lo, hi]))) {
37
+ const isMultiRange = Array.isArray(range[0]);
38
+ const isValueArray = Array.isArray(value);
39
+ if (!isValueArray && !isMultiRange) {
40
+ const r = range;
41
+ const min = r[0], max = r[1];
42
+ const v = value;
43
+ return min <= max ? v >= min && v <= max : v >= max && v <= min;
44
+ }
45
+ if (!isValueArray && isMultiRange) {
46
+ const ranges = range;
47
+ const v = value;
48
+ for (let i = 0; i < ranges.length; i++) {
49
+ const min = ranges[i][0], max = ranges[i][1];
50
+ const lo = min <= max ? min : max;
51
+ const hi = min <= max ? max : min;
52
+ if (v >= lo && v <= hi) {
48
53
  return true;
49
54
  }
50
55
  }
51
56
  return false;
52
57
  }
53
- const [min, max] = range;
54
- const [lo, hi] = min <= max ? [min, max] : [max, min];
55
- return values.every((n) => inRange(n, [lo, hi]));
58
+ const values = value;
59
+ if (values.length === 0) {
60
+ return true;
61
+ }
62
+ if (!isMultiRange) {
63
+ const r = range;
64
+ const min = r[0], max = r[1];
65
+ const lo = min <= max ? min : max;
66
+ const hi = min <= max ? max : min;
67
+ for (let i = 0; i < values.length; i++) {
68
+ if (values[i] < lo || values[i] > hi) {
69
+ return false;
70
+ }
71
+ }
72
+ return true;
73
+ }
74
+ const ranges = range;
75
+ for (let i = 0; i < ranges.length; i++) {
76
+ const min = ranges[i][0], max = ranges[i][1];
77
+ const lo = min <= max ? min : max;
78
+ const hi = min <= max ? max : min;
79
+ let allInRange = true;
80
+ for (let j = 0; j < values.length; j++) {
81
+ if (values[j] < lo || values[j] > hi) {
82
+ allInRange = false;
83
+ break;
84
+ }
85
+ }
86
+ if (allInRange) {
87
+ return true;
88
+ }
89
+ }
90
+ return false;
56
91
  }
57
92
 
58
- const TABLE = [
93
+ const TABLE = new Uint16Array([
59
94
  0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01,
60
95
  0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0,
61
96
  0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581,
@@ -72,11 +107,11 @@ const TABLE = [
72
107
  0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0,
73
108
  0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081,
74
109
  0x4040,
75
- ];
76
- function crc(data, start = 0, end = data.length) {
110
+ ]);
111
+ function crc(data, start, end) {
77
112
  let crc = 0xffff;
78
113
  for (let index = start; index < end; index++) {
79
- crc = (TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8)) & 0xffff;
114
+ crc = TABLE[(crc ^ data[index]) & 0xff] ^ (crc >> 8);
80
115
  }
81
116
  return crc;
82
117
  }
@@ -89,15 +124,15 @@ function crc(data, start = 0, end = data.length) {
89
124
  * Infinity, and out-of-range values uniformly.
90
125
  */
91
126
  function isUint8(n) {
92
- return Number.isInteger(n) && n >= 0 && n <= 255;
127
+ return (n & 0xff) === n;
93
128
  }
94
129
 
95
- function lrc(data, start = 0, end = data.length) {
130
+ function lrc(data, start, end) {
96
131
  let sum = 0;
97
132
  for (let i = start; i < end; i++) {
98
133
  sum += data[i];
99
134
  }
100
- return (~sum + 1) & 0xff;
135
+ return -sum & 0xff;
101
136
  }
102
137
 
103
138
  /**
@@ -140,103 +175,93 @@ var ConformityLevel;
140
175
  /** Shared empty Buffer to avoid repeated allocations. */
141
176
  Buffer.alloc(0);
142
177
 
143
- const REQUEST_FIXED_LENGTHS = {
144
- [FunctionCode.READ_COILS]: 8,
145
- [FunctionCode.READ_DISCRETE_INPUTS]: 8,
146
- [FunctionCode.READ_HOLDING_REGISTERS]: 8,
147
- [FunctionCode.READ_INPUT_REGISTERS]: 8,
148
- [FunctionCode.WRITE_SINGLE_COIL]: 8,
149
- [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
150
- [FunctionCode.REPORT_SERVER_ID]: 4,
151
- [FunctionCode.MASK_WRITE_REGISTER]: 10,
152
- [FunctionCode.READ_DEVICE_IDENTIFICATION]: 7,
153
- };
154
- const REQUEST_BYTE_COUNT = {
155
- [FunctionCode.WRITE_MULTIPLE_COILS]: { offset: 6, extra: 9 },
156
- [FunctionCode.WRITE_MULTIPLE_REGISTERS]: { offset: 6, extra: 9 },
157
- [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 10, extra: 13 },
158
- };
159
- const RESPONSE_FIXED_LENGTHS = {
160
- [FunctionCode.WRITE_SINGLE_COIL]: 8,
161
- [FunctionCode.WRITE_SINGLE_REGISTER]: 8,
162
- [FunctionCode.WRITE_MULTIPLE_COILS]: 8,
163
- [FunctionCode.WRITE_MULTIPLE_REGISTERS]: 8,
164
- [FunctionCode.MASK_WRITE_REGISTER]: 10,
165
- };
166
- const RESPONSE_BYTE_COUNT = {
167
- [FunctionCode.READ_COILS]: { offset: 2, extra: 5 },
168
- [FunctionCode.READ_DISCRETE_INPUTS]: { offset: 2, extra: 5 },
169
- [FunctionCode.READ_HOLDING_REGISTERS]: { offset: 2, extra: 5 },
170
- [FunctionCode.READ_INPUT_REGISTERS]: { offset: 2, extra: 5 },
171
- [FunctionCode.REPORT_SERVER_ID]: { offset: 2, extra: 5 },
172
- [FunctionCode.READ_WRITE_MULTIPLE_REGISTERS]: { offset: 2, extra: 5 },
173
- };
174
- /** Sentinel: caller needs to feed more bytes before length can be determined. */
175
178
  const PREDICT_NEED_MORE = 0;
176
- /** Sentinel: function code is not in the standard tables. */
177
179
  const PREDICT_UNKNOWN = -1;
178
- /**
179
- * Predict the total RTU frame length (PDU + 2-byte CRC) given the leading bytes.
180
- *
181
- * Returns a sentinel-encoded number to avoid per-call object allocation on the
182
- * RTU decode hot path:
183
- * - Positive integer (>= 4): total frame length, function code is known.
184
- * - {@link PREDICT_NEED_MORE} (0): function code is known but more bytes are
185
- * required (typically waiting on the byteCount byte).
186
- * - {@link PREDICT_UNKNOWN} (-1): function code is not in the standard tables —
187
- * the framing layer must defer to a registered `CustomFunctionCode` or treat
188
- * this as a framing error.
189
- */
180
+ const REQ_TABLE = new Int32Array(256);
181
+ const RES_TABLE = new Int32Array(256);
182
+ (function initTables() {
183
+ REQ_TABLE[FunctionCode.READ_COILS] = 8;
184
+ REQ_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = 8;
185
+ REQ_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = 8;
186
+ REQ_TABLE[FunctionCode.READ_INPUT_REGISTERS] = 8;
187
+ REQ_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
188
+ REQ_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
189
+ REQ_TABLE[FunctionCode.REPORT_SERVER_ID] = 4;
190
+ REQ_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
191
+ REQ_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = 7;
192
+ REQ_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = -1545;
193
+ REQ_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = -1545;
194
+ REQ_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -2573;
195
+ RES_TABLE[FunctionCode.WRITE_SINGLE_COIL] = 8;
196
+ RES_TABLE[FunctionCode.WRITE_SINGLE_REGISTER] = 8;
197
+ RES_TABLE[FunctionCode.WRITE_MULTIPLE_COILS] = 8;
198
+ RES_TABLE[FunctionCode.WRITE_MULTIPLE_REGISTERS] = 8;
199
+ RES_TABLE[FunctionCode.MASK_WRITE_REGISTER] = 10;
200
+ RES_TABLE[FunctionCode.READ_COILS] = -517;
201
+ RES_TABLE[FunctionCode.READ_DISCRETE_INPUTS] = -517;
202
+ RES_TABLE[FunctionCode.READ_HOLDING_REGISTERS] = -517;
203
+ RES_TABLE[FunctionCode.READ_INPUT_REGISTERS] = -517;
204
+ RES_TABLE[FunctionCode.REPORT_SERVER_ID] = -517;
205
+ RES_TABLE[FunctionCode.READ_WRITE_MULTIPLE_REGISTERS] = -517;
206
+ RES_TABLE[FunctionCode.READ_DEVICE_IDENTIFICATION] = -999;
207
+ })();
190
208
  function predictRtuFrameLength(buffer, start, end, isResponse) {
191
- if (end - start < 2) {
209
+ const len = end - start;
210
+ if (len < 2) {
192
211
  return PREDICT_NEED_MORE;
193
212
  }
194
213
  const fc = buffer[start + 1];
195
- if (isResponse && (fc & EXCEPTION_OFFSET) !== 0) {
196
- return 5;
197
- }
198
- const fixed = (isResponse ? RESPONSE_FIXED_LENGTHS : REQUEST_FIXED_LENGTHS)[fc];
199
- if (fixed !== undefined) {
200
- return fixed;
201
- }
202
- const bc = (isResponse ? RESPONSE_BYTE_COUNT : REQUEST_BYTE_COUNT)[fc];
203
- if (bc !== undefined) {
204
- if (end - start <= bc.offset) {
205
- return PREDICT_NEED_MORE;
214
+ if (isResponse) {
215
+ if ((fc & EXCEPTION_OFFSET) !== 0) {
216
+ return 5;
217
+ }
218
+ const val = RES_TABLE[fc];
219
+ if (val > 0) {
220
+ return val;
221
+ }
222
+ if (val < 0) {
223
+ if (val === -999) {
224
+ // FC 43 / MEI 14 response — inline to avoid function-call overhead on
225
+ // the framing hot path (even though this FC is uncommon).
226
+ if (end - start < 8) {
227
+ return PREDICT_NEED_MORE;
228
+ }
229
+ if (buffer[start + 2] !== MEI_READ_DEVICE_ID) {
230
+ return PREDICT_UNKNOWN;
231
+ }
232
+ const numObjs = buffer[start + 7];
233
+ let cursor = start + 8;
234
+ for (let i = 0; i < numObjs; i++) {
235
+ if (end < cursor + 2) {
236
+ return PREDICT_NEED_MORE;
237
+ }
238
+ cursor += 2 + buffer[cursor + 1];
239
+ }
240
+ return cursor - start + 2;
241
+ }
242
+ const decode = -val;
243
+ const offset = decode >> 8;
244
+ if (len <= offset) {
245
+ return PREDICT_NEED_MORE;
246
+ }
247
+ return (decode & 0xff) + buffer[start + offset];
206
248
  }
207
- return bc.extra + buffer[start + bc.offset];
208
- }
209
- if (isResponse && fc === FunctionCode.READ_DEVICE_IDENTIFICATION) {
210
- return predictFc43_14Response(buffer, start, end);
211
- }
212
- return PREDICT_UNKNOWN;
213
- }
214
- /**
215
- * Walk the variable-length FC 0x2B / MEI 0x0E (Read Device Identification)
216
- * response structure per Modbus V1.1b3 §6.21.
217
- *
218
- * Layout (after unit and fc):
219
- * mei(1) rdic(1) conformity(1) more(1) nextObjId(1) numObjs(1)
220
- * [objId(1) objLen(1) objData(objLen)] × numObjs
221
- * CRC(2)
222
- */
223
- function predictFc43_14Response(buffer, start, end) {
224
- if (end - start < 8) {
225
- return PREDICT_NEED_MORE;
226
- }
227
- if (buffer[start + 2] !== MEI_READ_DEVICE_ID) {
228
- return PREDICT_UNKNOWN;
229
249
  }
230
- const numObjs = buffer[start + 7];
231
- let cursor = start + 8;
232
- for (let i = 0; i < numObjs; i++) {
233
- if (end < cursor + 2) {
234
- return PREDICT_NEED_MORE;
250
+ else {
251
+ const val = REQ_TABLE[fc];
252
+ if (val > 0) {
253
+ return val;
254
+ }
255
+ if (val < 0) {
256
+ const decode = -val;
257
+ const offset = decode >> 8;
258
+ if (len <= offset) {
259
+ return PREDICT_NEED_MORE;
260
+ }
261
+ return (decode & 0xff) + buffer[start + offset];
235
262
  }
236
- const objLen = buffer[cursor + 1];
237
- cursor += 2 + objLen;
238
263
  }
239
- return cursor - start + 2;
264
+ return PREDICT_UNKNOWN;
240
265
  }
241
266
 
242
267
  /**
@@ -245,10 +270,12 @@ function predictFc43_14Response(buffer, start, end) {
245
270
  function promisifyCb(fn) {
246
271
  return new Promise((resolve, reject) => {
247
272
  fn((err) => {
248
- if (err)
273
+ if (err) {
249
274
  reject(err);
250
- else
275
+ }
276
+ else {
251
277
  resolve();
278
+ }
252
279
  });
253
280
  });
254
281
  }
@@ -301,102 +328,172 @@ function resolveRtuTiming(opts = {}, baudRate) {
301
328
  return { intervalBetweenFrames, interCharTimeout };
302
329
  }
303
330
 
304
- /** @internal
305
- * Zero-allocation binary min-heap for coalescing per-request timeouts.
331
+ /**
332
+ * Hybrid timer manager: uses native `setTimeout` for low concurrency
333
+ * and switches to a binary min-heap when concurrency exceeds the threshold.
334
+ *
335
+ * Benchmarks (add + clear throughput, Node 24, x64):
336
+ * 1 concurrent: setTimeout ~1.7× faster than heap
337
+ * 2 concurrent: setTimeout ~1.6× faster than heap
338
+ * 5 concurrent: setTimeout ~1.5-1.9× faster than heap
339
+ * 10 concurrent: roughly equal
340
+ * 20 concurrent: heap ~1.3× faster than setTimeout[]
341
+ * 50 concurrent: heap ~1.4-1.7× faster than setTimeout[]
306
342
  *
307
- * Uses two parallel numeric arrays (no object allocation per entry).
308
- * Lazy deletion: callers never remove from the heap; expired entries
309
- * are silently dropped when they surface at the top.
343
+ * The crossover point is around 10 concurrent timers, so the default
344
+ * `concurrentThreshold = 2` keeps the common 1-2 request case on the
345
+ * fast direct path while delegating to the heap for larger batches.
310
346
  */
311
347
  class TimerHeap {
312
348
  _deadlines = [];
313
349
  _ids = [];
350
+ _seqs = [];
351
+ _counter = 0;
314
352
  _timer = null;
315
353
  _onFire;
316
- /** Pre-bound tick handler to avoid creating a new arrow function on every setTimeout. */
317
354
  _boundTick;
318
- constructor(onFire) {
355
+ _threshold;
356
+ _mode = 'direct';
357
+ _directTimers = new Map();
358
+ /**
359
+ * @param onFire Callback invoked with the timer id when it expires.
360
+ * @param concurrentThreshold Maximum number of timers kept as individual
361
+ * native `setTimeout` handles. Once exceeded, all timers migrate to
362
+ * the internal heap and share a single native timer. Default is 2.
363
+ */
364
+ constructor(onFire, concurrentThreshold = 2) {
319
365
  this._onFire = onFire;
320
366
  this._boundTick = this._onTick.bind(this);
367
+ this._threshold = concurrentThreshold;
321
368
  }
322
- /** Number of pending timers in the heap. */
323
369
  get size() {
324
- return this._deadlines.length;
370
+ return this._mode === 'direct' ? this._directTimers.size : this._deadlines.length;
325
371
  }
326
- /** Schedule an entry. If it becomes the new top, the global timer is re-scheduled (pre-emption). */
327
372
  add(id, ms) {
373
+ if (this._mode === 'direct' && this._directTimers.size + 1 <= this._threshold) {
374
+ const deadline = performance.now() + ms;
375
+ const handle = setTimeout(() => {
376
+ if (this._mode !== 'direct') {
377
+ return;
378
+ }
379
+ this._directTimers.delete(id);
380
+ this._onFire(id);
381
+ }, ms);
382
+ this._directTimers.set(id, { handle, deadline });
383
+ return;
384
+ }
385
+ if (this._mode === 'direct') {
386
+ this._mode = 'heap';
387
+ for (const [existingId, { handle, deadline }] of this._directTimers) {
388
+ clearTimeout(handle);
389
+ const remaining = Math.max(0, Math.ceil(deadline - performance.now()));
390
+ if (remaining === 0) {
391
+ this._onFire(existingId);
392
+ }
393
+ else {
394
+ this._heapAdd(existingId, remaining);
395
+ }
396
+ }
397
+ this._directTimers.clear();
398
+ }
399
+ this._heapAdd(id, ms);
400
+ }
401
+ clear() {
402
+ for (const { handle } of this._directTimers.values()) {
403
+ clearTimeout(handle);
404
+ }
405
+ this._directTimers.clear();
406
+ this._mode = 'direct';
407
+ if (this._timer) {
408
+ clearTimeout(this._timer);
409
+ this._timer = null;
410
+ }
411
+ this._deadlines.length = 0;
412
+ this._ids.length = 0;
413
+ this._seqs.length = 0;
414
+ this._counter = 0;
415
+ }
416
+ _heapAdd(id, ms) {
328
417
  const deadline = performance.now() + ms;
418
+ const seq = this._counter++;
329
419
  let i = this._deadlines.length;
330
420
  this._deadlines.push(deadline);
331
421
  this._ids.push(id);
332
- // sift up
422
+ this._seqs.push(seq);
333
423
  while (i > 0) {
334
424
  const p = (i - 1) >> 1;
335
- if (this._deadlines[p] <= deadline)
425
+ const parentComesFirst = this._deadlines[p] < deadline || (this._deadlines[p] === deadline && this._seqs[p] < seq);
426
+ if (parentComesFirst) {
336
427
  break;
428
+ }
337
429
  this._deadlines[i] = this._deadlines[p];
338
430
  this._ids[i] = this._ids[p];
431
+ this._seqs[i] = this._seqs[p];
339
432
  i = p;
340
433
  }
341
434
  this._deadlines[i] = deadline;
342
435
  this._ids[i] = id;
343
- // Only reschedule when the new entry became the heap top.
344
- if (i === 0)
436
+ this._seqs[i] = seq;
437
+ if (i === 0) {
345
438
  this._refresh();
346
- }
347
- /** Dispose without firing callbacks. */
348
- clear() {
349
- if (this._timer) {
350
- clearTimeout(this._timer);
351
- this._timer = null;
352
439
  }
353
- this._deadlines.length = 0;
354
- this._ids.length = 0;
355
440
  }
356
441
  _refresh() {
357
442
  if (this._timer) {
358
443
  clearTimeout(this._timer);
359
444
  this._timer = null;
360
445
  }
361
- if (this._deadlines.length === 0)
446
+ if (this._deadlines.length === 0) {
362
447
  return;
448
+ }
363
449
  const delay = Math.max(0, Math.ceil(this._deadlines[0] - performance.now()));
364
- this._timer = setTimeout(this._boundTick, delay);
450
+ const safeDelay = Math.min(delay, 2147483647);
451
+ this._timer = setTimeout(this._boundTick, safeDelay);
365
452
  }
366
453
  _onTick() {
367
454
  this._timer = null;
368
455
  const now = performance.now();
369
- while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
370
- const id = this._pop();
371
- this._onFire(id);
456
+ try {
457
+ while (this._deadlines.length > 0 && this._deadlines[0] <= now) {
458
+ const id = this._pop();
459
+ this._onFire(id);
460
+ }
461
+ }
462
+ finally {
463
+ this._refresh();
372
464
  }
373
- this._refresh();
374
465
  }
375
- /** Pop and return the id with the earliest deadline. Assumes heap is non-empty. */
376
466
  _pop() {
377
467
  const topId = this._ids[0];
378
468
  const lastId = this._ids.pop();
379
469
  const lastDeadline = this._deadlines.pop();
470
+ const lastSeq = this._seqs.pop();
380
471
  const n = this._deadlines.length;
381
472
  if (n > 0) {
382
473
  let i = 0;
383
- // sift down
384
- while (true) {
385
- let min = i;
386
- const l = i * 2 + 1;
387
- const r = l + 1;
388
- if (l < n && this._deadlines[l] < this._deadlines[min])
389
- min = l;
390
- if (r < n && this._deadlines[r] < this._deadlines[min])
391
- min = r;
392
- if (min === i)
474
+ const half = n >> 1;
475
+ while (i < half) {
476
+ let minChild = (i << 1) + 1;
477
+ const rightChild = minChild + 1;
478
+ if (rightChild < n) {
479
+ const rightComesFirst = this._deadlines[rightChild] < this._deadlines[minChild] ||
480
+ (this._deadlines[rightChild] === this._deadlines[minChild] && this._seqs[rightChild] < this._seqs[minChild]);
481
+ if (rightComesFirst) {
482
+ minChild = rightChild;
483
+ }
484
+ }
485
+ const lastComesFirst = lastDeadline < this._deadlines[minChild] || (lastDeadline === this._deadlines[minChild] && lastSeq < this._seqs[minChild]);
486
+ if (lastComesFirst) {
393
487
  break;
394
- this._deadlines[i] = this._deadlines[min];
395
- this._ids[i] = this._ids[min];
396
- i = min;
488
+ }
489
+ this._deadlines[i] = this._deadlines[minChild];
490
+ this._ids[i] = this._ids[minChild];
491
+ this._seqs[i] = this._seqs[minChild];
492
+ i = minChild;
397
493
  }
398
494
  this._deadlines[i] = lastDeadline;
399
495
  this._ids[i] = lastId;
496
+ this._seqs[i] = lastSeq;
400
497
  }
401
498
  return topId;
402
499
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "njs-modbus",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "A pure JavaScript implementation of Modbus for Node.js.",
5
5
  "keywords": [
6
6
  "modbus",
@@ -46,8 +46,15 @@
46
46
  "build": "node -e \"fs.rmSync('dist', {recursive: true, force: true})\" && rollup -c && node -e \"fs.rmSync('dist/src', {recursive: true, force: true})\"",
47
47
  "test": "tsx --test test/**/*.test.ts",
48
48
  "benchmark": "tsx benchmark/encode-decode.ts && echo && tsx benchmark/tcp-throughput.ts && echo && tsx benchmark/concurrent.ts",
49
+ "benchmark:encode-decode": "tsx benchmark/encode-decode.ts",
50
+ "benchmark:tcp": "tsx benchmark/tcp-throughput.ts",
51
+ "benchmark:concurrent": "tsx benchmark/concurrent.ts",
52
+ "benchmark:rtu": "tsx benchmark/rtu-throughput.ts",
49
53
  "benchmark:all-fcs": "tsx benchmark/all-fcs.ts",
50
54
  "benchmark:report": "tsx benchmark/generate-report.ts",
55
+ "benchmark:report:quick": "tsx benchmark/generate-report.ts -- --duration 10s --runs 2",
56
+ "benchmark:report:full": "tsx benchmark/generate-report.ts -- --duration 120s --runs 3 --max-payload",
57
+ "benchmark:profile": "tsx benchmark/all-fcs.ts -- --duration 30s --runs 1 --only fc03 --libs njs-modbus --profile",
51
58
  "util:sort-package-json": "sort-package-json"
52
59
  },
53
60
  "devDependencies": {