@fails-components/webtransport 1.1.4 → 1.2.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.
Files changed (53) hide show
  1. package/dist/lib/dom.d.ts +27 -10
  2. package/dist/lib/dom.d.ts.map +1 -1
  3. package/dist/lib/http2/browser/browser.d.ts +5 -2
  4. package/dist/lib/http2/browser/browser.d.ts.map +1 -1
  5. package/dist/lib/http2/browser/browserparser.d.ts.map +1 -1
  6. package/dist/lib/http2/node/capsuleparser.d.ts.map +1 -1
  7. package/dist/lib/http2/node/client.d.ts +3 -2
  8. package/dist/lib/http2/node/client.d.ts.map +1 -1
  9. package/dist/lib/http2/node/server.d.ts +10 -5
  10. package/dist/lib/http2/node/server.d.ts.map +1 -1
  11. package/dist/lib/http2/node/websocketparser.d.ts.map +1 -1
  12. package/dist/lib/http2/parserbase.d.ts +33 -1
  13. package/dist/lib/http2/parserbase.d.ts.map +1 -1
  14. package/dist/lib/http2/priorityscheduler.d.ts +167 -0
  15. package/dist/lib/http2/priorityscheduler.d.ts.map +1 -0
  16. package/dist/lib/http2/session.d.ts +25 -16
  17. package/dist/lib/http2/session.d.ts.map +1 -1
  18. package/dist/lib/http2/stream.d.ts +10 -0
  19. package/dist/lib/http2/stream.d.ts.map +1 -1
  20. package/dist/lib/http2/websocketcommon.d.ts.map +1 -1
  21. package/dist/lib/server.d.ts.map +1 -1
  22. package/dist/lib/session.d.ts +31 -17
  23. package/dist/lib/session.d.ts.map +1 -1
  24. package/dist/lib/stream.d.ts +13 -0
  25. package/dist/lib/stream.d.ts.map +1 -1
  26. package/dist/lib/types.d.ts +18 -15
  27. package/dist/lib/types.d.ts.map +1 -1
  28. package/dist/lib/webtransport.browser.d.ts +3 -2
  29. package/dist/lib/webtransport.browser.d.ts.map +1 -1
  30. package/dist/lib/webtransportbase.d.ts +1 -0
  31. package/dist/lib/webtransportbase.d.ts.map +1 -1
  32. package/eslint.config.js +20 -22
  33. package/lib/dom.ts +31 -13
  34. package/lib/http2/browser/browser.js +28 -13
  35. package/lib/http2/browser/browserparser.js +4 -1
  36. package/lib/http2/node/capsuleparser.js +4 -1
  37. package/lib/http2/node/client.js +38 -27
  38. package/lib/http2/node/server.js +56 -19
  39. package/lib/http2/node/websocketparser.js +5 -2
  40. package/lib/http2/parserbase.js +76 -7
  41. package/lib/http2/priorityscheduler.js +654 -0
  42. package/lib/http2/session.js +69 -20
  43. package/lib/http2/stream.js +60 -10
  44. package/lib/http2/websocketcommon.js +1 -1
  45. package/lib/server.js +0 -1
  46. package/lib/session.js +128 -64
  47. package/lib/stream.js +50 -8
  48. package/lib/types.ts +15 -8
  49. package/lib/webtransport.browser.js +77 -18
  50. package/lib/webtransportbase.js +7 -0
  51. package/old_test/testsuite.js +6 -2
  52. package/package.json +6 -6
  53. package/readme.md +3 -1
