on a423682 help sign in
elk/runtime/Debugger.js
388 lines 9.2 kB history raw
2(function() {
3'use strict';
5Elm.Debugger = null;
6Elm.debuggerAttach = function(module) {
7 return {
8 make: function(runtime) {
9 var wrappedModule = debugModule(module, runtime);
10 Elm.Debugger = debuggerInit(wrappedModule, runtime);
11 if (parent.window) {
12 var dispatch = function(_) {
13 parent.window.postMessage("elmDebuggerInit", window.location.origin);
14 };
15 if (parent.window.document.readyState === "complete") {
16 dispatch();
17 }
18 else {
19 parent.window.addEventListener("load", dispatch);
20 }
21 }
22 return wrappedModule.moduleInstance;
23 }
24 };
25};
27function debugModule(module, runtime) {
28 var programPaused = false;
29 var recordedEvents = [];
30 var asyncCallbacks = [];
32 function wrapNotify(id, v) {
33 var timestep = Date.now();
35 if (programPaused) {
36 // ignore async events generated while playing back
37 // or user events while program is paused
38 }
39 else {
40 runtime.notify(id, v, timestep);
41 recordEvent(id, v, timestep);
42 if (parent.window) {
43 parent.window.postMessage("elmNotify", window.location.origin);
44 }
45 }
46 };
48 function wrapRunDelayed(func, delayMs) {
49 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
50 var timerId = setTimeout(function() {
51 cbObj.executed = true;
52 func();
53 }, delayMs);
54 cbObj.timerId = timerId;
55 asyncCallbacks.push(cbObj);
56 return timerId;
57 }
59 function recordEvent(id, v, timestep) {
60 recordedEvents.push({ id:id, value:v, timestep:timestep });
61 }
63 function clearAsyncCallbacks() {
64 asyncCallbacks.forEach(function(timer) {
65 if (!timer.executed) {
66 clearTimeout(timer.timerId);
67 }
68 });
69 }
71 function clearRecordedEvents() {
72 recordedEvents = [];
73 }
75 function getRecordedEventsLength() {
76 return recordedEvents.length;
77 }
79 function getRecordedEventAt(i) {
80 return recordedEvents[i];
81 }
83 function setPaused(v) {
84 programPaused = v;
85 if (programPaused) {
86 clearAsyncCallbacks();
87 tracePath.stopRecording();
88 }
89 else {
90 executeCallbacks(asyncCallbacks);
91 tracePath.startRecording();
92 }
93 }
95 function getPaused() {
96 return programPaused;
97 }
99 var wrappedRuntime = {
100 notify: wrapNotify,
101 runDelayed: wrapRunDelayed,
102 node: runtime.node,
103 display: runtime.display,
104 id: runtime.id,
105 addListener: runtime.addListener,
106 inputs: runtime.inputs
107 };
109 var moduleInstance = module.make(wrappedRuntime);
110 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
111 var tracePath = tracePathInit(runtime);
113 return {
114 moduleInstance: moduleInstance,
115 moduleNodes: moduleNodes,
116 initialNodeValues: saveNodeValues(moduleNodes),
117 initialAsyncCallbacks: asyncCallbacks.slice(),
118 // API functions
119 clearAsyncCallbacks: clearAsyncCallbacks,
120 clearRecordedEvents: clearRecordedEvents,
121 getRecordedEventsLength: getRecordedEventsLength,
122 getRecordedEventAt: getRecordedEventAt,
123 getPaused: getPaused,
124 setPaused: setPaused,
125 tracePath: tracePath
126 };
129function debuggerInit(debugModule, runtime) {
130 var Signal = Elm.Signal.make(runtime);
132 var tracePathNode = A2(Signal.lift, debugModule.tracePath.graphicsUpdate, debugModule.moduleInstance.main);
133 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
135 var eventCounter = 0;
137 function resetProgram() {
138 debugModule.clearAsyncCallbacks();
139 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
140 redrawGraphics();
141 }
143 function restartProgram() {
144 resetProgram();
145 debugModule.clearRecordedEvents();
146 executeCallbacks(debugModule.initialAsyncCallbacks);
147 }
149 function pauseProgram() {
150 debugModule.setPaused(true);
151 eventCounter = debugModule.getRecordedEventsLength();
152 }
154 function continueProgram() {
155 if (debugModule.getPaused())
156 {
157 debugModule.setPaused(false);
158 }
159 }
161 function stepTo(index) {
162 if (!debugModule.getPaused()) {
163 debugModule.setPaused(true);
164 resetProgram();
165 }
167 if (index < 0 || index > getMaxSteps()) {
168 throw "Index out of bounds: " + index;
169 }
171 if (index < eventCounter) {
172 resetProgram();
173 eventCounter = 0;
174 }
176 assert(index >= eventCounter, "index must be bad");
177 while (eventCounter < index) {
178 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
179 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
181 eventCounter += 1;
182 }
183 assert(eventCounter == index, "while loop didn't work");
184 }
186 function getMaxSteps() {
187 return debugModule.getRecordedEventsLength();
188 }
190 function doPlayback(eventList) {
191 var x = eventList.shift();
192 var time = x[2];
193 runtime.notify(x[0], x[1], x[2]);
195 if (eventList.length > 0) {
196 var delta = eventList[0][2] - time;
197 setTimeout(function() { doPlayback(eventList); }, delta);
198 }
199 }
201 function redrawGraphics() {
202 var graphicsNode = debugModule.moduleInstance.main.kids[0];
203 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
204 }
206 var elmDebugger = {
207 restart: restartProgram,
208 pause: pauseProgram,
209 kontinue: continueProgram,
210 getMaxSteps: getMaxSteps,
211 stepTo: stepTo,
212 getPaused: debugModule.getPaused
213 };
215 return elmDebugger;
218function Point(x, y) {
219 this.x = x;
220 this.y = y;
222 this.translate = function(x, y) {
223 return new Point(this.x + x, this.y + y);
224 }
226 this.equals = function(p) {
227 return this.x == p.x && this.y == p.y;
228 }
231function tracePathInit(runtime) {
232 var List = Elm.List.make(runtime);
233 var tracePathCanvas = createCanvas();
234 var tracePositions = {};
235 var recordingTraces = true;
237 function findPositions(currentScene) {
238 var positions = {};
239 function processElement(elem, offset) {
240 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
241 {
242 List.map(F2(processForm)(offset))(elem.element.model.forms);
243 }
244 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
245 {
246 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
247 }
248 }
250 function processForm(offset, form) {
251 if (form.form.ctor == "FElement")
252 {
253 processElement(form.form._0, offset.translate(form.x, -form.y));
254 }
255 }
257 processElement(currentScene, new Point(0, 0));
258 return positions;
259 }
261 function appendPositions(positions) {
262 for (var id in positions) {
263 var pos = positions[id];
264 if (tracePositions.hasOwnProperty(id)) {
265 var points = tracePositions[id];
266 if (!pos.equals(points[points.length-1])) {
267 points.push(pos);
268 }
269 }
270 else {
271 tracePositions[id] = [pos];
272 }
273 }
274 }
276 function graphicsUpdate(currentScene) {
277 if (!recordingTraces) {
278 return;
279 }
281 var ctx = tracePathCanvas.getContext('2d');
282 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
284 ctx.save();
285 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
286 appendPositions(findPositions(currentScene));
287 for (var id in tracePositions)
288 {
289 ctx.beginPath();
290 var points = tracePositions[id];
291 for (var i=0; i < points.length; i++)
292 {
293 var p = points[i];
294 if (i == 0) {
295 ctx.moveTo(p.x, p.y);
296 }
297 else {
298 ctx.lineTo(p.x, p.y);
299 }
300 }
301 }
302 ctx.lineWidth = 1;
303 ctx.strokeStyle = "black";
304 ctx.stroke();
306 ctx.restore();
307 }
309 function clearTraces() {
310 tracePositions = {};
311 }
313 function stopRecording() {
314 recordingTraces = false;
315 }
317 function startRecording() {
318 recordingTraces = true;
319 }
321 return {
322 graphicsUpdate: graphicsUpdate,
323 canvas: tracePathCanvas,
324 clearTraces: clearTraces,
325 stopRecording: stopRecording,
326 startRecording: startRecording
327 }
331function executeCallbacks(callbacks) {
332 callbacks.forEach(function(timer) {
333 if (!timer.executed) {
334 var func = timer.func;
335 func();
336 }
337 });
340function createCanvas() {
341 var c = document.createElement('canvas');
342 c.width = window.innerWidth;
343 c.height = window.innerHeight;
344 c.style.position = "absolute";
345 c.style.top = "0";
346 c.style.left = "0";
347 return c;
350function assert(bool, msg) {
351 if (!bool) {
352 throw "Assertion error: " + msg;
353 }
356function saveNodeValues(allNodes) {
357 var nodeValues = [];
359 allNodes.forEach(function(node) {
360 nodeValues.push({ value: node.value, id: node.id });
361 });
363 return nodeValues;
364};
367function restoreNodeValues(allNodes, nodeStates) {
368 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
369 for (var i=0; i < allNodes.length; i++) {
370 var node = allNodes[i];
371 var state = nodeStates[i];
372 assert(node.id == state.id, "the nodes moved position");
374 node.value = state.value;
375 }
378function flattenNodes(nodes) {
379 var treeReduce = function(prev, cur, i, arr) {
380 var flatKids = cur.kids.reduce(treeReduce, []);
381 flatKids.push(cur);
382 return prev.concat(flatKids);
383 }
385 return nodes.reduce(treeReduce, []);
386};
388}());