Last active
November 29, 2020 04:06
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
========================================================================== | |
== 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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
========================================================================== | |
== 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
========================================================================== | |
== 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FastBitBuffer vs SlowBitBuffer:
FastBitBuffer vs NexusAvenger BitBuffer: