on c03c27f help sign in
elk/runtime/Debugger.js
395 lines 9.4 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 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 }
43 else {
44 runtime.notify(id, v, timestep);
45 recordEvent(id, v, timestep);
46 if (parent.window) {
47 parent.window.postMessage("elmNotify", window.location.origin);
48 }
49 }
50 };
52 function wrapRunDelayed(func, delayMs) {
53 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
54 var timerId = setTimeout(function() {
55 cbObj.executed = true;
56 func();
57 }, delayMs);
58 cbObj.timerId = timerId;
59 asyncCallbacks.push(cbObj);
60 return timerId;
61 }
63 function recordEvent(id, v, timestep) {
64 recordedEvents.push({ id:id, value:v, timestep:timestep });
65 }
67 function clearAsyncCallbacks() {
68 asyncCallbacks.forEach(function(timer) {
69 if (!timer.executed) {
70 clearTimeout(timer.timerId);
71 }
72 });
73 }
75 function clearRecordedEvents() {
76 recordedEvents = [];
77 }
79 function getRecordedEventsLength() {
80 return recordedEvents.length;
81 }
83 function getRecordedEventAt(i) {
84 return recordedEvents[i];
85 }
87 function setPaused(v) {
88 programPaused = v;
89 if (programPaused) {
90 clearAsyncCallbacks();
91 tracePath.stopRecording();
92 }
93 else {
94 // executeCallbacks should only be called when continuing, not when restarting.
95 //executeCallbacks(asyncCallbacks, false);
96 tracePath.startRecording();
97 }
98 }
100 function getPaused() {
101 return programPaused;
102 }
104 var wrappedRuntime = {
105 notify: wrapNotify,
106 runDelayed: wrapRunDelayed,
107 node: runtime.node,
108 display: runtime.display,
109 id: runtime.id,
110 addListener: runtime.addListener,
111 inputs: runtime.inputs
112 };
114 var moduleInstance = module.make(wrappedRuntime);
115 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
116 var tracePath = tracePathInit(runtime);
118 return {
119 moduleInstance: moduleInstance,
120 moduleNodes: moduleNodes,
121 initialNodeValues: saveNodeValues(moduleNodes),
122 initialAsyncCallbacks: asyncCallbacks.slice(),
123 // API functions
124 clearAsyncCallbacks: clearAsyncCallbacks,
125 clearRecordedEvents: clearRecordedEvents,
126 getRecordedEventsLength: getRecordedEventsLength,
127 getRecordedEventAt: getRecordedEventAt,
128 getPaused: getPaused,
129 setPaused: setPaused,
130 tracePath: tracePath
131 };
134function debuggerInit(debugModule, runtime) {
135 var Signal = Elm.Signal.make(runtime);
137 var tracePathNode = A2(Signal.lift, debugModule.tracePath.graphicsUpdate, debugModule.moduleInstance.main);
138 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
140 var eventCounter = 0;
142 function resetProgram() {
143 debugModule.clearAsyncCallbacks();
144 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
145 redrawGraphics();
146 }
148 function restartProgram() {
149 resetProgram();
150 debugModule.tracePath.clearTraces();
151 debugModule.clearRecordedEvents();
152 debugModule.setPaused(false);
153 executeCallbacks(debugModule.initialAsyncCallbacks, true);
154 }
156 function pauseProgram() {
157 debugModule.setPaused(true);
158 eventCounter = debugModule.getRecordedEventsLength();
159 }
161 function continueProgram() {
162 if (debugModule.getPaused())
163 {
164 debugModule.setPaused(false);
165 }
166 }
168 function stepTo(index) {
169 if (!debugModule.getPaused()) {
170 debugModule.setPaused(true);
171 resetProgram();
172 }
174 if (index < 0 || index > getMaxSteps()) {
175 throw "Index out of bounds: " + index;
176 }
178 if (index < eventCounter) {
179 resetProgram();
180 eventCounter = 0;
181 }
183 assert(index >= eventCounter, "index must be bad");
184 while (eventCounter < index) {
185 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
186 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
188 eventCounter += 1;
189 }
190 assert(eventCounter == index, "while loop didn't work");
191 }
193 function getMaxSteps() {
194 return debugModule.getRecordedEventsLength();
195 }
197 function doPlayback(eventList) {
198 var x = eventList.shift();
199 var time = x[2];
200 runtime.notify(x[0], x[1], x[2]);
202 if (eventList.length > 0) {
203 var delta = eventList[0][2] - time;
204 setTimeout(function() { doPlayback(eventList); }, delta);
205 }
206 }
208 function redrawGraphics() {
209 var graphicsNode = debugModule.moduleInstance.main.kids[0];
210 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
211 }
213 var elmDebugger = {
214 restart: restartProgram,
215 pause: pauseProgram,
216 kontinue: continueProgram,
217 getMaxSteps: getMaxSteps,
218 stepTo: stepTo,
219 getPaused: debugModule.getPaused
220 };
222 return elmDebugger;
225function Point(x, y) {
226 this.x = x;
227 this.y = y;
229 this.translate = function(x, y) {
230 return new Point(this.x + x, this.y + y);
231 }
233 this.equals = function(p) {
234 return this.x == p.x && this.y == p.y;
235 }
238function tracePathInit(runtime) {
239 var List = Elm.List.make(runtime);
240 var tracePathCanvas = createCanvas();
241 var tracePositions = {};
242 var recordingTraces = true;
244 function findPositions(currentScene) {
245 var positions = {};
246 function processElement(elem, offset) {
247 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
248 {
249 List.map(F2(processForm)(offset))(elem.element.model.forms);
250 }
251 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
252 {
253 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
254 }
255 }
257 function processForm(offset, form) {
258 if (form.form.ctor == "FElement")
259 {
260 processElement(form.form._0, offset.translate(form.x, -form.y));
261 }
262 }
264 processElement(currentScene, new Point(0, 0));
265 return positions;
266 }
268 function appendPositions(positions) {
269 for (var id in positions) {
270 var pos = positions[id];
271 if (tracePositions.hasOwnProperty(id)) {
272 var points = tracePositions[id];
273 if (!pos.equals(points[points.length-1])) {
274 points.push(pos);
275 }
276 }
277 else {
278 tracePositions[id] = [pos];
279 }
280 }
281 }
283 function graphicsUpdate(currentScene) {
284 if (!recordingTraces) {
285 return;
286 }
288 var ctx = tracePathCanvas.getContext('2d');
289 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
291 ctx.save();
292 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
293 appendPositions(findPositions(currentScene));
294 for (var id in tracePositions)
295 {
296 ctx.beginPath();
297 var points = tracePositions[id];
298 for (var i=0; i < points.length; i++)
299 {
300 var p = points[i];
301 if (i == 0) {
302 ctx.moveTo(p.x, p.y);
303 }
304 else {
305 ctx.lineTo(p.x, p.y);
306 }
307 }
308 }
309 ctx.lineWidth = 1;
310 ctx.strokeStyle = "black";
311 ctx.stroke();
313 ctx.restore();
314 }
316 function clearTraces() {
317 tracePositions = {};
318 }
320 function stopRecording() {
321 recordingTraces = false;
322 }
324 function startRecording() {
325 recordingTraces = true;
326 }
328 return {
329 graphicsUpdate: graphicsUpdate,
330 canvas: tracePathCanvas,
331 clearTraces: clearTraces,
332 stopRecording: stopRecording,
333 startRecording: startRecording
334 }
338function executeCallbacks(callbacks, reexecute) {
339 callbacks.forEach(function(timer) {
340 if (reexecute || !timer.executed) {
341 var func = timer.func;
342 func();
343 }
344 });
347function createCanvas() {
348 var c = document.createElement('canvas');
349 c.width = window.innerWidth;
350 c.height = window.innerHeight;
351 c.style.position = "absolute";
352 c.style.top = "0";
353 c.style.left = "0";
354 return c;
357function assert(bool, msg) {
358 if (!bool) {
359 throw "Assertion error: " + msg;
360 }
363function saveNodeValues(allNodes) {
364 var nodeValues = [];
366 allNodes.forEach(function(node) {
367 nodeValues.push({ value: node.value, id: node.id });
368 });
370 return nodeValues;
371};
374function restoreNodeValues(allNodes, nodeStates) {
375 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
376 for (var i=0; i < allNodes.length; i++) {
377 var node = allNodes[i];
378 var state = nodeStates[i];
379 assert(node.id == state.id, "the nodes moved position");
381 node.value = state.value;
382 }
385function flattenNodes(nodes) {
386 var treeReduce = function(prev, cur, i, arr) {
387 var flatKids = cur.kids.reduce(treeReduce, []);
388 flatKids.push(cur);
389 return prev.concat(flatKids);
390 }
392 return nodes.reduce(treeReduce, []);
393};
395}());