on master help sign in
Merge pull request #701 from michaelbjames/debugger-snapshots
d29ba52Evan Czaplicki
runtime/Debugger.js+82−16
@@ -20,6 +20,9 @@ Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
20 };
20 };
21};
21};
22
22
23+var EVENTS_PER_SAVE = 100;
24+var eventCounter = 0;
25+
23function dispatchElmDebuggerInit() {
26function dispatchElmDebuggerInit() {
24 if (parent.window) {
27 if (parent.window) {
25 var dispatch = function() {
28 var dispatch = function() {
@@ -38,8 +41,10 @@ function debugModule(module, runtime) {
38 var programPaused = false;
41 var programPaused = false;
39 var recordedEvents = [];
42 var recordedEvents = [];
40 var asyncCallbacks = [];
43 var asyncCallbacks = [];
44+ var snapshots = [];
41 var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;
45 var watchTracker = Elm.Native.Debug.make(runtime).watchTracker;
42 var pauseTime = 0;
46 var pauseTime = 0;
47+ var eventsUntilSnapshot = EVENTS_PER_SAVE;
43
48
44 // runtime is the prototype of wrappedRuntime
49 // runtime is the prototype of wrappedRuntime
45 // so we can access all runtime properties too
50 // so we can access all runtime properties too
@@ -66,6 +71,8 @@ function debugModule(module, runtime) {
66 var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);
71 var signalGraphNodes = flattenSignalGraph(wrappedRuntime.inputs);
67 var tracePath = tracePathInit(runtime, debuggedModule.main);
72 var tracePath = tracePathInit(runtime, debuggedModule.main);
68
73
74+ snapshots.push(snapshotSignalGraph(signalGraphNodes));
75+
69 function wrapNotify(id, v) {
76 function wrapNotify(id, v) {
70 var timestep = runtime.timer.now();
77 var timestep = runtime.timer.now();
71
78
@@ -75,8 +82,9 @@ function debugModule(module, runtime) {
75 return false;
82 return false;
76 }
83 }
77 else {
84 else {
78− var changed = runtime.notify(id, v, timestep);
79 recordEvent(id, v, timestep);
85 recordEvent(id, v, timestep);
86+ var changed = runtime.notify(id, v, timestep);
87+ snapshotOnCheckpoint();
80 if (parent.window) {
88 if (parent.window) {
81 parent.window.postMessage("elmNotify", window.location.origin);
89 parent.window.postMessage("elmNotify", window.location.origin);
82 }
90 }
@@ -103,6 +111,7 @@ function debugModule(module, runtime) {
103 function recordEvent(id, v, timestep) {
111 function recordEvent(id, v, timestep) {
104 watchTracker.pushFrame();
112 watchTracker.pushFrame();
105 recordedEvents.push({ id:id, value:v, timestep:timestep });
113 recordedEvents.push({ id:id, value:v, timestep:timestep });
114+ eventCounter += 1;
106 }
115 }
107
116
108 function clearAsyncCallbacks() {
117 function clearAsyncCallbacks() {
@@ -115,6 +124,7 @@ function debugModule(module, runtime) {
115
124
116 function clearRecordedEvents() {
125 function clearRecordedEvents() {
117 recordedEvents = [];
126 recordedEvents = [];
127+ eventCounter = 0;
118 }
128 }
119
129
120 function getRecordedEventsLength() {
130 function getRecordedEventsLength() {
@@ -133,6 +143,26 @@ function debugModule(module, runtime) {
133 recordedEvents = events.slice();
143 recordedEvents = events.slice();
134 }
144 }
135
145
146+ function clearSnapshots() {
147+ snapshots = [snapshotSignalGraph(signalGraphNodes)];
148+ }
149+
150+ function getSnapshotAt(i) {
151+ var snapshotEvent = Math.floor(i / EVENTS_PER_SAVE);
152+ assert(snapshotEvent < snapshots.length && snapshotEvent >= 0,
153+ "Out of bounds index: " + snapshotEvent);
154+ return snapshots[snapshotEvent];
155+ }
156+
157+ function snapshotOnCheckpoint() {
158+ if (eventsUntilSnapshot === 1) {
159+ snapshots.push(snapshotSignalGraph(signalGraphNodes));
160+ eventsUntilSnapshot = EVENTS_PER_SAVE;
161+ } else {
162+ eventsUntilSnapshot -= 1;
163+ }
164+ }
165+
136 function setPaused() {
166 function setPaused() {
137 programPaused = true;
167 programPaused = true;
138 clearAsyncCallbacks();
168 clearAsyncCallbacks();
@@ -147,7 +177,12 @@ function debugModule(module, runtime) {
147 if (position > 0) {
177 if (position > 0) {
148 // If we're not unpausing at the head, then we need to dump the
178 // If we're not unpausing at the head, then we need to dump the
149 // events that are ahead of where we're continuing.
179 // events that are ahead of where we're continuing.
180+ var lastSnapshotPosition = Math.floor(position / EVENTS_PER_SAVE);
181+ eventsUntilSnapshot = EVENTS_PER_SAVE - (position % EVENTS_PER_SAVE);
182+ snapshots = snapshots.slice(0, lastSnapshotPosition + 1);
150 recordedEvents = recordedEvents.slice(0, position);
183 recordedEvents = recordedEvents.slice(0, position);
184+ tracePath.clearTracesAfter(position);
185+ eventCounter = position;
151 executeCallbacks(asyncCallbacks, false);
186 executeCallbacks(asyncCallbacks, false);
152 }
187 }
153 tracePath.startRecording();
188 tracePath.startRecording();
@@ -169,6 +204,9 @@ function debugModule(module, runtime) {
169 getRecordedEventAt: getRecordedEventAt,
204 getRecordedEventAt: getRecordedEventAt,
170 copyRecordedEvents: copyRecordedEvents,
205 copyRecordedEvents: copyRecordedEvents,
171 loadRecordedEvents: loadRecordedEvents,
206 loadRecordedEvents: loadRecordedEvents,
207+ clearSnapshots: clearSnapshots,
208+ getSnapshotAt: getSnapshotAt,
209+ snapshotOnCheckpoint: snapshotOnCheckpoint,
172 getPaused: getPaused,
210 getPaused: getPaused,
173 setPaused: setPaused,
211 setPaused: setPaused,
174 setContinue: setContinue,
212 setContinue: setContinue,
@@ -180,17 +218,19 @@ function debugModule(module, runtime) {
180function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
218function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
181 var currentEventIndex = 0;
219 var currentEventIndex = 0;
182
220
183− function resetProgram() {
221+ function resetProgram(position) {
222+ var closestSnapshot = debugModule.getSnapshotAt(position);
184 debugModule.clearAsyncCallbacks();
223 debugModule.clearAsyncCallbacks();
185− restoreSnapshot(debugModule.signalGraphNodes, debugModule.initialSnapshot);
224+ restoreSnapshot(debugModule.signalGraphNodes, closestSnapshot);
186 redrawGraphics();
225 redrawGraphics();
187 }
226 }
188
227
189 function restartProgram() {
228 function restartProgram() {
190− resetProgram();
229+ resetProgram(0);
191 debugModule.watchTracker.clear();
230 debugModule.watchTracker.clear();
192 debugModule.tracePath.clearTraces();
231 debugModule.tracePath.clearTraces();
193 debugModule.clearRecordedEvents();
232 debugModule.clearRecordedEvents();
233+ debugModule.clearSnapshots();
194 debugModule.setContinue(0);
234 debugModule.setContinue(0);
195 executeCallbacks(debugModule.initialAsyncCallbacks, true);
235 executeCallbacks(debugModule.initialAsyncCallbacks, true);
196 }
236 }
@@ -207,11 +247,11 @@ function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
207 restartProgram();
247 restartProgram();
208 return;
248 return;
209 }
249 }
250+ var closestSnapshotIndex =
251+ Math.floor(currentEventIndex / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
210− resetProgram();
252+ resetProgram(currentEventIndex);
211 var continueIndex = currentEventIndex;
253 var continueIndex = currentEventIndex;
212− currentEventIndex = 0;
254+ currentEventIndex = closestSnapshotIndex;
213− debugModule.tracePath.clearTraces();
214− debugModule.tracePath.startRecording();
215 stepTo(continueIndex);
255 stepTo(continueIndex);
216 debugModule.setContinue(currentEventIndex);
256 debugModule.setContinue(currentEventIndex);
217 }
257 }
@@ -228,8 +268,9 @@ function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
228 }
268 }
229
269
230 if (index < currentEventIndex) {
270 if (index < currentEventIndex) {
271+ var closestSnapshotIndex = Math.floor(index / EVENTS_PER_SAVE) * EVENTS_PER_SAVE;
231− resetProgram();
272+ resetProgram(index);
232− currentEventIndex = 0;
273+ currentEventIndex = closestSnapshotIndex;
233 }
274 }
234
275
235 while (currentEventIndex < index) {
276 while (currentEventIndex < index) {
@@ -277,10 +318,19 @@ function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
277 var paused = top.debug.paused;
318 var paused = top.debug.paused;
278 debugModule.setPaused();
319 debugModule.setPaused();
279 debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
320 debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
321+ var index = getMaxSteps();
322+ eventCounter = 0;
323+ debugModule.tracePath.clearTraces();
280
324
281 // draw new trace path
325 // draw new trace path
282 debugModule.tracePath.startRecording();
326 debugModule.tracePath.startRecording();
283− stepTo(getMaxSteps());
327+ while(currentEventIndex < index) {
328+ var nextEvent = debugModule.getRecordedEventAt(currentEventIndex);
329+ eventCounter += 1;
330+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
331+ debugModule.snapshotOnCheckpoint();
332+ currentEventIndex += 1;
333+ }
284 debugModule.tracePath.stopRecording();
334 debugModule.tracePath.stopRecording();
285
335
286 stepTo(hotSwapState.currentEventIndex);
336 stepTo(hotSwapState.currentEventIndex);
@@ -356,14 +406,20 @@ function tracePathInit(runtime, signalGraphMain) {
356 for (var id in positions) {
406 for (var id in positions) {
357 var pos = positions[id];
407 var pos = positions[id];
358 if (tracePositions.hasOwnProperty(id)) {
408 if (tracePositions.hasOwnProperty(id)) {
359− var points = tracePositions[id];
409+ tracePositions[id].push(pos);
360− if (!pos.equals(points[points.length-1])) {
361− points.push(pos);
362− }
363 }
410 }
364 else {
411 else {
365 tracePositions[id] = [pos];
412 tracePositions[id] = [pos];
366 }
413 }
414+ if (tracePositions[id].length < eventCounter) {
415+ var padCount = eventCounter - tracePositions[id].length;
416+ var lastTracePosition = tracePositions[id][tracePositions[id].length - 1];
417+ for (var i = padCount; i--;) {
418+ tracePositions[id].push(lastTracePosition)
419+ }
420+ }
421+ assert(tracePositions[id].length === eventCounter,
422+ "We don't have a 1-1 mapping of trace positions to events");
367 }
423 }
368 }
424 }
369
425
@@ -421,10 +477,19 @@ function tracePathInit(runtime, signalGraphMain) {
421 recordingTraces = true;
477 recordingTraces = true;
422 }
478 }
423
479
480+ function clearTracesAfter(position) {
481+ var newTraces = {};
482+ for (var id in tracePositions) {
483+ newTraces[id] = tracePositions[id].slice(0,position);
484+ }
485+ tracePositions = newTraces;
486+ }
487+
424 return {
488 return {
425 graphicsUpdate: graphicsUpdate,
489 graphicsUpdate: graphicsUpdate,
426 canvas: tracePathCanvas,
490 canvas: tracePathCanvas,
427 clearTraces: clearTraces,
491 clearTraces: clearTraces,
492+ clearTracesAfter: clearTracesAfter,
428 stopRecording: stopRecording,
493 stopRecording: stopRecording,
429 startRecording: startRecording
494 startRecording: startRecording
430 }
495 }
@@ -468,7 +533,8 @@ function snapshotSignalGraph(signalGraphNodes) {
468};
533};
469
534
470function restoreSnapshot(signalGraphNodes, snapshot) {
535function restoreSnapshot(signalGraphNodes, snapshot) {
471− assert(signalGraphNodes.length == snapshot.length, "saved program state has wrong length");
536+ assert(signalGraphNodes.length == snapshot.length,
537+ "saved program state has wrong length");
472 for (var i=0; i < signalGraphNodes.length; i++) {
538 for (var i=0; i < signalGraphNodes.length; i++) {
473 var node = signalGraphNodes[i];
539 var node = signalGraphNodes[i];
474 var state = snapshot[i];
540 var state = snapshot[i];