diff options
Diffstat (limited to 'tools/reproduceriter.js')
-rw-r--r-- | tools/reproduceriter.js | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/tools/reproduceriter.js b/tools/reproduceriter.js new file mode 100644 index 00000000..9dce8199 --- /dev/null +++ b/tools/reproduceriter.js @@ -0,0 +1,216 @@ + +var Recorder = (function() { + var recorder; + var init = 'reproduce='; + var initLocation = window.location.search.indexOf(init); + var replaying = initLocation >= 0; + var raf = window['requestAnimationFrame'] || + window['mozRequestAnimationFrame'] || + window['webkitRequestAnimationFrame'] || + window['msRequestAnimationFrame'] || + window['oRequestAnimationFrame']; + if (!replaying) { + // Prepare to record + recorder = {}; + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + alert("Starting recording! Don't forget to Recorder.finish() afterwards!"); + function count() { + recorder.frameCounter++; + raf(count); + } + count(); + recorder.started = true; + }; + // Math.random + recorder.randoms = []; + recorder.random = Math.random; + Math.random = function() { + var ret = recorder.random(); + recorder.randoms.push(ret); + return ret; + }; + // Date.now, performance.now + recorder.dnows = []; + recorder.dnow = Date.now; + Date.now = function() { + var ret = recorder.dnow(); + recorder.dnows.push(ret); + return ret; + }; + recorder.pnows = []; + var pnow = performance.now || performance.webkitNow || performance.mozNow || performance.oNow || performance.msNow; + recorder.pnow = function() { return pnow.call(performance) }; + performance.now = function() { + var ret = recorder.pnow(); + recorder.pnows.push(ret); + return ret; + }; + // Events + recorder.devents = []; // document events + recorder.onEvent = function(which, callback) { + document['on' + which] = function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.devents.push(event); + return callback(event); // XXX do we need to record the return value? + }; + }; + recorder.tevents = []; // custom-target events. Currently we assume a single such custom target (aside from document), e.g., a canvas for the game. + recorder.addListener = function(target, which, callback, arg) { + target.addEventListener(which, function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.tevents.push(event); + return callback(event); // XXX do we need to record the return value? + }, arg); + }; + // Finish + recorder.finish = function() { + // Reorder data because pop() is faster than shift() + recorder.randoms.reverse(); + recorder.dnows.reverse(); + recorder.pnows.reverse(); + recorder.devents.reverse(); + recorder.tevents.reverse(); + // Make JSON.stringify work on data from native event objects (and only store relevant ones) + var importantProperties = { + type: 1, + movementX: 1, mozMovementX: 1, webkitMovementX: 1, + movementY: 1, mozMovementY: 1, webkitMovementY: 1, + detail: 1, + wheelDelta: 1, + pageX: 1, + pageY: 1, + button: 1, + keyCode: 1, + frameCounter: 1 + }; + function importantize(event) { + var ret = {}; + for (var prop in importantProperties) { + if (prop in event) { + ret[prop] = event[prop]; + } + } + return ret; + } + recorder.devents = recorder.devents.map(importantize); + recorder.tevents = recorder.tevents.map(importantize); + // Write out + alert('Writing out data, remember to save!'); + setTimeout(function() { + document.open(); + document.write(JSON.stringify(recorder)); + document.close(); + }, 0); + return '.'; + }; + } else { + // Load recording + var dataPath = window.location.search.substring(initLocation + init.length); + var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/').slice(0, -1).join('/').replace('cheez999', '://'); + if (baseURL[baseURL.length-1] != '/') baseURL += '/'; + var path = baseURL + dataPath; + alert('Loading replay from ' + path); + var request = new XMLHttpRequest(); + request.open('GET', path, false); + request.send(); + var raw = request.responseText; + raw = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}')+1); // remove <html> etc + recorder = JSON.parse(raw); + // prepare to replay + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + function count() { + recorder.frameCounter++; + raf(count); + // replay relevant events for this frame + while (recorder.devents.length && recorder.devents[recorder.devents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.devents.pop(); + recorder['on' + event.type](event); + } + while (recorder.tevents.length && recorder.tevents[recorder.tevents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.tevents.pop(); + recorder['event' + event.type](event); + } + } + count(); + }; + // Math.random + recorder.random = Math.random; + Math.random = function() { + if (recorder.randoms.length > 0) { + return recorder.randoms.pop(); + } else { + recorder.finish(); + throw 'consuming too many random values!'; + } + }; + // Date.now, performance.now + recorder.dnow = Date.now; + Date.now = function() { + if (recorder.dnows.length > 0) { + return recorder.dnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many Date.now values!'; + } + }; + var pnow = performance.now || performance.webkitNow || performance.mozNow || performance.oNow || performance.msNow; + recorder.pnow = function() { return pnow.call(performance) }; + performance.now = function() { + if (recorder.pnows.length > 0) { + return recorder.pnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many performance.now values!'; + } + }; + // Events + recorder.onEvent = function(which, callback) { + recorder['on' + which] = callback; + }; + recorder.eventCallbacks = {}; + recorder.addListener = function(target, which, callback, arg) { + recorder['event' + which] = callback; + }; + recorder.onFinish = []; + // Benchmarking hooks - emscripten specific + setTimeout(function() { + var totalTime = 0; + var totalSquared = 0; + var iterations = 0; + var maxTime = 0; + var curr = 0; + Module.preMainLoop = function() { + curr = recorder.pnow(); + } + Module.postMainLoop = function() { + var time = recorder.pnow() - curr; + totalTime += time; + totalSquared += time*time; + maxTime = Math.max(maxTime, time); + iterations++; + }; + recorder.onFinish.push(function() { + var mean = totalTime / iterations; + var meanSquared = totalSquared / iterations; + console.log('mean frame : ' + mean + ' ms'); + console.log('frame std dev: ' + Math.sqrt(meanSquared - (mean*mean)) + ' ms'); + console.log('max frame : ' + maxTime + ' ms'); + }); + }); + // Finish + recorder.finish = function() { + recorder.onFinish.forEach(function(finish) { + finish(); + }); + }; + } + recorder.replaying = replaying; + return recorder; +})(); + |