BifBuffer - Fast and compact Bitwise buffer

Long story short this is a buffer that manipulates bits rather than bytes (what the usual buffer does)

it has everything a bit buffer would need except writing/reading floats, which I find unnecessary because you wouldn’t find yourself doing that

The BitBuffer uses buffer as the base rather than the old method of using string methods

Documentation


BitBufferLibrary methods

BitBufferLibrary.new(size: number) : BitBuffer

  • Creates a BitBuffer based on given size (in bits), the number can be any positive integer, if the size is not divisible by 8 it would just round it to the closest possible byte

BitBufferLibrary.fromBuffer(buffer: buffer) : BitBuffer

  • Creates a BitBuffer from a buffer

BitBuffer

BitBuffer.tostring(i: number?, j: number?) : string

  • Reads out the bits from the BitBuffer or from given range from the BitBuffer

BitBuffer.getRawBuffer() : buffer

  • Returns you the BitBuffer as a buffer

BitBuffer.canRead(bit: number) : boolean

  • Tells you if the buffer can read certian amounts of bits, this method is primary used to detect when the BitBuffer reaches it end since there is no manual reading

BitBuffer.readBit() : number

  • Reads the current bit

BitBuffer.readUnsignedInteger(size: number) : number

  • Reads unsigned integer from given size in bits

BitBuffer.readSignedInteger(size: number) : number

  • Reads signed integer from given size in bits

BitBuffer.rawReadBit(offset: number) : number

  • Reads the current bit at given offset

BitBuffer.rawReadUnsignedInteger(size: number, offset: number) : number

  • Reads unsigned integer from given size in bits at given offset

BitBuffer.rawReadSignedInteger(size: number, offset: number) : number

  • Reads signed integer from given size in bits at given offset

BitBuffer.setReaderOffset(offset: number?)

  • Either resets or sets the reading offset

BitBuffer.writeBit(offset: number, value: number)

  • Writes a bit at certain offset

BitBuffer.writeUnsignedInteger(offset: number, size: number, value: number)

  • Writes a unsigned integer from given bit size at certain offset

BitBuffer.writeSignedInteger(offset: number, size: number, value: number)

  • Writes a signed integer from given bit size at certain offset

Important information

  • The BitBuffer works exactly how the buffer does, meaning it uses little endian.
  • Just as buffers the first element starts from 0 meaning its not the traditional way (for lua at least) of accessing data (from tables as example where you access the first element with index 1 and not 0)
  • This currently does not support floats but its in the works

Code

export type BitBuffer = {	
	tostring: (from: number?, to: number?) -> string,
	canRead: (Bits: number?) -> boolean,
	getRawBuffer: () -> buffer,

	readBit: () -> (number),
	readUnsignedInteger: (IntegerSize: number) -> (number),
	readSignedInteger: (IntegerSize: number) -> (number),
	
	rawReadBit: (Offset: number) -> (number),
	rawReadUnsignedInteger: (IntegerSize: number, Offset: number) -> (number),
	rawReadSignedInteger: (IntegerSize: number, Offset: number) -> (number),
	
	setReaderOffset: (Offset: number?) -> (),

	writeBit: (Offset: number, Value: number) -> (),
	writeUnsignedInteger: (Offset: number, IntegerSize: number, Value: number) -> (),
	writeSignedInteger: (Offset: number, IntegerSize: number, Value: number) -> (),
}

local BitBufferLibrary = {}

local function ToBase2(number)
	local base2 = ""

	while number > 0 do
		base2 = (number % 2) .. base2
		number //= 2
	end

	return base2
end

