How can i make a fixed update loop like 1000 / 60 exactly.
Not like delta time which can be different each time, but a frame independent 16 ms fixed update each and every time ?
How can i make a fixed update loop like 1000 / 60 exactly.
Not like delta time which can be different each time, but a frame independent 16 ms fixed update each and every time ?
Hey!
I think you could do it by comparing the current time in millis from Date.now()
in every frame and perform the update when delta time >= update period
. You get the point…
Here’s how you would normally do it:
let startTime = performance.now()
//const maxSubsteps = 3
let simulationSpeedFactor = 1//Not required, but a cool way to speed up/down a simulation in order to dynamically adjust an input buffer in a networked simulation, as used by Overwatch
scene.registerBeforeRender(()=>{
//let substeps = 0
const now = performance.now()
const delta = (now-startTime)/1000
startTime = now
accumulator += delta// <= 0.4 ? delta : 0.4
while (accumulator >= 0.016*simulationSpeedFactor// && substeps <= maxSubsteps) {
//substeps++
accumulator -= 0.016
world.update(0.016)
}
})
You can do it in your render loop as well.
You need to either clamp your delta or your accumulator, or use substeps in order to prevent the spiral of death condition. This of course depends on your game, but if you, due to lag or something else, calculates a delta of let’s say 2000-3000 ms, then there’s a chance your physics or whatever won’t be capable of catching up.
Be aware that you’ll need to interpolate transforms used in a fixed loop, as otherwise they’ll appear laggy/choppy.
Hi @Raggar!
I never used performance before just heard about it and was too busy to look it up so I just proposed an idea of measuring the elapsed time between two frames. Your solution is perfect! I did my homework now and I’ve read a few articles. Datetime.now is too slow for this and the only way to do it precisely is using performance.now()
Thanks a lot for your solution!
Yo @Raggar … I dont quite understand how i can use your solution yet. I will keep studying it.
Actually, smooth network movement is what i am really working on. I am trying to interpolate/extrapolate network entity movement in the scene but it ALWAYS choppy/jittery.
I have tried many different Unity style mechanics for LERPING/MOVING TOWARDS but i still get choppy movement. Most of the solutions from unity for smooth multiplayer movement is ran in the FIXEDUPDATE loop. so that is why asked how to make a fixed update loop.
Would you (or anyone else) be interested in helping me with smooth network movement for my Babylon Toolkit Pro Tools ? I can pay you for time and effort. (Plus give you a Free Lifetime Pro Tools License for use with the Babylon Toolkit Pro Features… Which networking is a part of)
Please let me know
hmmm… I tried this code verbatim, and it does NOT seem to run at world.update(0.016) or in my case this.tester function at FIXED interval
let startTime = performance.now()
let accumulator = 0;
let simulationSpeedFactor = 1
this.scene.registerBeforeRender(()=>{
const now = performance.now()
const delta = (now-startTime)/1000
startTime = now
accumulator += delta
while (accumulator >= 0.016*simulationSpeedFactor) {
//substeps++
accumulator -= 0.016
this.tester(0.016)
}
});
Unfortunately I don’t really have that kind of time on my hands for the time being.
But there are loads of examples both as tutorials and finished code on Github.
My own implementation is more or less based on this:
But I don’t use the synced clocks for interpolation, I simply use the current time, the latency from server to client as well as the desired offset in the past - to calculate how far back in time I want to interpolate to/from.
A fixed update loop isn’t meant to run in exact intervals. It is meant as a way of running your simulation in fixed time frames, as opposed to variable time steps. If two physics bodies are colliding, there’s potentially a huge difference in response between resolving a collision with 3x16 ms fixed steps, and doing the same with a variable time step of 48 ms. This can result in anything from spring explosion, to objects clipping through walls.
Fixed time steps are usually used to make physics and animations predictable and deterministic.
One update loop might do 2 x 16 ms, while the next might only have 7 ms worth of accumulated time, and will be skipped. This also leads to the lag you’ll encounter with a fixed loop.
Here’s an example of the loop in action, showing how choppy the box appears:
And here is the same example, but this time interpolating based on previous data:
In this example I interpolate positions and orientations, but I think a better alternative would be to simple interpolate the entire matrices.
Would it possible to see this implementation. This is the kind of stuff i was trying before. Converting to babylon of course… I buffer the updates and lerp to ref and everything… but still comes out choppy. Not animations… just the raw movement of the remote entities are just fucked.
So i am obviously not doing something right.
I been trying to port stuff like this for the Snapshot buffer implementations
using UnityEngine;
using System.Collections;
public class NetworkInterpolatedTransform : MonoBehaviour
{
public double interpolationBackTime = 0.1;
internal struct State
{
internal double timestamp;
internal Vector3 pos;
internal Quaternion rot;
}
// We store twenty states with "playback" information
State[] m_BufferedState = new State[20];
// Keep track of what slots are used
int m_TimestampCount;
void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
{
// Always send transform (depending on reliability of the network view)
if (stream.isWriting)
{
Vector3 pos = transform.position;
Quaternion rot = transform.rotation;
stream.Serialize(ref pos);
stream.Serialize(ref rot);
}
// When receiving, buffer the information
else
{
// Receive latest state information
Vector3 pos = Vector3.zero;
Quaternion rot = Quaternion.identity;
stream.Serialize(ref pos);
stream.Serialize(ref rot);
// Shift buffer contents, oldest data erased, 18 becomes 19, ... , 0 becomes 1
for (int i=m_BufferedState.Length-1;i>=1;i--)
{
m_BufferedState[i] = m_BufferedState[i-1];
}
// Save currect received state as 0 in the buffer, safe to overwrite after shifting
State state;
state.timestamp = info.timestamp;
state.pos = pos;
state.rot = rot;
m_BufferedState[0] = state;
// Increment state count but never exceed buffer size
m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);
// Check integrity, lowest numbered state in the buffer is newest and so on
for (int i=0;i<m_TimestampCount-1;i++)
{
if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)
Debug.Log("State inconsistent");
}
//Debug.Log("stamp: " + info.timestamp + "my time: " + Network.time + "delta: " + (Network.time - info.timestamp));
}
}
// This only runs where the component is enabled, which is only on remote peers (server/clients)
void Update () {
double currentTime = Network.time;
double interpolationTime = currentTime - interpolationBackTime;
// We have a window of interpolationBackTime where we basically play
// By having interpolationBackTime the average ping, you will usually use interpolation.
// And only if no more data arrives we will use extrapolation
// Use interpolation
// Check if latest state exceeds interpolation time, if this is the case then
// it is too old and extrapolation should be used
if (m_BufferedState[0].timestamp > interpolationTime)
{
for (int i=0;i<m_TimestampCount;i++)
{
// Find the state which matches the interpolation time (time+0.1) or use last state
if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)
{
// The state one slot newer (<100ms) than the best playback state
State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
// The best playback state (closest to 100 ms old (default time))
State lhs = m_BufferedState[i];
// Use the time between the two slots to determine if interpolation is necessary
double length = rhs.timestamp - lhs.timestamp;
float t = 0.0F;
// As the time difference gets closer to 100 ms t gets closer to 1 in
// which case rhs is only used
if (length > 0.0001)
t = (float)((interpolationTime - lhs.timestamp) / length);
// if t=0 => lhs is used directly
transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
return;
}
}
}
// Use extrapolation. Here we do something really simple and just repeat the last
// received state. You can do clever stuff with predicting what should happen.
else
{
State latest = m_BufferedState[0];
transform.localPosition = latest.pos;
transform.localRotation = latest.rot;
}
}
}
Hey!
I was thinking about how to mimic the Unity’s FixedUpdate
method. I don’t think there is a solution using the main thread so you have to use a webworker which will loop forever on maximum speed and you can send back messages to the main thread at fixed intervals.
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Babylon Template</title>
<style>
html,
body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#renderCanvas {
width: 100%;
height: 100%;
touch-action: none;
}
</style>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>
</head>
<body>
<canvas id="renderCanvas" touch-action="none"></canvas>
<!-- touch-action="none" for best results from PEP -->
<script>
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const createScene = function () {
const scene = new BABYLON.Scene(engine);
BABYLON.MeshBuilder.CreateBox("box", {});
const camera = new BABYLON.ArcRotateCamera(
"camera",
-Math.PI / 2,
Math.PI / 2.5,
15,
new BABYLON.Vector3(0, 0, 0)
);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight(
"light",
new BABYLON.Vector3(1, 1, 0)
);
return scene;
};
const scene = createScene(); //Call the createScene function
let frames = 0;
let steps = 0;
engine.runRenderLoop(function () {
frames++;
scene.render();
});
const worker = new Worker("worker.js");
worker.addEventListener("message", (e) => {
steps++;
console.log("fixedUpdate: steps", steps, "frames", frames);
});
worker.postMessage({ deltaTime: 1 / 60 });
window.addEventListener("resize", function () {
engine.resize();
});
</script>
</body>
</html>
worker.js
self.onmessage = function (evt) {
const deltaTime = evt.data.deltaTime * 1000;
let lastNow = performance.now();
while (true) {
const now = self.performance.now();
if (now > lastNow + deltaTime) {
lastNow = now;
self.postMessage("fixedUpdate");
}
}
};
I’m running on 100 FPS so: