on b261585 help sign in
elk/runtime/Debugger.js
365 lines 8.6 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 }
43 };
45 function wrapRunDelayed(func, delayMs) {
46 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
47 var timerId = setTimeout(function() {
48 cbObj.executed = true;
49 func();
50 }, delayMs);
51 cbObj.timerId = timerId;
52 asyncCallbacks.push(cbObj);
53 return timerId;
54 }
56 function recordEvent(id, v, timestep) {
57 recordedEvents.push({ id:id, value:v, timestep:timestep });
58 }
60 function clearAsyncCallbacks() {
61 asyncCallbacks.forEach(function(timer) {
62 if (!timer.executed) {
63 clearTimeout(timer.timerId);
64 }
65 });
66 }
68 function clearRecordedEvents() {
69 recordedEvents = [];
70 }
72 function getRecordedEventsLength() {
73 return recordedEvents.length;
74 }
76 function getRecordedEventAt(i) {
77 return recordedEvents[i];
78 }
80 function setPaused(v) {
81 programPaused = v;
82 if (programPaused) {
83 clearAsyncCallbacks();
84 }
85 else {
86 executeCallbacks(asyncCallbacks);
87 }
88 }
90 function getPaused() {
91 return programPaused;
92 }
94 var wrappedRuntime = {
95 notify: wrapNotify,
96 runDelayed: wrapRunDelayed,
97 node: runtime.node,
98 display: runtime.display,
99 id: runtime.id,
100 addListener: runtime.addListener,
101 inputs: runtime.inputs
102 };
104 var moduleInstance = module.make(wrappedRuntime);
105 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
107 return {
108 moduleInstance: moduleInstance,
109 moduleNodes: moduleNodes,
110 initialNodeValues: saveNodeValues(moduleNodes),
111 initialAsyncCallbacks: asyncCallbacks.slice(),
112 // API functions
113 clearAsyncCallbacks: clearAsyncCallbacks,
114 clearRecordedEvents: clearRecordedEvents,
115 getRecordedEventsLength: getRecordedEventsLength,
116 getRecordedEventAt: getRecordedEventAt,
117 getPaused: getPaused,
118 setPaused: setPaused,
119 tracePath: tracePathInit(runtime)
120 };
123function debuggerInit(debugModule, runtime) {
124 var Signal = Elm.Signal.make(runtime);
126 var tracePathNode = A2(Signal.lift, debugModule.tracePath.graphicsUpdate, debugModule.moduleInstance.main);
127 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
129 var eventCounter = 0;
131 function resetProgram() {
132 debugModule.clearAsyncCallbacks();
133 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
134 redrawGraphics();
135 }
137 function restartProgram() {
138 resetProgram();
139 debugModule.clearRecordedEvents();
140 executeCallbacks(debugModule.initialAsyncCallbacks);
141 }
143 function pauseProgram() {
144 debugModule.setPaused(true);
145 }
147 function continueProgram() {
148 if (debugModule.getPaused())
149 {
150 debugModule.setPaused(false);
151 }
152 }
154 function stepTo(index) {
155 if (!debugModule.getPaused()) {
156 debugModule.setPaused(true);
157 resetProgram();
158 }
160 if (index < 0 || index > getMaxSteps()) {
161 throw "Index out of bounds: " + index;
162 }
164 if (index < eventCounter) {
165 resetProgram();
166 eventCounter = 0;
167 }
169 assert(index >= eventCounter, "index must be bad");
170 while (eventCounter < index) {
171 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
172 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
174 eventCounter += 1;
175 }
176 assert(eventCounter == index, "while loop didn't work");
177 }
179 function getMaxSteps() {
180 return debugModule.getRecordedEventsLength() - 1;
181 }
183 function doPlayback(eventList) {
184 var x = eventList.shift();
185 var time = x[2];
186 runtime.notify(x[0], x[1], x[2]);
188 if (eventList.length > 0) {
189 var delta = eventList[0][2] - time;
190 setTimeout(function() { doPlayback(eventList); }, delta);
191 }
192 }
194 function redrawGraphics() {
195 var graphicsNode = debugModule.moduleInstance.main.kids[0];
196 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
197 }
199 var elmDebugger = {
200 restart: restartProgram,
201 pause: pauseProgram,
202 kontinue: continueProgram,
203 getMaxSteps: getMaxSteps,
204 stepTo: stepTo
205 };
207 return elmDebugger;
210function Point(x, y) {
211 this.x = x;
212 this.y = y;
214 this.translate = function(x, y) {
215 return new Point(this.x + x, this.y + y);
216 }
218 this.equals = function(p) {
219 return this.x == p.x && this.y == p.y;
220 }
223function tracePathInit(runtime) {
224 var List = Elm.List.make(runtime);
225 var tracePathCanvas = createCanvas();
226 var tracePositions = {};
228 function findPositions(currentScene) {
229 var positions = {};
230 function processElement(elem, offset) {
231 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
232 {
233 List.map(F2(processForm)(offset))(elem.element.model.forms);
234 }
235 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
236 {
237 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
238 }
239 }
241 function processForm(offset, form) {
242 if (form.form.ctor == "FElement")
243 {
244 processElement(form.form._0, offset.translate(form.x, -form.y));
245 }
246 }
248 processElement(currentScene, new Point(0, 0));
249 return positions;
250 }
252 function appendPositions(positions) {
253 for (var id in positions) {
254 var pos = positions[id];
255 if (tracePositions.hasOwnProperty(id)) {
256 var points = tracePositions[id];
257 if (!pos.equals(points[points.length-1])) {
258 points.push(pos);
259 }
260 }
261 else {
262 tracePositions[id] = [pos];
263 }
264 }
265 }
267 function graphicsUpdate(currentScene) {
268 var ctx = tracePathCanvas.getContext('2d');
269 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
271 ctx.save();
272 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
273 appendPositions(findPositions(currentScene));
274 for (var id in tracePositions)
275 {
276 ctx.beginPath();
277 var points = tracePositions[id];
278 for (var i=0; i < points.length; i++)
279 {
280 var p = points[i];
281 if (i == 0) {
282 ctx.moveTo(p.x, p.y);
283 }
284 else {
285 ctx.lineTo(p.x, p.y);
286 }
287 }
288 }
289 ctx.lineWidth = 1;
290 ctx.strokeStyle = "black";
291 ctx.stroke();
293 ctx.restore();
294 }
296 function clearTraces() {
297 tracePositions = {};
298 }
300 return {
301 graphicsUpdate: graphicsUpdate,
302 canvas: tracePathCanvas,
303 clearTraces: clearTraces
304 }
308function executeCallbacks(callbacks) {
309 callbacks.forEach(function(timer) {
310 if (!timer.executed) {
311 var func = timer.func;
312 func();
313 }
314 });
317function createCanvas() {
318 var c = document.createElement('canvas');
319 c.width = window.innerWidth;
320 c.height = window.innerHeight;
321 c.style.position = "absolute";
322 c.style.top = "0";
323 c.style.left = "0";
324 return c;
327function assert(bool, msg) {
328 if (!bool) {
329 throw "Assertion error: " + msg;
330 }
333function saveNodeValues(allNodes) {
334 var nodeValues = [];
336 allNodes.forEach(function(node) {
337 nodeValues.push({ value: node.value, id: node.id });
338 });
340 return nodeValues;
341};
344function restoreNodeValues(allNodes, nodeStates) {
345 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
346 for (var i=0; i < allNodes.length; i++) {
347 var node = allNodes[i];
348 var state = nodeStates[i];
349 assert(node.id == state.id, "the nodes moved position");
351 node.value = state.value;
352 }
355function flattenNodes(nodes) {
356 var treeReduce = function(prev, cur, i, arr) {
357 var flatKids = cur.kids.reduce(treeReduce, []);
358 flatKids.push(cur);
359 return prev.concat(flatKids);
360 }
362 return nodes.reduce(treeReduce, []);
363};
365}());