• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / loaders.gl / 24139120841

08 Apr 2026 01:53PM UTC coverage: 65.319% (+30.2%) from 35.137%
24139120841

push

github

web-flow
chore: Replace puppeteer with playwright (#3350)

14216 of 18890 branches covered (75.26%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

3248 existing lines in 369 files now uncovered.

73509 of 115413 relevant lines covered (63.69%)

5763.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.14
/modules/shapefile/src/lib/streaming/binary-chunk-reader.ts
1
// loaders.gl
1✔
2
// SPDX-License-Identifier: MIT
105✔
3
// Copyright (c) vis.gl contributors
105✔
4

105✔
5
export type BinaryChunkReaderOptions = {
105✔
6
  maxRewindBytes: number;
105✔
7
};
105✔
8

105✔
9
export class BinaryChunkReader {
105✔
10
  offset: number;
105✔
11
  arrayBuffers: ArrayBuffer[];
187✔
12
  ended: boolean;
187✔
13
  maxRewindBytes: number;
105✔
14

105✔
15
  constructor(options?: BinaryChunkReaderOptions) {
105✔
16
    const {maxRewindBytes = 0} = options || {};
187✔
17

187✔
18
    /** current global offset into current array buffer*/
187✔
19
    this.offset = 0;
117✔
20
    /** current buffer from iterator */
117✔
21
    this.arrayBuffers = [];
95✔
22
    this.ended = false;
95✔
23

187✔
24
    /** bytes behind offset to hold on to */
187✔
25
    this.maxRewindBytes = maxRewindBytes;
187✔
26
  }
187✔
27
  /**
1✔
28
   * @param arrayBuffer
1✔
29
   */
1✔
30
  write(arrayBuffer: ArrayBuffer): void {
1✔
31
    this.arrayBuffers.push(arrayBuffer);
199✔
32
  }
199✔
33

1✔
34
  end(): void {
1✔
35
    this.arrayBuffers = [];
177✔
36
    this.ended = true;
177✔
37
  }
177✔
38

1✔
39
  /**
1✔
40
   * Has enough bytes available in array buffers
1✔
41
   *
1✔
42
   * @param bytes Number of bytes
1✔
43
   * @return boolean
1✔
44
   */
1✔
45
  hasAvailableBytes(bytes: number): boolean {
1✔
46
    let bytesAvailable = -this.offset;
792✔
47
    for (const arrayBuffer of this.arrayBuffers) {
792✔
48
      bytesAvailable += arrayBuffer.byteLength;
700✔
49
      if (bytesAvailable >= bytes) {
700✔
50
        return true;
676✔
51
      }
676✔
52
    }
676✔
53
    return false;
676✔
54
  }
676✔
55

676✔
56
  /**
676✔
57
   * Find offsets of byte ranges within this.arrayBuffers
676✔
58
   *
676✔
59
   * @param  bytes Byte length to read
676✔
60
   * @return Arrays with byte ranges pointing to this.arrayBuffers, Output type is nested array, e.g. [ [0, [1, 2]], ...]
676✔
61
   */
676✔
62
  findBufferOffsets(bytes: number): any[] | null {
676✔
63
    let offset = -this.offset;
1,224✔
64
    const selectedBuffers: any = [];
1,224✔
65

1,224✔
66
    for (let i = 0; i < this.arrayBuffers.length; i++) {
1,224✔
67
      const buf = this.arrayBuffers[i];
1,238✔
68

1,238✔
69
      // Current buffer isn't long enough to reach global offset
1,238✔
70
      if (offset + buf.byteLength <= 0) {
1,238!
71
        offset += buf.byteLength;
7✔
72
        // eslint-disable-next-line no-continue
7✔
73
        continue;
7✔
74
      }
7✔
75

1,231✔
76
      // Find start/end offsets for this buffer
1,231✔
77
      // When offset < 0, need to skip over Math.abs(offset) bytes
668!
UNCOV
78
      // When offset > 0, implies bytes in previous buffer, start at 0
×
79
      const start = offset <= 0 ? Math.abs(offset) : 0;
668!
80
      let end: number;
668✔
81

665✔
82
      // Length of requested bytes is contained in current buffer
665✔
83
      if (start + bytes <= buf.byteLength) {
665✔
84
        end = start + bytes;
665✔
85
        selectedBuffers.push([i, [start, end]]);
665✔
86
        return selectedBuffers;
668✔
87
      }
3✔
88

3✔
89
      // Will need to look into next buffer
3✔
90
      end = buf.byteLength;
3✔
91
      selectedBuffers.push([i, [start, end]]);
7✔
92

7✔
93
      // Need to read fewer bytes in next iter
7✔
94
      bytes -= buf.byteLength - start;
7✔
95
      offset += buf.byteLength;
675✔
96
    }
675✔
97

675!
98
    // Should only finish loop if exhausted all arrays
675✔
99
    return null;
×
UNCOV
100
  }
×
101

1✔
102
  /**
1✔
103
   * Get the required number of bytes from the iterator
1✔
104
   *
1✔
105
   * @param bytes Number of bytes
1✔
106
   * @return DataView with data
1✔
107
   */
1✔
108
  getDataView(bytes: number): DataView | null {
1✔
109
    const bufferOffsets = this.findBufferOffsets(bytes);
1,216✔
110
    // return `null` if not enough data, except if end() already called, in
1,216✔
111
    // which case throw an error.
1,216✔
112
    if (!bufferOffsets && this.ended) {
1,216!
113
      throw new Error('binary data exhausted');
✔
114
    }
3✔
115

3✔
116
    if (!bufferOffsets) {
3!
117
      return null;
3✔
118
    }
6✔
119

6✔
120
    // If only one arrayBuffer needed, return DataView directly
6✔
121
    if (bufferOffsets.length === 1) {
3✔
122
      const [bufferIndex, [start, end]] = bufferOffsets[0];
3✔
123
      const arrayBuffer = this.arrayBuffers[bufferIndex];
3✔
124
      const view = new DataView(arrayBuffer, start, end - start);
6✔
125

6✔
126
      this.offset += bytes;
6✔
127
      this.disposeBuffers();
6✔
128
      return view;
6✔
129
    }
6✔
130

6✔
131
    // Concatenate portions of multiple ArrayBuffers
6✔
132
    const view = new DataView(this._combineArrayBuffers(bufferOffsets));
3✔
133
    this.offset += bytes;
3✔
134
    this.disposeBuffers();
146✔
135
    return view;
3✔
136
  }
3✔
137

1✔
138
  /**
1✔
139
   * Dispose of old array buffers
1✔
140
   */
1✔
141
  disposeBuffers(): void {
1✔
142
    while (
1,216✔
143
      this.arrayBuffers.length > 0 &&
1,216✔
144
      this.offset - this.maxRewindBytes >= this.arrayBuffers[0].byteLength
1,223✔
145
    ) {
1,216✔
146
      this.offset -= this.arrayBuffers[0].byteLength;
89✔
147
      this.arrayBuffers.shift();
89✔
148
    }
89✔
149
  }
1,216✔
150

1✔
151
  /**
1✔
152
   * Copy multiple ArrayBuffers into one contiguous ArrayBuffer
1✔
153
   *
1✔
154
   * In contrast to concatenateArrayBuffers, this only copies the necessary
1✔
155
   * portions of the source arrays, rather than first copying the entire arrays
1✔
156
   * then taking a part of them.
1✔
157
   *
1✔
158
   * @param bufferOffsets List of internal array offsets
1✔
159
   * @return New contiguous ArrayBuffer
1✔
160
   */
1✔
161
  _combineArrayBuffers(bufferOffsets: any[]): ArrayBufferLike {
1✔
162
    let byteLength: number = 0;
3✔
163
    for (const bufferOffset of bufferOffsets) {
3✔
164
      const [start, end] = bufferOffset[1];
6✔
165
      byteLength += end - start;
6✔
166
    }
6✔
167

3✔
168
    const result = new Uint8Array(byteLength);
3✔
169

3✔
170
    // Copy the subarrays
3✔
171
    let resultOffset: number = 0;
3✔
172
    for (const bufferOffset of bufferOffsets) {
3✔
173
      const [bufferIndex, [start, end]] = bufferOffset;
6✔
174
      const sourceArray = new Uint8Array(this.arrayBuffers[bufferIndex]);
6✔
175
      result.set(sourceArray.subarray(start, end), resultOffset);
6✔
176
      resultOffset += end - start;
6✔
177
    }
6✔
178

3✔
179
    return result.buffer;
3✔
180
  }
3✔
181
  /**
1✔
182
   * @param bytes
1✔
183
   */
1✔
184
  skip(bytes: number): void {
1✔
185
    this.offset += bytes;
444✔
186
  }
444✔
187
  /**
1✔
188
   * @param bytes
1✔
189
   */
1✔
190
  rewind(bytes: number): void {
1✔
191
    // TODO - only works if offset is already set
306✔
192
    this.offset -= bytes;
306✔
193
  }
306✔
194
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc