aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2012-09-07 10:48:37 -0700
committerAlon Zakai <alonzakai@gmail.com>2012-09-07 10:48:37 -0700
commit35d03167dbe6c62c8a2157d4babcb392123e4ba5 (patch)
treeca5b5a49a33249fd64e06dda7978eeb9bd32dc7a
parentce5523f896495b571e321970be68a8da4b1059fb (diff)
complete initial writing of reproduceriter
-rw-r--r--tools/reproduceriter.py175
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) + ');')
+