How do I convert a unit vector3 into byte form

I am sending a direction from the client through remotes and I need it to fire every frame for short amounts of time. I would prefer not to send a large amount of data to keep the game performant.

I have seen posts about string.pack and string.unpack however I don’t understand how these work and how I can read it on the server and use the direction. I also heard about bitpacking but I know very little about this stuff.

At the moment I am just sending a ray direciton which tends to have a lot of decimals
-0.05158676952123642, -0.5347210168838501, 0.8434526920318604
So I assume something like this can’t be packed down, but rounding to the 2nd or 3rd decimal would probs work fine.

1 Like

Alright, I have figured it out.

Neither bitpacking nor string.pack will be useful to you. Bitpacking would need to be handled in Lua which would cost performance, and string.pack is for converting various types to the shortest string possible to save space over what tostring does, but it won’t really save space when the data is sent in a form other than strings.

I think the best idea is to either reconsider why you need to do this and maybe revise your strategy, or handle everything normally and deal with issues as they arise.

I definitely don’t and it’s definitely overengineering but it was fun learning. I agree with you and I really doubt it would have impacted performance but regardless I’ll post what I found.

I honestly don’t know if this even does anything, and I’m not sure how to test that. I assume sending a string through a remote is slightly more performant than numbers.

I found a function that encodes strings into base64.

Original script:

Devforum Post:

Alongside this I found a function that packs a CFrame into string using the encoder. However, I wanted just a vector 3 so using the function as reference I made my own.

With these I made an encoder and decoder for vector3’s and strings. It definitely isn’t written in the best way, but it’s readable to me so it’s alright.

local module = {}

local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

local function Encode(data)
	return ((data:gsub('.', function(x) 
		local r,b='',x:byte()
		for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end
		return r;
	end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
		if (#x < 6) then return '' end
		local c=0
		for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
		return b:sub(c+1,c+1)
	end)..({ '', '==', '=' })[#data%3+1])
end

local function Decode(data)
	data = string.gsub(data, '[^'..b..'=]', '')
	return (data:gsub('.', function(x)
		if (x == '=') then return '' end
		local r,f='',(b:find(x)-1)
		for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
		return r;
	end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
		if (#x ~= 8) then return '' end
		local c=0
		for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
		return string.char(c)
	end))
end

function module:EncodeString(text)
	local packedString = string.pack("z", text)
	local encodedString = Encode(packedString)

	return encodedString
end

function module:DecodeString(text)
	local decodedString = Decode(text)
	local unpackedString = string.unpack("z", decodedString)

	return decodedString
end

function module:EncodeVector3(vectorValue)
	local packedX = string.pack("f", vectorValue.X)
	local encodedX = Encode(packedX)
	
	local packedY = string.pack("f", vectorValue.Y)
	local encodedY = Encode(packedY)
	
	local packedZ = string.pack("f", vectorValue.Z)
	local encodedZ = Encode(packedZ)
	
	local result = encodedX .. encodedY .. encodedZ
	
	return result
end

function module:DecodeVector3(vectorValue)
	local xValue = vectorValue:sub(1,8)
	local xDecoded = Decode(xValue)
	local xUnpacked = string.unpack("f", xDecoded)
	
	local yValue = vectorValue:sub(9,16)
	local yDecoded = Decode(yValue)
	local yUnpacked = string.unpack("f", yDecoded)
	
	local zValue = vectorValue:sub(17,24)
	local zDecoded = Decode(zValue)
	local zUnpacked = string.unpack("f", zDecoded)
	
	local result = Vector3.new(xUnpacked, yUnpacked, zUnpacked)
	
	return result
end

return module

I’m going to leave these links here for anyone curious as well since they were helpful to me.

2 Likes

That’s fair enough. That’s a respectable amount of research you’ve done on it.

Here are some numbers if you’re still curious.


Vector3s use 4-byte floats to represent the numbers for the sake of space. Luau uses 8-byte floats. If you try to handle the axes of a Vector3 manually, they become 8 bytes. In other words, if you care about space, the first of these is better.

-- only sends 12 bytes, plus 1 or 2 more bytes to specify that this is a Vector3.
remote_event:FireServer(some_vector3)
---
local x, y, z = some_vector3.X, some_vector3.Y, some_vector3.Z
-- sends 24 bytes.
remote_event:FireServer(x, y, z)

If you do want to store something using strings, base-64 is the equivalent of 0.75 bytes per letter. string.pack essentially uses base-256 and stores a full byte per letter. Given the options, string.pack is both faster and more space efficient. In addition, string.pack is capable of converting 8-byte floats into 4-byte floats for the sake of space, same as how Vector3s are stored. The caveat in our scenario here is that a 4-byte-per-axis Vector3, which takes up 12 bytes total, would take up the same 12 bytes with string.pack.

That’s one of the main purposes of string.pack though: fitting x bytes of data into a string that takes up the same x bytes of space for compatibility with systems that use strings.


As far as shortening stuff and shaving off decimals, it’s really not worth it. You have a couple of options, and none of them are good. The first thought is that you could just chop off the decimals. Mathematically, this would still be an 8-byte float. You could do it as a string, and store it as "4.56", but that alone takes up 4 bytes for no extra gain. It’s one byte per letter, but you’re only storing 0-9 and a decimal, essentially base-10—the worst of all possible cases.

"4.56" could theoretically be stored in a smaller amount of space if you convert it to a custom base-256 system, but you’d run into a few snags. You could overcome them with some understanding of float systems or what-have-you, but ultimately it would bring us back to performing processing in Luau, which is overhead you probably don’t want.


In summary, this is a great concept to think of and learn about, but existing systems are designed to handle this as efficiently as possible. Except in the case of long-term storage of massive quantities of data, it’s best to let the CPU’s natural affinity for floats and Roblox’s decade-and-a-half of network experience do the work for you.

2 Likes

Hi thank you so much that was very detailed. I hadn’t even thought of storing the vector3 into 3 variables, I’ll do that now.

By long-term storage are you talking about datastores? I do plan to make one sooner or later, would what i have be useful for it?

That’s backwards, I’m suggesting against that. I’ll make that a little more clear.

Yeah, but it depends on how much you need to store and what you want done with it. One guy I met favored the ability to edit the DataStores as needed, and preferred a hexadecimal system (7F33FF, six bytes) over the shortest form (three bytes) to store RGB color data. That’s not a huge difference when you’re also saving the CFrame of the part (36 bytes), the size (12 bytes), and other data for the part.

Oh okay interesting. Also my bad I think I misread apologies.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.