4

I'm trying to get a good animation with constant fps, but nothing works. I'm using threejs, webgl to render the scene and for the animation loop I found two ways (is there a third?), which is either requestAnimationFrame(...) or by setTimeOut(). Both don't guarantee that the fps is constant, but I'm fixing it by updating the object position by the timedelta of window.performance.now(). But I still have lagspikes which one can clearly see. So how can I fix this? It's obviously possibly because there are games like doom which don't lag.

My example with full src-code can be found here:

http://sc2tube.com:8080/test/three.html

the relevant code:

function animate() {

requestAnimationFrame( animate );

// calculate how long the last frame was 
var timefix = (window.performance.now() - last)/(1000/30);

last = window.performance.now();

var oldX = object.position.x;
// calculate updateX including the timefix
var updateX = oldX + (10 / 30 * 100) * dx * timefix;

// update the position of the object
object.position.x = updateX;  
// render the scene
renderer.render(scene, camera);

}

worker.js:

self.addEventListener('message', function(e) {

setInterval(function(){ 

    now = self.performance.now()
    timefix = (now - last)/(1000/100);
    last = now;

    x += 5*timefix*dx;

    self.postMessage(x);

}, 1000/100);

}, false);

var test;  
 var dx = 1, dy = 0;
 var speed = 0.5;

 var activeKey = 0;
    // Set up the scene, camera, and renderer as global variables.
    var scene, camera, renderer;

    init();
    animate();

    // Sets up the scene.
    function init() {

      // Create the scene and set the scene size.
      scene = new THREE.Scene();
      var WIDTH = window.innerWidth - 50,
          HEIGHT = 500;

      // Create a renderer and add it to the DOM.
      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);

      camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
      
      camera.position.set(0,0,100);
      scene.add(camera);
      console.log(WIDTH);
      
      
      window.addEventListener('resize', function() {
       var WIDTH = window.innerWidth - 50,
          HEIGHT = window.innerHeight - 50;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
        
      });
      renderer.setClearColor();

      var loader = new THREE.ObjectLoader();
     
      loader.parse({
    "metadata" : {
        "type" : "Object",
        "version" : 4.3,
        "generator" : "Blender Script"
    },
    "object" : {
        "name" : "red_cube.Material",
        "type" : "Mesh",
        "uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
        "matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
        "material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
    },
    "geometries" : [{
            "name" : "red_cube.Material",
            "type" : "BufferGeometry",
            "uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
            "data" : {
                "attributes" : {
                    "position" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
                    },
                    "normal" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
                    },
                    "index" : {
                        "type" : "Uint32Array",
                        "itemSize" : 1,
                        "array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
                    }
                }
            }
        }],
    "materials" : [{
            "name" : "Material",
            "type" : "MeshBasicMaterial",
            "uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
            "transparent" : false,
            "opacity" : 1,
            "color" : 10682379
        }]
}, function(object){
         test = object;
         object.scale.set(50,50,50);
         scene.add(object)
         
      });
      
   document.addEventListener('keydown', function(e) {
       if (activeKey == e.keyCode) return;
       activeKey = e.keyCode;
       
       //left
       if (e.keyCode == 37) {
           dx = -1;
       }
       //top
       else if (e.keyCode == 38) {
           dy = 1;
       }
       //right
       else if (e.keyCode == 39) {
           dx = 1;
       }
       //bottom
       else if (e.keyCode == 40) {
           dy = -1;
       }
   });
   document.addEventListener('keyup', function(e) {
       switch (e.keyCode) {
           case 37: // left
           case 39: // right
               dx = 0;
               break;
               
           case 38: // up
           case 40: // down
               dy = 0;
               break;
       }
       
       activeKey = 0;
   });

    }
    
    var start;
    var last;

    function animate() {

        requestAnimationFrame( animate );
                
     if(start == null) {
      start = window.performance.now();
      last = start;
     }
     
     var timefix = (window.performance.now() - last)/(1000/30);
     
     last = window.performance.now();
     
        if(test != null) {
            var oldX = test.position.x;
            var oldY = test.position.y;
            
            var updateX = oldX + (10 / 30 * 100) * dx * speed * timefix;
            var updateY = oldY + (10 / 30 * 100) * dy * speed * timefix;  
            
            if(updateX > 1800 ) {
             dx = -1;
            } else if(updateX < 100) {             
             dx = 1;
            }
            
            test.position.x = updateX;  
            test.position.y =  oldY + (10 / 30 * 100) * dy * speed * timefix;  
            
          var text = document.getElementById('panel');
          text.innerHTML =  timefix;
         renderer.render(scene, camera);
        }

    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">

<div id="panel">TEST
</div>
<br>

</body>
Vertago
  • 315
  • 2
  • 16

1 Answers1

1

The problem is something called game ticks.

What you need is threads. one rendering thread. one game thread.

For the game thread I advice a webworker:

https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers

You let the game thread run every 50ms to update game logic. it should 'sleep" inbetween. You post stuff back to rendering thread, which updates everything and interprolates the trajectories for currentpos to next pos in 50 ms.

  • tick 1. 0MS

    • game thread:
      • spawn block at 0.0
      • push gamestate to rendering thread
    • rendering thread
      • gather entities.
      • update entities for location if moving
      • draw entities at pos
  • tick 2. 50ms

    • game thread:
      • get entities
      • trigger update functions
        • red block moves left 20
        • push gamestate to rendering thread
    • rendering thread
      • gather entities.
      • update entities for location if moving
        • red block moves from 0 to 20
        • avg frames per tick 4
        • interprolated movement per tick, 5 px.
        • update red to 5
      • draw entities at pos

edit Added example code of how to utilise render threads.

Basically you have the same objects in the game thread(webworker) as in the render thread. Only difference is, render thread has has render instructions(onRender) and game loop has Update instructions(on update)

So they are the same, but also different.

Take a look.

function getInlineJS() {
    var js = document.querySelector('[type="javascript/worker"]').textContent;
    var blob = new Blob([js], {"type": "text\/plain"});
    return URL.createObjectURL(blob);
}



var RedCube = function(id) {
    this.cube = null;
    this.type = 'redcube';
    if(typeof id === undefined) {
       this.entityId = generateId();
    }
    else {
        this.entityId = id;
    }
    this.lastX = 0;
    this.x = 0;
}
RedCube.prototype.getType = function() {
   return this.type;
}
RedCube.prototype.onUpdate = function() {
    this.x += 20;
}
RedCube.prototype.loadCube = function(scene, renderer) {
var that = this;
       
        var loader = new THREE.ObjectLoader();
           loader.parse({
    "metadata" : {
        "type" : "Object",
        "version" : 4.3,
        "generator" : "Blender Script"
    },
    "object" : {
        "name" : "red_cube.Material",
        "type" : "Mesh",
        "uuid" : "6071e8f2-79ae-5660-8d2b-aa675c566703",
        "matrix" : [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
        "geometry" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
        "material" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561"
    },
    "geometries" : [{
            "name" : "red_cube.Material",
            "type" : "BufferGeometry",
            "uuid" : "5d6cbd93-cf58-58a9-b0a7-5be9e5794547",
            "data" : {
                "attributes" : {
                    "position" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [0.79906648,-0.73424673,-0.87263167,0.79906648,-0.73424661,1.1273682,-1.2009337,-0.73424661,1.1273681,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,-1.2009335,1.2657533,-0.87263179,-1.2009339,1.2657533,1.1273677,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424673,-0.87263167,0.79906696,1.2657533,-0.87263131,0.79906583,1.2657533,1.1273688,0.79906648,-0.73424661,1.1273682,0.79906648,-0.73424661,1.1273682,0.79906583,1.2657533,1.1273688,-1.2009339,1.2657533,1.1273677,-1.2009337,-0.73424661,1.1273681,-1.2009337,-0.73424661,1.1273681,-1.2009339,1.2657533,1.1273677,-1.2009335,1.2657533,-0.87263179,-1.2009332,-0.73424673,-0.87263215,0.79906696,1.2657533,-0.87263131,0.79906648,-0.73424673,-0.87263167,-1.2009332,-0.73424673,-0.87263215,-1.2009335,1.2657533,-0.87263179]
                    },
                    "normal" : {
                        "type" : "Float32Array",
                        "itemSize" : 3,
                        "array" : [-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,-1.0658141e-14,-1,5.9604645e-08,0,1,0,0,1,0,0,1,0,0,1,0,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,1,4.4703416e-08,2.8312209e-07,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-2.9802322e-07,-5.9604723e-08,1,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,-1,-1.1920929e-07,-2.3841858e-07,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1,2.3841858e-07,1.7881393e-07,-1]
                    },
                    "index" : {
                        "type" : "Uint32Array",
                        "itemSize" : 1,
                        "array" : [0,1,2,2,3,0,4,5,6,6,7,4,8,9,10,10,11,8,12,13,14,14,15,12,16,17,18,18,19,16,20,21,22,22,23,20]
                    }
                }
            }
        }],
    "materials" : [{
            "name" : "Material",
            "type" : "MeshBasicMaterial",
            "uuid" : "5e847bd4-84a9-5d4b-a8fb-c567e27f7561",
            "transparent" : false,
            "opacity" : 1,
            "color" : 10682379
        }]
}, function(object){
         that.cube = object;
         object.scale.set(50,50,50);
         scene.add(object)
         
      });
}
RedCube.prototype.onRender = function(scene, renderer) {
   if(this.cube === null) {
       this.loadCube(scene, renderer);
   }
   
   // Some interprolation logic here to move from lastpos to next pos in average frames
   // per tick.
   this.cube.position.x = this.x;
}

RedCube.prototype.getType = function() {
   return type;
}

RedCube.prototype.generateSyncPacket = function() {
   return {
            type: this.getType(),
            x : this.x
          };
}

RedCube.prototype.parseSyncPacket = function(syncpacket) {
   this.setPosition(syncpacket.x);
}

RedCube.prototype.generateId = function() {
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });

}


RedCube.prototype.getEntityId = function() {
    return this.entityId;
}
RedCube.prototype.beforeDeath = function() {
}

RedCube.prototype.die = function() {
}

RedCube.prototype.setPosition = function(newpos) {
    this.lastX = this.x;
    this.x = newpos;
}

RedCube.prototype.getPosition = function() {
    return this.x;
}
RedCube.prototype.getLastX = function() {
    return this.lastX;
}

var EntityRegistry = function() {
   this.entities = {};
   this.types = {};
}

EntityRegistry.prototype.register = function(entity) {
   this.entities[entity.getEntityId()] = entity;
   
}
EntityRegistry.prototype.callUpdate = function() {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onUpdate();
      }
   }
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onRender(scene, renderer);
      }
   }
}
EntityRegistry.prototype.remove = function(entity) {
   entity.beforeDeath();
   delete this.entities[entity.getEntityId()]
   entity.die();
}

EntityRegistry.prototype.registerType = function(name, entityClass) {
    this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(syncpacket, entityId) {
    var entity = new this.types[syncpacket.type](entityId);
    entity.parseSyncPacket(syncpacket);
    this.register(entity);
}

EntityRegistry.prototype.getSyncData = function() {
   var syncpacket = {};
   for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
      }
   }
   return syncpacket;
}

EntityRegistry.prototype.parseSyncData = function(syncpacket) {
   for(entityId in syncpacket) {
      
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].parseSyncPacket(syncpacket[entityId]);
      }
      else {
          this.startEntity(syncpacket[entityId], entityId);
      }
   }
   return syncpacket;
}
var REGISTRY = new EntityRegistry();
REGISTRY.registerType('redcube', RedCube);

  var test = "d";  
 var dx = 1, dy = 0;
 var speed = 0.5;

 var activeKey = 0;
    // Set up the scene, camera, and renderer as global variables.
    var scene, camera, renderer;

    var worker = new Worker(getInlineJS());
    worker.postMessage("dasd");
  
    worker.addEventListener('message', function(e) {
       REGISTRY.parseSyncData(e.data);
     }, false);
    
    
    console.log("asd " + test);
    init();
    animate();

    // Sets up the scene.
    function init() {

      // Create the scene and set the scene size.
      scene = new THREE.Scene();
      var WIDTH = window.innerWidth - 50,
          HEIGHT = 500;

      // Create a renderer and add it to the DOM.
      renderer = new THREE.WebGLRenderer({antialias:true});
      renderer.setSize(WIDTH, HEIGHT);
      document.body.appendChild(renderer.domElement);

      camera = new THREE.OrthographicCamera( 0, WIDTH, 200, -HEIGHT, 1, 1000 );
      
      camera.position.set(0,0,100);
      scene.add(camera);
      console.log(WIDTH);
      
      
      window.addEventListener('resize', function() {
       var WIDTH = window.innerWidth - 50,
          HEIGHT = window.innerHeight - 50;
        renderer.setSize(WIDTH, HEIGHT);
        camera.aspect = WIDTH / HEIGHT;
        camera.updateProjectionMatrix();
        
      });
      renderer.setClearColor();

     
      
   document.addEventListener('keydown', function(e) {
       if (activeKey == e.keyCode) return;
       activeKey = e.keyCode;
       
       //left
       if (e.keyCode == 37) {
           dx = -1;
       }
       //top
       else if (e.keyCode == 38) {
           dy = 1;
       }
       //right
       else if (e.keyCode == 39) {
           dx = 1;
       }
       //bottom
       else if (e.keyCode == 40) {
           dy = -1;
       }
   });
   document.addEventListener('keyup', function(e) {
       switch (e.keyCode) {
           case 37: // left
           case 39: // right
               dx = 0;
               break;
               
           case 38: // up
           case 40: // down
               dy = 0;
               break;
       }
       
       activeKey = 0;
   });

    }
    
    var start;
    var last;

    var timefix, oldX,oldY, updateX,updateY,text;
    
    function animate() {
     
       REGISTRY.callOnRender(scene, renderer);
        renderer.render(scene, camera);       
        requestAnimationFrame( animate );

    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.js"></script>
<body style="margin: 0;">

<div id="panel">TEST
</div>
<br>
<script type="javascript/worker">
var RedCube = function(id) {
    this.direction = false;
    this.type = 'redcube';
    if(typeof id === 'undefined') {
       this.entityId = this.generateId();
    }
    else {
        this.entityId = id;
    }
    this.lastX = 0;
    this.x = 0;
}
RedCube.prototype.getType = function() {
   return this.type;
}

RedCube.prototype.generateSyncPacket = function() {
   return {
            type: this.getType(),
            x : this.x
          };
}

RedCube.prototype.parseSyncPacket = function(syncpacket) {
   this.setPosition(syncpacket.x);
}

RedCube.prototype.generateId = function() {
    var d = new Date().getTime();
    if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
        d += performance.now(); //use high-precision timer if available
    }
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });

}

RedCube.prototype.getEntityId = function() {
    return this.entityId;
}

RedCube.prototype.beforeDeath = function() {
}

RedCube.prototype.die = function() {
}

RedCube.prototype.setPosition = function(newpos) {
    this.lastX = this.x;
    this.x = newpos;
}

RedCube.prototype.getPosition = function() {
    return this.x;
}
RedCube.prototype.getLastX = function() {
    return this.lastX;
}

var EntityRegistry = function() {
   this.entities = {};
   this.types = {};
}

RedCube.prototype.onUpdate = function() {
    if(this.x > 500) {
       this.direction = true;
    }
    if(this.x <= 0) {
       this.direction = false;
    }
    this.x += !this.direction ? 20 : -20;
}
RedCube.prototype.onRender = function(scene, renderer) {
/// this is not a rendering thread. leave it empty
}
EntityRegistry.prototype.register = function(entity) {   
   this.entities[entity.getEntityId()] = entity;
   
}

EntityRegistry.prototype.remove = function(entity) {
   entity.beforeDeath();
   delete this.entities[entity.getEntityId()]
   entity.die();
}

EntityRegistry.prototype.registerType = function(name, entityClass) {
    this.types[name] = entityClass;
}
EntityRegistry.prototype.startEntity = function(entityId, syncpacket) {
    var entity = this.types[syncpacket.type](entityId);
    entity.parseSyncPacket(syncpacket);
    this.register(entity);
}

EntityRegistry.prototype.getSyncData = function() {
   var syncpacket = {};
   
   for(entityId in this.entities) {
      
      if(this.entities.hasOwnProperty(entityId)) {
          syncpacket[entityId] = this.entities[entityId].generateSyncPacket();
      }
   }
   return syncpacket;
}
EntityRegistry.prototype.callUpdate = function() {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onUpdate();
      }
   }
}
EntityRegistry.prototype.callOnRender = function(scene, renderer) {
  for(entityId in this.entities) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].onRender(scene, renderer);
      }
   }
}
EntityRegistry.prototype.parseSyncData = function(syncpacket) {
   for(entityId in syncpacket) {
      if(this.entities.hasOwnProperty(entityId)) {
          this.entities[entityId].parseSyncPacket(syncpacket[entityid]);
      }
      else {
          this.startEntity(syncpacket, entityId);
      }
   }
   return syncpacket;
}
var REGISTRY = new EntityRegistry();
var little_red = new RedCube();

REGISTRY.register(little_red);    

var x = 0;
var timefix = 0;
var last = 0;
var dx = 1;
var loopInterval = 0;
loopInterval = setInterval(function(){    
    REGISTRY.callUpdate()
  var msg = REGISTRY.getSyncData();
  self.postMessage(msg);
  
 }, 1000/60);
  
self.addEventListener('message', function(e) {
   
 

}, false);
</script>
</body>
Tschallacka
  • 27,901
  • 14
  • 88
  • 133
  • So you think that's the problem? I thought having only that few lines in the rendering thread shouldnt be a problem performance wise. – Vertago Mar 03 '17 at 10:44
  • I moved the update to a worker thread, but it still wont work without lags: http://sc2tube.com:8080/test/three_worker.html Another idea or am I doing it wrong? – Vertago Mar 03 '17 at 12:05
  • That isn't a worker thread. A webworker lives in a seperate file. Read the link I posted in my answer. Also this might http://stackoverflow.com/questions/19152772/how-to-pass-large-data-to-web-workers/30296330 help you implementing large data transfers. – Tschallacka Mar 03 '17 at 12:10
  • Are you sure? I have an extra file for the worker and also added these lines: var worker = new Worker('js/worker.js'); worker.postMessage("dasd"); worker.addEventListener('message', function(e) { test.position.x = e.data; }, false); The worker file runs in a loop and sends the updated X-position back. – Vertago Mar 03 '17 at 12:24
  • hmm, i'll have to have a closer look. One moment. Okay, saw it, i'll edit it into my answer. Please hold. – Tschallacka Mar 03 '17 at 12:27
  • I'm looking at it, but cant get it to run. Should the first snippet be in a js-file and the second in the html file? This way document.querySelector('[type="javascript/worker"]') returns undefined. – Vertago Mar 03 '17 at 14:09
  • try using chrome. I used a cheat to get it running in a snippet. Yes it should be a seperate file – Tschallacka Mar 03 '17 at 14:16
  • document.querySelector('[type="javascript/worker"]') I cant get this line to find the script. I think I'm doing something very wrong. Should the javascript-worker thread really be in the html file? And if not, where? – Vertago Mar 03 '17 at 14:37
  • Okey, I got it to work, but it got worse. Does it run smoothly for you? http://sc2tube.com:8080/test/test2.html – Vertago Mar 03 '17 at 14:49
  • Ish, there is of course the 20px step you need to interprolate. I didnt add that code. This is the basis you need to seperate a gameloop from a renderloop. You now need to add code that measures how many frames are rendered per tick, calculate how many intermediate positions you have calculate and then run each step from that queue til next tick update. This is a basis. Ill let you do the maths. You might consider making a new question or looking for existing pnes "how do i interprolate frames between tick updates" – Tschallacka Mar 03 '17 at 15:33
  • Thanks, I'll add the interpolation. Just one more question. I added my worker-code in the original question. Whats the principal difference between my worker and yours? Seems like the same way. You even call onRender in the animation loop, which seems to me like my first version, where I interpolated right in there. – Vertago Mar 04 '17 at 09:06
  • the difference is the transport mechanics really. Now you have the the possibiluty to add more entities and communicate more easely between the threads, easely adding more entities. And by doing it like this the the updates were more fluid on my system. The reder thread follows the game loop thread. When game loop registers new entity itll spawnn in the render thread. – Tschallacka Mar 04 '17 at 09:15