diff options
author | Alon Zakai <alonzakai@gmail.com> | 2012-09-07 10:48:37 -0700 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2012-09-07 10:48:37 -0700 |
commit | 35d03167dbe6c62c8a2157d4babcb392123e4ba5 (patch) | |
tree | ca5b5a49a33249fd64e06dda7978eeb9bd32dc7a | |
parent | ce5523f896495b571e321970be68a8da4b1059fb (diff) |
complete initial writing of reproduceriter
-rw-r--r-- | tools/reproduceriter.py | 175 |
1 files changed, 136 insertions, 39 deletions
diff --git a/tools/reproduceriter.py b/tools/reproduceriter.py index df5b7df7..6cccbca1 100644 --- a/tools/reproduceriter.py +++ b/tools/reproduceriter.py @@ -17,14 +17,28 @@ Usage: 1. Run this script as - reproduceriter.py IN_DIR OUT_DIR FIRST_JS START_COMMAND + reproduceriter.py IN_DIR OUT_DIR FIRST_JS IN_DIR should be the project directory, and OUT_DIR will be - created with the instrumented code (OUT_DIR should not - exist). FIRST_JS should be a path (relative to IN_DIR) to + created with the instrumented code (OUT_DIR will be overwritten + if it exists). FIRST_JS should be a path (relative to IN_DIR) to the first JavaScript file loaded by the project (this tool will add code to that). + You will need to call + + Recorder.start(); + + at the right time to start the relevant event loop. For + example, if your application is a game that starts receiving + events when in fullscreen, add something like + + if (typeof Recorder != 'undefined') Recorder.start(); + + in the button that launches fullscreen. start() will start + either recording when in record mode, or replaying when + in replay mode, so you need this in both modes. + 2. Run the instrumented project in OUR_DIR and interact with the program. When you are done recording, open the web console and run @@ -43,6 +57,12 @@ Usage: &reproduce=repro.data + Note that as mentioned above you need to call + + Recorder.start(); + + when the recorded event loop should start to replay. + Notes: * When we start to replay events, the assumption is that @@ -63,7 +83,8 @@ in_dir = sys.argv[1] out_dir = sys.argv[2] first_js = sys.argv[3] -assert not os.path.exists(out_dir), 'OUT_DIR must not exist' +if os.path.exists(out_dir): + shutil.rmtree(out_dir) assert os.path.exists(os.path.join(in_dir, first_js)) # Copy project @@ -77,9 +98,81 @@ var Recorder = (function() { var recorder; var init = '&reproduce='; var initLocation = window.location.search.indexOf(init); - var replaying = initLocation >= 0 - if (replaying) { - // load recording + 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() { + function count() { + recorder.frameCounter++; + raf(count); + } + count(); + }; + // Math.random + recorder.randoms = []; + var random = Math.random() + Math.random = function() { + var ret = random(); + recorder.randoms.push(ret); + return ret; + }; + // Date.now, performance.now + recorder.dnows = []; + var dnow = Date.now(); + Date.now = function() { + var ret = dnow(); + recorder.dnows.push(ret); + return ret; + }; + recorder.pnows = []; + var pnow = performance.now(); + performance.now = function() { + var ret = pnow(); + recorder.pnows.push(ret); + return ret; + }; + // Events + recorder.devents = []; // document events + recorder.onEvent = function(which, callback) { + document['on' + which] = function(event) { + event.frameCounter = recorder.frameCounter; + event.which = which; + 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) { + event.frameCounter = recorder.frameCounter; + event.which = which; + 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(); + // Write out + alert('Writing out data, remember to save!'); + document.write(JSON.stringify(recorder)); + throw 'all done!'; + }; + } else { + // Load recording var dataPath = window.location.search.substring(initLocation); var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/')[0].replace('cheez999', '://'); if (baseURL[baseURL.length-1] != '/') baseURL += '/'; @@ -88,11 +181,29 @@ var Recorder = (function() { request.send(); recorder = JSON.parse(request.responseText); // 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.which](event); + } + while (recorder.tevents.length && recorder.tevents[recorder.tevents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.tevents.pop(); + recorder['event' + event.which](event); + } + } + count(); + }; // Math.random var warned = false; Math.random = function() { if (recorder.randoms.length > 0) { - return recorder.randoms.shift(); + return recorder.randoms.pop(); } else { if (!warned) { console.log('warning: consuming too many values!') @@ -105,7 +216,7 @@ var Recorder = (function() { var warned = false; Date.now = function() { if (recorder.dnows.length > 0) { - return recorder.dnows.shift(); + return recorder.dnows.pop(); } else { if (!warned) { console.log('warning: consuming too many values!') @@ -117,7 +228,7 @@ var Recorder = (function() { var warned = false; performance.now = function() { if (recorder.pnows.length > 0) { - return recorder.pnows.shift(); + return recorder.pnows.pop(); } else { if (!warned) { console.log('warning: consuming too many values!') @@ -126,36 +237,13 @@ var Recorder = (function() { return performance.now(); } }; - } else { - // prepare to record - recorder = {}; - // Math.random - recorder.randoms = []; - var random = Math.random() - Math.random = function() { - var ret = random(); - recorder.randoms.push(ret); - return ret; - }; - // Date.now, performance.now - recorder.dnows = []; - var dnow = Date.now(); - Date.now = function() { - var ret = dnow(); - recorder.dnows.push(ret); - return ret; - }; - recorder.pnows = []; - var pnow = performance.now(); - performance.now = function() { - var ret = pnow(); - recorder.pnows.push(ret); - return ret; + // Events + recorder.onEvent = function(which, callback) { + recorder['on' + which] = callback; }; - // finish - recorder.finish = function() { - document.write(JSON.stringify(recorder)); - throw 'all done, remember to save!'; + recorder.eventCallbacks = {}; + recorder.addListener = function(target, which, callback, arg) { + recorder['event' + which] = callback; }; } return recorder; @@ -163,3 +251,12 @@ var Recorder = (function() { ''' + open(os.path.join(in_dir, first_js)).read() ) +# Add customizations in all JS files + +for filename in os.walk(out_dir): + if filename.endswith('.js'): + fullname = os.path.join(out_dir, filename) + js = open(fullname).read() + js = js.replace(r'document\.on(\w+) = ([\w.]+);', lambda m: 'Recorder.onEvent("' + m.group(0) + '", ' + m.group(1) + ');') + js = js.replace(r"([\w'\[\]]+)\.addEventListener\(([\w,. ]+)\);", lambda m: 'Recorder.addListener(' + m.group(0) + ',' + m.group(1) + ');') + |