Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Last active November 29, 2020 04:06
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save howmanysmall/2833e067a20db62ffe8f0207dd9f2785 to your computer and use it in GitHub Desktop.
Save howmanysmall/2833e067a20db62ffe8f0207dd9f2785 to your computer and use it in GitHub Desktop.
MOVED TO A REPO - https://github.com/howmanysmall/FastBitBuffer. My BitBuffer module versus other BitBuffers. Mine is the FastBitBuffer one below. CC0 license.
--[[
==========================================================================
== API ==
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.
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(self) return self.ClassName end;
}
BitBuffer.__index = BitBuffer
local CHAR_0X10 = string.char(0x10)
local LOG_10_OF_2 = math.log10(2)
local NumberToBase64, Base64ToNumber = {}, {} do
local CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for Index = 1, #CHARACTERS do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase64[Index - 1] = Character
Base64ToNumber[Character] = Index - 1
end
end
local NumberToBase128, Base128ToNumber = {}, {} do -- edit
local CHARACTERS = ""
for Index = 0, 127 do CHARACTERS = CHARACTERS .. string.char(Index) end
for Index = 1, #CHARACTERS do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase128[Index - 1] = Character
Base128ToNumber[Character] = Index - 1
end
end --/edit
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
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
function BitBuffer.new()
return setmetatable({
BitPointer = 0;
mBitBuffer = {};
}, BitBuffer)
end
function BitBuffer:ResetPointer()
self.BitPointer = 0
end
function BitBuffer:Reset()
self.mBitBuffer, self.BitPointer = {}, 0
end
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
for Index = 1, #String do
local ByteCharacter = string.byte(string.sub(String, Index, Index))
for _ = 1, 8 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = string.byte(Character)
-- for _ = 1, 8 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
-- end
self.BitPointer = 0
end
function BitBuffer:ToString()
local String = ""
local Accumulator = 0
local Power = 0
for Index = 1, math.ceil(#self.mBitBuffer / 8) * 8 do
Accumulator = Accumulator + PowerOfTwo[Power] * (self.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
-- Read / Write to base64
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
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), 1) end
for _ = 1, 6 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base64ToNumber[Character], 16) .. " too large", 1)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = Base64ToNumber[Character]
-- if not ByteCharacter then error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 1) end
--
-- for _ = 1, 6 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
--
-- if ByteCharacter ~= 0 then
-- error("Character value 0x" .. ToBase(Base64ToNumber[Character], 16) .. " too large", 1)
-- end
-- end
self.BitPointer = 0
end
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
-- Read / Write to base128 -- edit
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
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), 1)
end
for _ = 1, 7 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base128ToNumber[Character], 16) .. " too large", 1)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = Base128ToNumber[Character]
-- if not ByteCharacter then
-- error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 1)
-- end
--
-- for _ = 1, 7 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
--
-- if ByteCharacter ~= 0 then
-- error("Character value 0x" .. ToBase(Base128ToNumber[Character], 16) .. " too large", 1)
-- end
-- end
self.BitPointer = 0
end
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 -- /edit
-- Dump
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("Bytes:", String)
print("Bits:", String2)
end
function BitBuffer:_readBit()
self.BitPointer = self.BitPointer + 1
return self.mBitBuffer[self.BitPointer]
end
local function DetermineType(Value)
local ActualType = typeof(Value)
if ActualType == "number" then
if Value % 1 == 0 then
ActualType = Value < 0 and "negative integer" or "positive integer"
else
ActualType = Value < 0 and "negative number" or "positive number"
end
elseif ActualType == "table" then
local Key = next(Value)
if DetermineType(Key) == "positive integer" then
ActualType = "array"
else
ActualType = "dictionary"
end
end
return ActualType
end
function BitBuffer:WriteUnsigned(Width, Value)
if not Width then
error("bad argument #1 in BitBuffer::WriteUnsigned (missing Width)", 1)
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)), 1)
end
-- Store LSB first
for _ = 1, Width do
self.BitPointer = self.BitPointer + 1
self.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", 1)
end
end
function BitBuffer:ReadUnsigned(Width)
local Value = 0
for Index = 1, Width do Value = Value + self:_readBit() * PowerOfTwo[Index - 1] end
return Value
end
-- Read / Write a signed number
function BitBuffer:WriteSigned(Width, Value)
if not (Width and Value) then error("bad arguments in BitBuffer::WriteSigned (missing values)", 1) end
if Value % 1 ~= 0 then error("Non-integer value to BitBuffer::WriteSigned", 1) end
-- Write sign
if Value < 0 then
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = 1
Value = 0 - Value
else
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = 0
end
self:WriteUnsigned(Width - 1, Value)
end
function BitBuffer:ReadSigned(Width)
self.BitPointer = self.BitPointer + 1
return ((-1) ^ self.mBitBuffer[self.BitPointer]) * self:ReadUnsigned(Width - 1)
-- return ((-1) ^ self:_readBit()) * self:ReadUnsigned(Width - 1)
end
-- Read / Write a string. May contain embedded nulls (string.char(0))
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)), 1)
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.sub(String, Index, Index)) > 127 then
BitWidth = 8
break
end
end
-- for Character in string.gmatch(String, ".") do
-- if string.byte(Character) > 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.sub(String, Index, Index))
if ByteCharacter == 0x10 then
self:WriteUnsigned(BitWidth, 0x10)
self:WriteUnsigned(1, 1)
else
self:WriteUnsigned(BitWidth, ByteCharacter)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = string.byte(Character)
-- 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 [String]
**--]]
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 [Boolean] Boolean The value you are writing to the BitBuffer.
**--]]
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)), 1)
end
self:WriteUnsigned(1, Boolean and 1 or 0)
end
--[[**
Reads the BitBuffer for a boolean.
@returns [Boolean]
**--]]
function BitBuffer:ReadBool()
return self:ReadUnsigned(1) == 1
end
-- Read / Write a floating point number with |wfrac| fraction part
-- bits, |wexp| exponent part bits, and one sign bit.
--[[**
Write a floating point number with |wfrac| fraction part to the BitBuffer.
@param [Integer] wfrac The number of bits.
@param [Integer] wexp
@param [Number] f The float itself.
**--]]
function BitBuffer:WriteFloat(Fraction, WriteExponent, Float)
if not (Fraction and WriteExponent and Float) then error("missing argument(s)", 1) 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
function BitBuffer:ReadFloat(Fraction, WriteExponent)
if not (Fraction and WriteExponent) then error("missing argument(s)", 1) 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
function BitBuffer:WriteFloat8(Float)
self:WriteFloat(3, 4, Float)
end
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
-- Read / Write half precision floating point
function BitBuffer:WriteFloat16(Float)
self:WriteFloat(10, 5, Float)
end
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
-- Read / Write single precision floating point
function BitBuffer:WriteFloat32(Float)
self:WriteFloat(23, 8, Float)
end
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
-- Read / Write double precision floating point
function BitBuffer:WriteFloat64(Float)
self:WriteFloat(52, 11, Float)
end
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:
-- Read / Write a BrickColor
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)), 1)
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
function BitBuffer:ReadBrickColor()
return NumberToBrickColor[self:ReadUnsigned(6)]
end
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)), 1)
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) * (PowerOfTwo[21] - 1)) + 0.5
Azumith = Azumith - Azumith % 1
Roll = ((Roll / 3.1415926535898) * (PowerOfTwo[20] - 1)) + 0.5
Roll = Roll - Roll % 1
Elevation = ((Elevation / 1.5707963267949) * (PowerOfTwo[20] - 1)) + 0.5
Elevation = Elevation - Elevation % 1
self:WriteSigned(22, Azumith)
self:WriteSigned(21, Roll)
self:WriteSigned(21, Elevation)
end
function BitBuffer:ReadRotation()
local Azumith = self:ReadSigned(22)
local Roll = self:ReadSigned(21)
local Elevation = self:ReadSigned(21)
Azumith = 3.1415926535898 * (Azumith / (PowerOfTwo[21] - 1))
Roll = 3.1415926535898 * (Roll / (PowerOfTwo[20] - 1))
Elevation = 3.1415926535898 * (Elevation / (PowerOfTwo[20] - 1))
local Rotation = CFrame.Angles(0, Azumith, 0)
Rotation = Rotation * CFrame.Angles(Elevation, 0, 0)
Rotation = Rotation * CFrame.Angles(0, 0, Roll)
return Rotation
end
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)), 1)
end
local R, G, B = Color.R * 255, Color.G * 255, Color.B * 255
R, G, B = R - R % 1, G - G % 1, B - B % 1
self:WriteUnsigned(8, R)
self:WriteUnsigned(8, G)
self:WriteUnsigned(8, B)
end
function BitBuffer:ReadColor3()
return Color3.fromRGB(self:ReadUnsigned(8), self:ReadUnsigned(8), self:ReadUnsigned(8))
end
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)), 1)
end
self:WriteFloat32(Vector.X)
self:WriteFloat32(Vector.Y)
self:WriteFloat32(Vector.Z)
end
function BitBuffer:ReadVector3()
return Vector3.new(self:ReadFloat32(), self:ReadFloat32(), self:ReadFloat32())
end
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)), 1)
end
self:WriteVector3Float64(CoordinateFrame.Position)
self:WriteRotation(CoordinateFrame)
end
function BitBuffer:ReadCFrame()
return CFrame.new(self:ReadVector3Float64()) * self:ReadRotation()
end
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)), 1)
end
self:WriteFloat32(Vector.X)
self:WriteFloat32(Vector.Y)
end
function BitBuffer:ReadVector2()
return Vector2.new(self:ReadFloat32(), self:ReadFloat32())
end
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)), 1)
end
self:WriteSigned(17, Value.X.Offset)
self:WriteFloat32(Value.X.Scale)
self:WriteSigned(17, Value.Y.Offset)
self:WriteFloat32(Value.Y.Scale)
end
function BitBuffer:ReadUDim2()
return UDim2.new(self:ReadSigned(17), self:ReadFloat32(), self:ReadSigned(17), self:ReadFloat32())
end
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)), 1)
end
self:WriteFloat64(Vector.X)
self:WriteFloat64(Vector.Y)
self:WriteFloat64(Vector.Z)
end
function BitBuffer:ReadVector3Float64()
return Vector3.new(self:ReadFloat64(), self:ReadFloat64(), self:ReadFloat64())
end
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)), 1)
end
self:WriteFloat64(Vector.X)
self:WriteFloat64(Vector.Y)
end
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
function BitBuffer:Destroy()
setmetatable(self, nil)
end
function BitBuffer.BitsNeeded(Number)
local Bits = math.log10(Number + 1) / LOG_10_OF_2
return Bits + (1 - Bits % 1)
end
return BitBuffer
--[[
This is the chainable BitBuffer module. Haven't tested it, but it should work.
==========================================================================
== API ==
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.
Constructor: BitBuffer.new()
Read/Write pairs for reading data from or writing data to the BitBuffer:
BitBuffer::WriteUnsigned(BitWidth: number, Value: number): BitBuffer
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): BitBuffer
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): BitBuffer
BitBuffer:ReadFloat(MantissaBitWidth, ExponentBitWidth): number
Read / Write a floating point number with a given mantissa and
exponent size in bits.
BitBuffer::WriteFloat8(Float: number): BitBuffer
BitBuffer::ReadFloat8(): number
BitBuffer::WriteFloat16(Float: number): BitBuffer
BitBuffer::ReadFloat16(): number
BitBuffer::WriteFloat32(Float: number): BitBuffer
BitBuffer::ReadFloat32(): number
BitBuffer::WriteFloat64(Float: number): BitBuffer
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): BitBuffer
BitBuffer::ReadBool(): boolean
Read / Write a boolean (true / false) value. Takes one bit worth of space to store.
BitBuffer::WriteString(String: string): BitBuffer
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): BitBuffer
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): BitBuffer
BitBuffer::ReadColor3(): Color3
Read / Write a Roblox Color3. Use this over the BrickColor methods, PLEASE.
BitBuffer::WriteRotation(CoordinateFrame: CFrame): BitBuffer
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): BitBuffer
BitBuffer::ReadVector3(): Vector3
BitBuffer::WriteVector3Float32(Vector: Vector3): BitBuffer
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): BitBuffer
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): BitBuffer
BitBuffer::ReadVector2(): Vector2
BitBuffer::WriteVector2Float32(Vector: Vector2): BitBuffer
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): BitBuffer
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): BitBuffer
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): BitBuffer
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): BitBuffer
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): BitBuffer
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): BitBuffer
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(): BitBuffer
Will Reset the point in the buffer that is being read / written
to back to the start of the buffer.
BitBuffer::Reset(): BitBuffer
Will reset the buffer to a clean state, with no contents.
Example Usage:
local function SaveToBuffer(buffer, userData)
buffer:WriteString(userData.HeroName)
:WriteUnsigned(14, userData.Score) --> 14 bits -> [0, 2^14-1] -> [0, 16383]
:WriteBool(userData.HasDoneSomething)
:WriteUnsigned(10, #userData.ItemList) --> [0, 1023]
for _, itemInfo in pairs(userData.ItemList) do
buffer:WriteString(itemInfo.Identifier)
: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(self) return self.ClassName end;
}
BitBuffer.__index = BitBuffer
local CHAR_0X10 = string.char(0x10)
local LOG_10_OF_2 = math.log10(2)
local NumberToBase64, Base64ToNumber = {}, {} do
local CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for Index = 1, #CHARACTERS do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase64[Index - 1] = Character
Base64ToNumber[Character] = Index - 1
end
end
local NumberToBase128, Base128ToNumber = {}, {} do -- edit
local CHARACTERS = ""
for Index = 0, 127 do CHARACTERS = CHARACTERS .. string.char(Index) end
for Index = 1, #CHARACTERS do
local Character = string.sub(CHARACTERS, Index, Index)
NumberToBase128[Index - 1] = Character
Base128ToNumber[Character] = Index - 1
end
end --/edit
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
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
function BitBuffer.new()
return setmetatable({
BitPointer = 0;
mBitBuffer = {};
}, BitBuffer)
end
function BitBuffer:ResetPointer()
self.BitPointer = 0
return self
end
function BitBuffer:Reset()
self.mBitBuffer, self.BitPointer = {}, 0
return self
end
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
for Index = 1, #String do
local ByteCharacter = string.byte(string.sub(String, Index, Index))
for _ = 1, 8 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = string.byte(Character)
-- for _ = 1, 8 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
-- end
self.BitPointer = 0
return self
end
function BitBuffer:ToString()
local String = ""
local Accumulator = 0
local Power = 0
for Index = 1, math.ceil(#self.mBitBuffer / 8) * 8 do
Accumulator = Accumulator + PowerOfTwo[Power] * (self.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
-- Read / Write to base64
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
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), 1) end
for _ = 1, 6 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base64ToNumber[Character], 16) .. " too large", 1)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = Base64ToNumber[Character]
-- if not ByteCharacter then error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 1) end
--
-- for _ = 1, 6 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
--
-- if ByteCharacter ~= 0 then
-- error("Character value 0x" .. ToBase(Base64ToNumber[Character], 16) .. " too large", 1)
-- end
-- end
self.BitPointer = 0
return self
end
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
-- Read / Write to base128 -- edit
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)), 1)
end
self.mBitBuffer, self.BitPointer = {}, 0
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), 1)
end
for _ = 1, 7 do
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
ByteCharacter = ByteCharacter / 2
ByteCharacter = ByteCharacter - ByteCharacter % 1
end
if ByteCharacter ~= 0 then
error("Character value 0x" .. ToBase(Base128ToNumber[Character], 16) .. " too large", 1)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = Base128ToNumber[Character]
-- if not ByteCharacter then
-- error("Bad character: 0x" .. ToBase(string.byte(Character), 16), 1)
-- end
--
-- for _ = 1, 7 do
-- self.BitPointer = self.BitPointer + 1
-- self.mBitBuffer[self.BitPointer] = ByteCharacter % 2
-- ByteCharacter = ByteCharacter / 2
-- ByteCharacter = ByteCharacter - ByteCharacter % 1
-- end
--
-- if ByteCharacter ~= 0 then
-- error("Character value 0x" .. ToBase(Base128ToNumber[Character], 16) .. " too large", 1)
-- end
-- end
self.BitPointer = 0
return self
end
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 -- /edit
-- Dump
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("Bytes:", String)
print("Bits:", String2)
return self
end
function BitBuffer:_readBit()
self.BitPointer = self.BitPointer + 1
return self.mBitBuffer[self.BitPointer]
end
local function DetermineType(Value)
local ActualType = typeof(Value)
if ActualType == "number" then
if Value % 1 == 0 then
ActualType = Value < 0 and "negative integer" or "positive integer"
else
ActualType = Value < 0 and "negative number" or "positive number"
end
elseif ActualType == "table" then
local Key = next(Value)
if DetermineType(Key) == "positive integer" then
ActualType = "array"
else
ActualType = "dictionary"
end
end
return ActualType
end
function BitBuffer:WriteUnsigned(Width, Value)
if not Width then
error("bad argument #1 in BitBuffer::WriteUnsigned (missing Width)", 1)
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)), 1)
end
-- Store LSB first
for _ = 1, Width do
self.BitPointer = self.BitPointer + 1
self.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", 1)
end
return self
end
function BitBuffer:ReadUnsigned(Width)
local Value = 0
for Index = 1, Width do Value = Value + self:_readBit() * PowerOfTwo[Index - 1] end
return Value
end
-- Read / Write a signed number
function BitBuffer:WriteSigned(Width, Value)
if not (Width and Value) then error("bad arguments in BitBuffer::WriteSigned (missing values)", 1) end
if Value % 1 ~= 0 then error("Non-integer value to BitBuffer::WriteSigned", 1) end
-- Write sign
if Value < 0 then
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = 1
Value = 0 - Value
else
self.BitPointer = self.BitPointer + 1
self.mBitBuffer[self.BitPointer] = 0
end
return self:WriteUnsigned(Width - 1, Value)
end
function BitBuffer:ReadSigned(Width)
self.BitPointer = self.BitPointer + 1
return ((-1) ^ self.mBitBuffer[self.BitPointer]) * self:ReadUnsigned(Width - 1)
-- return ((-1) ^ self:_readBit()) * self:ReadUnsigned(Width - 1)
end
-- Read / Write a string. May contain embedded nulls (string.char(0))
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)), 1)
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.sub(String, Index, Index)) > 127 then
BitWidth = 8
break
end
end
-- for Character in string.gmatch(String, ".") do
-- if string.byte(Character) > 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.sub(String, Index, Index))
if ByteCharacter == 0x10 then
self:WriteUnsigned(BitWidth, 0x10)
:WriteUnsigned(1, 1)
else
self:WriteUnsigned(BitWidth, ByteCharacter)
end
end
-- for Character in string.gmatch(String, ".") do
-- local ByteCharacter = string.byte(Character)
-- if ByteCharacter == 0x10 then
-- self:WriteUnsigned(BitWidth, 0x10)
-- self:WriteUnsigned(1, 1)
-- else
-- self:WriteUnsigned(BitWidth, ByteCharacter)
-- end
-- end
-- Write terminator
return self:WriteUnsigned(BitWidth, 0x10)
:WriteUnsigned(1, 0)
end
--[[**
Reads the BitBuffer for a string.
@returns [String]
**--]]
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 [Boolean] Boolean The value you are writing to the BitBuffer.
**--]]
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)), 1)
end
return self:WriteUnsigned(1, Boolean and 1 or 0)
end
--[[**
Reads the BitBuffer for a boolean.
@returns [Boolean]
**--]]
function BitBuffer:ReadBool()
return self:ReadUnsigned(1) == 1
end
-- Read / Write a floating point number with |wfrac| fraction part
-- bits, |wexp| exponent part bits, and one sign bit.
--[[**
Write a floating point number with |wfrac| fraction part to the BitBuffer.
@param [Integer] wfrac The number of bits.
@param [Integer] wexp
@param [Number] f The float itself.
**--]]
function BitBuffer:WriteFloat(Fraction, WriteExponent, Float)
if not (Fraction and WriteExponent and Float) then error("missing argument(s)", 1) 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 self -- ????
else
Mantissa = (Mantissa - 0.5) / 0.5 * PowerOfTwo[Fraction]
end
Mantissa = Mantissa + 0.5
Mantissa = Mantissa - Mantissa % 1 -- Not really correct, should round up/down based on the parity of |wexp|
local MaxExp = PowerOfTwo[WriteExponent - 1] - 1
-- Write sign, mantissa, and exponent
return self:WriteUnsigned(1, Sign == -1 and 1 or 0)
:WriteUnsigned(Fraction, Mantissa)
:WriteSigned(WriteExponent, Exponent > MaxExp and MaxExp or Exponent < -MaxExp and -MaxExp or Exponent)
end
function BitBuffer:ReadFloat(Fraction, WriteExponent)
if not (Fraction and WriteExponent) then error("missing argument(s)", 1) 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
function BitBuffer:WriteFloat8(Float)
return self:WriteFloat(3, 4, Float)
end
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
-- Read / Write half precision floating point
function BitBuffer:WriteFloat16(Float)
return self:WriteFloat(10, 5, Float)
end
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
-- Read / Write single precision floating point
function BitBuffer:WriteFloat32(Float)
self:WriteFloat(23, 8, Float)
end
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
-- Read / Write double precision floating point
function BitBuffer:WriteFloat64(Float)
return self:WriteFloat(52, 11, Float)
end
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:
-- Read / Write a BrickColor
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)), 1)
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
return self:WriteUnsigned(6, BrickColorNumber)
end
function BitBuffer:ReadBrickColor()
return NumberToBrickColor[self:ReadUnsigned(6)]
end
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)), 1)
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) * (PowerOfTwo[21] - 1)) + 0.5
Azumith = Azumith - Azumith % 1
Roll = ((Roll / 3.1415926535898) * (PowerOfTwo[20] - 1)) + 0.5
Roll = Roll - Roll % 1
Elevation = ((Elevation / 1.5707963267949) * (PowerOfTwo[20] - 1)) + 0.5
Elevation = Elevation - Elevation % 1
return self:WriteSigned(22, Azumith)
:WriteSigned(21, Roll)
:WriteSigned(21, Elevation)
end
function BitBuffer:ReadRotation()
local Azumith = self:ReadSigned(22)
local Roll = self:ReadSigned(21)
local Elevation = self:ReadSigned(21)
Azumith = 3.1415926535898 * (Azumith / (PowerOfTwo[21] - 1))
Roll = 3.1415926535898 * (Roll / (PowerOfTwo[20] - 1))
Elevation = 3.1415926535898 * (Elevation / (PowerOfTwo[20] - 1))
local Rotation = CFrame.Angles(0, Azumith, 0)
Rotation = Rotation * CFrame.Angles(Elevation, 0, 0)
Rotation = Rotation * CFrame.Angles(0, 0, Roll)
return Rotation
end
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)), 1)
end
local R, G, B = Color.R * 255, Color.G * 255, Color.B * 255
R, G, B = R - R % 1, G - G % 1, B - B % 1
return self:WriteUnsigned(8, R)
:WriteUnsigned(8, G)
:WriteUnsigned(8, B)
end
function BitBuffer:ReadColor3()
return Color3.fromRGB(self:ReadUnsigned(8), self:ReadUnsigned(8), self:ReadUnsigned(8))
end
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)), 1)
end
return self:WriteFloat32(Vector.X)
:WriteFloat32(Vector.Y)
:WriteFloat32(Vector.Z)
end
function BitBuffer:ReadVector3()
return Vector3.new(self:ReadFloat32(), self:ReadFloat32(), self:ReadFloat32())
end
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)), 1)
end
return self:WriteVector3Float64(CoordinateFrame.Position)
:WriteRotation(CoordinateFrame)
end
function BitBuffer:ReadCFrame()
return CFrame.new(self:ReadVector3Float64()) * self:ReadRotation()
end
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)), 1)
end
return self:WriteFloat32(Vector.X)
:WriteFloat32(Vector.Y)
end
function BitBuffer:ReadVector2()
return Vector2.new(self:ReadFloat32(), self:ReadFloat32())
end
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)), 1)
end
return self:WriteSigned(17, Value.X.Offset)
:WriteFloat32(Value.X.Scale)
:WriteSigned(17, Value.Y.Offset)
:WriteFloat32(Value.Y.Scale)
end
function BitBuffer:ReadUDim2()
return UDim2.new(self:ReadSigned(17), self:ReadFloat32(), self:ReadSigned(17), self:ReadFloat32())
end
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)), 1)
end
return self:WriteFloat64(Vector.X)
:WriteFloat64(Vector.Y)
:WriteFloat64(Vector.Z)
end
function BitBuffer:ReadVector3Float64()
return Vector3.new(self:ReadFloat64(), self:ReadFloat64(), self:ReadFloat64())
end
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)), 1)
end
return self:WriteFloat64(Vector.X)
:WriteFloat64(Vector.Y)
end
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
function BitBuffer:Destroy()
setmetatable(self, nil)
end
function BitBuffer.BitsNeeded(Number)
local Bits = math.log10(Number + 1) / LOG_10_OF_2
return Bits + (1 - Bits % 1)
end
return BitBuffer
--[[
==========================================================================
== API ==
== (Most of the API is the same as Stravant's BitBuffer) ==
NOTE IF YOU HAVE BEEN USING STRAVANT'S BITBUFFER MODULE:
The majority of the functionality is the same, with reading being
10% faster. Anything serialized with his BitBuffer module is
compatible with the module except for any removed functions. Both
WriteUnicodeString() and ReadUnicodeString() were added for future-
proofing for Unicode support. ReadBrick and WriteBrickColor were
not implemented because they relied on the color pallete, which
does change. Dump was also not implemented because it isn't really
important. (Private TheNexusAvenger if you actually use the function,
I can just re-add it)
Constructor: BitBuffer.Create()
Read/Write pairs for reading data from or writing data to the BitBuffer:
BitBuffer:WriteUnsigned(bitWidth, value)
BitBuffer:ReadUnsigned(bitWidth)
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, value)
BitBuffer:ReadSigned(bitWidth)
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, exponentBitWidth, value)
BitBuffer:ReadFloat(mantissaBitWidth, exponentBitWidth)
Read / Write a floating point number with a given mantissa and
exponent size in bits.
BitBuffer:WriteFloat32(value)
BitBuffer:ReadFloat32()
BitBuffer:WriteFloat64(value)
BitBuffer:ReadFloat64()
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(value)
BitBuffer:ReadBool()
Read / Write a boolean (true / false) value. Takes one bit worth
of space to store.
BitBuffer:WriteString(str)
BitBuffer:ReadString()
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).
BitBuffer:WriteUnicodeString(str)
BitBuffer:ReadUnicodeString()
Read / Write a variable length string. The string may contain
embedded nulls. Uses 5 bits to store the bit width of all the
characters, and each character is the largest character bit
width in the string. This exists for future compatibilty with
Unicode.
BitBuffer:WriteRotation(cframe)
BitBuffer:ReadRotation()
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")
From/To pairs for dumping out the BitBuffer to another format:
BitBuffer:ToString()
BitBuffer:FromString(str)
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()
BitBuffer:FromBase64(str)
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.
Buffer / Position Manipulation
BitBuffer:ResetPtr()
Will Reset the point in the buffer that is being read / written
to back to the start of the buffer.
BitBuffer:Reset()
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.Create()
SaveToBuffer(buff, someUserData)
myDataStore:SetAsync(somePlayer.userId, buff:ToBase64())
--...
local data = myDataStore:GetAsync(somePlayer.userId)
local buff = BitBuffer.Create()
buff:FromBase64(data)
LoadFromBuffer(buff, someUserData)
--]]
local Module = {}
function Module.new()
local Buffer = {}
local abs,ldexp,frexp,floor = math.abs,math.ldexp,math.frexp,math.floor
local atan2,pi,ceil,log10 = math.atan2,math.pi,math.ceil,math.log10
local len,sub,byte,find,char = string.len,string.sub,string.byte,string.find,string.char
local CFramenew,CFrameAngles = CFrame.new,CFrame.Angles
local BinaryString = ""
local Pointer = 0
local NullCharacter = 0x10
local function ResetPtr()
Pointer = 0
end
local function Reset()
BinaryString = ""
Pointer = 0
end
local function WriteBits(String)
BinaryString = BinaryString..String
Pointer = Pointer + len(String)
end
local function ReadBits(BitWidth)
local Binary = sub(BinaryString,Pointer + 1, Pointer + BitWidth)
Pointer = Pointer + BitWidth
return Binary
end
local function WriteUnsigned(BitWidth,Number)
local BinNum = ""
for i = 1, BitWidth do
BinNum = BinNum..(Number % 2)
Number = floor(Number / 2)
end
WriteBits(BinNum)
end
local function ReadUnsigned(BitWidth)
local Binary = ReadBits(BitWidth)
local Length = len(Binary)
local Number = 0
for Spot = 1, Length do
local NumberSpot = sub(Binary,Spot,Spot)
Number = Number + (tonumber(NumberSpot) * 2^(Spot - 1))
end
return Number
end
local function WriteSigned(BitWidth,Number)
WriteBits((Number < 0 and "1" or "0"))
WriteUnsigned(BitWidth - 1,abs(Number))
end
local function ReadSigned(BitWidth)
local SignBit = ReadBits(1)
local Number = ReadUnsigned(BitWidth - 1)
return Number * (SignBit == "0" and 1 or -1)
end
local function WriteFloat(MantissaBitWidth,ExponentBitWidth,Float)
--Code adopted from Stravant
-- Sign
local Sign = 1
if Float < 0 then
Float = -Float
Sign = -1
end
-- Decompose
local Mantissa, Exponent = frexp(Float)
if Exponent == 0 and Mantissa == 0 then
WriteUnsigned(MantissaBitWidth + ExponentBitWidth + 1, 0)
return
else
Mantissa = ((Mantissa - 0.5)/0.5 * (2^MantissaBitWidth))
end
-- Write sign
if Sign == -1 then
WriteBits("1")
else
WriteBits("0")
end
-- Write mantissa
Mantissa = floor(Mantissa + 0.5) -- Not really correct, should round up/down based on the parity of |wexp|
WriteUnsigned(MantissaBitWidth, Mantissa)
-- Write exponent
local MaxExp = (2^(ExponentBitWidth-1))-1
if Exponent > MaxExp then
Exponent = MaxExp
end
if Exponent < -MaxExp then
Exponent = -MaxExp
end
WriteSigned(ExponentBitWidth , Exponent)
end
local function ReadFloat(MantissaBitWidth,ExponentBitWidth)
--Code adopted from Stravant
-- Read sign
local Sign = 1
if ReadBits(1) == "1" then
Sign = -1
end
-- Read mantissa
local Mantissa = ReadUnsigned(MantissaBitWidth)
-- Read exponent
local Exponent = ReadSigned(ExponentBitWidth)
if Exponent == 0 and Mantissa == 0 then
return 0
end
-- Convert mantissa
Mantissa = Mantissa / (2^MantissaBitWidth) * 0.5 + 0.5
-- Output
return Sign * ldexp(Mantissa, Exponent)
end
function Buffer:WriteUnsigned(BitWidth,Number)
if not BitWidth then error("No BitWidth given.") return end
if not Number then error("No Number given.") return end
if Number < 0 then error("Number is signed.") return end
if (2 ^ BitWidth) - 1 < Number then error("Number does not fit in BitWidth.") return end
if floor(Number) ~= Number then error("Number is not an integer.") return end
WriteUnsigned(BitWidth,Number)
end
function Buffer:WriteSigned(BitWidth,Number)
if not BitWidth then error("No BitWidth given.") return end
if not Number then error("No Number given.") return end
if (2 ^ (BitWidth - 1)) - 1 < abs(Number) then error("Number does not fit in BitWidth.") return end
if floor(Number) ~= Number then error("Number is not an integer.") return end
WriteSigned(BitWidth,Number)
end
function Buffer:WriteBool(Value)
if Value ~= true and Value ~= false then error("Value is not a bool") return end
WriteBits(Value == true and "1" or "0")
end
function Buffer:WriteString(String)
if not String then error("No String given.") return end
local BitWidth = 7
for i = 1, #String do
if byte(sub(String, i, i)) > 127 then
BitWidth = 8
break
end
end
if BitWidth == 7 then
WriteBits("0")
else
WriteBits("1")
end
for i = 1, #String do
local Character = byte(sub(String, i, i))
if Character == NullCharacter then
WriteUnsigned(BitWidth,NullCharacter)
WriteBits("1")
else
WriteUnsigned(BitWidth,Character)
end
end
WriteUnsigned(BitWidth,NullCharacter)
WriteBits("0")
end
local log10of2 = log10(2)
local function NeededBits(Num)
return ceil(log10(Num + 1)/log10of2)
end
function Buffer:WriteUnicodeString(String)
if not String then error("No String given.") return end
local BitWidth = 0
for i = 1, #String do
local NeededBitLength = NeededBits(byte(sub(String, i, i)))
if NeededBitLength > BitWidth then
BitWidth = NeededBitLength
end
end
WriteUnsigned(5,BitWidth)
for i = 1, #String do
local Character = byte(sub(String, i, i))
if Character == NullCharacter then
WriteUnsigned(BitWidth,NullCharacter)
WriteBits("1")
else
WriteUnsigned(BitWidth,Character)
end
end
WriteUnsigned(BitWidth,NullCharacter)
WriteBits("0")
end
function Buffer:WriteFloat(MantissaBitWidth,ExponentBitWidth,Float)
if not MantissaBitWidth then error("No MantissaBitWidth given.") return end
if not ExponentBitWidth then error("No ExponentBitWidth given.") return end
if not Float then error("No Float given.") return end
WriteFloat(MantissaBitWidth,ExponentBitWidth,Float)
end
function Buffer:WriteFloat32(Float)
WriteFloat(23, 8, Float)
end
function Buffer:WriteFloat64(Float)
WriteFloat(52, 11, Float)
end
function Buffer:ReadUnsigned(BitWidth)
if not BitWidth then error("No BitWidth given.") return end
return ReadUnsigned(BitWidth)
end
function Buffer:ReadSigned(BitWidth)
if not BitWidth then error("No BitWidth given.") return end
return ReadSigned(BitWidth)
end
function Buffer:ReadBool(Value)
return (ReadBits(1) == "1" and true or false)
end
function Buffer:ReadString()
local BitWidth = (ReadBits(1) == "1" and 8 or 7)
local String = ""
while true do
local NextCharacter = ReadUnsigned(BitWidth)
if NextCharacter == NullCharacter then
local Next = ReadBits(1)
if Next == "0" then
break
end
end
String = String..char(NextCharacter)
end
return String
end
function Buffer:ReadUnicodeString()
local BitWidth = ReadUnsigned(5)
local String = ""
while true do
local NextCharacter = ReadUnsigned(BitWidth)
if NextCharacter == NullCharacter then
local Next = ReadBits(1)
if Next == "0" then
break
end
end
String = String..char(NextCharacter)
end
return String
end
function Buffer:ReadFloat(MantissaBitWidth,ExponentBitWidth)
if not MantissaBitWidth then error("No MantissaBitWidth given.") return end
if not ExponentBitWidth then error("No ExponentBitWidth given.") return end
return ReadFloat(MantissaBitWidth,ExponentBitWidth)
end
function Buffer:ReadFloat32()
return ReadFloat(23, 8)
end
function Buffer:ReadFloat64()
return ReadFloat(52, 11)
end
local function round(n)
return floor(n + 0.5)
end
function Buffer:WriteRotation(CF)
--Code adopted from Stravant
local LookVector = CF.lookVector
local LookX,LookZ = LookVector.X,LookVector.Z
local Azumith = atan2(-LookX, -LookZ)
local YBase = (LookX^2 + LookZ^2)^0.5
local Elevation = atan2(LookVector.Y, YBase)
--local WithoutRoll = CFramenew(CF.p) * CFrameAngles(0, Azumith, 0) * CFrameAngles(Elevation, 0, 0)
local WithoutRoll = CFrameAngles(0, Azumith, 0) * CFrameAngles(Elevation, 0, 0)
local X, Y, Z = (WithoutRoll:inverse()*CF):toEulerAnglesXYZ()
local Roll = Z
Azumith = round((Azumith / pi ) * (2^21-1))
Roll = round((Roll / pi ) * (2^20-1))
Elevation = round((Elevation / (pi/2)) * (2^20-1))
WriteSigned(22,Azumith)
WriteSigned(21,Roll)
WriteSigned(21,Elevation)
end
function Buffer:ReadRotation()
--Code adopted from Stravant
local Azumith = ReadSigned(22)
local Roll = ReadSigned(21)
local Elevation = ReadSigned(21)
Azumith = pi * (Azumith / (2^21-1))
Roll = pi * (Roll / (2^20-1))
Elevation = (pi/2) * (Elevation / (2^20-1))
local Rot = CFrameAngles(0, Azumith, 0)
Rot = Rot * CFrameAngles(Elevation, 0, 0)
Rot = Rot * CFrameAngles(0, 0, Roll)
return Rot
end
local Base64Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
function Buffer:ToBase64()
--Code adopted from Stravant
local Power,Accumulate = 0,0
local Base64 = ""
for Spot = 1, ceil(len(BinaryString)/6) * 6 do
Accumulate = Accumulate + (2^Power)*(sub(BinaryString,Spot,Spot) == "1" and 1 or 0)
Power = Power + 1
if Power >= 6 then
Base64 = Base64..sub(Base64Characters,Accumulate + 1,Accumulate + 1)
Accumulate = 0
Power = 0
end
end
return Base64
end
function Buffer:FromBase64(Base64)
--Code adopted from Stravant
Reset()
for Spot = 1, #Base64 do
local Character = find(Base64Characters,sub(Base64,Spot,Spot)) - 1
for i = 1, 6 do
Pointer = Pointer + 1
BinaryString = BinaryString..(Character % 2)
Character = floor(Character / 2)
end
end
ResetPtr()
end
function Buffer:ToString()
--Code adopted from Stravant
local Power,Accumulate = 0,0
local String = ""
for Spot = 1, ceil(len(BinaryString)/6) * 6 do
Accumulate = Accumulate + (2^Power)*(sub(BinaryString,Spot,Spot) == "1" and 1 or 0)
Power = Power + 1
if Power >= 8 then
String = String..char(Accumulate)
Accumulate = 0
Power = 0
end
end
return String
end
function Buffer:FromString(String)
--Code adopted from Stravant
Reset()
for Spot = 1, #String do
local Character = byte(sub(String,Spot,Spot))
for i = 1, 8 do
Pointer = Pointer + 1
BinaryString = BinaryString..(Character % 2)
Character = floor(Character / 2)
end
end
ResetPtr()
end
function Buffer:Reset()
Reset()
end
function Buffer:ResetPtr()
ResetPtr()
end
function Buffer:Destroy()
Buffer = nil
end
return Buffer
end
return Module
--[[
==========================================================================
== API ==
Constructor: BitBuffer.Create()
Read/Write pairs for reading data from or writing data to the BitBuffer:
BitBuffer:WriteUnsigned(bitWidth, value)
BitBuffer:ReadUnsigned(bitWidth)
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, value)
BitBuffer:ReadSigned(bitWidth)
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, exponentBitWidth, value)
BitBuffer:ReadFloat(mantissaBitWidth, exponentBitWidth)
Read / Write a floating point number with a given mantissa and
exponent size in bits.
BitBuffer:WriteFloat32(value)
BitBuffer:ReadFloat32()
BitBuffer:WriteFloat64(value)
BitBuffer:ReadFloat64()
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(value)
BitBuffer:ReadBool()
Read / Write a boolean (true / false) value. Takes one bit worth
of space to store.
BitBuffer:WriteString(str)
BitBuffer:ReadString()
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).
BitBuffer:WriteBrickColor(color)
BitBuffer:ReadBrickColor()
Read / Write a roblox BrickColor. Provided as an example of
reading / writing a derived data type.
BitBuffer:WriteRotation(cframe)
BitBuffer:ReadRotation()
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")
From/To pairs for dumping out the BitBuffer to another format:
BitBuffer:ToString()
BitBuffer:FromString(str)
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()
BitBuffer:FromBase64(str)
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()
BitBuffer:FromBase128(str)
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:ResetPtr()
Will Reset the point in the buffer that is being read / written
to back to the start of the buffer.
BitBuffer:Reset()
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.Create()
SaveToBuffer(buff, someUserData)
myDataStore:SetAsync(somePlayer.userId, buff:ToBase64())
--...
local data = myDataStore:GetAsync(somePlayer.userId)
local buff = BitBuffer.Create()
buff:FromBase64(data)
LoadFromBuffer(buff, someUserData)
--]]
local BitBuffer = {
ClassName = "BitBuffer";
__tostring = function(self) return self.ClassName end;
} BitBuffer.__index = BitBuffer
local NumberToBase64, Base64ToNumber = {}, {} do
local chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i = 1, #chars do
local ch = string.sub(chars, i, i)
NumberToBase64[i - 1] = ch
Base64ToNumber[ch] = i - 1
end
end
local NumberToBase128, Base128ToNumber = {}, {} do -- edit
local chars = ""
for i = 0, 127 do chars = chars .. string.char(i) end
for i = 1, #chars do
local ch = string.sub(chars, i, i)
NumberToBase128[i - 1] = ch
Base128ToNumber[ch] = i - 1
end
end --/edit
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
local digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local function ToBase(n, b)
n = n - n % 1
if not b or b == 10 then return tostring(n) end
local t = {}
local sign = ""
if n < 0 then
sign = "-"
n = -n
end
repeat
local d = (n % b) + 1
n = n / b n = n - n % 1
table.insert(t, 1, string.sub(digits, d, d))
until n == 0
return sign .. table.concat(t, "")
end
function BitBuffer.new()
return setmetatable({
mBitPtr = 0;
mBitBuffer = {};
mDebug = false;
}, BitBuffer)
end
function BitBuffer:ResetPtr()
self.mBitPtr = 0
end
function BitBuffer:Reset()
self.mBitBuffer = {}
self.mBitPtr = 0
end
function BitBuffer:SetDebug(state) self.mDebug = state end
function BitBuffer:FromString(str)
assert(type(str) == "string", string.format("bad argument #1 in BitBuffer::FromString (string expected, instead got %s)", typeof(str)))
self:Reset()
for i in string.gmatch(str, ".") do
local ch = string.byte(i)
for _ = 1, 8 do
self.mBitPtr = self.mBitPtr + 1
self.mBitBuffer[self.mBitPtr] = ch % 2
ch = ch / 2 ch = ch - ch % 1
end
end
self.mBitPtr = 0
end
function BitBuffer:ToString()
local str = ""
local accum = 0
local pow = 0
for i = 1, math.ceil(#self.mBitBuffer / 8) * 8 do
accum = accum + PowerOfTwo[pow] * (self.mBitBuffer[i] or 0)
pow = pow + 1
if pow >= 8 then
str = str .. string.char(accum)
accum = 0
pow = 0
end
end
return str
end
-- Read / Write to base64
function BitBuffer:FromBase64(str)
assert(type(str) == "string", string.format("bad argument #1 in BitBuffer::FromBase64 (string expected, instead got %s)", typeof(str)))
self:Reset()
for i in string.gmatch(str, ".") do
local ch = Base64ToNumber[i]
assert(ch, "Bad character: 0x" .. ToBase(string.byte(i), 16))
for _ = 1, 6 do
self.mBitPtr = self.mBitPtr + 1
self.mBitBuffer[self.mBitPtr] = ch % 2
ch = ch / 2 ch = ch - ch % 1
end
assert(ch == 0, "Character value 0x" .. ToBase(Base64ToNumber[i], 16) .. " too large")
end
self:ResetPtr()
end
function BitBuffer:ToBase64()
local strtab = {}
local length = 0
local accum = 0
local pow = 0
for i = 1, math.ceil(#self.mBitBuffer / 6) * 6 do
accum = accum + PowerOfTwo[pow] * (self.mBitBuffer[i] or 0)
pow = pow + 1
if pow >= 6 then
length = length + 1
strtab[length] = NumberToBase64[accum]
accum = 0
pow = 0
end
end
return table.concat(strtab)
end
-- Read / Write to base128 -- edit
function BitBuffer:FromBase128(str)
assert(type(str) == "string", string.format("bad argument #1 in BitBuffer::FromBase128 (string expected, instead got %s)", typeof(str)))
self:Reset()
for i in string.gmatch(str, ".") do
local ch = Base128ToNumber[i]
assert(ch, "Bad character: 0x" .. ToBase(string.byte(i), 16))
for _ = 1, 7 do
self.mBitPtr = self.mBitPtr + 1
self.mBitBuffer[self.mBitPtr] = ch % 2
ch = ch / 2 ch = ch - ch % 1
end
assert(ch == 0, "Character value 0x" .. ToBase(Base128ToNumber[i], 16) .. " too large")
end
self:ResetPtr()
end
function BitBuffer:ToBase128()
local strtab = {}
local length = 0
local accum = 0
local pow = 0
for i = 1, math.ceil(#self.mBitBuffer / 7) * 7 do
accum = accum + PowerOfTwo[pow] * (self.mBitBuffer[i] or 0)
pow = pow + 1
if pow >= 7 then
length = length + 1
strtab[length] = NumberToBase128[accum]
accum = 0
pow = 0
end
end
return table.concat(strtab)
end -- /edit
-- Dump
function BitBuffer:Dump()
local str = ""
local str2 = ""
local accum = 0
local pow = 0
for i = 1, math.ceil(#self.mBitBuffer / 8) * 8 do
str2 = str2 .. (self.mBitBuffer[i] or 0)
accum = accum + PowerOfTwo[pow] * (self.mBitBuffer[i] or 0)
pow = pow + 1
if pow >= 8 then
str2 = str2 .. " "
str = str .. "0x" .. ToBase(accum, 16) .. " "
accum = 0
pow = 0
end
end
print("Bytes:", str)
print("Bits:", str2)
end
function BitBuffer:_writeBit(v)
self.mBitPtr = self.mBitPtr + 1
self.mBitBuffer[self.mBitPtr] = v
end
function BitBuffer:_readBit()
self.mBitPtr = self.mBitPtr + 1
return self.mBitBuffer[self.mBitPtr]
end
function BitBuffer:WriteUnsigned(w, value, printoff)
assert(w, "Bad arguments to BitBuffer::WriteUnsigned (Missing BitWidth)")
assert(value, "Bad arguments to BitBuffer::WriteUnsigned (Missing Value)")
assert(value >= 0, "Negative value to BitBuffer::WriteUnsigned")
assert(value % 1 == 0, "Non-integer value to BitBuffer::WriteUnsigned")
if self.mDebug and not printoff then print("WriteUnsigned[" .. w .. "]:", value) end
-- Store LSB first
for _ = 1, w do
self:_writeBit(value % 2)
value = value / 2 value = value - value % 1
end
assert(value == 0, "Value " .. tostring(value) .. " has width greater than " .. w .. "bits")
end
function BitBuffer:ReadUnsigned(w, printoff)
local value = 0
for i = 1, w do value = value + self:_readBit() * PowerOfTwo[i - 1] end
if self.mDebug and not printoff then print("ReadUnsigned[" .. w .. "]:", value) end
return value
end
-- Read / Write a signed number
function BitBuffer:WriteSigned(w, value)
assert(w and value, "Bad arguments to BitBuffer::WriteSigned (Did you forget a bitWidth?)")
assert(value % 1 == 0, "Non-integer value to BitBuffer::WriteSigned")
if self.mDebug then print("WriteSigned[" .. w .. "]:", value) end
-- Write sign
if value < 0 then
self:_writeBit(1)
value = 0 - value
else
self:_writeBit(0)
end
self:WriteUnsigned(w - 1, value, true)
end
function BitBuffer:ReadSigned(w)
local sign = (-1) ^ self:_readBit()
local value = self:ReadUnsigned(w - 1, true)
if self.mDebug then print("ReadSigned[" .. w .. "]:", sign * value) end
return sign * value
end
-- Read / Write a string. May contain embedded nulls (string.char(0))
function BitBuffer:WriteString(s)
assert(type(s) == "string", string.format("bad argument #1 in BitBuffer::WriteString (string expected, instead got %s)", typeof(s)))
-- First check if it's a 7 or 8 bit width of string
local bitWidth = 7
for character in string.gmatch(s, ".") do
if string.byte(character) > 127 then
bitWidth = 8
break
end
end
-- Write the bit width flag
if bitWidth == 7 then
self:WriteBool(false)
else
self:WriteBool(true) -- wide chars
end
-- Now write out the string, terminated with "0x10, 0b0"
-- 0x10 is encoded as "0x10, 0b1"
for i in string.gmatch(s, ".") do
local ch = string.byte(i)
if ch == 0x10 then
self:WriteUnsigned(bitWidth, 0x10)
self:WriteBool(true)
else
self:WriteUnsigned(bitWidth, ch)
end
end
-- Write terminator
self:WriteUnsigned(bitWidth, 0x10)
self:WriteBool(false)
end
function BitBuffer:ReadString()
-- Get bit width
local bitWidth = self:ReadBool() and 8 or 7
-- Loop
local str = ""
while true do
local ch = self:ReadUnsigned(bitWidth)
if ch == 0x10 then
local flag = self:ReadBool()
if flag then
str = str .. string.char(0x10)
else
break
end
else
str = str .. string.char(ch)
end
end
return str
end
-- Read / Write a bool
function BitBuffer:WriteBool(v)
assert(type(v) == "boolean", string.format("bad argument #1 in BitBuffer::WriteBool (boolean expected, instead got %s)", typeof(v)))
if self.mDebug then print("WriteBool[1]:", v and "1" or "0") end
if v then
self:WriteUnsigned(1, 1, true)
else
self:WriteUnsigned(1, 0, true)
end
end
function BitBuffer:ReadBool()
local v = self:ReadUnsigned(1, true) == 1
if self.mDebug then print("ReadBool[1]:", v and "1" or "0") end
return v
end
-- Read / Write a floating point number with |wfrac| fraction part
-- bits, |wexp| exponent part bits, and one sign bit.
function BitBuffer:WriteFloat(wfrac, wexp, f)
assert(wfrac and wexp and f)
-- Sign
local sign = 1
if f < 0 then
f = 0 - f
sign = -1
end
-- Decompose
local mantissa, exponent = math.frexp(f)
if exponent == 0 and mantissa == 0 then
self:WriteUnsigned(wfrac + wexp + 1, 0)
return
else
mantissa = ((mantissa - 0.5) / 0.5 * PowerOfTwo[wfrac])
end
-- Write sign
if sign == -1 then
self:WriteBool(true)
else
self:WriteBool(false)
end
-- 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(wfrac, mantissa)
-- Write exponent
local maxExp = PowerOfTwo[wexp - 1] - 1
if exponent > maxExp then exponent = maxExp end
if exponent < -maxExp then exponent = -maxExp end
self:WriteSigned(wexp, exponent)
end
function BitBuffer:ReadFloat(wfrac, wexp)
assert(wfrac and wexp)
local sign = self:ReadBool() and -1 or 1
local mantissa = self:ReadUnsigned(wfrac)
local exponent = self:ReadSigned(wexp)
if exponent == 0 and mantissa == 0 then return 0 end
mantissa = mantissa / PowerOfTwo[wfrac] / 2 + 0.5
return sign * math.ldexp(mantissa, exponent)
end
-- Read / Write single precision floating point
function BitBuffer:WriteFloat32(f)
self:WriteFloat(23, 8, f)
end
function BitBuffer:ReadFloat32()
return self:ReadFloat(23, 8)
end
-- Read / Write double precision floating point
function BitBuffer:WriteFloat64(f)
self:WriteFloat(52, 11, f)
end
function BitBuffer:ReadFloat64()
return self:ReadFloat(52, 11)
end
-- Read / Write a BrickColor
function BitBuffer:WriteBrickColor(b)
assert(typeof(b) == "BrickColor", string.format("bad argument #1 in BitBuffer::WriteBrickColor (BrickColor expected, instead got %s)", typeof(b)))
local pnum = BrickColorToNumber[b.Number]
if not pnum then
warn("Attempt to serialize non-pallete BrickColor `" .. tostring(b) .. "` (#" .. b.Number .. "), using Light Stone Grey instead.")
pnum = BrickColorToNumber[1032]
end
self:WriteUnsigned(6, pnum)
end
function BitBuffer:ReadBrickColor()
return NumberToBrickColor[self:ReadUnsigned(6)]
end
function BitBuffer:WriteRotation(cf)
assert(typeof(cf) == "CFrame", string.format("bad argument #1 in BitBuffer::WriteRotation (CFrame expected, instead got %s)", typeof(cf)))
local lookVector = cf.LookVector
local azumith = math.atan2(-lookVector.X, -lookVector.Z)
local ybase = math.sqrt(lookVector.X * lookVector.X + lookVector.Z * lookVector.Z)
local elevation = math.atan2(lookVector.Y, ybase)
local withoutRoll = CFrame.new(cf.Position) * CFrame.Angles(0, azumith, 0) * CFrame.Angles(elevation, 0, 0)
local _, _, roll = (withoutRoll:Inverse() * cf):ToEulerAnglesXYZ()
-- Atan2 -> in the range [-pi, pi]
azumith = math.floor(((azumith / 3.141592653589793115997963468544185161590576171875) * (PowerOfTwo[21] - 1)) + 0.5)
roll = math.floor(((roll / 3.141592653589793115997963468544185161590576171875) * (PowerOfTwo[20] - 1)) + 0.5)
elevation = math.floor(((elevation / 1.5707963267948965579989817342720925807952880859375) * (PowerOfTwo[20] - 1)) + 0.5)
self:WriteSigned(22, azumith)
self:WriteSigned(21, roll)
self:WriteSigned(21, elevation)
end
function BitBuffer:ReadRotation()
local azumith = self:ReadSigned(22)
local roll = self:ReadSigned(21)
local elevation = self:ReadSigned(21)
azumith = 3.141592653589793115997963468544185161590576171875 * (azumith / (PowerOfTwo[21] - 1))
roll = 3.141592653589793115997963468544185161590576171875 * (roll / (PowerOfTwo[20] - 1))
elevation = 1.5707963267948965579989817342720925807952880859375 * (elevation / (PowerOfTwo[20] - 1))
local rot = CFrame.Angles(0, azumith, 0)
rot = rot * CFrame.Angles(elevation, 0, 0)
rot = rot * CFrame.Angles(0, 0, roll)
return rot
end
function BitBuffer:WriteColor3(Color)
assert(typeof(Color) == "Color3", string.format("bad argument #1 in BitBuffer::WriteColor3 (Color3 expected, instead got %s)", typeof(Color)))
local R, G, B = Color.R * 255, Color.G * 255, Color.B * 255
R, G, B = math.floor(R), math.floor(G), math.floor(B)
self:WriteUnsigned(8, R)
self:WriteUnsigned(8, G)
self:WriteUnsigned(8, B)
end
function BitBuffer:ReadColor3()
return Color3.fromRGB(self:ReadUnsigned(8), self:ReadUnsigned(8), self:ReadUnsigned(8))
end
function BitBuffer:Destroy()
setmetatable(self, nil)
end
return BitBuffer
@howmanysmall
Copy link
Author

howmanysmall commented Oct 13, 2019

FastBitBuffer vs SlowBitBuffer:
image

local Workspace = game:GetService("Workspace")
local BitBuffer = require(Workspace.SlowBuffer:Clone())
local FastBuffer = require(Workspace.FastBuffer:Clone())

local UserData = {
	Unsigned = 10;
	Signed = -12;
	String = "Hello world";
	Bool = true;
	Float32 = 0.42314;
	Float64 = 0.54372583468;
	BrickColor = BrickColor.new("Dark green");
	Rotation = Workspace.Baseplate.CFrame;
	Color3 = Workspace.Baseplate.Color;
}

local function SaveToBuffer(Buffer)
	Buffer:WriteUnsigned(4, UserData.Unsigned)
	Buffer:WriteSigned(6, UserData.Signed)
	Buffer:WriteString(UserData.String)
	Buffer:WriteBool(UserData.Bool)
	Buffer:WriteFloat32(UserData.Float32)
	Buffer:WriteFloat64(UserData.Float64)
	Buffer:WriteBrickColor(UserData.BrickColor)
	Buffer:WriteRotation(UserData.Rotation)
	Buffer:WriteColor3(UserData.Color3)
end

local function LoadFromBuffer(Buffer)
	return{
		Unsigned = Buffer:ReadUnsigned(4);
		Signed = Buffer:ReadSigned(6);
		String = Buffer:ReadString();
		Bool = Buffer:ReadBool();
		Float32 = Buffer:ReadFloat32();
		Float64 = Buffer:ReadFloat64();
		BrickColor = Buffer:ReadBrickColor();
		Rotation = Buffer:ReadRotation();
		Color3 = Buffer:ReadColor3();
	}
end

local Functions = {}

Functions["BitBuffer"] = function()
	local Base64 = (function()
		local Buffer = BitBuffer.new()
		SaveToBuffer(Buffer)
		local Base64 = Buffer:ToBase64()
		Buffer:Destroy()
		return Base64
	end)()

	local NewData = (function()
		local Buffer = BitBuffer.new()
		Buffer:FromBase64(Base64)
		local NewData = LoadFromBuffer(Buffer)
		Buffer:Destroy()
		return NewData
	end)()

	return Base64, NewData
end

Functions["FastBuffer"] = function()
	local Base64 = (function()
		local Buffer = FastBuffer.new()
		SaveToBuffer(Buffer)
		local Base64 = Buffer:ToBase64()
		Buffer:Destroy()
		return Base64
	end)()

	local NewData = (function()
		local Buffer = FastBuffer.new()
		Buffer:FromBase64(Base64)
		local NewData = LoadFromBuffer(Buffer)
		Buffer:Destroy()
		return NewData
	end)()

	return Base64, NewData
end

require(2110831719).new(1, "FastBitBuffer vs SlowBitBuffer", Functions)

FastBitBuffer vs NexusAvenger BitBuffer:
image

local Workspace = game:GetService("Workspace")
local BitBuffer = require(Workspace.BitBuffer:Clone())
local FastBuffer = require(Workspace.FastBuffer:Clone())

local UserData = {
	Unsigned = 10;
	Signed = -12;
	String = "Hello world";
	Bool = true;
	Float32 = 0.42314;
	Float64 = 0.54372583468;
--	BrickColor = BrickColor.new("Dark green");
	Rotation = Workspace.Baseplate.CFrame;
--	Color3 = Workspace.Baseplate.Color;
}

local function SaveToBuffer(Buffer)
	Buffer:WriteUnsigned(4, UserData.Unsigned)
	Buffer:WriteSigned(6, UserData.Signed)
	Buffer:WriteString(UserData.String)
	Buffer:WriteBool(UserData.Bool)
	Buffer:WriteFloat32(UserData.Float32)
	Buffer:WriteFloat64(UserData.Float64)
--	Buffer:WriteBrickColor(UserData.BrickColor)
	Buffer:WriteRotation(UserData.Rotation)
--	Buffer:WriteColor3(UserData.Color3)
end

local function LoadFromBuffer(Buffer)
	return{
		Unsigned = Buffer:ReadUnsigned(4);
		Signed = Buffer:ReadSigned(6);
		String = Buffer:ReadString();
		Bool = Buffer:ReadBool();
		Float32 = Buffer:ReadFloat32();
		Float64 = Buffer:ReadFloat64();
--		BrickColor = Buffer:ReadBrickColor();
		Rotation = Buffer:ReadRotation();
--		Color3 = Buffer:ReadColor3();
	}
end

local Functions = {}

Functions["BitBuffer"] = function()
	local Base64 = (function()
		local Buffer = BitBuffer.new()
		SaveToBuffer(Buffer)
		local Base64 = Buffer:ToBase64()
		Buffer:Destroy()
		return Base64
	end)()

	local NewData = (function()
		local Buffer = BitBuffer.new()
		Buffer:FromBase64(Base64)
		local NewData = LoadFromBuffer(Buffer)
		Buffer:Destroy()
		return NewData
	end)()

	return Base64, NewData
end

Functions["FastBuffer"] = function()
	local Base64 = (function()
		local Buffer = FastBuffer.new()
		SaveToBuffer(Buffer)
		local Base64 = Buffer:ToBase64()
		Buffer:Destroy()
		return Base64
	end)()

	local NewData = (function()
		local Buffer = FastBuffer.new()
		Buffer:FromBase64(Base64)
		local NewData = LoadFromBuffer(Buffer)
		Buffer:Destroy()
		return NewData
	end)()

	return Base64, NewData
end

require(2110831719).new(1, "FastBitBuffer vs NexusAvenger BitBuffer", Functions)

@howmanysmall
Copy link
Author

Check the new repo instead of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment