local N = require("flatbuffers.numTypes") local ba = require("flatbuffers.binaryarray") local compat = require("flatbuffers.compat") local string_unpack = compat.string_unpack local m = {} local mt = {} -- get locals for faster access local VOffsetT = N.VOffsetT local UOffsetT = N.UOffsetT local SOffsetT = N.SOffsetT local Bool = N.Bool local Uint8 = N.Uint8 local Uint16 = N.Uint16 local Uint32 = N.Uint32 local Uint64 = N.Uint64 local Int8 = N.Int8 local Int16 = N.Int16 local Int32 = N.Int32 local Int64 = N.Int64 local Float32 = N.Float32 local Float64 = N.Float64 local MAX_BUFFER_SIZE = 0x80000000 -- 2 GB local VtableMetadataFields = 2 local getAlignSize = compat.GetAlignSize local function vtableEqual(a, objectStart, b) UOffsetT:EnforceNumber(objectStart) if (#a * 2) ~= #b then return false end for i, elem in ipairs(a) do local x = string_unpack(VOffsetT.packFmt, b, 1 + (i - 1) * 2) if x ~= 0 or elem ~= 0 then local y = objectStart - elem if x ~= y then return false end end end return true end function m.New(initialSize) assert(0 <= initialSize and initialSize < MAX_BUFFER_SIZE) local o = { finished = false, bytes = ba.New(initialSize), nested = false, head = initialSize, minalign = 1, vtables = {} } setmetatable(o, {__index = mt}) return o end -- Clears the builder and resets the state. It does not actually clear the backing binary array, it just reuses it as -- needed. This is a performant way to use the builder for multiple constructions without the overhead of multiple -- builder allocations. function mt:Clear() self.finished = false self.nested = false self.minalign = 1 self.currentVTable = nil self.objectEnd = nil self.head = self.bytes.size -- place the head at the end of the binary array -- clear vtables instead of making a new table local vtable = self.vtables local vtableCount = #vtable for i=1,vtableCount do vtable[i] = nil end end function mt:Output(full) assert(self.finished, "Builder Not Finished") if full then return self.bytes:Slice() else return self.bytes:Slice(self.head) end end function mt:StartObject(numFields) assert(not self.nested) local vtable = {} for _=1,numFields do table.insert(vtable, 0) end self.currentVTable = vtable self.objectEnd = self:Offset() self.nested = true end function mt:WriteVtable() self:PrependSOffsetTRelative(0) local objectOffset = self:Offset() local exisitingVTable local i = #self.vtables while i >= 1 do if self.vtables[i] == 0 then table.remove(self.vtables,i) end i = i - 1 end i = #self.vtables while i >= 1 do local vt2Offset = self.vtables[i] local vt2Start = self.bytes.size - vt2Offset local vt2lenstr = self.bytes:Slice(vt2Start, vt2Start+1) local vt2Len = string_unpack(VOffsetT.packFmt, vt2lenstr, 1) local metadata = VtableMetadataFields * 2 local vt2End = vt2Start + vt2Len local vt2 = self.bytes:Slice(vt2Start+metadata,vt2End) if vtableEqual(self.currentVTable, objectOffset, vt2) then exisitingVTable = vt2Offset break end i = i - 1 end if not exisitingVTable then i = #self.currentVTable while i >= 1 do local off = 0 local a = self.currentVTable[i] if a and a ~= 0 then off = objectOffset - a end self:PrependVOffsetT(off) i = i - 1 end local objectSize = objectOffset - self.objectEnd self:PrependVOffsetT(objectSize) local vBytes = #self.currentVTable + VtableMetadataFields vBytes = vBytes * 2 self:PrependVOffsetT(vBytes) local objectStart = self.bytes.size - objectOffset self.bytes:Set(SOffsetT:Pack(self:Offset() - objectOffset),objectStart) table.insert(self.vtables, self:Offset()) else local objectStart = self.bytes.size - objectOffset self.head = objectStart self.bytes:Set(SOffsetT:Pack(exisitingVTable - objectOffset),self.head) end self.currentVTable = nil return objectOffset end function mt:EndObject() assert(self.nested) self.nested = false return self:WriteVtable() end local function growByteBuffer(self, desiredSize) local s = self.bytes.size assert(s < MAX_BUFFER_SIZE, "Flat Buffers cannot grow buffer beyond 2 gigabytes") local newsize = s repeat newsize = math.min(newsize * 2, MAX_BUFFER_SIZE) if newsize == 0 then newsize = 1 end until newsize > desiredSize self.bytes:Grow(newsize) end function mt:Head() return self.head end function mt:Offset() return self.bytes.size - self.head end function mt:Pad(n) if n > 0 then -- pads are 8-bit, so skip the bytewidth lookup local h = self.head - n -- UInt8 self.head = h self.bytes:Pad(n, h) end end function mt:Prep(size, additionalBytes) if size > self.minalign then self.minalign = size end local h = self.head local k = self.bytes.size - h + additionalBytes local alignsize = getAlignSize(k, size) local desiredSize = alignsize + size + additionalBytes while self.head < desiredSize do local oldBufSize = self.bytes.size growByteBuffer(self, desiredSize) local updatedHead = self.head + self.bytes.size - oldBufSize self.head = updatedHead end self:Pad(alignsize) end function mt:PrependSOffsetTRelative(off) self:Prep(4, 0) assert(off <= self:Offset(), "Offset arithmetic error") local off2 = self:Offset() - off + 4 self:Place(off2, SOffsetT) end function mt:PrependUOffsetTRelative(off) self:Prep(4, 0) local soffset = self:Offset() if off <= soffset then local off2 = soffset - off + 4 self:Place(off2, UOffsetT) else error("Offset arithmetic error") end end function mt:StartVector(elemSize, numElements, alignment) assert(not self.nested) self.nested = true local elementSize = elemSize * numElements self:Prep(4, elementSize) -- Uint32 length self:Prep(alignment, elementSize) return self:Offset() end function mt:EndVector(vectorNumElements) assert(self.nested) self.nested = false self:Place(vectorNumElements, UOffsetT) return self:Offset() end function mt:CreateString(s) assert(not self.nested) self.nested = true assert(type(s) == "string") self:Prep(4, #s + 1) self:Place(0, Uint8) local l = #s self.head = self.head - l self.bytes:Set(s, self.head, self.head + l) return self:EndVector(l) end function mt:CreateByteVector(x) assert(not self.nested) self.nested = true local l = #x self:Prep(4, l) self.head = self.head - l self.bytes:Set(x, self.head, self.head + l) return self:EndVector(l) end function mt:Slot(slotnum) assert(self.nested) -- n.b. slot number is 0-based self.currentVTable[slotnum + 1] = self:Offset() end local function finish(self, rootTable, sizePrefix) UOffsetT:EnforceNumber(rootTable) self:Prep(self.minalign, sizePrefix and 8 or 4) self:PrependUOffsetTRelative(rootTable) if sizePrefix then local size = self.bytes.size - self.head Int32:EnforceNumber(size) self:PrependInt32(size) end self.finished = true return self.head end function mt:Finish(rootTable) return finish(self, rootTable, false) end function mt:FinishSizePrefixed(rootTable) return finish(self, rootTable, true) end function mt:Prepend(flags, off) self:Prep(flags.bytewidth, 0) self:Place(off, flags) end function mt:PrependSlot(flags, o, x, d) flags:EnforceNumbers(x,d) -- flags:EnforceNumber(x) -- flags:EnforceNumber(d) if x ~= d then self:Prepend(flags, x) self:Slot(o) end end function mt:PrependBoolSlot(...) self:PrependSlot(Bool, ...) end function mt:PrependByteSlot(...) self:PrependSlot(Uint8, ...) end function mt:PrependUint8Slot(...) self:PrependSlot(Uint8, ...) end function mt:PrependUint16Slot(...) self:PrependSlot(Uint16, ...) end function mt:PrependUint32Slot(...) self:PrependSlot(Uint32, ...) end function mt:PrependUint64Slot(...) self:PrependSlot(Uint64, ...) end function mt:PrependInt8Slot(...) self:PrependSlot(Int8, ...) end function mt:PrependInt16Slot(...) self:PrependSlot(Int16, ...) end function mt:PrependInt32Slot(...) self:PrependSlot(Int32, ...) end function mt:PrependInt64Slot(...) self:PrependSlot(Int64, ...) end function mt:PrependFloat32Slot(...) self:PrependSlot(Float32, ...) end function mt:PrependFloat64Slot(...) self:PrependSlot(Float64, ...) end function mt:PrependUOffsetTRelativeSlot(o,x,d) if x~=d then self:PrependUOffsetTRelative(x) self:Slot(o) end end function mt:PrependStructSlot(v,x,d) UOffsetT:EnforceNumber(d) if x~=d then UOffsetT:EnforceNumber(x) assert(x == self:Offset(), "Tried to write a Struct at an Offset that is different from the current Offset of the Builder.") self:Slot(v) end end function mt:PrependBool(x) self:Prepend(Bool, x) end function mt:PrependByte(x) self:Prepend(Uint8, x) end function mt:PrependUint8(x) self:Prepend(Uint8, x) end function mt:PrependUint16(x) self:Prepend(Uint16, x) end function mt:PrependUint32(x) self:Prepend(Uint32, x) end function mt:PrependUint64(x) self:Prepend(Uint64, x) end function mt:PrependInt8(x) self:Prepend(Int8, x) end function mt:PrependInt16(x) self:Prepend(Int16, x) end function mt:PrependInt32(x) self:Prepend(Int32, x) end function mt:PrependInt64(x) self:Prepend(Int64, x) end function mt:PrependFloat32(x) self:Prepend(Float32, x) end function mt:PrependFloat64(x) self:Prepend(Float64, x) end function mt:PrependVOffsetT(x) self:Prepend(VOffsetT, x) end function mt:Place(x, flags) local d = flags:EnforceNumberAndPack(x) local h = self.head - flags.bytewidth self.head = h self.bytes:Set(d, h) end return m