local function LeftPad(str, len)
	return string.rep(0, len - #str) .. str
end

local function readBit(Buffer, Offset)
	local Byte = Offset // 8
	local Num = buffer.readu8(Buffer, Byte)

	return bit32.extract(Num, 7 - Offset % 8)
end

local function writeBit(Buffer, Offset, Value)
	local Byte = Offset // 8
	local Num = bit32.replace(buffer.readu8(Buffer, Byte), Value, 7 - Offset % 8)

	buffer.writeu8(Buffer, Byte, Num)
end

local function readSignedInteger(Buffer, Offset, Size)
	local ByteSize = Size // 8
	local Num = 2 ^ Size * -readBit(Buffer, Offset + ByteSize * 8 - 8)

	for j = 0, ByteSize - 1 do
		local ByteOffset = j * 8

		for i = 7, 0, -1 do
			local Bit = readBit(Buffer, Offset + ByteOffset + i)

			Num += Bit * 2 ^ (j * 8 + 7 - i)
		end
	end

	return Num
end

local function writeSignedInteger(Buffer, Offset, Size, Value)
	local ByteSize = Size // 8

	if Value < 0 then
		writeBit(Buffer, Offset + ByteSize * 8 - 7, 1)
	else
		writeBit(Buffer, Offset + ByteSize * 8 - 7, 0)
	end

	for j = 0, ByteSize do
		local ByteOffset = j * 8

		for i = 7, 0, -1 do
			writeBit(Buffer, Offset + ByteOffset + i, Value % 2)
			Value //= 2
		end
	end
end

local function readUnsignedInteger(Buffer, Offset, Size)
	local ByteSize = Size // 8
	local Num = 0

	for j = 0, ByteSize - 1 do
		local ByteOffset = j * 8

		for i = 7, 0, -1 do
			local Bit = readBit(Buffer, Offset + ByteOffset + i)

			Num += Bit * 2 ^ (j * 8 + 7 - i)
		end
	end

	return Num
end

local function writeUnsignedInteger(Buffer, Offset, Size, Value)
	local ByteSize = Size // 8

	for j = 0, ByteSize do
		local ByteOffset = j * 8

		for i = 7, 0, -1 do
			writeBit(Buffer, Offset + ByteOffset + i, Value % 2)
			Value //= 2
		end
	end
end

local function CreateBitBuffer(Buffer: buffer) : BitBuffer
	local BitBufferLength = buffer.len(Buffer) * 8
	local OffsetReader = 0

	return {
		tostring = function(i, j)
			i = i or 0
			j = j and j - 1 or BitBufferLength - 1

			local String = ""
			local StartByte, EndByte = i // 8, j // 8

			for k = StartByte, EndByte do
				String ..= LeftPad(ToBase2(buffer.readu8(Buffer, k)), 8)
			end

			return String
		end,

		getRawBuffer = function()
			return Buffer
		end,

		canRead = function(Bit)
			return OffsetReader + Bit <= BitBufferLength
		end,


		readBit = function()
			local Bit = readBit(Buffer, OffsetReader)
			OffsetReader += 1

			return Bit
		end,

		readUnsignedInteger = function(Size)
			local IntSize = readUnsignedInteger(Buffer, OffsetReader, Size)
			OffsetReader += Size

			return IntSize
		end,

		readSignedInteger = function(Size)
			local IntSize = readSignedInteger(Buffer, OffsetReader, Size)
			OffsetReader += Size

			return IntSize
		end,
		
		
		rawReadBit = function(Offset)
			return readBit(Buffer, Offset)
		end,

		rawReadUnsignedInteger = function(Size , Offset)
			return readUnsignedInteger(Buffer, OffsetReader, Size)
		end,

		rawReadSignedInteger = function(Size, Offset)
			return readSignedInteger(Buffer, Offset, Size)
		end,


		setReaderOffset = function(Offset)
			OffsetReader = Offset or 0
		end,


		writeBit = function(Offset, Value)
			writeBit(Buffer, Offset, Value)
		end,

		writeUnsignedInteger = function(Offset, Size, Value)
			writeUnsignedInteger(Buffer, Offset, Size, Value)
		end,

		writeSignedInteger = function(Offset, Size, Value)
			writeSignedInteger(Buffer, Offset, Size, Value)
		end,
	}
end

function BitBufferLibrary.fromBuffer(Buffer: buffer) : BitBuffer
	return CreateBitBuffer(Buffer)
end

function BitBufferLibrary.new(BitSize: number) : BitBuffer
	local Buffer = buffer.create(BitSize // 8 + 1)
	buffer.fill(Buffer, 0, 0)

	return CreateBitBuffer(Buffer)
end

return BitBufferLibrary

Example

local BitBufferLibrary = require(script.BitBufferLibrary)

local Buffer = buffer.create(32)
local BitBuffer = BitBufferLibrary.fromBuffer(Buffer)

buffer.writeu8(Buffer, 0, 231)
buffer.writei8(Buffer, 1, -67)

buffer.writeu16(Buffer, 2, 32561)
buffer.writei16(Buffer, 4, -23561)

buffer.writeu32(Buffer, 6, 2365236126)
buffer.writei32(Buffer, 10, -1235235235)

print(BitBuffer.readUnsignedInteger(8))
print(BitBuffer.readSignedInteger(8))

print(BitBuffer.readUnsignedInteger(16))
print(BitBuffer.readSignedInteger(16))

print(BitBuffer.readUnsignedInteger(32))
print(BitBuffer.readSignedInteger(32))

Images


Thats all

5 Likes