on 98dd9fe help sign in
elk/runtime/Debugger.js
447 lines 11 kB history raw
2(function() {
3'use strict';
5Elm.Debugger = null;
6Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
7 return {
8 make: function(runtime) {
9 var wrappedModule = debugModule(module, runtime);
10 Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
11 dispatchElmDebuggerInit();
12 return wrappedModule.moduleInstance;
13 }
14 };
15};
17function dispatchElmDebuggerInit() {
18 if (parent.window) {
19 var dispatch = function(_) {
20 parent.window.postMessage("elmDebuggerInit", window.location.origin);
21 };
22 if (parent.window.document.readyState === "complete") {
23 dispatch();
24 }
25 else {
26 parent.window.addEventListener("load", dispatch);
27 }
28 }
31function debugModule(module, runtime) {
32 var programPaused = false;
33 var recordedEvents = [];
34 var asyncCallbacks = [];
36 function wrapNotify(id, v) {
37 var timestep = Date.now();
39 if (programPaused) {
40 // ignore async events generated while playing back
41 // or user events while program is paused
42 return false;
43 }
44 else {
45 var changed = runtime.notify(id, v, timestep);
46 recordEvent(id, v, timestep);
47 if (parent.window) {
48 parent.window.postMessage("elmNotify", window.location.origin);
49 }
50 return changed;
51 }
52 };
54 function wrapRunDelayed(func, delayMs) {
55 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
56 var timerId = setTimeout(function() {
57 cbObj.executed = true;
58 func();
59 }, delayMs);
60 cbObj.timerId = timerId;
61 asyncCallbacks.push(cbObj);
62 return timerId;
63 }
65 function recordEvent(id, v, timestep) {
66 recordedEvents.push({ id:id, value:v, timestep:timestep });
67 }
69 function clearAsyncCallbacks() {
70 asyncCallbacks.forEach(function(timer) {
71 if (!timer.executed) {
72 clearTimeout(timer.timerId);
73 }
74 });
75 }
77 function clearRecordedEvents() {
78 recordedEvents = [];
79 }
81 function getRecordedEventsLength() {
82 return recordedEvents.length;
83 }
85 function getRecordedEventAt(i) {
86 return recordedEvents[i];
87 }
89 function copyRecordedEvents() {
90 return recordedEvents.slice();
91 }
93 function loadRecordedEvents(events) {
94 recordedEvents = events.slice();
95 }
97 function setPaused(v) {
98 programPaused = v;
99 if (programPaused) {
100 clearAsyncCallbacks();
101 tracePath.stopRecording();
102 }
103 else {
104 // executeCallbacks should only be called when continuing, not when restarting.
105 //executeCallbacks(asyncCallbacks, false);
106 tracePath.startRecording();
107 }
108 }
110 function getPaused() {
111 return programPaused;
112 }
114 var wrappedRuntime = {
115 notify: wrapNotify,
116 runDelayed: wrapRunDelayed,
117 node: runtime.node,
118 display: runtime.display,
119 id: runtime.id,
120 addListener: runtime.addListener,
121 inputs: runtime.inputs
122 };
124 var moduleInstance = module.make(wrappedRuntime);
125 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
126 var tracePath = tracePathInit(runtime);
128 return {
129 moduleInstance: moduleInstance,
130 moduleNodes: moduleNodes,
131 initialNodeValues: saveNodeValues(moduleNodes),
132 initialAsyncCallbacks: asyncCallbacks.slice(),
133 // API functions
134 clearAsyncCallbacks: clearAsyncCallbacks,
135 clearRecordedEvents: clearRecordedEvents,
136 getRecordedEventsLength: getRecordedEventsLength,
137 getRecordedEventAt: getRecordedEventAt,
138 copyRecordedEvents: copyRecordedEvents,
139 loadRecordedEvents: loadRecordedEvents,
140 getPaused: getPaused,
141 setPaused: setPaused,
142 tracePath: tracePath
143 };
146function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
147 var Signal = Elm.Signal.make(runtime);
149 var tracePathNode = A2(Signal.lift, debugModule.tracePath.graphicsUpdate, debugModule.moduleInstance.main);
150 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
152 var eventCounter = 0;
154 function resetProgram() {
155 debugModule.clearAsyncCallbacks();
156 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
157 redrawGraphics();
158 }
160 function restartProgram() {
161 resetProgram();
162 debugModule.tracePath.clearTraces();
163 debugModule.clearRecordedEvents();
164 debugModule.setPaused(false);
165 executeCallbacks(debugModule.initialAsyncCallbacks, true);
166 }
168 function pauseProgram() {
169 debugModule.setPaused(true);
170 eventCounter = debugModule.getRecordedEventsLength();
171 }
173 function continueProgram() {
174 if (debugModule.getPaused())
175 {
176 debugModule.setPaused(false);
177 }
178 }
180 function stepTo(index) {
181 if (!debugModule.getPaused()) {
182 debugModule.setPaused(true);
183 resetProgram();
184 }
186 if (index < 0 || index > getMaxSteps()) {
187 throw "Index out of bounds: " + index;
188 }
190 if (index < eventCounter) {
191 resetProgram();
192 eventCounter = 0;
193 }
195 assert(index >= eventCounter, "index must be bad");
196 while (eventCounter < index) {
197 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
198 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
200 eventCounter += 1;
201 }
202 assert(eventCounter == index, "while loop didn't work");
203 }
205 function getMaxSteps() {
206 return debugModule.getRecordedEventsLength();
207 }
209 function doPlayback(eventList) {
210 var x = eventList.shift();
211 var time = x[2];
212 runtime.notify(x[0], x[1], x[2]);
214 if (eventList.length > 0) {
215 var delta = eventList[0][2] - time;
216 setTimeout(function() { doPlayback(eventList); }, delta);
217 }
218 }
220 function redrawGraphics() {
221 var graphicsNode = debugModule.moduleInstance.main.kids[0];
222 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
223 }
225 function getHotSwapState() {
226 var counter = eventCounter;
227 if (!debugModule.getPaused()) {
228 counter = getMaxSteps();
229 }
230 return {
231 recordedEvents: debugModule.copyRecordedEvents(),
232 eventCounter: counter
233 };
234 }
236 function dispose() {
237 var parentNode = runtime.node.parentNode;
238 parentNode.removeChild(debugModule.tracePath.canvas);
239 parentNode.removeChild(runtime.node);
240 }
242 if (hotSwapState) {
243 debugModule.setPaused(true);
244 debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
246 // draw new trace path
247 debugModule.tracePath.startRecording();
248 stepTo(getMaxSteps());
249 debugModule.tracePath.stopRecording();
251 stepTo(hotSwapState.eventCounter);
252 }
254 var elmDebugger = {
255 restart: restartProgram,
256 pause: pauseProgram,
257 kontinue: continueProgram,
258 getMaxSteps: getMaxSteps,
259 stepTo: stepTo,
260 getPaused: debugModule.getPaused,
261 getHotSwapState: getHotSwapState,
262 dispose: dispose
263 };
265 return elmDebugger;
268function Point(x, y) {
269 this.x = x;
270 this.y = y;
272 this.translate = function(x, y) {
273 return new Point(this.x + x, this.y + y);
274 }
276 this.equals = function(p) {
277 return this.x == p.x && this.y == p.y;
278 }
281function tracePathInit(runtime) {
282 var List = Elm.List.make(runtime);
283 var tracePathCanvas = createCanvas();
284 var tracePositions = {};
285 var recordingTraces = true;
287 function findPositions(currentScene) {
288 var positions = {};
289 function processElement(elem, offset) {
290 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
291 {
292 List.map(F2(processForm)(offset))(elem.element.model.forms);
293 }
294 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
295 {
296 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
297 }
298 }
300 function processForm(offset, form) {
301 if (form.form.ctor == "FElement")
302 {
303 processElement(form.form._0, offset.translate(form.x, -form.y));
304 }
305 }
307 processElement(currentScene, new Point(0, 0));
308 return positions;
309 }
311 function appendPositions(positions) {
312 for (var id in positions) {
313 var pos = positions[id];
314 if (tracePositions.hasOwnProperty(id)) {
315 var points = tracePositions[id];
316 if (!pos.equals(points[points.length-1])) {
317 points.push(pos);
318 }
319 }
320 else {
321 tracePositions[id] = [pos];
322 }
323 }
324 }
326 function graphicsUpdate(currentScene) {
327 if (!recordingTraces) {
328 return;
329 }
331 var ctx = tracePathCanvas.getContext('2d');
332 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
334 ctx.save();
335 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
336 appendPositions(findPositions(currentScene));
337 for (var id in tracePositions)
338 {
339 ctx.beginPath();
340 var points = tracePositions[id];
341 for (var i=0; i < points.length; i++)
342 {
343 var p = points[i];
344 if (i == 0) {
345 ctx.moveTo(p.x, p.y);
346 }
347 else {
348 ctx.lineTo(p.x, p.y);
349 }
350 }
351 ctx.lineWidth = 1;
352 ctx.strokeStyle = "#888a85";
353 ctx.stroke();
355 for (var i=0; i < points.length; i++)
356 {
357 var p = points[i];
358 ctx.beginPath();
359 ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
360 ctx.fillStyle = "#2e3436";
361 ctx.fill();
362 }
363 }
365 ctx.restore();
366 }
368 function clearTraces() {
369 tracePositions = {};
370 }
372 function stopRecording() {
373 recordingTraces = false;
374 }
376 function startRecording() {
377 recordingTraces = true;
378 }
380 return {
381 graphicsUpdate: graphicsUpdate,
382 canvas: tracePathCanvas,
383 clearTraces: clearTraces,
384 stopRecording: stopRecording,
385 startRecording: startRecording
386 }
390function executeCallbacks(callbacks, reexecute) {
391 callbacks.forEach(function(timer) {
392 if (reexecute || !timer.executed) {
393 var func = timer.func;
394 func();
395 }
396 });
399function createCanvas() {
400 var c = document.createElement('canvas');
401 c.width = window.innerWidth;
402 c.height = window.innerHeight;
403 c.style.position = "absolute";
404 c.style.top = "0";
405 c.style.left = "0";
406 return c;
409function assert(bool, msg) {
410 if (!bool) {
411 throw "Assertion error: " + msg;
412 }
415function saveNodeValues(allNodes) {
416 var nodeValues = [];
418 allNodes.forEach(function(node) {
419 nodeValues.push({ value: node.value, id: node.id });
420 });
422 return nodeValues;
423};
426function restoreNodeValues(allNodes, nodeStates) {
427 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
428 for (var i=0; i < allNodes.length; i++) {
429 var node = allNodes[i];
430 var state = nodeStates[i];
431 assert(node.id == state.id, "the nodes moved position");
433 node.value = state.value;
434 }
437function flattenNodes(nodes) {
438 var treeReduce = function(prev, cur, i, arr) {
439 var flatKids = cur.kids.reduce(treeReduce, []);
440 flatKids.push(cur);
441 return prev.concat(flatKids);
442 }
444 return nodes.reduce(treeReduce, []);
445};
447}());