Packet - Networking library

yup and also I don’t want to put other projects down the other devs have put a lot of hard work into them and would be rude of me to say “there rubbish and packet is the best”

so its best for you guys to inspect the source for the projects your intrested in and come to your own conclusion’s

but I’m happy to answer any questions you have about packet and the reason behind any of the design choices

and if you feel that one of the other projects does something better also tell me and I’ll see if I can’t use that information to improve packet

5 Likes

Hello, it is not necessary to use f32s to store rotation. You can use 3 f32s for position and 3 f16s for rotation, the 3 f16s is to represent axis, and you can encode rotation in that.
Example:

local axis, angle = cf:ToAxisAngle()
axis *= math.sin(angle/2)

writef32(posX)
writef32(posY)
writef32(posZ)

writef16(axis.X)
writef16(axis.Y)
writef16(axis.Z)

Reading:

local posX, posY, posZ, axisX, axisY, axisZ = …
local angle = math.sqrt(1 - axisX^2 - axisY^2 - axisZ^2)
local cf = CFrame.new(posX, posY, posZ, axisX, axisY, axisZ, angle)

This is the method i use for BufferConverter2 to store CFrames in 18 bytes with little to no loss
However, BufferConverter2 isn’t exactly the fastest way to do networking, I only use it for storage and compression. Hopefully this info would be useful for Packet

1 Like

In packet it uses U16s for rotation and F32s for position for the built in 18 byte cframe

because rotation has a fixed range you can get beter precision with U16 over F16 you will be using all 16 bits where you would be wasting bits with F16

1 Like

Yea both works, point is you can store rotation as 3 2 byte values and have plenty of precision; Good to know you already have this, ill probably use Packet :V

Yer both will work but with F16 your wasting like 5 bits out of the 16 bits it would have a reasonably large difference in precision

1 Like

Wait…but rotation is signed right, so that means you have around 32,000 digits to work with (both signed and unsigned) with i16, if you just multiply the angle by like 32,000 so it fits within the range (assuming angle is -1 → 1) then thats like a few decimal places of precision, if you multiply it by some power of 10 thats 10,000 max which is 4 decimal places of precision, probably good but you can get more with f16…

Tho if you add the angle by 1, you can use u16 and then the max is 20,000, and you can infer negative or positive by subtracting that back

I dunno though, maybe theres a better way to do this or sign just doesnt matter

the sign is not really important its 2 * pi / 65535 = 0.00009587526218325454 rad increments

here is a small benchmark

-- server
local packet = Packet("Name", Packet.NumberF64, Packet.CFrameF32U16, Packet.CFrameF32F16)

task.wait(1)

for index = 20000, 22000 do
	local angle = index / 10000
	local cframe = CFrame.fromAxisAngle(Vector3.yAxis, angle)
	packet:Fire(angle, cframe, cframe)
end
-- client
local packet = Packet("Name", Packet.NumberF64, Packet.CFrameF32U16, Packet.CFrameF32F16)

local u16Wins = 0
local f16Wins = 0

packet.OnClientEvent:Connect(function(number, U16, F16)
	warn(number)
	local axis, angle1 = U16:ToAxisAngle()
	local delta1 = math.abs(number - angle1)
	print("U16", angle1)
	local axis, angle2 = F16:ToAxisAngle()
	local delta2 = math.abs(number - angle2)
	print("F16", angle2)
	if delta1 < delta2 then warn("Winner: U16") u16Wins += 1 else warn("Winner: F16") f16Wins += 1 end
	print("")
end)

task.wait(3)

warn("U16", u16Wins)
warn("F16", f16Wins)

and here are the results

notice how F16 printed 2.1992104053497314 5 times in a row

this is because n / 10000 goes past a F16s precision but not a U16s precision
U16 would not repeat up to n / 10430.219195527361
so hopefully this shows that U16 is a lot more accurate then F16 (about 5x more accurate)

and this is what the 2 types look like


so in short U16 has about 5x more precision then F16
F16 has 0.00048828125 radian increments in this benchmark
U16 has 0.00009587526218325454 radian increments

but also take note that the increments for F16 are not fixed as the number gets higher the precision will get worse

also as a bonus writing/reading a U16 will also use less CPU then a F16

3 Likes

Really useful information, thank you :slight_smile:

I have to ask why only one RemoteEvent is used, and why a RemoteFunction isn’t used for Packet():response, but instead a RemoteEvent is?

every time you fire a remote event it uses 14 bytes of data so packet batches all your fires into 1 fire so you save 13 bytes per fire because packet still uses 1 byte per fire

packet also does not use remote functions because there not safe to use a exsploiter could never respond and it will cause the server to have a memory leak so packet uses remote events and also has a timeout feature so that exspoiters cannot give the server a memory leak

1 Like

It is a good module however I tested it against bytenet and I noticed while it approximatley halves the amount of data sent (4100 bytes down to 2075) and averages around 3.27kb, it can sometimes spike up to 8 - 10kb while byetnet keeps it constant at 5 - 3.5

humm sounds a bit strange that it would spike would you mind sharing your code so I can test it on my end as well and see where the problem your experiencing might be

Sure.

Server:

local RunService = game:GetService('RunService')

local This = require(game.ReplicatedStorage.Packet)

local OOP = require(game.ReplicatedStorage.OOP)

local data = table.create(2^8 - 1)

for i = 1,2^8 - 1 do
	data[i] = OOP.new(i)
	
end

local R = This("UpdatePosition",{hash = This.NumberU8,position = This.Vector3S16})

local timesPerSecond = 10
local Delay = 1 / timesPerSecond

local t = 0

RunService.Heartbeat:Connect(function(dt)
	
	if t >= Delay then
		t = 0
		for i = 1,2^8 - 1 do
			data[i].Position += Vector3.new(1,1,1)
			R:Fire({hash = data[i].hash,position = data[i].Position})
		end
	end
	
	t += dt
end)

Client:

local RepStorage = game:GetService('ReplicatedStorage')
local Packet = require(RepStorage:WaitForChild('Packet'))

local R = Packet("UpdatePosition",{hash = Packet.NumberU8,position = Packet.Vector3S16})

R.OnClientEvent:Connect(function(data)
	local index = data.hash
	local pos = data.position
	
end)

Thank you for sharing, its getting late here so ill test it tomorrow and see what results I get

2 Likes

wait so, you made secure version of InvokeClient, that honestly impressive.

1 Like

You assign the yielding thread with some id and when the client sends that id back you know what thread to resume, and if they take too long just resume it on your own. The last part is what remotefunctions don’t do

I just ran your test and did not experience any spikes

Recv stayed around ~4 KB/s
Packet Profiler also never showed any abnormal packets being sent

KingBobPacketTest.rbxl (73.7 KB)

here is the project I used to test

1 Like

Hey! I just tried out your library and it worked perfectly :partying_face:. But I have a question, when looking at the modules, is there a particular reason why you aren’t using --!native ? I’ve seen other people use it and they claim it’s much faster. Is it because --!native does not apply here since it’s network related? I’m not too sure, but let me know if you have the time! :smiling_face_with_three_hearts:

Anyways, love your work. Thanks for helping out the community Suphi! :heart: :heart: :heart:

I have a very old computer that does not support native so I personally cannot test to see how well it will work with it turned on

but feel free to turn it on if you like

native should improve CPU performance in exchange for memory usage

you can read more about it here: Luau Native Code Generation Preview [Studio Beta]

2 Likes

Odd it occurs in my original place but not the file you sent me. Here is a video of the occurrence (it spikes at 13 seconds)