How we reduced bandwidth usage by 60x in Astro Force (Roblox RTS)

Linear interpolation!

Torso.CFrame = Torso.CFrame:Lerp(GoalCFrame, LerpAmountHere)

For best results, LerpAmountHere should be tied to dt from heartbeat

1 Like

silly me it says it right there! :sweat_smile:

1 Like

This is a wonderful guide for server optimization! :smiley:
May I translate this article and post it in Korean Community?
I would leave the original link!

1 Like

Yeah, that sounds good to me! :slight_smile:

1 Like

Awesome topic and alot that i can learn of. One question (Im not sure whether this is asked alread). How are the AI’s themselves done? Are they humanoids? I can imagine the server having an easy job by only needing to send position data but the client having a very hard job to move all those units.

Thanks!

No humanoids – that would be far too laggy. The server moves the units and sends position updates to the clients. The clients render units at the given positions. Performance is not too bad having the client move many parts. :slight_smile:

Slightly unrelated question, how did you calculate the hash so there weren’t any duplicates, and the server and clients always stayed in sync?

We created a module to get unique hashes.

When a new unit is created, we request a new hash from the module. The module checks if there are any inactive hashes and if there are, it reactivates a hash and returns it. If there are no inactive hashes, we increment an internal counter and return this new hash.

When a unit dies, we return the hash to the module and it gets added to the list of inactive hashes.

1 Like

I think this is an amazing resource, and gives a lot of information.
I also liked pretending to understand what version 3 was about while I was reading.

2 Likes

This post was a life saver for me, I was working on a similar system since Roblox’s physics were too rigid (needed smoother collisions) and expensive for me, but the lag was unreal. I was totally unaware of the bandwidth usage of updating CFrames every frame, everyone should be made aware of this! I was getting like 500+ KB/s updating only 350 objects per frame.
Thank you for sharing this and for making it very easy to understand.

Question that doesn’t really pertain to original post: (feel free to not answer if you don’t want to give away something or maybe move to PMs)
I was wondering what your custom unit/soldier ‘collision function’ looked like? Mine is linear with distance and is something like: (1 - (distanceBtwEntities/(Entity.Radius + otherEntity.Radius))) * otherEntity.Weight. I’ve experimented with others like inverse-square but this seems to be the smoothest. Perhaps you do something different altogether? Heres a quick implementation example:
Note:
– Each ‘Entity.BounceVelocity’ is set to 0,0,0 at the end of each frame.
– I’m using arrays of size 3 for Vector3s for (very slim) performance gains and for a bit more flexibility for other functions I have. (1 == X, 2 == Y, 3 == Z)

local Entity = -- thisEntity
local otherEntity = -- other Entity
local bounceVectorX = Entity.Position[1] - otherEntity.Position[1]
local bounceVectorZ = Entity.Position[3] - otherEntity.Position[3]
local distanceSquared = bounceVectorX^2 + bounceVectorZ^2
local minDistance = Entity.Radius + otherEntity.Radius -- TODO: Better variable name for minDistance
if distanceSquared < (minDistance*minDistance) then
	local distance = math.sqrt(distanceSquared)
	
	local bounceAccel = (1 - (distance/minDistance))
	local bounceDirectionX = (bounceVectorX/distance)
	local bounceDirectionZ = (bounceVectorZ/distance)
	
	local EntityBounceVelocity = Entity.BounceVelocity
	local EntityBounceAccel = bounceAccel * otherEntity.Weight
	EntityBounceVelocity[1] = EntityBounceVelocity[1] + (bounceDirectionX * EntityBounceAccel)
	EntityBounceVelocity[3] = EntityBounceVelocity[3] + (bounceDirectionZ * EntityBounceAccel)
	
	
	local otherEntityBounceVelocity = otherEntity.BounceVelocity
	local otherEntityBounceAccel = bounceAccel * Entity.Weight
	otherEntityBounceVelocity[1] = otherEntityBounceVelocity[1] - (bounceDirectionX * otherEntityBounceAccel)
	otherEntityBounceVelocity[3] = otherEntityBounceVelocity[3] - (bounceDirectionZ * otherEntityBounceAccel)
end
1 Like

Hey! Sorry if I haven’t understood your post thoroughly, but could you possibly save some memory by using a pairing function, generating a unique number for the two axes (X and Z)? Since pairing functions only work on natural numbers, you’d have to make sure they don’t go below 0.

1 Like

https://cafe.naver.com/korearoblox/183743
https://cafe.naver.com/robloxstudio/2746
I translated this post. Thank you for your permission!

2 Likes

Great post! Though I have some questions left unanswered:

You said in previous replies that you won’t only send through positional data of the goal, so I assume that you tween the positional data on teh server, but how would you re-mark that the position has changed from a specific unit until it has tweened to the goal position?

Kind regards,

1 Like

I think this would take the same amount of bits to cover all the possible X and Z coordinates.

We don’t tween the position on the server, we use a custom movement function (called each game tick if the unit needs to move toward a goal) that takes into account acceleration and current velocity. So whenever this movement function is used, we know the unit has moved and we mark this unit for replication this game tick. Otherwise, the unit didn’t move and we don’t mark it for replication this game tick.

Hope this helps :slight_smile:

Thank you for the elaborate response, though I’m still a bit confused;

game tick

Do you mean like tick()?
like

while task.wait(tick()) do
    movementFunction()
end

?

that takes into account acceleration and current velocity.

How would you involve the physics engine in objects that don’t have a physical form?

Lastly

we use a custom movement function

I had first thought this would mean you’re just doing positionData = newPositionData, is it like that?
Thanks in advance for dedicating time to read this and apologies for thanking you at a later notice than expected.

Kind regards,

I think he means something like Heartbeat, although it’s possible he’s running the function less than 60 times a second to minimize performance cost.
For the rest of the questions, I don’t really have an answer.

1 Like

How do you account for varying arrival times causing jumpiness in position if you tie it to dt. If it arrives before the next packet is due does it not stutter with a brief stop?

Can you explain more about the heightmap with the Y position ?
How it’s possible to make heightmaps on Roblox ?

Just out of curiosity, how were you able to tell the client to start and stop rendering objects?

One of my projects uses a similar approach to rendering, and that’s had me stumped for a while.