Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Created January 15, 2020 01:59
Show Gist options
  • Save howmanysmall/e87f8f8c40ef71c9418f6715aafdf001 to your computer and use it in GitHub Desktop.
Save howmanysmall/e87f8f8c40ef71c9418f6715aafdf001 to your computer and use it in GitHub Desktop.
-- This is the original module, 7% slower with type checks on both, 14% without type checks.
--[[
Differences from the original:
Using metatables instead of a function returning a table.
Added Vector3, Color3, Vector2, and UDim2 support.
Deprecated BrickColors.
Changed the creation method from BitBuffer.Create to BitBuffer.new.
OPTIMIZED!
Added a ::Destroy method.
THE API:
Constructor: BitBuffer.new()
Read/Write pairs for reading data from or writing data to the BitBuffer:
BitBuffer::WriteUnsigned(BitWidth: number, Value: number): void
BitBuffer::ReadUnsigned(BitWidth: number): number
Read / Write an unsigned value with a given number of bits. The
value must be a positive integer. For instance, if BitWidth is
4, then there will be 4 magnitude bits, for a value in the
range [0, 2 ^ 4 - 1] = [0, 15]
BitBuffer::WriteSigned(BitWidth: number, Value: number): void
BitBuffer::ReadSigned(BitWidth: number): number
Read / Write a a signed value with a given number of bits. For
instance, if BitWidth is 4 then there will be 1 sign bit and
3 magnitude bits, a value in the range [-2 ^ 3 + 1, 2 ^ 3 - 1] = [-7, 7]
BitBuffer:WriteFloat(MantissaBitWidth: number, ExponentBitWidth: number, Value: number): void
BitBuffer:ReadFloat(MantissaBitWidth, ExponentBitWidth): number
Read / Write a floating point number with a given mantissa and
exponent size in bits.
BitBuffer::WriteFloat8(Float: number): void
BitBuffer::ReadFloat8(): number
BitBuffer::WriteFloat16(Float: number): void
BitBuffer::ReadFloat16(): number
BitBuffer::WriteFloat32(Float: number): void
BitBuffer::ReadFloat32(): number
BitBuffer::WriteFloat64(Float: number): void
BitBuffer::ReadFloat64(): number
Read and write the common types of floating point number that
are used in code. If you want to 100% accurately save an
arbitrary Lua number, then you should use the Float64 format. If
your number is known to be smaller, or you want to save space
and don't need super high precision, then a Float32 will often
suffice. For instance, the Transparency of an object will do
just fine as a Float32.
BitBuffer::WriteBool(Boolean: boolean): void
BitBuffer::ReadBool(): boolean
Read / Write a boolean (true / false) value. Takes one bit worth of space to store.
BitBuffer::WriteString(String: string): void
BitBuffer::ReadString(): string
Read / Write a variable length string. The string may contain embedded nulls.
Only 7 bits / character will be used if the string contains no non-printable characters (greater than 0x80).
****** PLEASE DON'T USE THIS. USE ::WRITECOLOR3 INSTEAD. ******
BitBuffer::WriteBrickColor(Color: BrickColor): void
BitBuffer::ReadBrickColor(): BrickColor
Read / Write a Roblox BrickColor. Provided as an example of reading / writing a derived data type.
Please don't actually use this, just use ::WriteColor3 instead.
BitBuffer::WriteColor3(Color: Color3): void
BitBuffer::ReadColor3(): Color3
Read / Write a Roblox Color3. Use this over the BrickColor methods, PLEASE.
BitBuffer::WriteRotation(CoordinateFrame: CFrame): void
BitBuffer::ReadRotation(): CFrame
Read / Write the rotation part of a given CFrame. Encodes the
rotation in question into 64bits, which is a good size to get
a pretty dense packing, but still while having errors well within
the threshold that Roblox uses for stuff like MakeJoints()
detecting adjacency. Will also perfectly reproduce rotations which
are orthagonally aligned, or inverse-power-of-two rotated on only
a single axix. For other rotations, the results may not be
perfectly stable through read-write cycles (if you read/write an
arbitrary rotation thousands of times there may be detectable
"drift")
BitBuffer::WriteVector3(Vector: Vector3): void
BitBuffer::ReadVector3(): Vector3
BitBuffer::WriteVector3Float32(Vector: Vector3): void
BitBuffer::ReadVector3Float32(): Vector3
Read / write a Vector3. Encodes the vector using 32-bit precision.
For more precision, use BitBuffer::WriteVector3Float64 instead.
BitBuffer::WriteVector3Float64(Vector: Vector3): void
BitBuffer::ReadVector3Float64(): Vector3
Read / write a Vector3. Encodes the vector using 64-bit precision.
For less precision, use BitBuffer::WriteVector3 instead.
BitBuffer::WriteVector2(Vector: Vector2): void
BitBuffer::ReadVector2(): Vector2
BitBuffer::WriteVector2Float32(Vector: Vector2): void
BitBuffer::ReadVector2Float32(): Vector2
Read / write a Vector2. Encodes the vector using 32-bit precision.
For more precision, use BitBuffer::WriteVector2Float64 instead.
BitBuffer::WriteVector2Float64(Vector: Vector2): void
BitBuffer::ReadVector2Float64(): Vector2
Read / write a Vector2. Encodes the vector using 64-bit precision.
For less precision, use BitBuffer::WriteVector2Float32 instead.
BitBuffer::WriteCFrame(CoordinateFrame: CFrame): void
BitBuffer::ReadCFrame(): CFrame
Read / write the whole CFrame. This will call both ::WriteVector3Float64 and ::WriteRotation
to save the entire CFrame, and encodes it using 64-bit precision.
BitBuffer::WriteUDim2(Value: UDim2): void
BitBuffer::ReadUDim2(): UDim2
Read / write a UDim2. Encodes the value using 32-bit precision.
From/To pairs for dumping out the BitBuffer to another format:
BitBuffer::ToString(): string
BitBuffer::FromString(String: string): void
Will replace / dump out the contents of the buffer to / from
a binary chunk encoded as a Lua string. This string is NOT
suitable for storage in the Roblox DataStores, as they do
not handle non-printable characters well.
BitBuffer::ToBase64(): string
BitBuffer::FromBase64(String: string): void
Will replace / dump out the contents of the buffer to / from
a set of Base64 encoded data, as a Lua string. This string
only consists of Base64 printable characters, so it is
ideal for storage in Roblox DataStores.
BitBuffer::ToBase128(): string
BitBuffer::FromBase128(String: string): void
Defaultio added this function. 128 characters can all be written
to DataStores, so this function packs more tightly than saving
in only 64 bit strings. Full disclosure: I have no idea what I'm
doing but I think this is useful.
Buffer / Position Manipulation
BitBuffer::ResetPointer(): void
Will Reset the point in the buffer that is being read / written
to back to the start of the buffer.
BitBuffer::Reset(): void
Will reset the buffer to a clean state, with no contents.
Example Usage:
local function SaveToBuffer(buffer, userData)
buffer:WriteString(userData.HeroName)
buffer:WriteUnsigned(14, userData.Score) --> 14 bits -> [0, 2^14-1] -> [0, 16383]
buffer:WriteBool(userData.HasDoneSomething)
buffer:WriteUnsigned(10, #userData.ItemList) --> [0, 1023]
for _, itemInfo in pairs(userData.ItemList) do
buffer:WriteString(itemInfo.Identifier)
buffer:WriteUnsigned(10, itemInfo.Count) --> [0, 1023]
end
end
local function LoadFromBuffer(buffer, userData)
userData.HeroName = buffer:ReadString()
userData.Score = buffer:ReadUnsigned(14)
userData.HasDoneSomething = buffer:ReadBool()
local itemCount = buffer:ReadUnsigned(10)
for i = 1, itemCount do
local itemInfo = {}
itemInfo.Identifier = buffer:ReadString()
itemInfo.Count = buffer:ReadUnsigned(10)
table.insert(userData.ItemList, itemInfo)
end
end
--...
local buff = BitBuffer.new()
SaveToBuffer(buff, someUserData)
myDataStore:SetAsync(somePlayer.userId, buff:ToBase64())
--...
local data = myDataStore:GetAsync(somePlayer.userId)
local buff = BitBuffer.new()
buff:FromBase64(data)
LoadFromBuffer(buff, someUserData)
--]]
-- This is quite possibly the fastest BitBuffer module.
local BitBuffer = {
ClassName = "BitBuffer";
__tostring = function()
return "BitBuffer"
end;
}
BitBuffer.__index = BitBuffer
local CHAR_0X10 = string.char(0x10)
local LOG_10_OF_2 = math.log10(2)
local DEPRECATED_WARNING = true
local DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local function ToBase(Number, Base)
Number = Number - Number % 1
if not Base or Base == 10 then
return tostring(Number)
end
local Array = {}
local Sign = ""
if Number < 0 then
Sign = "-"
Number = 0 - Number
end
repeat
local Index = (Number % Base) + 1
Number = Number / Base
Number = Number - Number % 1
table.insert(Array, 1, string.sub(DIGITS, Index, Index))
until Number == 0
return Sign .. table.concat(Array)
end
local function DetermineType(Value)
local ActualType = typeof(Value)
if ActualType == "number" then
if Value % 1 == 0 then
return Value < 0 and "negative integer" or "positive integer"
else
return Value < 0 and "negative number" or "positive number"
end
elseif ActualType == "table" then
local Key = next(Value)
if DetermineType(Key) == "positive integer" then
return "array"
else
return "dictionary"
end
else
return ActualType
end
end
local NumberToBase64, Base64ToNumber = {}, {}
do
local CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for Index = 1, 64 do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase64[Index - 1] = Character
Base64ToNumber[Character] = Index - 1
end
end
-- Credit to Defaultio.
local NumberToBase128, Base128ToNumber = {}, {}
do
local CHARACTERS = ""
for Index = 0, 127 do
CHARACTERS = CHARACTERS .. string.char(Index)
end
for Index = 1, 128 do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase128[Index - 1] = Character
Base128ToNumber[Character] = Index - 1
end
end
local PowerOfTwo = setmetatable({}, {
__index = function(self, Index)
local Value = 2 ^ Index
self[Index] = Value
return Value
end;
})
for Index = 0, 128 do
local _ = PowerOfTwo[Index]
end
local BrickColorToNumber, NumberToBrickColor = {}, {}
do
for Index = 0, 63 do
local Color = BrickColor.palette(Index)
BrickColorToNumber[Color.Number] = Index
NumberToBrickColor[Index] = Color
end
end
--[[**
Creates a new BitBuffer.
@returns [BitBuffer] The new BitBuffer.
**--]]
function BitBuffer.new()
return setmetatable({
BitPointer = 0;
mBitBuffer = {};
}, BitBuffer)
end
--[[**
Resets the BitBuffer's BitPointer.
@returns [void]
**--]]
function BitBuffer:ResetPointer()
self.BitPointer = 0
end
--[[**
Resets the BitBuffer's BitPointer and buffer table.
@returns [void]
**--]]
function BitBuffer:Reset()
self.mBitBuffer, self.BitPointer = {}, 0
end
--[[**
Reads the given string and writes to the BitBuffer accordingly. Not really useful.
@param [t:string] String The string.
@returns [void]
**--]]
function BitBuffer:FromString(String)
if type(String) ~= "string" then
error(string.format("bad argument #1 in BitBuffer::FromString (string expected, instead got %s)", typeof(String)), 2)
end
self.mBitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, #String do
local ByteCharacter = string.byte(String, Index, Index)
for _ = 1, 8 do
BitPointerValue = BitPointerValue + 1
self.BitPointer = BitPointerValue
mBitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
end
self.BitPointer = 0
end
--[[**
Writes the BitBuffer to a string.
@returns [t:string] The BitBuffer string.
**--]]
function BitBuffer:ToString()
local String = ""
local Accumulator = 0
local Power = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, math.ceil(#mBitBuffer / 8) * 8 do
Accumulator = Accumulator + PowerOfTwo[Power] * (mBitBuffer[Index] or 0)
Power = Power + 1
if Power >= 8 then
String = String .. string.char(Accumulator)
Accumulator = 0
Power = 0
end
end
return String
end
--[[**
Reads the given Base64 string and writes to the BitBuffer accordingly.
@param [t:string] String The Base64 string.
@returns [void]
**--]]
function BitBuffer:FromBase64(String)
if type(String) ~= "string" then
error(string.format("bad argument #1 in BitBuffer::FromBase64 (string expected, instead got %s)", typeof(String)), 2)
end
self.mBitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, #String do
local Character = string.sub(String, Index, Index)
local ByteCharacter = Base64ToNumber[Character]
if not ByteCharacter then
error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 2)
end
for _ = 1, 6 do
BitPointerValue = BitPointerValue + 1
self.BitPointer = BitPointerValue
mBitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base64ToNumber[Character], 16) .. " too large", 2)
end
end
self.BitPointer = 0
end
--[[**
Writes the BitBuffer to a Base64 string.
@returns [t:string] The BitBuffer encoded in Base64.
**--]]
function BitBuffer:ToBase64()
local Array = {}
local Length = 0
local Accumulator = 0
local Power = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, math.ceil(#mBitBuffer / 6) * 6 do
Accumulator = Accumulator + PowerOfTwo[Power] * (mBitBuffer[Index] or 0)
Power = Power + 1
if Power >= 6 then
Length = Length + 1
Array[Length] = NumberToBase64[Accumulator]
Accumulator = 0
Power = 0
end
end
return table.concat(Array)
end
--[[**
Reads the given Base128 string and writes to the BitBuffer accordingly. Not recommended. Credit to Defaultio for the original functions.
@param [t:string] String The Base128 string.
@returns [void]
**--]]
function BitBuffer:FromBase128(String)
if type(String) ~= "string" then
error(string.format("bad argument #1 in BitBuffer::FromBase128 (string expected, instead got %s)", typeof(String)), 2)
end
self.mBitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, #String do
local Character = string.sub(String, Index, Index)
local ByteCharacter = Base128ToNumber[Character]
if not ByteCharacter then
error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 2)
end
for _ = 1, 7 do
BitPointerValue = BitPointerValue + 1
self.BitPointer = BitPointerValue
mBitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base128ToNumber[Character], 16) .. " too large", 2)
end
end
self.BitPointer = 0
end
--[[**
Writes the BitBuffer to Base128. Not recommended. Credit to Defaultio for the original functions.
@returns [t:string] The BitBuffer encoded in Base128.
**--]]
function BitBuffer:ToBase128()
local Array = {}
local Length = 0
local Accumulator = 0
local Power = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, math.ceil(#mBitBuffer / 7) * 7 do
Accumulator = Accumulator + PowerOfTwo[Power] * (mBitBuffer[Index] or 0)
Power = Power + 1
if Power >= 7 then
Length = Length + 1
Array[Length] = NumberToBase128[Accumulator]
Accumulator = 0
Power = 0
end
end
return table.concat(Array)
end
--[[**
Dumps the BitBuffer data and prints it.
@returns [void]
**--]]
function BitBuffer:Dump()
local String = ""
local String2 = ""
local Accumulator = 0
local Power = 0
local mBitBuffer = self.mBitBuffer
for Index = 1, math.ceil(#mBitBuffer / 8) * 8 do
String2 = String2 .. (mBitBuffer[Index] or 0)
Accumulator = Accumulator + PowerOfTwo[Power] * (mBitBuffer[Index] or 0)
Power = Power + 1
if Power >= 8 then
String2 = String2 .. " "
String = String .. "0x" .. ToBase(Accumulator, 16) .. " "
Accumulator = 0
Power = 0
end
end
print("[Dump] Bytes:", String)
print("[Dump] Bits:", String2)
end
function BitBuffer:_ReadBit()
self.BitPointer = self.BitPointer + 1
return self.mBitBuffer[self.BitPointer]
end
--[[**
Writes an unsigned number to the BitBuffer.
@param [t:integer] Width The bit width of the value.
@param [t:integer] Value The unsigned integer.
@returns [void]
**--]]
function BitBuffer:WriteUnsigned(Width, Value)
if type(Width) ~= "number" then
error(string.format("bad argument #1 in BitBuffer::WriteUnsigned (number expected, instead got %s)", DetermineType(Width)), 2)
end
if not (Value or type(Value) == "number" or Value >= 0 or Value % 1 == 0) then
error(string.format("bad argument #2 in BitBuffer::WriteUnsigned (positive integer expected, instead got %s)", DetermineType(Value)), 2)
end
local mBitBuffer = self.mBitBuffer
-- Store LSB first
for _ = 1, Width do
self.BitPointer = self.BitPointer + 1
mBitBuffer[self.BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. Width .. " bits", 2)
end
end
--[[**
Reads an unsigned integer from the BitBuffer.
@param [t:integer] Width The bit width of the value.
@returns [t:integer] The unsigned integer.
**--]]
function BitBuffer:ReadUnsigned(Width)
local Value = 0
for Index = 1, Width do
Value = Value + self:_ReadBit() * PowerOfTwo[Index - 1]
end
return Value
end
--[[**
Writes a signed integer to the BitBuffer.
@param [t:integer] Width The bit width of the value.
@param [t:integer] Value The signed integer.
@returns [void]
**--]]
function BitBuffer:WriteSigned(Width, Value)
if not (Width and Value) then
error("bad arguments in BitBuffer::WriteSigned (missing values)", 2)
end
if Value % 1 ~= 0 then
error("Non-integer value to BitBuffer::WriteSigned", 2)
end
local mBitBuffer = self.mBitBuffer
-- Write sign
if Value < 0 then
self.BitPointer = self.BitPointer + 1
mBitBuffer[self.BitPointer] = 1
Value = 0 - Value
else
self.BitPointer = self.BitPointer + 1
mBitBuffer[self.BitPointer] = 0
end
self:WriteUnsigned(Width - 1, Value)
end
--[[**
Reads a signed integer from the BitBuffer.
@param [t:integer] Width The bit width of the value.
@returns [t:integer] The signed integer.
**--]]
function BitBuffer:ReadSigned(Width)
self.BitPointer = self.BitPointer + 1
return ((-1) ^ self.mBitBuffer[self.BitPointer]) * self:ReadUnsigned(Width - 1)
end
--[[**
Writes a string to the BitBuffer.
@param [t:string] String The string you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteString(String)
if type(String) ~= "string" then
error(string.format("bad argument #1 in BitBuffer::WriteString (string expected, instead got %s)", typeof(String)), 2)
end
-- First check if it's a 7 or 8 bit width of string
local StringLength = #String
local BitWidth = 7
for Index = 1, StringLength do
if string.byte(String, Index, Index) > 127 then
BitWidth = 8
break
end
end
-- Write the bit width flag
self:WriteUnsigned(1, BitWidth == 7 and 0 or 1) -- 1 for wide chars
-- Now write out the string, terminated with "0x10, 0b0"
-- 0x10 is encoded as "0x10, 0b1"
for Index = 1, StringLength do
local ByteCharacter = string.byte(String, Index, Index)
if ByteCharacter == 0x10 then
self:WriteUnsigned(BitWidth, 0x10)
self:WriteUnsigned(1, 1)
else
self:WriteUnsigned(BitWidth, ByteCharacter)
end
end
-- Write terminator
self:WriteUnsigned(BitWidth, 0x10)
self:WriteUnsigned(1, 0)
end
--[[**
Reads the BitBuffer for a string.
@returns [t:string] The string written to the BitBuffer.
**--]]
function BitBuffer:ReadString()
-- Get bit width
local BitWidth = self:ReadUnsigned(1) == 1 and 8 or 7
-- Loop
local String = ""
while true do
local Character = self:ReadUnsigned(BitWidth)
if Character == 0x10 then
if self:ReadUnsigned(1) == 1 then
String = String .. CHAR_0X10
else
break
end
else
String = String .. string.char(Character)
end
end
return String
end
--[[**
Writes a boolean to the BitBuffer.
@param [t:boolean] Boolean The value you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteBool(Boolean)
if type(Boolean) ~= "boolean" then
error(string.format("bad argument #1 in BitBuffer::WriteBool (boolean expected, instead got %s)", typeof(Boolean)), 2)
end
self:WriteUnsigned(1, Boolean and 1 or 0)
end
BitBuffer.WriteBoolean = BitBuffer.WriteBool
--[[**
Reads the BitBuffer for a boolean.
@returns [t:boolean] The boolean.
**--]]
function BitBuffer:ReadBool()
return self:ReadUnsigned(1) == 1
end
BitBuffer.ReadBoolean = BitBuffer.ReadBool
-- Read / Write a floating point number with |wfrac| fraction part
-- bits, |wexp| exponent part bits, and one sign bit.
--[[**
Writes a float to the BitBuffer.
@param [t:integer] Fraction The number of bits (probably).
@param [t:integer] WriteExponent The number of bits for the decimal (probably).
@param [t:number] Float The actual number you are writing.
@returns [void]
**--]]
function BitBuffer:WriteFloat(Fraction, WriteExponent, Float)
if not (Fraction and WriteExponent and Float) then
error("missing argument(s)", 2)
end
-- Sign
local Sign = 1
if Float < 0 then
Float = 0 - Float
Sign = -1
end
-- Decompose
local Mantissa, Exponent = math.frexp(Float)
if Exponent == 0 and Mantissa == 0 then
self:WriteUnsigned(Fraction + WriteExponent + 1, 0)
return
else
Mantissa = (Mantissa - 0.5) / 0.5 * PowerOfTwo[Fraction]
end
-- Write sign
self:WriteUnsigned(1, Sign == -1 and 1 or 0)
-- Write mantissa
Mantissa = Mantissa + 0.5
Mantissa = Mantissa - Mantissa % 1 -- Not really correct, should round up/down based on the parity of |wexp|
self:WriteUnsigned(Fraction, Mantissa)
-- Write exponent
local MaxExp = PowerOfTwo[WriteExponent - 1] - 1
self:WriteSigned(WriteExponent, Exponent > MaxExp and MaxExp or Exponent < -MaxExp and -MaxExp or Exponent)
end
--[[**
Reads a float from the BitBuffer.
@param [t:integer] Fraction The number of bits (probably).
@param [t:integer] WriteExponent The number of bits for the decimal (probably).
@returns [t:number] The float.
**--]]
function BitBuffer:ReadFloat(Fraction, WriteExponent)
if not (Fraction and WriteExponent) then
error("missing argument(s)", 2)
end
local Sign = self:ReadUnsigned(1) == 1 and -1 or 1
local Mantissa = self:ReadUnsigned(Fraction)
local Exponent = self:ReadSigned(WriteExponent)
if Exponent == 0 and Mantissa == 0 then
return 0
end
Mantissa = Mantissa / PowerOfTwo[Fraction] / 2 + 0.5
return Sign * math.ldexp(Mantissa, Exponent)
end
--[[**
Writes a float8 (quarter precision) to the BitBuffer.
@param [t:number] The float8.
@returns [void]
**--]]
function BitBuffer:WriteFloat8(Float)
self:WriteFloat(3, 4, Float)
end
--[[**
Reads a float8 (quarter precision) from the BitBuffer.
@returns [t:number] The float8.
**--]]
function BitBuffer:ReadFloat8()
local Sign = self:ReadUnsigned(1) == 1 and -1 or 1
local Mantissa = self:ReadUnsigned(3)
local Exponent = self:ReadSigned(4)
if Exponent == 0 and Mantissa == 0 then
return 0
end
Mantissa = Mantissa / PowerOfTwo[3] / 2 + 0.5
return Sign * math.ldexp(Mantissa, Exponent)
end
--[[**
Writes a float16 (half precision) to the BitBuffer.
@param [t:number] The float16.
@returns [void]
**--]]
function BitBuffer:WriteFloat16(Float)
self:WriteFloat(10, 5, Float)
end
--[[**
Reads a float16 (half precision) from the BitBuffer.
@returns [t:number] The float16.
**--]]
function BitBuffer:ReadFloat16()
local Sign = self:ReadUnsigned(1) == 1 and -1 or 1
local Mantissa = self:ReadUnsigned(10)
local Exponent = self:ReadSigned(5)
if Exponent == 0 and Mantissa == 0 then
return 0
end
Mantissa = Mantissa / PowerOfTwo[10] / 2 + 0.5
return Sign * math.ldexp(Mantissa, Exponent)
end
--[[**
Writes a float32 (single precision) to the BitBuffer.
@param [t:number] The float32.
@returns [void]
**--]]
function BitBuffer:WriteFloat32(Float)
self:WriteFloat(23, 8, Float)
end
--[[**
Reads a float32 (single precision) from the BitBuffer.
@returns [t:number] The float32.
**--]]
function BitBuffer:ReadFloat32()
local Sign = self:ReadUnsigned(1) == 1 and -1 or 1
local Mantissa = self:ReadUnsigned(23)
local Exponent = self:ReadSigned(8)
if Exponent == 0 and Mantissa == 0 then
return 0
end
Mantissa = Mantissa / PowerOfTwo[23] / 2 + 0.5
return Sign * math.ldexp(Mantissa, Exponent)
end
--[[**
Writes a float64 (double precision) to the BitBuffer.
@param [t:number] The float64.
@returns [void]
**--]]
function BitBuffer:WriteFloat64(Float)
self:WriteFloat(52, 11, Float)
end
--[[**
Reads a float64 (double precision) from the BitBuffer.
@returns [t:number] The float64.
**--]]
function BitBuffer:ReadFloat64()
local Sign = self:ReadUnsigned(1) == 1 and -1 or 1
local Mantissa = self:ReadUnsigned(52)
local Exponent = self:ReadSigned(11)
if Exponent == 0 and Mantissa == 0 then
return 0
end
Mantissa = Mantissa / PowerOfTwo[52] / 2 + 0.5
return Sign * math.ldexp(Mantissa, Exponent)
end
-- Roblox DataTypes
if DEPRECATED_WARNING then
--[[**
[DEPRECATED] Writes a BrickColor to the BitBuffer.
@param [t:BrickColor] Color The BrickColor you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteBrickColor(Color)
if typeof(Color) ~= "BrickColor" then
error(string.format("bad argument #1 in BitBuffer::WriteBrickColor (BrickColor expected, instead got %s)", typeof(Color)), 2)
end
warn("::WriteBrickColor is deprecated. Using ::WriteColor3 is suggested instead.")
local BrickColorNumber = BrickColorToNumber[Color.Number]
if not BrickColorNumber then
warn("Attempt to serialize non-pallete BrickColor \"" .. tostring(Color) .. "\" (#" .. Color.Number .. "), using Light Stone Grey instead.")
BrickColorNumber = BrickColorToNumber[1032]
end
self:WriteUnsigned(6, BrickColorNumber)
end
else
--[[**
[DEPRECATED] Writes a BrickColor to the BitBuffer.
@param [t:BrickColor] Color The BrickColor you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteBrickColor(Color)
if typeof(Color) ~= "BrickColor" then
error(string.format("bad argument #1 in BitBuffer::WriteBrickColor (BrickColor expected, instead got %s)", typeof(Color)), 2)
end
local BrickColorNumber = BrickColorToNumber[Color.Number]
if not BrickColorNumber then
warn("Attempt to serialize non-pallete BrickColor \"" .. tostring(Color) .. "\" (#" .. Color.Number .. "), using Light Stone Grey instead.")
BrickColorNumber = BrickColorToNumber[1032]
end
self:WriteUnsigned(6, BrickColorNumber)
end
end
--[[**
[DEPRECATED] Reads a BrickColor from the BitBuffer.
@returns [t:BrickColor] The BrickColor read from the BitBuffer.
**--]]
function BitBuffer:ReadBrickColor()
return NumberToBrickColor[self:ReadUnsigned(6)]
end
--[[**
Writes the rotation part of a CFrame into the BitBuffer.
@param [t:CFrame] CoordinateFrame The CFrame you wish to write.
@returns [void]
**--]]
function BitBuffer:WriteRotation(CoordinateFrame)
if typeof(CoordinateFrame) ~= "CFrame" then
error(string.format("bad argument #1 in BitBuffer::WriteRotation (CFrame expected, instead got %s)", typeof(CoordinateFrame)), 2)
end
local LookVector = CoordinateFrame.LookVector
local Azumith = math.atan2(-LookVector.X, -LookVector.Z)
local Elevation = math.atan2(LookVector.Y, math.sqrt(LookVector.X * LookVector.X + LookVector.Z * LookVector.Z))
local WithoutRoll = CFrame.new(CoordinateFrame.Position) * CFrame.Angles(0, Azumith, 0) * CFrame.Angles(Elevation, 0, 0)
local _, _, Roll = (WithoutRoll:Inverse() * CoordinateFrame):ToEulerAnglesXYZ()
-- Atan2 -> in the range [-pi, pi]
Azumith = ((Azumith / 3.1415926535898) * 2097151) + 0.5
Azumith = Azumith - Azumith % 1
Roll = ((Roll / 3.1415926535898) * 1048575) + 0.5
Roll = Roll - Roll % 1
Elevation = ((Elevation / 1.5707963267949) * 1048575) + 0.5
Elevation = Elevation - Elevation % 1
self:WriteSigned(22, Azumith)
self:WriteSigned(21, Roll)
self:WriteSigned(21, Elevation)
end
--[[**
Reads the rotation part of a CFrame saved in the BitBuffer.
@returns [t:CFrame] The rotation read from the BitBuffer.
**--]]
function BitBuffer:ReadRotation()
local Azumith = self:ReadSigned(22)
local Roll = self:ReadSigned(21)
local Elevation = self:ReadSigned(21)
Azumith = 3.1415926535898 * (Azumith / 2097151)
Roll = 3.1415926535898 * (Roll / 1048575)
Elevation = 3.1415926535898 * (Elevation / 1048575)
local Rotation = CFrame.Angles(0, Azumith, 0)
Rotation = Rotation * CFrame.Angles(Elevation, 0, 0)
Rotation = Rotation * CFrame.Angles(0, 0, Roll)
return Rotation
end
--[[**
Writes a Color3 to the BitBuffer.
@param [t:Color3] Color The color you want to write into the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteColor3(Color)
if typeof(Color) ~= "Color3" then
error(string.format("bad argument #1 in BitBuffer::WriteColor3 (Color3 expected, instead got %s)", typeof(Color)), 2)
end
local R, G, B = Color.R * 255, Color.G * 255, Color.B * 255
self:WriteUnsigned(8, R - R % 1)
self:WriteUnsigned(8, G - G % 1)
self:WriteUnsigned(8, B - B % 1)
end
--[[**
Reads a Color3 from the BitBuffer.
@returns [t:Color3] The color read from the BitBuffer.
**--]]
function BitBuffer:ReadColor3()
return Color3.fromRGB(self:ReadUnsigned(8), self:ReadUnsigned(8), self:ReadUnsigned(8))
end
--[[**
Writes a Vector3 to the BitBuffer. Writes with Float32 precision.
@param [t:Vector3] Vector The vector you want to write into the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteVector3(Vector)
if typeof(Vector) ~= "Vector3" then
error(string.format("bad argument #1 in BitBuffer::WriteVector3 (Vector3 expected, instead got %s)", typeof(Vector)), 2)
end
self:WriteFloat32(Vector.X)
self:WriteFloat32(Vector.Y)
self:WriteFloat32(Vector.Z)
end
--[[**
Reads a Vector3 from the BitBuffer. Uses Float32 precision.
@returns [t:Vector3] The vector read from the BitBuffer.
**--]]
function BitBuffer:ReadVector3()
return Vector3.new(self:ReadFloat32(), self:ReadFloat32(), self:ReadFloat32())
end
--[[**
Writes a full CFrame (position and rotation) to the BitBuffer. Uses Float64 precision.
@param [t:CFrame] CoordinateFrame The CFrame you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteCFrame(CoordinateFrame)
if typeof(CoordinateFrame) ~= "CFrame" then
error(string.format("bad argument #1 in BitBuffer::WriteCFrame (CFrame expected, instead got %s)", typeof(CoordinateFrame)), 2)
end
self:WriteVector3Float64(CoordinateFrame.Position)
self:WriteRotation(CoordinateFrame)
end
--[[**
Reads a full CFrame (position and rotation) from the BitBuffer. Uses Float64 precision.
@returns [t:CFrame] The CFrame you are reading from the BitBuffer.
**--]]
function BitBuffer:ReadCFrame()
local Position = CFrame.new(self:ReadVector3Float64())
local Azumith = self:ReadSigned(22)
local Roll = self:ReadSigned(21)
local Elevation = self:ReadSigned(21)
Azumith = 3.1415926535898 * (Azumith / 2097151)
Roll = 3.1415926535898 * (Roll / 1048575)
Elevation = 3.1415926535898 * (Elevation / 1048575)
local Rotation = CFrame.Angles(0, Azumith, 0)
Rotation = Rotation * CFrame.Angles(Elevation, 0, 0)
Rotation = Rotation * CFrame.Angles(0, 0, Roll)
return Position * Rotation
end
--[[**
Writes a Vector2 to the BitBuffer. Writes with Float32 precision.
@param [t:Vector2] Vector The vector you want to write into the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteVector2(Vector)
if typeof(Vector) ~= "Vector2" then
error(string.format("bad argument #1 in BitBuffer::WriteVector2 (Vector2 expected, instead got %s)", typeof(Vector)), 2)
end
self:WriteFloat32(Vector.X)
self:WriteFloat32(Vector.Y)
end
--[[**
Reads a Vector2 from the BitBuffer. Uses Float32 precision.
@returns [t:Vector2] The vector read from the BitBuffer.
**--]]
function BitBuffer:ReadVector2()
return Vector2.new(self:ReadFloat32(), self:ReadFloat32())
end
--[[**
Writes a UDim2 to the BitBuffer. Uses Float32 precision for the scale.
@param [t:UDim2] Value The UDim2 you are writing to the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteUDim2(Value)
if typeof(Value) ~= "UDim2" then
error(string.format("bad argument #1 in BitBuffer::WriteUDim2 (UDim2 expected, instead got %s)", typeof(Value)), 2)
end
self:WriteFloat32(Value.X.Scale)
self:WriteSigned(17, Value.X.Offset)
self:WriteFloat32(Value.Y.Scale)
self:WriteSigned(17, Value.Y.Offset)
end
--[[**
Reads a UDim2 from the BitBuffer. Uses Float32 precision for the scale.
@returns [t:UDim2] The UDim2 read from the BitBuffer.
**--]]
function BitBuffer:ReadUDim2()
return UDim2.new(self:ReadFloat32(), self:ReadSigned(17), self:ReadFloat32(), self:ReadSigned(17))
end
--[[**
Writes a Vector3 to the BitBuffer. Writes with Float64 precision.
@param [t:Vector3] Vector The vector you want to write into the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteVector3Float64(Vector)
if typeof(Vector) ~= "Vector3" then
error(string.format("bad argument #1 in BitBuffer::WriteVector3Float64 (Vector3 expected, instead got %s)", typeof(Vector)), 2)
end
self:WriteFloat64(Vector.X)
self:WriteFloat64(Vector.Y)
self:WriteFloat64(Vector.Z)
end
--[[**
Reads a Vector3 from the BitBuffer. Reads with Float64 precision.
@returns [t:Vector3] The vector read from the BitBuffer.
**--]]
function BitBuffer:ReadVector3Float64()
return Vector3.new(self:ReadFloat64(), self:ReadFloat64(), self:ReadFloat64())
end
--[[**
Writes a Vector2 to the BitBuffer. Writes with Float64 precision.
@param [t:Vector2] Vector The vector you want to write into the BitBuffer.
@returns [void]
**--]]
function BitBuffer:WriteVector2Float64(Vector)
if typeof(Vector) ~= "Vector2" then
error(string.format("bad argument #1 in BitBuffer::WriteVector2Float64 (Vector2 expected, instead got %s)", typeof(Vector)), 2)
end
self:WriteFloat64(Vector.X)
self:WriteFloat64(Vector.Y)
end
--[[**
Reads a Vector2 from the BitBuffer. Reads with Float64 precision.
@returns [t:Vector2] The vector read from the BitBuffer.
**--]]
function BitBuffer:ReadVector2Float64()
return Vector2.new(self:ReadFloat64(), self:ReadFloat64())
end
BitBuffer.WriteVector3Float32 = BitBuffer.WriteVector3
BitBuffer.ReadVector3Float32 = BitBuffer.ReadVector3
BitBuffer.WriteVector2Float32 = BitBuffer.WriteVector2
BitBuffer.ReadVector2Float32 = BitBuffer.ReadVector2
--[[**
Destroys the BitBuffer metatable.
@returns [void]
**--]]
function BitBuffer:Destroy()
self.mBitBuffer = nil
setmetatable(self, nil)
end
--[[**
Calculates the amount of bits needed for a given number.
@param [t:number] Number The number you want to use.
@returns [t:number] The amount of bits needed.
**--]]
function BitBuffer.BitsNeeded(Number)
if type(Number) ~= "number" then
error(string.format("bad argument #1 in BitBuffer.BitsNeeded (number expected, instead got %s)", typeof(Number)), 2)
end
local Bits = math.log10(Number + 1) / LOG_10_OF_2
return Bits + (1 - Bits % 1) -- Equivalent to ceil
end
-- Lower camel case support!
BitBuffer.fromString = BitBuffer.FromString
BitBuffer.toBase64 = BitBuffer.ToBase64
BitBuffer.toBase128 = BitBuffer.ToBase128
BitBuffer.readVector3Float64 = BitBuffer.ReadVector3Float64
BitBuffer.readUnsigned = BitBuffer.ReadUnsigned
BitBuffer.writeBoolean = BitBuffer.WriteBoolean
BitBuffer.bitsNeeded = BitBuffer.BitsNeeded
BitBuffer.readSigned = BitBuffer.ReadSigned
BitBuffer.readFloat8 = BitBuffer.ReadFloat8
BitBuffer.readFloat16 = BitBuffer.ReadFloat16
BitBuffer.readString = BitBuffer.ReadString
BitBuffer.readVector2Float64 = BitBuffer.ReadVector2Float64
BitBuffer.destroy = BitBuffer.Destroy
BitBuffer.readColor3 = BitBuffer.ReadColor3
BitBuffer.writeVector2Float64 = BitBuffer.WriteVector2Float64
BitBuffer.readVector2 = BitBuffer.ReadVector2
BitBuffer.readBool = BitBuffer.ReadBool
BitBuffer.readBrickColor = BitBuffer.ReadBrickColor
BitBuffer.dump = BitBuffer.Dump
BitBuffer.readVector3Float32 = BitBuffer.ReadVector3Float32
BitBuffer.writeVector3Float32 = BitBuffer.WriteVector3Float32
BitBuffer.writeVector3Float64 = BitBuffer.WriteVector3Float64
BitBuffer.readRotation = BitBuffer.ReadRotation
BitBuffer.writeVector2 = BitBuffer.WriteVector2
BitBuffer.readCFrame = BitBuffer.ReadCFrame
BitBuffer.writeFloat16 = BitBuffer.WriteFloat16
BitBuffer.writeRotation = BitBuffer.WriteRotation
BitBuffer.writeVector2Float32 = BitBuffer.WriteVector2Float32
BitBuffer.readUDim2 = BitBuffer.ReadUDim2
BitBuffer.writeCFrame = BitBuffer.WriteCFrame
BitBuffer.readVector3 = BitBuffer.ReadVector3
BitBuffer.readFloat = BitBuffer.ReadFloat
BitBuffer.writeUnsigned = BitBuffer.WriteUnsigned
BitBuffer.writeVector3 = BitBuffer.WriteVector3
BitBuffer.readVector2Float32 = BitBuffer.ReadVector2Float32
BitBuffer.writeColor3 = BitBuffer.WriteColor3
BitBuffer.writeUDim2 = BitBuffer.WriteUDim2
BitBuffer.writeBrickColor = BitBuffer.WriteBrickColor
BitBuffer.fromBase128 = BitBuffer.FromBase128
BitBuffer.readBoolean = BitBuffer.ReadBoolean
BitBuffer.writeFloat64 = BitBuffer.WriteFloat64
BitBuffer.writeBool = BitBuffer.WriteBool
BitBuffer.readFloat32 = BitBuffer.ReadFloat32
BitBuffer.writeFloat32 = BitBuffer.WriteFloat32
BitBuffer.writeFloat8 = BitBuffer.WriteFloat8
BitBuffer.toString = BitBuffer.ToString
BitBuffer.writeSigned = BitBuffer.WriteSigned
BitBuffer.writeString = BitBuffer.WriteString
BitBuffer.writeFloat = BitBuffer.WriteFloat
BitBuffer.readFloat64 = BitBuffer.ReadFloat64
BitBuffer.fromBase64 = BitBuffer.FromBase64
BitBuffer.resetPointer = BitBuffer.ResetPointer
BitBuffer.reset = BitBuffer.Reset
return BitBuffer
local Utility = require(script.Utility)
local t = require(script.t)
local FastBitBuffer = {}
FastBitBuffer.__index = FastBitBuffer
local t_string = t.string
local t_boolean = t.boolean
local t_integer = t.integer
local t_number = t.number
local NonNegativeNumber = t.numberMin(0)
local PositiveInteger = t.intersection(t_integer, NonNegativeNumber)
local WriteUnsignedTuple = t.tuple(t_integer, PositiveInteger)
local WriteSignedTuple = t.tuple(t_integer, t_integer)
local WriteFloatTuple = t.tuple(t_integer, t_integer, t_number)
local CHAR_0X10 = string.char(0x10)
local LOG_10_OF_2 = math.log10(2)
local DEPRECATED_WARNING = true
local NumberToBase64 = {}
local Base64ToNumber = {}
do
local CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for Index = 1, 64 do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase64[Index - 1] = Character
Base64ToNumber[Character] = Index - 1
end
end
-- Credit to Defaultio.
local NumberToBase128 = {}
local Base128ToNumber = {}
do
local CHARACTERS = ""
for Index = 0, 127 do
CHARACTERS = CHARACTERS .. string.char(Index)
end
for Index = 1, 128 do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase128[Index - 1] = Character
Base128ToNumber[Character] = Index - 1
end
end
local PowerOfTwo = setmetatable({}, {
__index = function(self, Index)
local Value = 2 ^ Index
self[Index] = Value
return Value
end;
})
for Index = 0, 128 do
local _ = PowerOfTwo[Index]
end
local BrickColorToNumber = {}
local NumberToBrickColor = {}
for Index = 0, 63 do
local Color = BrickColor.palette(Index)
BrickColorToNumber[Color.Number] = Index
NumberToBrickColor[Index] = Color
end
--[[**
Creates a new FastBitBuffer.
@returns [FastBitBuffer] The new FastBitBuffer.
**--]]
function FastBitBuffer.new()
return setmetatable({
BitPointer = 0;
BitBuffer = {};
}, FastBitBuffer)
end
--[[**
Resets the BitBuffer's BitPointer.
@returns [void]
**--]]
function FastBitBuffer:ResetPointer()
self.BitPointer = 0
end
--[[**
Resets the BitBuffer's BitPointer and buffer table.
@returns [void]
**--]]
function FastBitBuffer:Reset()
self.BitBuffer, self.BitPointer = {}, 0
end
--[[**
Reads the given string and writes to the BitBuffer accordingly. Not really useful.
@param [t:string] String The BitBuffer string you want to use.
@returns [void]
**--]]
function FastBitBuffer:FromString(String)
assert(t_string(String))
self.BitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local BitBuffer = self.BitBuffer
for Index = 1, #String do
local ByteCharacter = string.byte(String, Index, Index)
for _ = 1, 8 do
BitPointerValue = BitPointerValue + 1
BitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
end
end
--[[**
Writes the BitBuffer to a string.
@returns [t:string] The BitBuffer string.
**--]]
function FastBitBuffer:ToString()
local String = ""
local Accumulator = 0
local Power = 0
local BitBuffer = self.BitBuffer
for Index = 1, math.ceil(#BitBuffer / 8) * 8 do
Accumulator = Accumulator + PowerOfTwo[Power] * (BitBuffer[Index] or 0)
Power = Power + 1
if Power >= 8 then
String = String .. string.char(Accumulator)
Accumulator = 0
Power = 0
end
end
return String
end
--[[**
Reads the given Base64 string and writes to the BitBuffer accordingly.
@param [t:string] String The Base64 string.
@returns [void]
**--]]
function FastBitBuffer:FromBase64(String)
assert(t_string(String))
self.BitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local BitBuffer = self.BitBuffer
for Index = 1, #String do
local Character = string.sub(String, Index, Index)
local ByteCharacter = Base64ToNumber[Character]
if not ByteCharacter then
error("Bad character: 0x" .. Utility.ToBase(string.byte(Character), 16), 2)
end
for _ = 1, 6 do
BitPointerValue = BitPointerValue + 1
self.BitPointer = BitPointerValue
BitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. Utility.ToBase(Base64ToNumber[Character], 16) .. " too large", 2)
end
end
self.BitPointer = 0
end
--[[**
Writes the BitBuffer to a Base64 string.
@returns [t:string] The BitBuffer encoded in Base64.
**--]]
function FastBitBuffer:ToBase64()
local Array = {}
local Length = 0
local Accumulator = 0
local Power = 0
local BitBuffer = self.BitBuffer
for Index = 1, math.ceil(#BitBuffer / 6) * 6 do
Accumulator = Accumulator + PowerOfTwo[Power] * (BitBuffer[Index] or 0)
Power = Power + 1
if Power >= 6 then
Length = Length + 1
Array[Length] = NumberToBase64[Accumulator]
Accumulator = 0
Power = 0
end
end
return table.concat(Array)
end
--[[**
Reads the given Base128 string and writes to the BitBuffer accordingly. Not recommended. Credit to Defaultio for the original functions.
@param [t:string] String The Base128 string.
@returns [void]
**--]]
function FastBitBuffer:FromBase128(String)
assert(t_string(String))
self.BitBuffer, self.BitPointer = {}, 0
local BitPointerValue = 0
local BitBuffer = self.BitBuffer
for Index = 1, #String do
local Character = string.sub(String, Index, Index)
local ByteCharacter = Base128ToNumber[Character]
if not ByteCharacter then
error("Bad character: 0x" .. Utility.ToBase(string.byte(Character), 16), 2)
end
for _ = 1, 7 do
BitPointerValue = BitPointerValue + 1
BitBuffer[BitPointerValue] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. Utility.ToBase(Base128ToNumber[Character], 16) .. " too large", 2)
end
end
end
--[[**
Writes the BitBuffer to Base128. Credit to Defaultio for the original implementation.
@returns [t:string] The BitBuffer encoded in Base128.
**--]]
function FastBitBuffer:ToBase128()
local Array = {}
local Length = 0
local Accumulator = 0
local Power = 0
local BitBuffer = self.BitBuffer
for Index = 1, math.ceil(#BitBuffer / 7) * 7 do
Accumulator = Accumulator + PowerOfTwo[Power] * (BitBuffer[Index] or 0)
Power = Power + 1
if Power >= 7 then
Length = Length + 1
Array[Length] = NumberToBase128[Accumulator]
Accumulator = 0
Power = 0
end
end
return table.concat(Array)
end
--[[**
Dumps the BitBuffer data and prints it.
@returns [void]
**--]]
function FastBitBuffer:Dump()
local String = ""
local String2 = ""
local Accumulator = 0
local Power = 0
local BitBuffer = self.BitBuffer
for Index = 1, math.ceil(#BitBuffer / 8) * 8 do
String2 = String2 .. (BitBuffer[Index] or 0)
Accumulator = Accumulator + PowerOfTwo[Power] * (BitBuffer[Index] or 0)
Power = Power + 1
if Power >= 8 then
String2 = String2 .. " "
String = String .. "0x" .. Utility.ToBase(Accumulator, 16) .. " "
Accumulator = 0
Power = 0
end
end
print("[Dump] Bytes:", String)
print("[Dump] Bits:", String2)
end
function FastBitBuffer:_ReadBit()
self.BitPointer = self.BitPointer + 1
return self.BitBuffer[self.BitPointer]
end
--[[**
Writes an unsigned integer to the BitBuffer.
@param [t:integer] Width The bit width of the value.
@param [t:intersection<t:integer, t:numberMin<0>>] Value The unsigned integer.
@returns [void]
**--]]
function FastBitBuffer:WriteUnsigned(Width, Value)
assert(WriteUnsignedTuple(Width, Value))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer
-- Store LSB first
for _ = 1, Width do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. Width .. " bits", 2)
end
end
--[[**
Reads an unsigned integer from the BitBuffer.
@param [t:integer] Width The bit width of the value.
@returns [t:intersection<t:integer, t:numberMin<0>>] The unsigned integer.
**--]]
function FastBitBuffer:ReadUnsigned(Width)
assert(t_integer(Width))
local Value = 0
local BitPointer = self.BitPointer
local BitBuffer = self.BitBuffer
for Index = 1, Width do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
self.BitPointer = BitPointer
return Value
end
--[[**
Writes a signed integer to the BitBuffer.
@param [t:integer] Width The bit width of the value.
@param [t:integer] Value The signed integer.
@returns [void]
**--]]
function FastBitBuffer:WriteSigned(Width, Value)
assert(WriteSignedTuple(Width, Value))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer + 1
-- Write sign
if Value < 0 then
BitBuffer[BitPointer] = 1
Value = 0 - Value
else
BitBuffer[BitPointer] = 0
end
-- WriteUnsigned call
for _ = 1, Width - 1 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. Width .. " bits", 2)
end
end
--[[**
Reads a signed integer from the BitBuffer.
@param [t:integer] Width The bit width of the value.
@returns [t:integer] The signed integer.
**--]]
function FastBitBuffer:ReadSigned(Width)
assert(t_integer(Width))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer + 1
local SignedValue = (-1) ^ BitBuffer[BitPointer]
-- ReadUnsigned call
local Value = 0
for Index = 1, Width - 1 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
self.BitPointer = BitPointer
return SignedValue * Value
end
--[[**
Writes a string to the BitBuffer.
@param [t:string] String The string you are writing to the BitBuffer.
@returns [void]
**--]]
function FastBitBuffer:WriteString(String)
assert(t_string(String))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer
-- First check if it's a 7 or 8 bit width of string
local StringLength = #String
local BitWidth = 7
for Index = 1, StringLength do
if string.byte(String, Index, Index) > 127 then
BitWidth = 8
break
end
end
-- Write the bit width flag
-- WriteUnsigned(1, BitWidth == 7 and 0 or 1)
local Value = BitWidth == 7 and 0 or 1 -- 1 for wide chars
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
-- Now write out the string, terminated with "0x10, 0b0"
-- 0x10 is encoded as "0x10, 0b1"
for Index = 1, StringLength do
local ByteCharacter = string.byte(String, Index, Index)
if ByteCharacter == 0x10 then
-- WriteUnsigned(BitWidth, 0x10)
Value = 0x10
for _ = 1, BitWidth do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. BitWidth .. " bits", 2)
end
-- WriteUnsigned(1, 1)
Value = 1
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
else
-- WriteUnsigned(BitWidth, ByteCharacter)
Value = ByteCharacter
for _ = 1, BitWidth do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. BitWidth .. " bits", 2)
end
end
end
-- Write terminator
-- WriteUnsigned(BitWidth, 0x10)
Value = 0x10
for _ = 1, BitWidth do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. BitWidth .. " bits", 2)
end
-- WriteUnsigned(1, 0)
Value = 0
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
self.BitPointer = BitPointer
end
--[[**
Reads the BitBuffer for a string.
@returns [t:string] The string written to the BitBuffer.
**--]]
function FastBitBuffer:ReadString()
-- Get bit width
-- ReadUnsigned(1)
local Value = 0
local BitPointer = self.BitPointer + 1
local BitBuffer = self.BitBuffer
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[0]
local BitWidth = Value == 1 and 8 or 7
-- Loop
local String = ""
while true do
-- ReadUnsigned(BitWidth)
Value = 0
for Index = 1, BitWidth do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
self.BitPointer = BitPointer
local Character = Value
if Character == 0x10 then
-- ReadUnsigned(1)
Value = 0
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[0]
self.BitPointer = BitPointer
if Value == 1 then
String = String .. CHAR_0X10
else
break
end
else
String = String .. string.char(Character)
end
end
return String
end
--[[**
Writes a boolean to the BitBuffer.
@param [t:boolean] Boolean The value you are writing to the BitBuffer.
@returns [void]
**--]]
function FastBitBuffer:WriteBool(Boolean)
assert(t_boolean(Boolean))
local Value = Boolean and 1 or 0
-- WriteUnsigned(1, Boolean and 1 or 0)
self.BitPointer = self.BitPointer + 1
self.BitBuffer[self.BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
end
FastBitBuffer.WriteBoolean = FastBitBuffer.WriteBool
--[[**
Reads the BitBuffer for a boolean.
@returns [t:boolean] The boolean.
**--]]
function FastBitBuffer:ReadBool()
-- ReadUnsigned(1)
local Value = 0
local BitPointer = self.BitPointer + 1
Value = Value + self.BitBuffer[BitPointer] * PowerOfTwo[0]
self.BitPointer = BitPointer
return Value == 1
end
FastBitBuffer.ReadBoolean = FastBitBuffer.ReadBool
--[[**
Writes a float to the BitBuffer.
@param [t:integer] Fraction The number of bits (probably).
@param [t:integer] WriteExponent The number of bits for the decimal (probably).
@param [t:number] Float The actual number you are writing.
@returns [void]
**--]]
function FastBitBuffer:WriteFloat(Fraction, WriteExponent, Float)
assert(WriteFloatTuple(Fraction, WriteExponent, Float))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer
local Value = 0
-- Sign
local Sign = 1
if Float < 0 then
Float = 0 - Float
Sign = -1
end
-- Decompose
local Mantissa, Exponent = math.frexp(Float)
if Exponent == 0 and Mantissa == 0 then
-- WriteUnsigned(Fraction + WriteExponent + 1, 0)
for _ = 1, Fraction + WriteExponent + 1 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. Fraction + WriteExponent + 1 .. " bits", 2)
end
return
else
Mantissa = (Mantissa - 0.5) / 0.5 * PowerOfTwo[Fraction]
end
-- Write sign
-- WriteUnsigned(1, Sign == -1 and 1 or 0)
Value = Sign == -1 and 1 or 0
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
-- Write mantissa
Mantissa = Mantissa + 0.5
Mantissa = Mantissa - Mantissa % 1 -- Not really correct, should round up/down based on the parity of |wexp|
Value = Mantissa
-- WriteUnsigned(Fraction, Mantissa)
for _ = 1, Fraction do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. Fraction .. " bits", 2)
end
-- Write exponent
-- WriteSigned(WriteExponent, Exponent)
local MaxExp = PowerOfTwo[WriteExponent - 1] - 1
Value = Exponent > MaxExp and MaxExp or Exponent < -MaxExp and -MaxExp or Exponent
if Value < 0 then
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 1
Value = 0 - Value
else
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 0
end
for _ = 1, WriteExponent - 1 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than " .. WriteExponent - 1 .. " bits", 2)
end
end
--[[**
Reads a float from the BitBuffer.
@param [t:integer] Fraction The number of bits (probably).
@param [t:integer] WriteExponent The number of bits for the decimal (probably).
@returns [t:number] The float.
**--]]
function FastBitBuffer:ReadFloat(Fraction, WriteExponent)
assert(WriteSignedTuple(Fraction, WriteExponent))
local Value = 0
local BitPointer = self.BitPointer + 1
local BitBuffer = self.BitBuffer
-- ReadUnsigned(1)
local Sign = (BitBuffer[BitPointer] * PowerOfTwo[0]) == 1 and -1 or 1
-- ReadUnsigned(Fraction)
for Index = 1, Fraction do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Mantissa = Value
-- ReadSigned(WriteExponent)
Value = 0
BitPointer = BitPointer + 1
local SignedValue = (-1) ^ BitBuffer[BitPointer]
for Index = 1, WriteExponent - 1 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Exponent = SignedValue * Value
if Exponent == 0 and Mantissa == 0 then
return 0
else
Mantissa = Mantissa / PowerOfTwo[Fraction] / 2 + 0.5
self.BitPointer = BitPointer
return Sign * math.ldexp(Mantissa, Exponent)
end
end
--[[**
Writes a float8 (quarter precision) to the BitBuffer.
@param [t:number] Float The float8.
@returns [void]
**--]]
function FastBitBuffer:WriteFloat8(Float)
assert(t_number(Float))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer
local Value = 0
-- Sign
local Sign = 1
if Float < 0 then
Float = 0 - Float
Sign = -1
end
-- Decompose
local Mantissa, Exponent = math.frexp(Float)
if Exponent == 0 and Mantissa == 0 then
-- WriteUnsigned(Fraction + WriteExponent + 1, 0)
for _ = 1, 8 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 8 bits", 2)
end
return
else
Mantissa = (Mantissa - 0.5) / 0.5 * PowerOfTwo[3]
end
-- Write sign
-- WriteUnsigned(1, Sign == -1 and 1 or 0)
Value = Sign == -1 and 1 or 0
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
-- Write mantissa
Mantissa = Mantissa + 0.5
Mantissa = Mantissa - Mantissa % 1 -- Not really correct, should round up/down based on the parity of |wexp|
Value = Mantissa
-- WriteUnsigned(Fraction, Mantissa)
for _ = 1, 3 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 3 bits", 2)
end
-- Write exponent
-- WriteSigned(WriteExponent, Exponent)
local MaxExp = PowerOfTwo[3] - 1
Value = Exponent > MaxExp and MaxExp or Exponent < -MaxExp and -MaxExp or Exponent
if Value < 0 then
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 1
Value = 0 - Value
else
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 0
end
for _ = 1, 3 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 3 bits", 2)
end
end
--[[**
Reads a float8 (quarter precision) from the BitBuffer.
@returns [t:number] The float8.
**--]]
function FastBitBuffer:ReadFloat8()
local Value = 0
local BitPointer = self.BitPointer + 1
local BitBuffer = self.BitBuffer
-- ReadUnsigned(1)
local Sign = (BitBuffer[BitPointer] * PowerOfTwo[0]) == 1 and -1 or 1
-- ReadUnsigned(Fraction)
for Index = 1, 3 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Mantissa = Value
-- ReadSigned(WriteExponent)
Value = 0
BitPointer = BitPointer + 1
local SignedValue = (-1) ^ BitBuffer[BitPointer]
for Index = 1, 3 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Exponent = SignedValue * Value
if Exponent == 0 and Mantissa == 0 then
return 0
else
Mantissa = Mantissa / PowerOfTwo[3] / 2 + 0.5
self.BitPointer = BitPointer
return Sign * math.ldexp(Mantissa, Exponent)
end
end
--[[**
Writes a float16 (half precision) to the BitBuffer.
@param [t:number] Float The float16.
@returns [void]
**--]]
function FastBitBuffer:WriteFloat16(Float)
assert(t_number(Float))
local BitBuffer = self.BitBuffer
local BitPointer = self.BitPointer
local Value = 0
-- Sign
local Sign = 1
if Float < 0 then
Float = 0 - Float
Sign = -1
end
-- Decompose
local Mantissa, Exponent = math.frexp(Float)
if Exponent == 0 and Mantissa == 0 then
-- WriteUnsigned(Fraction + WriteExponent + 1, 0)
for _ = 1, 16 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 16 bits", 2)
end
return
else
Mantissa = (Mantissa - 0.5) / 0.5 * PowerOfTwo[10]
end
-- Write sign
-- WriteUnsigned(1, Sign == -1 and 1 or 0)
Value = Sign == -1 and 1 or 0
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 1 bit", 2)
end
-- Write mantissa
Mantissa = Mantissa + 0.5
Mantissa = Mantissa - Mantissa % 1 -- Not really correct, should round up/down based on the parity of |wexp|
Value = Mantissa
-- WriteUnsigned(Fraction, Mantissa)
for _ = 1, 10 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 10 bits", 2)
end
-- Write exponent
-- WriteSigned(WriteExponent, Exponent)
local MaxExp = PowerOfTwo[4] - 1
Value = Exponent > MaxExp and MaxExp or Exponent < -MaxExp and -MaxExp or Exponent
if Value < 0 then
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 1
Value = 0 - Value
else
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = 0
end
for _ = 1, 4 do
BitPointer = BitPointer + 1
BitBuffer[BitPointer] = Value % 2
Value = Value / 2
Value = Value - Value % 1
end
self.BitPointer = BitPointer
if Value ~= 0 then
error("Value " .. tostring(Value) .. " has width greater than 4 bits", 2)
end
end
--[[**
Reads a float16 (half precision) from the BitBuffer.
@returns [t:number] The float16.
**--]]
function FastBitBuffer:ReadFloat16()
local Value = 0
local BitPointer = self.BitPointer + 1
local BitBuffer = self.BitBuffer
-- ReadUnsigned(1)
local Sign = (BitBuffer[BitPointer] * PowerOfTwo[0]) == 1 and -1 or 1
-- ReadUnsigned(Fraction)
for Index = 1, 10 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Mantissa = Value
-- ReadSigned(WriteExponent)
Value = 0
BitPointer = BitPointer + 1
local SignedValue = (-1) ^ BitBuffer[BitPointer]
for Index = 1, 4 do
BitPointer = BitPointer + 1
Value = Value + BitBuffer[BitPointer] * PowerOfTwo[Index - 1]
end
local Exponent = SignedValue * Value
if Exponent == 0 and Mantissa == 0 then
return 0
else
Mantissa = Mantissa / PowerOfTwo[10] / 2 + 0.5
self.BitPointer = BitPointer
return Sign * math.ldexp(Mantissa, Exponent)
end
end
--[[**
Destroys the BitBuffer metatable.
@returns [void]
**--]]
function FastBitBuffer:Destroy()
self.BitBuffer = nil
setmetatable(self, nil)
end
return FastBitBuffer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment