@@ -96,266 +96,254 @@ export const INT32_MAX = 0x7fffffff;
9696export const INT32_MIN = - 0x80000000 ;
9797
9898export class BinaryWriter {
99- /**
100- * We cannot allocate a buffer for the entire output
101- * because we don't know it's size.
102- *
103- * So we collect smaller chunks of known size and
104- * concat them later.
105- *
106- * Use `raw()` to push data to this array. It will flush
107- * `buf` first.
108- */
109- private chunks : Uint8Array [ ] ;
110-
111- /**
112- * A growing buffer for byte values. If you don't know
113- * the size of the data you are writing, push to this
114- * array.
115- */
116- protected buf : number [ ] ;
117-
118- /**
119- * Previous fork states.
120- */
121- private stack : Array < { chunks : Uint8Array [ ] ; buf : number [ ] } > = [ ] ;
122-
99+ private buffer : Uint8Array ;
100+ private pos : number ;
101+ private stackPos : number [ ] = [ ] ;
102+ private readonly initialSize = 128 ;
123103 constructor (
124104 private readonly encodeUtf8 : (
125105 text : string ,
126106 ) => Uint8Array = getTextEncoding ( ) . encodeUtf8 ,
127107 ) {
128- this . chunks = [ ] ;
129- this . buf = [ ] ;
108+ this . buffer = new Uint8Array ( this . initialSize ) ;
109+ this . pos = 0 ;
130110 }
131111
112+ private ensureCapacity ( size : number ) {
113+ if ( this . buffer . length - this . pos < size ) {
114+ let newLen = this . buffer . length ;
115+ while ( newLen - this . pos < size ) newLen *= 2 ;
116+ const newBuf = new Uint8Array ( newLen ) ;
117+ newBuf . set ( this . buffer . subarray ( 0 , this . pos ) ) ;
118+ this . buffer = newBuf ;
119+ }
120+ }
132121 /**
133122 * Return all bytes written and reset this writer.
134123 */
135124 finish ( ) : Uint8Array {
136- if ( this . buf . length ) {
137- this . chunks . push ( new Uint8Array ( this . buf ) ) ; // flush the buffer
138- this . buf = [ ] ;
139- }
140- let len = 0 ;
141- for ( let i = 0 ; i < this . chunks . length ; i ++ ) len += this . chunks [ i ] . length ;
142- let bytes = new Uint8Array ( len ) ;
143- let offset = 0 ;
144- for ( let i = 0 ; i < this . chunks . length ; i ++ ) {
145- bytes . set ( this . chunks [ i ] , offset ) ;
146- offset += this . chunks [ i ] . length ;
147- }
148- this . chunks = [ ] ;
149- return bytes ;
125+ const out = this . buffer . subarray ( 0 , this . pos ) ;
126+ // Return a copy to avoid mutation if writer is reused
127+ const result = new Uint8Array ( out ) ;
128+ this . pos = 0 ;
129+ this . stackPos = [ ] ;
130+ return result ;
150131 }
151-
152132 /**
153133 * Start a new fork for length-delimited data like a message
154134 * or a packed repeated field.
155135 *
156136 * Must be joined later with `join()`.
157137 */
158138 fork ( ) : this {
159- this . stack . push ( { chunks : this . chunks , buf : this . buf } ) ;
160- this . chunks = [ ] ;
161- this . buf = [ ] ;
139+ this . stackPos . push ( this . pos ) ;
162140 return this ;
163141 }
164-
165142 /**
166143 * Join the last fork. Write its length and bytes, then
167144 * return to the previous state.
168145 */
169146 join ( ) : this {
170- // get chunk of fork
171- let chunk = this . finish ( ) ;
172-
173- // restore previous state
174- let prev = this . stack . pop ( ) ;
175- if ( ! prev ) throw new Error ( "invalid state, fork stack empty" ) ;
176- this . chunks = prev . chunks ;
177- this . buf = prev . buf ;
178-
179- // write length of chunk as varint
180- this . uint32 ( chunk . byteLength ) ;
181- return this . raw ( chunk ) ;
147+ const forkPos = this . stackPos . pop ( ) ;
148+ if ( forkPos === undefined )
149+ throw new Error ( "invalid state, fork stack empty" ) ;
150+ const len = this . pos - forkPos ;
151+ const tmp : number [ ] = [ ] ;
152+ varint32write ( len , tmp ) ;
153+ this . ensureCapacity ( tmp . length ) ;
154+ for ( let i = this . pos - 1 ; i >= forkPos ; i -- ) {
155+ this . buffer [ i + tmp . length ] = this . buffer [ i ] ;
156+ }
157+ for ( let i = 0 ; i < tmp . length ; i ++ ) {
158+ this . buffer [ forkPos + i ] = tmp [ i ] ;
159+ }
160+ this . pos += tmp . length ;
161+ return this ;
182162 }
183163
184- /**
185- * Writes a tag (field number and wire type).
186- *
187- * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`.
188- *
189- * Generated code should compute the tag ahead of time and call `uint32()`.
190- */
191164 tag ( fieldNo : number , type : WireType ) : this {
192165 return this . uint32 ( ( ( fieldNo << 3 ) | type ) >>> 0 ) ;
193166 }
194-
195167 /**
196168 * Write a chunk of raw bytes.
197169 */
198170 raw ( chunk : Uint8Array ) : this {
199- if ( this . buf . length ) {
200- this . chunks . push ( new Uint8Array ( this . buf ) ) ;
201- this . buf = [ ] ;
202- }
203- this . chunks . push ( chunk ) ;
171+ this . ensureCapacity ( chunk . length ) ;
172+ this . buffer . set ( chunk , this . pos ) ;
173+ this . pos += chunk . length ;
204174 return this ;
205175 }
206-
207176 /**
208177 * Write a `uint32` value, an unsigned 32 bit varint.
209178 */
210179 uint32 ( value : number ) : this {
211180 assertUInt32 ( value ) ;
212-
213- // write value as varint 32, inlined for speed
214- while ( value > 0x7f ) {
215- this . buf . push ( ( value & 0x7f ) | 0x80 ) ;
216- value = value >>> 7 ;
181+ // Unrolled for single-byte varints
182+ if ( value < 0x80 ) {
183+ this . ensureCapacity ( 1 ) ;
184+ this . buffer [ this . pos ++ ] = value ;
185+ return this ;
217186 }
218- this . buf . push ( value ) ;
219-
187+ let tmp = value ;
188+ while ( tmp > 0x7f ) {
189+ this . ensureCapacity ( 1 ) ;
190+ this . buffer [ this . pos ++ ] = ( tmp & 0x7f ) | 0x80 ;
191+ tmp >>>= 7 ;
192+ }
193+ this . ensureCapacity ( 1 ) ;
194+ this . buffer [ this . pos ++ ] = tmp ;
220195 return this ;
221196 }
222197
223- /**
224- * Write a `int32` value, a signed 32 bit varint.
225- */
226198 int32 ( value : number ) : this {
227199 assertInt32 ( value ) ;
228- varint32write ( value , this . buf ) ;
200+ // Use varint32write for correct negative encoding
201+ const varintBytes : number [ ] = [ ] ;
202+ varint32write ( value , varintBytes ) ;
203+ this . raw ( Uint8Array . from ( varintBytes ) ) ;
229204 return this ;
230205 }
231-
232206 /**
233207 * Write a `bool` value, a variant.
234208 */
235209 bool ( value : boolean ) : this {
236- this . buf . push ( value ? 1 : 0 ) ;
210+ this . ensureCapacity ( 1 ) ;
211+ this . buffer [ this . pos ++ ] = value ? 1 : 0 ;
237212 return this ;
238213 }
239-
240214 /**
241215 * Write a `bytes` value, length-delimited arbitrary data.
242216 */
243217 bytes ( value : Uint8Array ) : this {
244- this . uint32 ( value . byteLength ) ; // write length of chunk as varint
218+ this . uint32 ( value . byteLength ) ;
245219 return this . raw ( value ) ;
246220 }
247-
248221 /**
249222 * Write a `string` value, length-delimited data converted to UTF-8 text.
250223 */
251224 string ( value : string ) : this {
252225 let chunk = this . encodeUtf8 ( value ) ;
253- this . uint32 ( chunk . byteLength ) ; // write length of chunk as varint
226+ this . uint32 ( chunk . byteLength ) ;
254227 return this . raw ( chunk ) ;
255228 }
256229
257- /**
258- * Write a `float` value, 32-bit floating point number.
259- */
260230 float ( value : number ) : this {
261231 assertFloat32 ( value ) ;
262- let chunk = new Uint8Array ( 4 ) ;
263- new DataView ( chunk . buffer ) . setFloat32 ( 0 , value , true ) ;
264- return this . raw ( chunk ) ;
232+ this . ensureCapacity ( 4 ) ;
233+ new DataView (
234+ this . buffer . buffer ,
235+ this . buffer . byteOffset ,
236+ this . buffer . byteLength ,
237+ ) . setFloat32 ( this . pos , value , true ) ;
238+ this . pos += 4 ;
239+ return this ;
265240 }
266-
267241 /**
268242 * Write a `double` value, a 64-bit floating point number.
269243 */
270244 double ( value : number ) : this {
271- let chunk = new Uint8Array ( 8 ) ;
272- new DataView ( chunk . buffer ) . setFloat64 ( 0 , value , true ) ;
273- return this . raw ( chunk ) ;
245+ this . ensureCapacity ( 8 ) ;
246+ new DataView (
247+ this . buffer . buffer ,
248+ this . buffer . byteOffset ,
249+ this . buffer . byteLength ,
250+ ) . setFloat64 ( this . pos , value , true ) ;
251+ this . pos += 8 ;
252+ return this ;
274253 }
275-
276254 /**
277255 * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer.
278256 */
279257 fixed32 ( value : number ) : this {
280258 assertUInt32 ( value ) ;
281- let chunk = new Uint8Array ( 4 ) ;
282- new DataView ( chunk . buffer ) . setUint32 ( 0 , value , true ) ;
283- return this . raw ( chunk ) ;
259+ this . ensureCapacity ( 4 ) ;
260+ new DataView (
261+ this . buffer . buffer ,
262+ this . buffer . byteOffset ,
263+ this . buffer . byteLength ,
264+ ) . setUint32 ( this . pos , value , true ) ;
265+ this . pos += 4 ;
266+ return this ;
284267 }
285-
286268 /**
287269 * Write a `sfixed32` value, a signed, fixed-length 32-bit integer.
288270 */
289271 sfixed32 ( value : number ) : this {
290272 assertInt32 ( value ) ;
291- let chunk = new Uint8Array ( 4 ) ;
292- new DataView ( chunk . buffer ) . setInt32 ( 0 , value , true ) ;
293- return this . raw ( chunk ) ;
273+ this . ensureCapacity ( 4 ) ;
274+ new DataView (
275+ this . buffer . buffer ,
276+ this . buffer . byteOffset ,
277+ this . buffer . byteLength ,
278+ ) . setInt32 ( this . pos , value , true ) ;
279+ this . pos += 4 ;
280+ return this ;
294281 }
295-
296282 /**
297283 * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint.
298284 */
299285 sint32 ( value : number ) : this {
300286 assertInt32 ( value ) ;
301287 // zigzag encode
302288 value = ( ( value << 1 ) ^ ( value >> 31 ) ) >>> 0 ;
303- varint32write ( value , this . buf ) ;
289+ const tmp : number [ ] = [ ] ;
290+ varint32write ( value , tmp ) ;
291+ this . raw ( Uint8Array . from ( tmp ) ) ;
304292 return this ;
305293 }
306-
307294 /**
308295 * Write a `fixed64` value, a signed, fixed-length 64-bit integer.
309296 */
310297 sfixed64 ( value : string | number | bigint ) : this {
311298 let chunk = new Uint8Array ( 8 ) ,
312- view = new DataView ( chunk . buffer ) ,
313- tc = protoInt64 . enc ( value ) ;
299+ view = new DataView ( chunk . buffer ) ;
300+ const tc = protoInt64 . enc ( value ) ;
314301 view . setInt32 ( 0 , tc . lo , true ) ;
315302 view . setInt32 ( 4 , tc . hi , true ) ;
316303 return this . raw ( chunk ) ;
317304 }
318-
319305 /**
320306 * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer.
321307 */
322308 fixed64 ( value : string | number | bigint ) : this {
323309 let chunk = new Uint8Array ( 8 ) ,
324- view = new DataView ( chunk . buffer ) ,
325- tc = protoInt64 . uEnc ( value ) ;
310+ view = new DataView ( chunk . buffer ) ;
311+ const tc = protoInt64 . uEnc ( value ) ;
326312 view . setInt32 ( 0 , tc . lo , true ) ;
327313 view . setInt32 ( 4 , tc . hi , true ) ;
328314 return this . raw ( chunk ) ;
329315 }
330-
331316 /**
332317 * Write a `int64` value, a signed 64-bit varint.
333318 */
334319 int64 ( value : string | number | bigint ) : this {
335- let tc = protoInt64 . enc ( value ) ;
336- varint64write ( tc . lo , tc . hi , this . buf ) ;
320+ const tc = protoInt64 . enc ( value ) ;
321+ const tmp : number [ ] = [ ] ;
322+ varint64write ( tc . lo , tc . hi , tmp ) ;
323+ this . raw ( Uint8Array . from ( tmp ) ) ;
337324 return this ;
338325 }
339-
340326 /**
341327 * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint.
342328 */
343329 sint64 ( value : string | number | bigint ) : this {
344- let tc = protoInt64 . enc ( value ) ,
345- // zigzag encode
330+ const tc = protoInt64 . enc ( value ) ,
346331 sign = tc . hi >> 31 ,
347332 lo = ( tc . lo << 1 ) ^ sign ,
348333 hi = ( ( tc . hi << 1 ) | ( tc . lo >>> 31 ) ) ^ sign ;
349- varint64write ( lo , hi , this . buf ) ;
334+ const tmp : number [ ] = [ ] ;
335+ varint64write ( lo , hi , tmp ) ;
336+ this . raw ( Uint8Array . from ( tmp ) ) ;
350337 return this ;
351338 }
352-
353339 /**
354340 * Write a `uint64` value, an unsigned 64-bit varint.
355341 */
356342 uint64 ( value : string | number | bigint ) : this {
357- let tc = protoInt64 . uEnc ( value ) ;
358- varint64write ( tc . lo , tc . hi , this . buf ) ;
343+ const tc = protoInt64 . uEnc ( value ) ;
344+ const tmp : number [ ] = [ ] ;
345+ varint64write ( tc . lo , tc . hi , tmp ) ;
346+ this . raw ( Uint8Array . from ( tmp ) ) ;
359347 return this ;
360348 }
361349}
0 commit comments