@@ -0,0 +1,654 @@
1
+ // Ported from libquiche, PriorityScheduler and BTreeScheduler so their license applies to the original in C++ and this javascript translation
2
+ // Copyright (c) 2018 The Chromium Authors. All rights reserved.
3
+ // Use of this source code is governed by a BSD-style license that can be
4
+ // found in the LICENSE file.
5
+
6
+ // A record for a registered stream.
7
+
8
+ /**
9
+ * @typedef {object} Priority
10
+ * @property {SendGroupId} sendGroupId
11
+ * @property {bigint} sendOrder
12
+ */
13
+
14
+ /**
15
+ * @typedef {bigint} SendGroupId
16
+ */
17
+
18
+ /**
19
+ * @typedef {bigint} SendOrder
20
+ */
21
+
22
+ /**
23
+ *
24
+ * @param {StreamId} sendGroupId
25
+ * @returns {string}
26
+ */
27
+ function sendGroupIdToKey(sendGroupId) {
28
+ return sendGroupId.toString()
29
+ }
30
+ /**
31
+ * @typedef {bigint} StreamId
32
+ */
33
+
34
+ /**
35
+ *
36
+ * @param {StreamId} streamId
37
+ * @returns {string}
38
+ */
39
+ function streamIdToKey(streamId) {
40
+ return streamId.toString()
41
+ }
42
+
43
+ class StreamEntry {
44
+ /**
45
+ * @param {SendOrder} priority
46
+ */
47
+ constructor(priority) {
48
+ this.priority = priority
49
+ /**
50
+ * @type {number|undefined}
51
+ */
52
+ this.currentSequenceNumber = undefined
53
+ }
54
+
55
+ scheduled() {
56
+ return typeof this.currentSequenceNumber !== 'undefined'
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @typedef {object} FullStreamEntry
62
+ * @property {SendGroupId} iD
63
+ * @property {StreamEntry} streamEntry
64
+ */
65
+
66
+ /**
67
+ * @typedef {object} ScheduleKey
68
+ * @property {SendOrder} priority
69
+ * @property {number} sequenceNumber
70
+ */
71
+
72
+ /*struct StreamEntry {
73
+ // The current priority of the stream.
74
+ ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS Priority priority;
75
+ // If present, the sequence number with which the stream is currently
76
+ // scheduled. If absent, indicates that the stream is not scheduled.
77
+ std::optional<int> currentSequenceNumber = std::nullopt;
78
+
79
+ bool scheduled() const { return currentSequenceNumber.has_value(); }
80
+ };
81
+ // The full entry for the stream (includes the ID that's used as a hashmap
82
+ // key).
83
+ using FullStreamEntry = std::pair<const Id, StreamEntry>;
84
+
85
+ // A key that is used to order entities within the schedule.
86
+ struct ScheduleKey {
87
+ // The main order key: the priority of the stream.
88
+ ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS Priority priority;
89
+ // The secondary order key: the sequence number.
90
+ int sequenceNumber;
91
+
92
+ // Orders schedule keys in order of decreasing priority.
93
+ bool operator<(const ScheduleKey& other) const {
94
+ return std::make_tuple(priority, sequenceNumber) >
95
+ std::make_tuple(other.priority, other.sequenceNumber);
96
+ }
97
+
98
+ // In order to find all entities with priority `p`, one can iterate between
99
+ // `lower_bound(MinForPriority(p))` and `upper_bound(MaxForPriority(p))`.
100
+ static ScheduleKey MinForPriority(Priority priority) {
101
+ return ScheduleKey{priority, std::numeric_limits<int>::max()};
102
+ }
103
+ static ScheduleKey MaxForPriority(Priority priority) {
104
+ return ScheduleKey{priority, std::numeric_limits<int>::min()};
105
+ }
106
+ };
107
+ */
108
+ // BTreeScheduler is a data structure that allows streams (and potentially other
109
+ // entities) to be scheduled according to the arbitrary priorities. The API for
110
+ // using the scheduler can be used as follows:
111
+ // - A stream has to be registered with a priority before being scheduled.
112
+ // - A stream can be unregistered, or can be re-prioritized.
113
+ // - A stream can be scheduled; that adds it into the queue.
114
+ // - PopFront() will return the stream with highest priority.
115
+ // - ShouldYield() will return if there is a stream with higher priority than
116
+ // the specified one.
117
+ //
118
+ // The prioritization works as following:
119
+ // - If two streams have different priorities, the higher priority stream goes
120
+ // first.
121
+ // - If two streams have the same priority, the one that got scheduled earlier
122
+ // goes first. Internally, this is implemented by assigning a monotonically
123
+ // decreasing sequence number to every newly scheduled stream.
124
+ //
125
+ // The Id type has to define operator==, be hashable via absl::Hash, and
126
+ // printable via operator<<; the Priority type has to define operator<.
127
+
128
+ /**
129
+ *
130
+ * @param {ScheduleKey} a
131
+ * @param {ScheduleKey} b
132
+ * @returns
133
+ */
134
+ function compareStreamEntries(a, b) {
135
+ // for sort
136
+ if (a.priority > b.priority) {
137
+ return -1
138
+ } else if (a.priority === b.priority) {
139
+ if (a.sequenceNumber > b.sequenceNumber) {
140
+ return -1
141
+ } else if (a.sequenceNumber === b.sequenceNumber) {
142
+ return 0
143
+ } else {
144
+ return 1
145
+ }
146
+ } else {
147
+ return 1
148
+ }
149
+ }
150
+ /**
151
+ *
152
+ * @param {{scheduleKey: ScheduleKey}} a
153
+ * @param {{scheduleKey: ScheduleKey}} b
154
+ * @returns
155
+ */
156
+
157
+ function compareFullStreamEntries(a, b) {
158
+ return compareStreamEntries(a.scheduleKey, b.scheduleKey)
159
+ }
160
+
161
+ class BTreeScheduler {
162
+ constructor() {
163
+ // The map of currently registered streams.
164
+ /**
165
+ * @type {Object<string, StreamEntry>}
166
+ */
167
+ this.streams_ = {} // Id, StreamEntry
168
+ // The stream schedule, ordered starting from the highest priority stream.
169
+ /**
170
+ * @type {{scheduleKey: ScheduleKey, fullStreamEntry: FullStreamEntry}[]}
171
+ */
172
+ this.schedule_ = [] // ScheduleKey, FullStreamEntry
173
+
174
+ // The counter that is used to ensure that streams with the same priority are
175
+ // handled in the FIFO order. Decreases with every write.
176
+ this.currentWriteSequenceNumber_ = 0
177
+ }
178
+
179
+ // Returns true if there are any streams registered.
180
+ HasRegistered() {
181
+ return Object.keys(this.streams_).length !== 0
182
+ }
183
+ // Returns true if there are any streams scheduled.
184
+ HasScheduled() {
185
+ return Object.keys(this.schedule_).length !== 0
186
+ }
187
+ // Returns the number of currently scheduled streams.
188
+ NumScheduled() {
189
+ return Object.keys(this.schedule_).length
190
+ }
191
+ // Returns the total number of currently registered streams.
192
+ NumRegistered() {
193
+ return Object.keys(this.streams_).length
194
+ }
195
+
196
+ // Counts the number of scheduled entries in the range [min, max]. If either
197
+ // min or max is omitted, negative or positive infinity is assumed.
198
+
199
+ /**
200
+ * @param {number} min
201
+ * @param {number} max
202
+ */
203
+ NumScheduledInPriorityRange(min, max) {
204
+ /*if (typeof min !== 'undefined' && typeof max !== 'undefined') {
205
+ //QUICHE_DCHECK(*min <= *max);
206
+ }*/
207
+ // This is reversed, since the schedule is ordered in the descending priority
208
+ // order.
209
+ const begin =
210
+ typeof max !== 'undefined'
211
+ ? this.schedule_.findIndex((el) => el.scheduleKey.priority <= max)
212
+ : 0
213
+ const end =
214
+ typeof min !== 'undefined'
215
+ ? this.schedule_.findIndex((el) => el.scheduleKey.priority >= min)
216
+ : this.schedule_.length
217
+ return end - begin
218
+ }
219
+
220
+ // Returns true if there is a stream that would go before `id` in the
221
+ // schedule.
222
+ /**
223
+ * @param {StreamId} streamId
224
+ */
225
+ ShouldYield(streamId) {
226
+ const stream = this.streams_[streamIdToKey(streamId)]
227
+ if (!stream) {
228
+ throw new Error('ID not registered')
229
+ }
230
+
231
+ if (this.schedule_.length === 0) {
232
+ return false
233
+ }
234
+ const next = this.schedule_[0]
235
+ if (BTreeScheduler.StreamId(next) == streamId) {
236
+ return false
237
+ }
238
+ return next.scheduleKey.priority >= stream.priority
239
+ }
240
+
241
+ // Returns the priority for `id`, or nullopt if stream is not registered.
242
+ /**
243
+ * @param {StreamId} id
244
+ */
245
+ GetPriorityFor(id) {
246
+ const it = this.streams_[streamIdToKey(id)]
247
+ if (!it) {
248
+ return undefined
249
+ }
250
+ return it.priority
251
+ }
252
+
253
+ // Pops the highest priority stream. Will fail if the schedule is empty.
254
+ PopFront() {
255
+ if (this.schedule_.length === 0) {
256
+ return undefined
257
+ }
258
+ const scheduleIt = this.schedule_[0]
259
+ //QUICHE_DCHECK(scheduleIt->second->second.scheduled());
260
+ scheduleIt.fullStreamEntry.streamEntry.currentSequenceNumber = undefined
261
+
262
+ const result = BTreeScheduler.StreamId(scheduleIt)
263
+ this.schedule_.shift()
264
+ return result
265
+ }
266
+
267
+ // Registers the specified stream with the supplied priority. The stream must
268
+ // not be already registered.
269
+ /**
270
+ * @param {StreamId} streamId
271
+ * @param {Priority} priority
272
+ */
273
+ Register(streamId, priority) {
274
+ if (this.streams_[streamIdToKey(streamId)]) {
275
+ throw new Error('ID already registered')
276
+ }
277
+ this.streams_[streamIdToKey(streamId)] = new StreamEntry(priority.sendOrder)
278
+ }
279
+ // Unregisters a previously registered stream.
280
+ /**
281
+ * @param {StreamId} streamId
282
+ */
283
+ Unregister(streamId) {
284
+ const it = /** @type {StreamEntry|undefined} */ (
285
+ this.streams_[streamIdToKey(streamId)]
286
+ )
287
+ if (!it) {
288
+ throw new Error('Stream not registered')
289
+ }
290
+
291
+ if (it.scheduled()) {
292
+ this.DescheduleStream(it)
293
+ }
294
+
295
+ delete this.streams_[streamIdToKey(streamId)]
296
+ }
297
+ // Alters the priority of an already registered stream.
298
+ /**
299
+ * @param {StreamId} streamId
300
+ * @param {SendOrder} newPriority
301
+ */
302
+ UpdatePriority(streamId, newPriority) {
303
+ const stream = this.streams_[streamIdToKey(streamId)]
304
+ if (!stream) {
305
+ return new Error('ID not registered')
306
+ }
307
+
308
+ let sequenceNumber
309
+ if (stream.scheduled()) {
310
+ const oldEntry = this.DescheduleStream(stream)
311
+ sequenceNumber = oldEntry.scheduleKey.sequenceNumber
312
+ //QUICHE_DCHECK_EQ(oldEntry->second, &*it);
313
+ }
314
+
315
+ stream.priority = newPriority
316
+ if (sequenceNumber) {
317
+ this.schedule_.push({
318
+ scheduleKey: { priority: stream.priority, sequenceNumber },
319
+ fullStreamEntry: {
320
+ iD: streamId,
321
+ streamEntry: stream
322
+ }
323
+ })
324
+ this.schedule_.sort(compareFullStreamEntries)
325
+ }
326
+ }
327
+
328
+ // Adds the `stream` into the schedule if it's not already there.
329
+
330
+ /**
331
+ * @param {StreamId} streamId
332
+ */
333
+ Schedule(streamId) {
334
+ const streamIt = this.streams_[streamIdToKey(streamId)]
335
+ if (!streamIt) {
336
+ return new Error('ID not registered')
337
+ }
338
+ if (streamIt.scheduled()) {
339
+ return
340
+ }
341
+ const newElement = {
342
+ scheduleKey: {
343
+ priority: streamIt.priority,
344
+ sequenceNumber: --this.currentWriteSequenceNumber_
345
+ },
346
+ fullStreamEntry: {
347
+ iD: streamId,
348
+ streamEntry: streamIt
349
+ }
350
+ }
351
+ this.schedule_.push(newElement)
352
+ this.schedule_.sort(compareFullStreamEntries)
353
+
354
+ streamIt.currentSequenceNumber = newElement.scheduleKey.sequenceNumber
355
+ }
356
+ // Deschedules a stream that is known to be currently scheduled.
357
+
358
+ /**
359
+ * @param {StreamId} streamId
360
+ */
361
+ Deschedule(streamId) {
362
+ const stream = this.streams_[streamIdToKey(streamId)]
363
+ if (!stream) {
364
+ throw new Error('Stream not registered')
365
+ }
366
+
367
+ if (!stream.scheduled()) {
368
+ throw new Error('Stream not scheduled')
369
+ }
370
+ this.DescheduleStream(stream)
371
+ stream.currentSequenceNumber = undefined
372
+ }
373
+ // Returns true if `stream` is in the schedule.
374
+
375
+ /**
376
+ * @param {StreamId} streamId
377
+ */
378
+ IsScheduled(streamId) {
379
+ const streamIt = this.streams_[streamIdToKey(streamId)]
380
+ if (!streamIt) {
381
+ return false
382
+ }
383
+ return streamIt.scheduled()
384
+ }
385
+
386
+ /*
387
+ using FullScheduleEntry = std::pair<const ScheduleKey, FullStreamEntry*>;
388
+ using ScheduleIterator =
389
+ typename absl::btree_map<ScheduleKey, FullStreamEntry*>::const_iterator;*/
390
+
391
+ // Convenience method to get the stream ID for a schedule entry.
392
+
393
+ /**
394
+ * @param {{scheduleKey: ScheduleKey, fullStreamEntry: FullStreamEntry}} entry
395
+ */
396
+ static StreamId(entry) {
397
+ return entry.fullStreamEntry.iD
398
+ }
399
+
400
+ // Removes a stream from the schedule, and returns the old entry if it were
401
+ // present.
402
+ /**
403
+ * @param {StreamEntry} entry
404
+ */
405
+ DescheduleStream(entry) {
406
+ //QUICHE_DCHECK(entry.scheduled());
407
+ const it = this.schedule_.findIndex(
408
+ (el) =>
409
+ entry.priority === el.scheduleKey.priority &&
410
+ entry.currentSequenceNumber ===
411
+ el.fullStreamEntry.streamEntry.currentSequenceNumber
412
+ )
413
+
414
+ if (it === -1) {
415
+ throw new Error(
416
+ 'Calling DescheduleStream() on an entry that is not in the schedule at ' +
417
+ 'the expected key.'
418
+ )
419
+ }
420
+ const result = this.schedule_[it]
421
+ this.schedule_.splice(it, 1)
422
+ return result
423
+ }
424
+ }
425
+
426
+ export class PriorityScheduler {
427
+ constructor() {
428
+ // using PerGroupScheduler = quiche:: BTreeScheduler<StreamId, SendOrder>;
429
+ // using GroupSchedulerPair = std:: pair<const SendGroupId, PerGroupScheduler>;
430
+
431
+ // Round-robin schedule for the groups.
432
+ this.activeGroups_ = new BTreeScheduler() // SendGroupId, SinglePriority
433
+ // Map group ID to the scheduler for the group in question.
434
+ /**
435
+ * @type {Object<string, BTreeScheduler>}
436
+ */
437
+ this.perGroupSchedulers_ = {} // absl:: node_hash_map < SendGroupId, PerGroupScheduler >
438
+ // Map stream ID to a pointer to the entry in `perGroupSchedulers_`.
439
+ /**
440
+ * @type {Object<string, {sendGroupId: SendGroupId, perGroupScheduler: BTreeScheduler }>}
441
+ */
442
+ this.streamToGroupMap_ = {} // absl:: flat_hash_map < StreamId, GroupSchedulerPair *>
443
+ }
444
+
445
+ // Returns true if there are any streams registered.
446
+ HasRegistered() {
447
+ return this.activeGroups_.HasRegistered()
448
+ }
449
+ // Returns true if there are any streams scheduled.
450
+ HasScheduled() {
451
+ return this.activeGroups_.HasScheduled()
452
+ }
453
+
454
+ // Returns the number of currently scheduled streams.
455
+ NumScheduled() {
456
+ let total = 0
457
+ for (const [, groupScheduler] of Object.entries(this.perGroupSchedulers_)) {
458
+ total += groupScheduler.NumScheduled()
459
+ }
460
+ return total
461
+ }
462
+
463
+ // Registers the specified stream with the supplied priority. The stream must
464
+ // not be already registered.
465
+ /**
466
+ * @param {StreamId} streamId
467
+ * @param {Priority} priority
468
+ */
469
+ Register(streamId, priority) {
470
+ if (this.streamToGroupMap_[streamIdToKey(streamId)]) {
471
+ throw new Error('Provided stream ID already registered')
472
+ }
473
+ if (!this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)]) {
474
+ this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)] =
475
+ new BTreeScheduler()
476
+ this.activeGroups_.Register(priority.sendGroupId, priority) // TODO, may be we have to add a scheduler
477
+ }
478
+ const scheduler =
479
+ this.perGroupSchedulers_[sendGroupIdToKey(priority.sendGroupId)]
480
+ this.streamToGroupMap_[streamIdToKey(streamId)] = {
481
+ sendGroupId: priority.sendGroupId,
482
+ perGroupScheduler: scheduler
483
+ }
484
+ scheduler.Register(streamId, priority)
485
+ }
486
+ // Unregisters a previously registered stream.
487
+ /**
488
+ * @param {StreamId} streamId
489
+ */
490
+ Unregister(streamId) {
491
+ const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
492
+ if (!stream) {
493
+ throw new Error('Stream ID not registered')
494
+ }
495
+ const groupId = stream.sendGroupId
496
+ const groupScheduler = stream.perGroupScheduler
497
+ delete this.streamToGroupMap_[streamIdToKey(streamId)]
498
+ groupScheduler.Unregister(streamId)
499
+
500
+ // Clean up the group if there are no more streams associated with it.
501
+ if (!groupScheduler.HasRegistered()) {
502
+ delete this.perGroupSchedulers_[sendGroupIdToKey(groupId)]
503
+ this.activeGroups_.Unregister(groupId)
504
+ }
505
+ }
506
+ // Alters the priority of an already registered stream.
507
+ /**
508
+ * @param {StreamId} streamId
509
+ * @param {bigint} newSendOrder
510
+ */
511
+ UpdateSendOrder(streamId, newSendOrder) {
512
+ const scheduler = this.SchedulerForStream(streamId)
513
+ if (!scheduler) {
514
+ throw new Error('Stream ID not registered')
515
+ }
516
+ return scheduler.UpdatePriority(streamId, newSendOrder)
517
+ }
518
+
519
+ /**
520
+ * @param {StreamId} streamId
521
+ * @param {SendGroupId} newSendGroup
522
+ */
523
+ UpdateSendGroup(streamId, newSendGroup) {
524
+ const scheduler = this.SchedulerForStream(streamId)
525
+ if (!scheduler) {
526
+ throw new Error('Stream ID not registered')
527
+ }
528
+ const isScheduled = scheduler.IsScheduled(streamId)
529
+ const sendOrder = scheduler.GetPriorityFor(streamId)
530
+ if (!sendOrder) {
531
+ throw new Error(
532
+ 'Stream registered at the top level scheduler, but not at the per-group one'
533
+ )
534
+ }
535
+ this.Unregister(streamId)
536
+
537
+ this.Register(streamId, { sendGroupId: newSendGroup, sendOrder })
538
+ if (isScheduled) {
539
+ this.Schedule(streamId)
540
+ }
541
+ }
542
+
543
+ // Returns true if there is a stream that would go before `id` in the
544
+ // schedule.
545
+ /**
546
+ * @param {StreamId} streamId
547
+ */
548
+ ShouldYield(streamId) {
549
+ const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
550
+ if (!stream) {
551
+ throw new Error('Stream ID not registered')
552
+ }
553
+ const { sendGroupId, perGroupScheduler } = stream
554
+
555
+ const perGroupResult = this.activeGroups_.ShouldYield(sendGroupId)
556
+ if (perGroupResult) {
557
+ return true
558
+ }
559
+
560
+ return perGroupScheduler.ShouldYield(streamId)
561
+ }
562
+
563
+ // Returns the priority for `id`, or nullopt if stream is not registered.
564
+ /**
565
+ * @param {StreamId} streamId
566
+ */
567
+ GetPriorityFor(streamId) {
568
+ const stream = this.streamToGroupMap_[streamIdToKey(streamId)]
569
+ if (!stream) {
570
+ return null
571
+ }
572
+ const { sendGroupId, perGroupScheduler } = stream
573
+ const sendOrder = perGroupScheduler.GetPriorityFor(streamId)
574
+ if (!sendOrder) {
575
+ return null
576
+ }
577
+ return { sendGroupId, sendOrder }
578
+ }
579
+
580
+ // Pops the highest priority stream. Will fail if the schedule is empty.
581
+ PopFront() {
582
+ const groupId = this.activeGroups_.PopFront()
583
+ if (typeof groupId === 'undefined') {
584
+ return undefined
585
+ }
586
+
587
+ const scheduler = this.perGroupSchedulers_[sendGroupIdToKey(groupId)]
588
+ if (!scheduler) {
589
+ throw new Error('Scheduled a group with no per-group scheduler attached')
590
+ }
591
+ const result = scheduler.PopFront()
592
+ if (typeof result === 'undefined') {
593
+ return undefined
594
+ }
595
+
596
+ // Reschedule the group if it has more active streams in it.
597
+ if (scheduler.HasScheduled()) {
598
+ this.activeGroups_.Schedule(groupId)
599
+ }
600
+ return result
601
+ }
602
+
603
+ // Adds `stream` to the schedule if it's not already there.
604
+ /**
605
+ * @param {StreamId} streamId
606
+ */
607
+ Schedule(streamId) {
608
+ const it = this.streamToGroupMap_[streamIdToKey(streamId)]
609
+ if (!it) {
610
+ return new Error('Stream ID not registered')
611
+ }
612
+ const { sendGroupId, perGroupScheduler } = it
613
+ this.activeGroups_.Schedule(sendGroupId)
614
+ return perGroupScheduler.Schedule(streamId)
615
+ }
616
+ // Returns true if `stream` is in the schedule.
617
+ /**
618
+ * @param {StreamId} streamId
619
+ */
620
+ IsScheduled(streamId) {
621
+ const scheduler = this.SchedulerForStream(streamId)
622
+ if (!scheduler) {
623
+ return false
624
+ }
625
+ return scheduler.IsScheduled(streamId)
626
+ }
627
+
628
+ /*
629
+ // All groups currently have the equal priority; this type represents the said
630
+ // single priority.
631
+ class SinglePriority {
632
+ public:
633
+ bool operator== (const SinglePriority&) const { return true; }
634
+ bool operator != (const SinglePriority&) const { return false; }
635
+
636
+ bool operator < (const SinglePriority&) const { return false; }
637
+ bool operator > (const SinglePriority&) const { return false; }
638
+ bool operator <= (const SinglePriority&) const { return true; }
639
+ bool operator >= (const SinglePriority&) const { return true; }
640
+ };
641
+
642
+ */
643
+
644
+ /**
645
+ * @param {StreamId} streamId
646
+ */
647
+ SchedulerForStream(streamId) {
648
+ const it = this.streamToGroupMap_[streamIdToKey(streamId)]
649
+ if (!it) {
650
+ return undefined
651
+ }
652
+ return it.perGroupScheduler
653
+ }
654
+ }