Determinism is a term we can use to describe algorithms, where given the same input they give the same output. Meanwhile this definitions seems to be simple it allows us to do one really cool thing: if we can sync the input to the function on client and server, we can make the client calculate updates to things instead of the server.
An example of this is a moving part, consider this script
local part = workspace.Part
RunService.Heartbeat:Connect(function(dt)
part:PivotTo(part:GetPivot() * CFrame.new(dt * 10, 0, 0))
end)
This being ran on the server will send a packet to the client every heartbeat, not really network efficient.
Moving the script to the client will have 2 problems
- Players will see the part in different positions, relative when they joined
- Even if they joined at the same time, the parts will slowly drive away from eachothers cause of small floating point errors
GetServerTimeNow
GetServerTimeNow is a method under workspace, which approximates the current time on server, which means from the last time it got a current time update from a server, it will approximate time it should be now, it slowly speeds up or slows down to account for innacuracies within the approximation.
Rewriting the algorithm to be deterministic
The deterministic version of the script will use 3 variables, startTime, currentTime, startPosition from which point on we will use the speed formula.
On server in the part we will make it keep a timestamp of when it was created
part:SetAttribute("StartTime", workspace:GetServerTimeNow())
part:SetAttribute("StartPosition", part:GetPivot())
and on client we will calculate the distance the part would have passed since that timestamp, every update treshould you would use (im gonna use RunService.RenderStepped)
local startTime = part:GetAttribute("StartTime")
local startPosition = part:GetAttribute("StartPosition")
RunService.RenderStepped:Connect(function()
local currentTime = workspace:GetServerTimeNow()
local distance = currentTime - startTime
local newCFrame = startPosition * CFrame.new(distance * SPEED, 0, 0)
part:PivotTo(newCFrame)
end)
Using this method causes all clients to be synced up even better than constantly sending the new position thru the server with less packets.
Cons
- If your algorithm uses randomness, and that randomness is supposed to be unpredictable to the client, then yeah you cant use this method (otherwise just send the seed)
- You have to convert your previous algorithm to a deterministic one which might be hard or impossible, consider other algorithms that achieve similiar things (Personal example: i was making a orb which looks like it wanders around, my original version would have the server choose the new position to go at random, for the derministic version i have used math.noise with argument being the difference between start time and time now, math.noise would decide the orbs distance from the startPoint)
Edit: Changed the title from “Using Determinism to Lower Network Strain” to “Why GetServerTimeNow is the GOAT of client-side simulation”