@@ -77,6 +77,8 @@ class ShareImpl { |
77 | 77 | #cancelled = false; |
78 | 78 | #pulling = false; |
79 | 79 | #pullWaiters = []; |
| 80 | + #cachedMinCursor = 0; |
| 81 | + #cachedMinCursorConsumers = 0; |
80 | 82 |
|
81 | 83 | constructor(source, options) { |
82 | 84 | this.#source = source; |
@@ -114,6 +116,14 @@ class ShareImpl { |
114 | 116 | }; |
115 | 117 |
|
116 | 118 | this.#consumers.add(state); |
| 119 | + if (this.#consumers.size === 1) { |
| 120 | + this.#cachedMinCursor = state.cursor; |
| 121 | + this.#cachedMinCursorConsumers = 1; |
| 122 | + } else if (state.cursor === this.#cachedMinCursor) { |
| 123 | + this.#cachedMinCursorConsumers++; |
| 124 | + } else { |
| 125 | + this.#recomputeMinCursor(); |
| 126 | + } |
117 | 127 | const self = this; |
118 | 128 |
|
119 | 129 | return { |
@@ -139,22 +149,26 @@ class ShareImpl { |
139 | 149 |
|
140 | 150 | if (self.#cancelled) { |
141 | 151 | state.detached = true; |
142 | | - self.#consumers.delete(state); |
| 152 | + self.#deleteConsumer(state); |
143 | 153 | return { __proto__: null, done: true, value: undefined }; |
144 | 154 | } |
145 | 155 |
|
146 | 156 | // Check if data is available in buffer |
147 | 157 | const bufferIndex = state.cursor - self.#bufferStart; |
148 | 158 | if (bufferIndex < self.#buffer.length) { |
149 | 159 | const chunk = self.#buffer.get(bufferIndex); |
| 160 | + const cursor = state.cursor; |
150 | 161 | state.cursor++; |
151 | | - self.#tryTrimBuffer(); |
| 162 | + if (cursor === self.#cachedMinCursor && |
| 163 | + --self.#cachedMinCursorConsumers === 0) { |
| 164 | + self.#tryTrimBuffer(); |
| 165 | + } |
152 | 166 | return { __proto__: null, done: false, value: chunk }; |
153 | 167 | } |
154 | 168 |
|
155 | 169 | if (self.#sourceExhausted) { |
156 | 170 | state.detached = true; |
157 | | - self.#consumers.delete(state); |
| 171 | + self.#deleteConsumer(state); |
158 | 172 | if (self.#sourceError) throw self.#sourceError; |
159 | 173 | return { __proto__: null, done: true, value: undefined }; |
160 | 174 | } |
@@ -163,7 +177,7 @@ class ShareImpl { |
163 | 177 | const canPull = await self.#waitForBufferSpace(); |
164 | 178 | if (!canPull) { |
165 | 179 | state.detached = true; |
166 | | - self.#consumers.delete(state); |
| 180 | + self.#deleteConsumer(state); |
167 | 181 | if (self.#sourceError) throw self.#sourceError; |
168 | 182 | return { __proto__: null, done: true, value: undefined }; |
169 | 183 | } |
@@ -176,17 +190,19 @@ class ShareImpl { |
176 | 190 | state.detached = true; |
177 | 191 | state.resolve = null; |
178 | 192 | state.reject = null; |
179 | | - self.#consumers.delete(state); |
180 | | - self.#tryTrimBuffer(); |
| 193 | + if (self.#deleteConsumer(state)) { |
| 194 | + self.#tryTrimBuffer(); |
| 195 | + } |
181 | 196 | return { __proto__: null, done: true, value: undefined }; |
182 | 197 | }, |
183 | 198 |
|
184 | 199 | async throw() { |
185 | 200 | state.detached = true; |
186 | 201 | state.resolve = null; |
187 | 202 | state.reject = null; |
188 | | - self.#consumers.delete(state); |
189 | | - self.#tryTrimBuffer(); |
| 203 | + if (self.#deleteConsumer(state)) { |
| 204 | + self.#tryTrimBuffer(); |
| 205 | + } |
190 | 206 | return { __proto__: null, done: true, value: undefined }; |
191 | 207 | }, |
192 | 208 | }; |
@@ -254,9 +270,11 @@ class ShareImpl { |
254 | 270 | this.#bufferStart++; |
255 | 271 | for (const consumer of this.#consumers) { |
256 | 272 | if (consumer.cursor < this.#bufferStart) { |
| 273 | + this.#deleteConsumerFromMin(consumer); |
257 | 274 | consumer.cursor = this.#bufferStart; |
258 | 275 | } |
259 | 276 | } |
| 277 | + this.#recomputeMinCursor(); |
260 | 278 | return true; |
261 | 279 | case 'drop-newest': |
262 | 280 | return true; |
@@ -324,18 +342,41 @@ class ShareImpl { |
324 | 342 | } |
325 | 343 |
|
326 | 344 | #tryTrimBuffer() { |
327 | | - const minCursor = getMinCursor( |
328 | | - this.#consumers, this.#bufferStart + this.#buffer.length); |
329 | | - const trimCount = minCursor - this.#bufferStart; |
| 345 | + if (this.#cachedMinCursorConsumers === 0) { |
| 346 | + this.#recomputeMinCursor(); |
| 347 | + } |
| 348 | + const trimCount = this.#cachedMinCursor - this.#bufferStart; |
330 | 349 | if (trimCount > 0) { |
331 | 350 | this.#buffer.trimFront(trimCount); |
332 | | - this.#bufferStart = minCursor; |
| 351 | + this.#bufferStart = this.#cachedMinCursor; |
333 | 352 | for (let i = 0; i < this.#pullWaiters.length; i++) { |
334 | 353 | this.#pullWaiters[i](); |
335 | 354 | } |
336 | 355 | this.#pullWaiters = []; |
337 | 356 | } |
338 | 357 | } |
| 358 | + |
| 359 | + #recomputeMinCursor() { |
| 360 | + const { minCursor, minCursorConsumers } = getMinCursor( |
| 361 | + this.#consumers, this.#bufferStart + this.#buffer.length); |
| 362 | + this.#cachedMinCursor = minCursor; |
| 363 | + this.#cachedMinCursorConsumers = minCursorConsumers; |
| 364 | + } |
| 365 | + |
| 366 | + #deleteConsumerFromMin(consumer) { |
| 367 | + if (consumer.cursor === this.#cachedMinCursor) { |
| 368 | + this.#cachedMinCursorConsumers--; |
| 369 | + return this.#cachedMinCursorConsumers === 0; |
| 370 | + } |
| 371 | + return false; |
| 372 | + } |
| 373 | + |
| 374 | + #deleteConsumer(consumer) { |
| 375 | + if (this.#consumers.delete(consumer)) { |
| 376 | + return this.#deleteConsumerFromMin(consumer); |
| 377 | + } |
| 378 | + return false; |
| 379 | + } |
339 | 380 | } |
340 | 381 |
|
341 | 382 | // ============================================================================= |
@@ -352,6 +393,8 @@ class SyncShareImpl { |
352 | 393 | #sourceExhausted = false; |
353 | 394 | #sourceError = null; |
354 | 395 | #cancelled = false; |
| 396 | + #cachedMinCursor = 0; |
| 397 | + #cachedMinCursorConsumers = 0; |
355 | 398 |
|
356 | 399 | constructor(source, options) { |
357 | 400 | this.#source = source; |
@@ -383,6 +426,14 @@ class SyncShareImpl { |
383 | 426 | }; |
384 | 427 |
|
385 | 428 | this.#consumers.add(state); |
| 429 | + if (this.#consumers.size === 1) { |
| 430 | + this.#cachedMinCursor = state.cursor; |
| 431 | + this.#cachedMinCursorConsumers = 1; |
| 432 | + } else if (state.cursor === this.#cachedMinCursor) { |
| 433 | + this.#cachedMinCursorConsumers++; |
| 434 | + } else { |
| 435 | + this.#recomputeMinCursor(); |
| 436 | + } |
386 | 437 | const self = this; |
387 | 438 |
|
388 | 439 | return { |
@@ -396,26 +447,30 @@ class SyncShareImpl { |
396 | 447 | } |
397 | 448 | if (self.#sourceError) { |
398 | 449 | state.detached = true; |
399 | | - self.#consumers.delete(state); |
| 450 | + self.#deleteConsumer(state); |
400 | 451 | throw self.#sourceError; |
401 | 452 | } |
402 | 453 | if (self.#cancelled) { |
403 | 454 | state.detached = true; |
404 | | - self.#consumers.delete(state); |
| 455 | + self.#deleteConsumer(state); |
405 | 456 | return { __proto__: null, done: true, value: undefined }; |
406 | 457 | } |
407 | 458 |
|
408 | 459 | const bufferIndex = state.cursor - self.#bufferStart; |
409 | 460 | if (bufferIndex < self.#buffer.length) { |
410 | 461 | const chunk = self.#buffer.get(bufferIndex); |
| 462 | + const cursor = state.cursor; |
411 | 463 | state.cursor++; |
412 | | - self.#tryTrimBuffer(); |
| 464 | + if (cursor === self.#cachedMinCursor && |
| 465 | + --self.#cachedMinCursorConsumers === 0) { |
| 466 | + self.#tryTrimBuffer(); |
| 467 | + } |
413 | 468 | return { __proto__: null, done: false, value: chunk }; |
414 | 469 | } |
415 | 470 |
|
416 | 471 | if (self.#sourceExhausted) { |
417 | 472 | state.detached = true; |
418 | | - self.#consumers.delete(state); |
| 473 | + self.#deleteConsumer(state); |
419 | 474 | return { __proto__: null, done: true, value: undefined }; |
420 | 475 | } |
421 | 476 |
|
@@ -436,13 +491,15 @@ class SyncShareImpl { |
436 | 491 | self.#bufferStart++; |
437 | 492 | for (const consumer of self.#consumers) { |
438 | 493 | if (consumer.cursor < self.#bufferStart) { |
| 494 | + self.#deleteConsumerFromMin(consumer); |
439 | 495 | consumer.cursor = self.#bufferStart; |
440 | 496 | } |
441 | 497 | } |
| 498 | + self.#recomputeMinCursor(); |
442 | 499 | break; |
443 | 500 | case 'drop-newest': |
444 | 501 | state.detached = true; |
445 | | - self.#consumers.delete(state); |
| 502 | + self.#deleteConsumer(state); |
446 | 503 | return { __proto__: null, done: true, value: undefined }; |
447 | 504 | } |
448 | 505 | } |
@@ -451,21 +508,25 @@ class SyncShareImpl { |
451 | 508 |
|
452 | 509 | if (self.#sourceError) { |
453 | 510 | state.detached = true; |
454 | | - self.#consumers.delete(state); |
| 511 | + self.#deleteConsumer(state); |
455 | 512 | throw self.#sourceError; |
456 | 513 | } |
457 | 514 |
|
458 | 515 | const newBufferIndex = state.cursor - self.#bufferStart; |
459 | 516 | if (newBufferIndex < self.#buffer.length) { |
460 | 517 | const chunk = self.#buffer.get(newBufferIndex); |
| 518 | + const cursor = state.cursor; |
461 | 519 | state.cursor++; |
462 | | - self.#tryTrimBuffer(); |
| 520 | + if (cursor === self.#cachedMinCursor && |
| 521 | + --self.#cachedMinCursorConsumers === 0) { |
| 522 | + self.#tryTrimBuffer(); |
| 523 | + } |
463 | 524 | return { __proto__: null, done: false, value: chunk }; |
464 | 525 | } |
465 | 526 |
|
466 | 527 | if (self.#sourceExhausted) { |
467 | 528 | state.detached = true; |
468 | | - self.#consumers.delete(state); |
| 529 | + self.#deleteConsumer(state); |
469 | 530 | return { __proto__: null, done: true, value: undefined }; |
470 | 531 | } |
471 | 532 |
|
@@ -474,15 +535,17 @@ class SyncShareImpl { |
474 | 535 |
|
475 | 536 | return() { |
476 | 537 | state.detached = true; |
477 | | - self.#consumers.delete(state); |
478 | | - self.#tryTrimBuffer(); |
| 538 | + if (self.#deleteConsumer(state)) { |
| 539 | + self.#tryTrimBuffer(); |
| 540 | + } |
479 | 541 | return { __proto__: null, done: true, value: undefined }; |
480 | 542 | }, |
481 | 543 |
|
482 | 544 | throw() { |
483 | 545 | state.detached = true; |
484 | | - self.#consumers.delete(state); |
485 | | - self.#tryTrimBuffer(); |
| 546 | + if (self.#deleteConsumer(state)) { |
| 547 | + self.#tryTrimBuffer(); |
| 548 | + } |
486 | 549 | return { __proto__: null, done: true, value: undefined }; |
487 | 550 | }, |
488 | 551 | }; |
@@ -532,13 +595,36 @@ class SyncShareImpl { |
532 | 595 | } |
533 | 596 |
|
534 | 597 | #tryTrimBuffer() { |
535 | | - const minCursor = getMinCursor( |
536 | | - this.#consumers, this.#bufferStart + this.#buffer.length); |
537 | | - const trimCount = minCursor - this.#bufferStart; |
| 598 | + if (this.#cachedMinCursorConsumers === 0) { |
| 599 | + this.#recomputeMinCursor(); |
| 600 | + } |
| 601 | + const trimCount = this.#cachedMinCursor - this.#bufferStart; |
538 | 602 | if (trimCount > 0) { |
539 | 603 | this.#buffer.trimFront(trimCount); |
540 | | - this.#bufferStart = minCursor; |
| 604 | + this.#bufferStart = this.#cachedMinCursor; |
| 605 | + } |
| 606 | + } |
| 607 | + |
| 608 | + #recomputeMinCursor() { |
| 609 | + const { minCursor, minCursorConsumers } = getMinCursor( |
| 610 | + this.#consumers, this.#bufferStart + this.#buffer.length); |
| 611 | + this.#cachedMinCursor = minCursor; |
| 612 | + this.#cachedMinCursorConsumers = minCursorConsumers; |
| 613 | + } |
| 614 | + |
| 615 | + #deleteConsumerFromMin(consumer) { |
| 616 | + if (consumer.cursor === this.#cachedMinCursor) { |
| 617 | + this.#cachedMinCursorConsumers--; |
| 618 | + return this.#cachedMinCursorConsumers === 0; |
| 619 | + } |
| 620 | + return false; |
| 621 | + } |
| 622 | + |
| 623 | + #deleteConsumer(consumer) { |
| 624 | + if (this.#consumers.delete(consumer)) { |
| 625 | + return this.#deleteConsumerFromMin(consumer); |
541 | 626 | } |
| 627 | + return false; |
542 | 628 | } |
543 | 629 | } |
544 | 630 |
|
|