Last active
December 2, 2022 05:20
-
-
Save sirisian/dbc628dde19771b54dec to your computer and use it in GitHub Desktop.
Type Proposal Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A binary bit-based writer and reader designed for packets. | |
* @class | |
*/ | |
export class Packet { | |
/** | |
* The byte buffer for the packet. | |
*/ | |
#buffer:[]<uint32> | |
/** | |
* The current bit index of the writer and reader. | |
*/ | |
#bitIndex:uint32 | |
/** | |
* The maximum bit index of the writer and reader. | |
*/ | |
#maximumBitIndex:uint32 | |
/** | |
* Error handler. | |
*/ | |
#error:(string) | |
/** | |
* Constructor for writing. | |
* @constructs | |
*/ | |
constructor() { | |
this.#bitIndex = 0; | |
this.#maximumBitIndex = 0; | |
// 1500 bytes - Websocket Header - TCP Header = Default Packet Size | |
this.#buffer = new [1400]<uint32>; | |
} | |
/** | |
* Constructor for reading. | |
* @constructs | |
* @param buffer The buffer to read from. | |
* @param error Error callback. | |
*/ | |
constructor(buffer:[]<uint8>, error:(string)) { | |
this.#buffer = []<uint32>(buffer.buffer); | |
this.#error = message => { | |
error(message); | |
this.#error = null; | |
}; | |
this.readHeader(); | |
} | |
/** | |
* Buffer | |
*/ | |
get buffer() { | |
return []<uint8>(this.#buffer.buffer, 0, (this.#bitIndex + 7) / 8); | |
} | |
/** | |
* BitIndex | |
*/ | |
get bitIndex() { | |
return this.#bitIndex; | |
} | |
set bitIndex(value) { | |
this.#bitIndex = value; | |
} | |
/** | |
* MaximumBitIndex | |
*/ | |
get maximumBitIndex() { | |
return this.#maximumBitIndex; | |
} | |
set maximumBitIndex(value) { | |
this.#maximumBitIndex = value; | |
} | |
/** | |
* headerSize | |
*/ | |
get headerSize() { | |
return 16; | |
} | |
/** | |
* Error | |
*/ | |
get error() { | |
return this.#error == null; | |
} | |
/** | |
* Moves the bit index after the header. | |
*/ | |
resetBitIndex() { | |
this.#bitIndex = this.headerSize; | |
} | |
/** | |
* Returns a string of 0s and 1s representing the bits in the this.#buffer. | |
*/ | |
trace() { | |
let s = ''; | |
for (let copyBits = 0; copyBits < Math.max(this.#bitIndex, this.#maximumBitIndex); ++copyBits) { | |
s += (this.#buffer[copyBits / 32] << copyBits % 32 >> 31) == 0 ? '0' : '1'; | |
} | |
return s; | |
} | |
/** | |
* Reads the length of the packet stored in the header. | |
*/ | |
readHeader() { | |
this.#maximumBitIndex = this.#buffer[0] >> 32 - this.headerSize; | |
this.#bitIndex += this.headerSize; | |
} | |
/** | |
* Writes a 16 bit header. | |
*/ | |
begin() { | |
this.#bitIndex += this.headerSize; | |
} | |
/** | |
* Writes maxBitIndex to the packet header created with Begin. | |
*/ | |
end() { | |
this.#maximumBitIndex = this.#bitIndex; | |
this.#buffer[0] |= this.#maximumBitIndex << 32 - this.headerSize; | |
} | |
/** | |
* Writes an event to the packet. | |
* @param value The event value. | |
*/ | |
writeEvent(value:uint8) { | |
this.writeUInt8(value); | |
} | |
/** | |
* Writes a boolean to the packet. | |
* @param value The boolean value. | |
*/ | |
writeBoolean(value:boolean) { | |
if (value) { | |
this.#buffer[this.#bitIndex / 32] |= 1 << 31 - this.#bitIndex % 32; | |
} | |
this.#bitIndex++; | |
} | |
/** | |
* Writes an 8-bit unsigned integer to the packet. | |
* @param value The 8 bit unsigned integer value. | |
*/ | |
writeUInt8(value:uint8) { | |
const offset = this.#bitIndex % 32; | |
this.#buffer[this.#bitIndex / 32] |= value << 24 >> offset; | |
if (offset > 24) { | |
this.#buffer[this.#bitIndex / 32 + 1] |= value << 56 - offset; | |
} | |
this.#bitIndex += 8; | |
} | |
/** | |
* Writes an 8-bit signed integer to the packet. | |
* @param value The 8 bit signed integer value. | |
*/ | |
writeInt8(value:int8) { | |
this.writeUInt8(uint8([]<int8>([value]).buffer)[0]); | |
} | |
/** | |
* Writes an 16-bit unsigned integer to the packet. | |
* @param value The 16 bit unsigned integer value. | |
*/ | |
writeUInt16(value:uint16) { | |
const offset = this.#bitIndex % 32; | |
this.#buffer[this.#bitIndex / 32] |= value << 16 >> offset; | |
if (offset > 16) { | |
this.#buffer[this.#bitIndex / 32 + 1] |= value << 48 - offset; | |
} | |
this.#bitIndex += 16; | |
} | |
/** | |
* Writes an 16-bit signed integer to the packet. | |
* @param value The 16 bit signed integer value. | |
*/ | |
writeInt16(value:int16) { | |
this.writeUInt16(uint16(int16[]([value]).buffer)[0]); | |
} | |
/** | |
* Writes an 32-bit unsigned integer to the packet. | |
* @param value The 32 bit unsigned integer value. | |
*/ | |
writeUInt32(value:uint32) { | |
const offset = this.#bitIndex % 32; | |
this.#buffer[this.#bitIndex / 32] |= value >> offset; | |
if (offset > 0) { | |
this.#buffer[this.#bitIndex / 32 + 1] |= value << 32 - offset; | |
} | |
this.#bitIndex += 32; | |
} | |
/** | |
* Writes an 32-bit signed integer to the packet. | |
* @param value The 32 bit signed integer value. | |
*/ | |
writeInt32(value:int32) { | |
this.writeUInt32(uint32(int32[]([value]).buffer)[0]); | |
} | |
/** | |
* Writes an n-bit unsigned integer to the packet. | |
* @param value The unsigned integer value. | |
* @param bits The number of bits to use. | |
*/ | |
writeUIntN(value:uint32, bits:uint32) { | |
const offset = this.#bitIndex % 32; | |
this.#buffer[this.#bitIndex / 32] |= value << 32 - bits >> offset; | |
if (offset > 32 - bits) { | |
this.#buffer[this.#bitIndex / 32 + 1] |= value << 64 - bits - offset; | |
} | |
this.#bitIndex += bits; | |
} | |
/** | |
* Writes an n-bit unsigned integer to the packet. | |
* @param value The signed integer value. | |
* @param bits The number of bits to use. | |
*/ | |
writeIntN(value:uint32, bits:uint32) { | |
this.writeUIntN(uint32(int32[]([value]).buffer)[0], bits); | |
} | |
/** | |
* Writes an unsigned integer to the packet. | |
* @param value The unsigned integer value. | |
* @param maximum The inclusive maximum for the range starting at 0. | |
*/ | |
writeUInt(value:uint32, maximum:uint32) { | |
const bits = this.Log2(maximum); | |
value -= minimum; | |
this.writeUIntN(value, bits); | |
} | |
/** | |
* Writes an unsigned integer to the packet. | |
* @param value The unsigned integer value. | |
* @param minimum The inclusive minimum for the range. | |
* @param maximum The inclusive maximum for the range. | |
*/ | |
writeUInt(value:uint32, minimum:uint32, maximum:uint32) { | |
const bits = this.Log2(maximum - minimum); | |
value -= minimum; | |
this.writeUIntN(value, bits); | |
} | |
/** | |
* Writes an signed integer to the packet. | |
* @param value The signed integer value. | |
* @param maximum The inclusive maximum for the range starting at 0. | |
*/ | |
writeInt(value:int32, maximum:int32) { | |
const bits = this.Log2(maximum); | |
value -= minimum; | |
this.writeUIntN(value, bits); | |
} | |
/** | |
* Writes an signed integer to the packet. | |
* @param value The signed integer value. | |
* @param minimum The inclusive minimum for the range. | |
* @param maximum The inclusive maximum for the range. | |
*/ | |
writeInt(value:int32, minimum:int32, maximum:int32) { | |
const bits = this.Log2(maximum - minimum); | |
value -= minimum; | |
this.writeUIntN(uint32(int32[]([value]).buffer)[0], bits); | |
} | |
/** | |
* Writes an unsigned integer to the packet using a variable width encoding. | |
* @param value The unsigned integer value. | |
* @param bits The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value. | |
*/ | |
writeVariableWidthUInt(value:uint32, bits:uint32) { | |
let shift = bits; | |
// Stop when our value can fit inside | |
for (; shift < 32 && value >= (0x1 << shift); shift += bits) { | |
this.writeBoolean(true); // Write a 1 for a continuation bit signifying one more interval is needed | |
} | |
if (shift < 32) { | |
this.writeBoolean(false); // Write a 0 for a continuation bit signifying the end | |
} | |
this.writeUIntN(value, shift > 32 ? 32 : shift); | |
} | |
/** | |
* Writes an signed integer to the packet using a variable width encoding. | |
* @param {uint32} value The signed integer value. | |
* @param {uint32} bits The number of bits to use for the sequence. Choose a bits value that represents the number of bits to hold the average value. | |
*/ | |
writeVariableWidthInt(value:uint32, bits:uint32) { | |
let shift = bits; | |
// Stop when our value can fit inside | |
for (; shift < 32 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits) { | |
this.writeBoolean(true); // Write a 1 for a continuation bit signifying one more interval is needed | |
} | |
if (shift < 32) { | |
this.writeBoolean(false); // Write a 0 for a continuation bit signifying the end | |
} | |
this.writeIntN(value, shift > 32 ? 32 : shift); | |
} | |
/** | |
* Writes a 32 bit float to the packet. | |
* @param value The floating point value. | |
*/ | |
writeFloat32(value:float32) { | |
this.writeUInt32(uint32([]<float32>([value]).buffer)[0]); | |
} | |
/** | |
* Writes a 32 bit float to the packet. | |
* @param value The floating point value. | |
* @param maximum The inclusive maximum for the range starting at 0. | |
* @param bits The number of bits to use. | |
*/ | |
writeFloat32(value:float32, maximum:float32, bits:uint32) { | |
this.writeUIntN(Math.round(value / maximum * ((0x1 << bits) - 1)), bits); | |
} | |
/** | |
* Writes a 32 bit float to the packet. | |
* @param value The floating point value. | |
* @param minimum The inclusive minimum for the range. | |
* @param maximum The inclusive maximum for the range. | |
* @param bits The number of bits to use. | |
*/ | |
writeFloat32(value:float32, minimum:float32, maximum:float32, bits:uint32) { | |
if (minimum < 0 && maximum > 0) { | |
this.writeUIntN(value == 0 ? 0 : Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 2)) + 1, bits); | |
} else { | |
this.writeUIntN(Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 1)), bits); | |
} | |
} | |
/** | |
* Writes a 64 bit float to the packet. | |
* @param value The floating point value. | |
*/ | |
writeFloat64(value:float64) { | |
const float64UInt32 = uint32([]<float64>([value]).buffer); | |
this.writeUInt32(float64UInt32[0]); | |
this.writeUInt32(float64UInt32[1]); | |
} | |
/** | |
* Writes a 64 bit float to the packet. | |
* @param value The floating point value. | |
* @param maximum The inclusive maximum for the range starting at 0. | |
* @param bits The number of bits to use. | |
*/ | |
writeFloat64(value:float64, maximum:float64, bits:uint32) { | |
this.writeUIntN(Math.round(value / maximum * ((0x1 << bits) - 1)), bits); | |
} | |
/** | |
* Writes a 64 bit float to the packet. | |
* @param value The floating point value. | |
* @param minimum The inclusive minimum for the range. | |
* @param maximum The inclusive maximum for the range. | |
* @param bits The number of bits to use. | |
*/ | |
writeFloat64(value:float64, minimum:float64, maximum:float64, bits:uint32) { | |
if (minimum < 0 && maximum > 0) { | |
this.writeUIntN(value == 0 ? 0 : Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 2)) + 1, bits); | |
} else { | |
this.writeUIntN(Math.round((value - minimum) / (maximum - minimum) * ((0x1 << bits) - 1)), bits); | |
} | |
} | |
/** | |
* Writes a string to the packet. | |
* @param value The string value. | |
*/ | |
writeString(value:string) { | |
this.writeUInt16(value.length); | |
for (let index = 0; index < value.length; ++index) { | |
this.writeUIntN(value.charCodeAt(index), 7); | |
} | |
} | |
/** | |
* Writes a packet to the packet. | |
* @param value The packet. | |
*/ | |
writePacket(value:Packet) { | |
value.#bitIndex = 0; | |
for (let copyBits = 0; copyBits < value.MaximumBitIndex; copyBits += 32) { | |
const bits = value.MaximumBitIndex - copyBits > 32 ? 32 : value.MaximumBitIndex - copyBits; | |
// Read n-bits from value. | |
let valueUIntN:uint32 = 0; | |
let offset = value.bitIndex % 32; | |
valueUIntN |= value.buffer[value.bitIndex / 32] << offset >> 32 - bits; | |
if (offset > 32 - bits) { | |
valueUIntN |= value.buffer[value.bitIndex / 32 + 1] >> 64 - bits - offset; | |
} | |
value.bitIndex += bits; | |
// Write n-bits to the buffer. | |
offset = this.#bitIndex % 32; | |
this.#buffer[this.#bitIndex / 32] |= valueUIntN << 32 - bits >> offset; | |
if (offset > 32 - bits) { | |
this.#buffer[this.#bitIndex / 32 + 1] |= valueUIntN << 64 - bits - offset; | |
} | |
this.#bitIndex += bits; | |
} | |
} | |
/** | |
* Reads an event from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readEvent(callback:(uint8, Packet):undefined) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 8 > this.#maximumBitIndex) { | |
this.#error('Event expected'); | |
} | |
this.readUInt8(callback); | |
} | |
/** | |
* Reads a 1-bit boolean from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readBoolean(callback:(boolean, Packet)) { | |
if (this.#error == null) return; | |
let value = false; | |
if (this.#bitIndex + 1 > this.#maximumBitIndex) { | |
this.#error('Boolean expected'); | |
} else { | |
value = (this.#buffer[this.#bitIndex / 32] << this.#bitIndex % 32 >> 31) == 1; | |
this.#bitIndex++; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads an 8-bit unsigned integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUInt8(callback:(uint8, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 8 > this.#maximumBitIndex) { | |
this.#error('uint8 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 24; | |
if (offset > 24) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 56 - offset; | |
} | |
this.#bitIndex += 8; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads an 8-bit signed integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readInt8(callback:(int8, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 8 > this.#maximumBitIndex) { | |
this.#error('int8 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 24; | |
if (offset > 24) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 56 - offset; | |
} | |
this.#bitIndex += 8; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads a 16-bit unsigned integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUInt16(callback:(uint16, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 16 > this.#maximumBitIndex) { | |
this.#error('uint16 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 16; | |
if (offset > 16) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 48 - offset; | |
} | |
this.#bitIndex += 16; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads a 16-bit signed integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readInt16(callback:(int16, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 16 > this.#maximumBitIndex) { | |
this.#error('int16 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 16; | |
if (offset > 16) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 48 - offset; | |
} | |
this.#bitIndex += 16; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads a 32-bit unsigned integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUInt32(callback:(uint32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 32 > this.#maximumBitIndex) { | |
this.#error('uint32 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset; | |
if (offset > 0) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 32 - offset; | |
} | |
this.#bitIndex += 32; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads a 32-bit signed integer from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readInt32(callback:(int32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 32 > this.#maximumBitIndex) { | |
this.#error('int32 expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset; | |
if (offset > 0) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 32 - offset; | |
} | |
this.#bitIndex += 32; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads an n-bit unsigned integer from the packet. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUIntN(bits:uint32, callback:(uint32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('uint' + bits + ' expected'); | |
} else { | |
let value = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 32 - bits; | |
if (offset > 32 - bits) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 64 - bits - offset; | |
} | |
this.#bitIndex += bits; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads an n-bit signed integer from the packet. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readIntN(bits:uint32, callback:(int32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('int' + bits + ' expected'); | |
} else { | |
let value:uint32 = 0; | |
const offset = this.#bitIndex % 32; | |
value |= this.#buffer[this.#bitIndex / 32] << offset >> 32 - bits; | |
if (offset > 32 - bits) { | |
value |= this.#buffer[this.#bitIndex / 32 + 1] >> 64 - bits - offset; | |
} | |
this.#bitIndex += bits; | |
callback(value, this); | |
} | |
} | |
/** | |
* Reads an unsigned integer from the packet. | |
* @param maximum The inclusive maximum used for the range starting at 0. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUInt(maximum:uint32, callback:(uint32, Packet)) { | |
if (this.#error == null) return; | |
const bits = this.Log2(maximum); | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('uint' + bits + ' with range [' + minimum + ', ' + maximum + '] expected'); | |
} else { | |
this.readUIntN(bits, (value) => { | |
callback(value + minimum, this); | |
}); | |
} | |
} | |
/** | |
* Reads an unsigned integer from the packet. | |
* @param minimum The inclusive minimum used for the range. | |
* @param maximum The inclusive maximum used for the range. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readUInt(minimum:uint32, maximum:uint32, callback:(uint32, Packet)) { | |
if (this.#error == null) return; | |
const bits = this.Log2(maximum - minimum); | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('uint' + bits + ' with range [' + minimum + ', ' + maximum + '] expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
callback(value + minimum, this); | |
}); | |
} | |
} | |
/** | |
* Reads a signed integer from the packet. | |
* @param maximum The inclusive maximum used for the range starting at 0. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readInt(maximum:int32, callback:(int32, Packet)) { | |
if (this.#error == null) return; | |
const bits = this.Log2(maximum); | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('int' + bits + ' with range [' + minimum + ', ' + maximum + '] expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
callback(value + minimum, this); | |
}); | |
} | |
} | |
/** | |
* Reads a signed integer from the packet. | |
* @param minimum The inclusive minimum used for the range. | |
* @param maximum The inclusive maximum used for the range. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readInt(minimum:int32, maximum:int32, callback:(int32, Packet)) { | |
if (this.#error == null) return; | |
const bits = this.Log2(maximum - minimum); | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('int' + bits + ' with range [' + minimum + ', ' + maximum + '] expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
callback(value + minimum, this); | |
}); | |
} | |
} | |
/** | |
* Reads an unsigned integer from the packet that was written using an n-bit variable width encoding. | |
* @param bits The number of bits used for the sequence. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readVariableWidthUInt(bits:uint32, callback:(uint32, Packet)) { | |
if (this.#error == null) return; | |
let bitCount = 0; | |
let continuationBitValue; | |
do { | |
continuationBitValue = false; | |
this.readBoolean(continuationBit => { | |
continuationBitValue = continuationBit; | |
bitCount += bits; | |
}); | |
} | |
while (continuationBitValue && bitCount < 32); | |
if (bitCount > 32) { | |
this.#error('uint expected'); | |
} else { | |
this.readUIntN(bitCount, callback); | |
} | |
} | |
/** | |
* Read a signed integer from the packet that was written using an n-bit variable width encoding. | |
* @param bits The number of bits used for the sequence. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readVariableWidthInt(bits, callback:(int32, Packet)) { | |
if (this.#error == null) return; | |
let bitCount = 0; | |
let continuationBitValue; | |
do { | |
continuationBitValue = false; | |
this.readBoolean(continuationBit => { | |
continuationBitValue = continuationBit; | |
bitCount += bits; | |
}); | |
} | |
while (continuationBitValue && bitCount < 32); | |
if (bitCount > 32) { | |
this.#error('int expected'); | |
} else { | |
this.readIntN(bitCount, callback); | |
} | |
} | |
/** | |
* Reads a 32-bit floating point from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat32(callback:(float32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 32 > this.#maximumBitIndex) { | |
this.#error('float32 expected'); | |
} else { | |
const float32Value = new [1]<float32>; | |
const uint32View = []<uint32>(float32Value.buffer); | |
this.readUInt32(value => { | |
uint32View[0] = value; | |
}); | |
callback(float32Value[0], this); | |
} | |
} | |
/** | |
* Reads a 32-bit floating point from the packet. | |
* @param maximum The inclusive maximum used for the range starting at 0. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat32(maximum:float32, bits:uint32, callback:(float32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('float32 expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
callback(value / ((0x1 << bits) - 1) * maximum, this); | |
}); | |
} | |
} | |
/** | |
* Reads a 32-bit floating point from the packet. | |
* @param minimum The inclusive minimum used for the range. | |
* @param maximum The inclusive maximum used for the range. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat32(minimum:float32, maximum:float32, bits:uint32, callback:(float32, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('float32 expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
if (minimum < 0 && maximum > 0) { | |
callback(value == 0 ? 0 : (value - 1) / ((0x1 << bits) - 2) * (maximum - minimum) + minimum, this); | |
} else { | |
callback(value / ((0x1 << bits) - 1) * (maximum - minimum) + minimum, this); | |
} | |
}); | |
} | |
} | |
/** | |
* Reads a 64-bit floating point from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat64(callback:(float64, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + 64 > this.#maximumBitIndex) { | |
this.#error('float64 expected'); | |
} else { | |
const float64Value = new [1]<float64>; | |
const uint32View = []<uint32>(float64Value.buffer); | |
this.readUInt32(value => { | |
uint32View[0] = value; | |
}); | |
this.readUInt32(value => { | |
uint32View[1] = value; | |
}); | |
callback(float64Value[0], this); | |
} | |
} | |
/** | |
* Reads a 64-bit floating point from the packet. | |
* @param maximum The inclusive maximum used for the range starting at 0. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat64(maximum:float64, bits:uint32, callback:(float64, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('float64 expected'); | |
} else { | |
this.readUIntN(bits, (value) => { | |
callback(value / ((0x1 << bits) - 1) * maximum, this); | |
}); | |
} | |
} | |
/** | |
* Reads a 64-bit floating point from the packet. | |
* @param minimum The inclusive minimum used for the range. | |
* @param maximum The inclusive maximum used for the range. | |
* @param bits The number of bits used. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readFloat64(minimum:float64, maximum:float64, bits:uint32 callback:(float64, Packet)) { | |
if (this.#error == null) return; | |
if (this.#bitIndex + bits > this.#maximumBitIndex) { | |
this.#error('float64 expected'); | |
} else { | |
this.readUIntN(bits, value => { | |
if (minimum < 0 && maximum > 0) { | |
callback(value == 0 ? 0 : (value - 1) / ((0x1 << bits) - 2) * (maximum - minimum) + minimum, this); | |
} else { | |
callback(value / ((0x1 << bits) - 1) * (maximum - minimum) + minimum, this); | |
} | |
}); | |
} | |
} | |
/** | |
* Reads a string from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readString(callback:(string, Packet)) { | |
let value = ''; | |
this.readUInt16(length => { | |
for (let index = 0; index < length; ++index) { | |
this.readUIntN(7, (charCode) => { | |
value += string.fromCharCode(charCode); | |
}); | |
} | |
callback(value, this); | |
}); | |
} | |
/** | |
* Reads a packet from the packet. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readPacket(callback:(Packet, Packet)) { | |
if (this.#error == null) return; | |
this.readUIntN(this.headerSize, maximumBitIndex => { | |
if (this.#bitIndex + maximumBitIndex - this.headerSize > this.#maximumBitIndex) { | |
this.#error('Packet expected'); | |
} else { | |
const value = new Packet(this.#buffer.buffer, this.#error); | |
value.#bitIndex = this.#bitIndex; | |
value.#maximumBitIndex = this.#bitIndex + maximumBitIndex - this.headerSize; | |
this.#bitIndex += maximumBitIndex - this.headerSize; | |
callback(value, this); | |
} | |
}); | |
} | |
/** | |
* Reads a block of data with one callback. | |
* @param readOperations A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string 'UInt8'. For ReadUIntN | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
read(...readOperations, callback:(..., Packet)) { | |
callback.call(this, ...readOperations.map(readOperation => { | |
if (typeof readOperation == 'string') { | |
let returnValue; | |
this['read' + readOperation](value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} else { | |
let returnValue; | |
this['read' + readOperation.shift()].call(call, ...readOperation, value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} | |
}), this); | |
} | |
/** | |
* Reads a boolean and executes read operations on true. | |
* @param trueReadOperations A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string 'UInt8'. | |
* @param callback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readConditional(...trueReadOperations, trueCallback:(..., Packet)) { | |
this.readBoolean(conditional => { | |
if (conditional) { | |
trueCallback(...trueReadOperations.map(readOperation => { | |
if (typeof readOperation == 'string') { | |
let returnValue; | |
this['read' + readOperation](value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} else { | |
let returnValue; | |
this['read' + readOperation.shift()].call(this, ...readOperation, value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} | |
}), this); | |
} | |
}); | |
} | |
/** | |
* Reads a boolean and executes read operations on true or false. | |
* @param trueReadOperations A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string 'UInt8'. | |
* @param trueCallback A callback with the result. Only executes if the read succeeds. | |
* @param falseReadOperations A datatype string or array starting with a datatype string followed by arguments. As an example to use ReadUInt8 you would use the datatype string 'UInt8'. | |
* @param falseCallback A callback with the result. Only executes if the read succeeds. | |
*/ | |
readConditional(...trueReadOperations, trueCallback:(..., Packet), ...falseReadOperations, falseCallback:(..., Packet)) { | |
this.readBoolean(conditional => { | |
if (conditional) { | |
trueCallback(...trueReadOperations.map(readOperation => { | |
if (typeof readOperation == 'string') { | |
let returnValue; | |
this['read' + readOperation](value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} else { | |
let returnValue; | |
this['read' + readOperation.shift()].call(this, ...readOperation, value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} | |
}), this); | |
} else { | |
falseCallback(...falseReadOperations.map(readOperation => { | |
if (typeof readOperation == 'string') { | |
let returnValue; | |
this['read' + readOperation](value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} else { | |
let returnValue; | |
this['read' + readOperation.shift()].call(this, ...readOperation, value => { | |
returnValue = value; | |
}); | |
return returnValue; | |
} | |
}), this); | |
} | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment