Team:Evry/arbor/src/kernel.js
From 2012.igem.org
// // kernel.js // // run-loop manager for physics and tween updates //
var Kernel = function(pSystem){ // in chrome, web workers aren't available to pages with file:// urls var chrome_local_file = window.location.protocol == "file:" && navigator.userAgent.toLowerCase().indexOf('chrome') > -1; var USE_WORKER = (window.Worker !== undefined && !chrome_local_file)
var _physics = null var _tween = null var _fpsWindow = [] // for keeping track of the actual frame rate _fpsWindow.last = new Date() var _screenInterval = null var _attached = null
var _tickInterval = null var _lastTick = null var _paused = false var that = { system:pSystem, tween:null, nodes:{},
init:function(){ if (typeof(Tween)!='undefined') _tween = Tween() else if (typeof(arbor.Tween)!='undefined') _tween = arbor.Tween() else _tween = {busy:function(){return false}, tick:function(){return true}, to:function(){ trace('Please include arbor-tween.js to enable tweens'); _tween.to=function(){}; return} } that.tween = _tween var params = pSystem.parameters() if(USE_WORKER){ trace('using web workers') _screenInterval = setInterval(that.screenUpdate, params.timeout)
_physics = new Worker(arbor_path()+'physics/worker.js') _physics.onmessage = that.workerMsg _physics.onerror = function(e){ trace('physics:',e) } _physics.postMessage({type:"physics", physics:objmerge(params, {timeout:Math.ceil(params.timeout)}) }) }else{ trace("couldn't use web workers, be careful...") _physics = Physics(params.dt, params.stiffness, params.repulsion, params.friction, that.system._updateGeometry) that.start() }
return that },
// // updates from the ParticleSystem graphChanged:function(changes){ // a node or edge was added or deleted if (USE_WORKER) _physics.postMessage({type:"changes","changes":changes}) else _physics._update(changes) that.start() // <- is this just to kick things off in the non-worker mode? (yes) },
particleModified:function(id, mods){ // a particle's position or mass is changed // trace('mod',objkeys(mods)) if (USE_WORKER) _physics.postMessage({type:"modify", id:id, mods:mods}) else _physics.modifyNode(id, mods) that.start() // <- is this just to kick things off in the non-worker mode? (yes) },
physicsModified:function(param){
// intercept changes to the framerate in case we're using a worker and // managing our own draw timer if (!isNaN(param.timeout)){ if (USE_WORKER){ clearInterval(_screenInterval) _screenInterval = setInterval(that.screenUpdate, param.timeout) }else{ // clear the old interval then let the call to .start set the new one clearInterval(_tickInterval) _tickInterval=null } }
// a change to the physics parameters if (USE_WORKER) _physics.postMessage({type:'sys',param:param}) else _physics.modifyPhysics(param) that.start() // <- is this just to kick things off in the non-worker mode? (yes) }, workerMsg:function(e){ var type = e.data.type if (type=='geometry'){ that.workerUpdate(e.data) }else{ trace('physics:',e.data) } }, _lastPositions:null, workerUpdate:function(data){ that._lastPositions = data that._lastBounds = data.bounds },
// // the main render loop when running in web worker mode _lastFrametime:new Date().valueOf(), _lastBounds:null, _currentRenderer:null, screenUpdate:function(){ var now = new Date().valueOf() var shouldRedraw = false if (that._lastPositions!==null){ that.system._updateGeometry(that._lastPositions) that._lastPositions = null shouldRedraw = true } if (_tween && _tween.busy()) shouldRedraw = true
if (that.system._updateBounds(that._lastBounds)) shouldRedraw=true
if (shouldRedraw){ var render = that.system.renderer if (render!==undefined){ if (render !== _attached){ render.init(that.system) _attached = render } if (_tween) _tween.tick() render.redraw()
var prevFrame = _fpsWindow.last _fpsWindow.last = new Date() _fpsWindow.push(_fpsWindow.last-prevFrame) if (_fpsWindow.length>50) _fpsWindow.shift() } } },
// // the main render loop when running in non-worker mode physicsUpdate:function(){ if (_tween) _tween.tick() _physics.tick()
var stillActive = that.system._updateBounds() if (_tween && _tween.busy()) stillActive = true
var render = that.system.renderer var now = new Date() var render = that.system.renderer if (render!==undefined){ if (render !== _attached){ render.init(that.system) _attached = render } render.redraw({timestamp:now}) }
var prevFrame = _fpsWindow.last _fpsWindow.last = now _fpsWindow.push(_fpsWindow.last-prevFrame) if (_fpsWindow.length>50) _fpsWindow.shift()
// but stop the simulation when energy of the system goes below a threshold var sysEnergy = _physics.systemEnergy() if ((sysEnergy.mean + sysEnergy.max)/2 < 0.05){ if (_lastTick===null) _lastTick=new Date().valueOf() if (new Date().valueOf()-_lastTick>1000){ // trace('stopping') clearInterval(_tickInterval) _tickInterval = null }else{ // trace('pausing') } }else{ // trace('continuing') _lastTick = null } },
fps:function(newTargetFPS){ if (newTargetFPS!==undefined){ var timeout = 1000/Math.max(1,targetFps) that.physicsModified({timeout:timeout}) } var totInterv = 0 for (var i=0, j=_fpsWindow.length; i<j; i++) totInterv+=_fpsWindow[i] var meanIntev = totInterv/Math.max(1,_fpsWindow.length) if (!isNaN(meanIntev)) return Math.round(1000/meanIntev) else return 0 },
// // start/stop simulation // start:function(unpause){ if (_tickInterval !== null) return; // already running if (_paused && !unpause) return; // we've been .stopped before, wait for unpause _paused = false if (USE_WORKER){ _physics.postMessage({type:"start"}) }else{ _lastTick = null _tickInterval = setInterval(that.physicsUpdate, that.system.parameters().timeout) } }, stop:function(){ _paused = true if (USE_WORKER){ _physics.postMessage({type:"stop"}) }else{ if (_tickInterval!==null){ clearInterval(_tickInterval) _tickInterval = null } } } } return that.init() }