on f20fa4e help sign in
elk/runtime/Debugger.js
321 lines 7.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 return wrappedModule.moduleInstance;
12 }
13 };
14};
16function debugModule(module, runtime) {
17 var programPaused = false;
18 var recordedEvents = [];
19 var eventCounter = 0;
20 var asyncTimers = [];
22 function wrapNotify(id, v) {
23 var timestep = Date.now();
25 if (programPaused) {
26 // ignore notify because we are stepping
27 }
28 else {
29 runtime.notify(id, v, timestep);
30 recordEvent(id, v, timestep);
31 }
32 };
34 function wrapRunDelayed(func, delayMs) {
35 return safeSetTimeout(func, delayMs);
36 };
38 function safeSetTimeout(func, delayMs) {
39 var timerId = setTimeout(func, delayMs);
40 asyncTimers.push({ func:func, delayMs:delayMs, timerId:timerId });
41 return timerId;
42 }
44 function recordEvent(id, v, timestep) {
45 recordedEvents.push({ id:id, value:v, timestep:timestep });
46 }
48 function clearAsyncEvents() {
49 asyncTimers.forEach(function(timer) {
50 clearTimeout(timer.timerId);
51 });
52 }
54 function clearRecordedEvents() {
55 recordedEvents = [];
56 }
58 function getRecordedEvents() {
59 return recordedEvents.slice();
60 }
62 function setPaused(v) {
63 programPaused = v;
64 }
66 function getPaused() {
67 return programPaused;
68 }
70 var wrappedRuntime = {
71 notify: wrapNotify,
72 runDelayed: wrapRunDelayed,
73 node: runtime.node,
74 display: runtime.display,
75 id: runtime.id,
76 addListener: runtime.addListener,
77 inputs: runtime.inputs
78 };
80 var moduleInstance = module.make(wrappedRuntime);
81 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
83 return {
84 moduleInstance: moduleInstance,
85 moduleNodes: moduleNodes,
86 initialNodeValues: saveNodeValues(moduleNodes),
87 initialAsyncTimers: asyncTimers.slice(),
88 // API functions
89 clearAsyncEvents: clearAsyncEvents,
90 clearRecordedEvents: clearRecordedEvents,
91 getRecordedEvents: getRecordedEvents,
92 getPaused: getPaused,
93 setPaused: setPaused
94 };
97function debuggerInit(debugModule, runtime) {
98 var Signal = Elm.Signal.make(runtime);
100 var tracePath = tracePathInit(runtime);
101 var tracePathNode = A2(Signal.lift, tracePath.graphicsUpdate, debugModule.moduleInstance.main);
102 runtime.node.parentNode.appendChild(tracePath.canvas);
104 function resetProgram() {
105 debugModule.clearAsyncEvents();
106 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
107 redrawGraphics();
108 }
110 function restartProgram() {
111 resetProgram();
112 debugModule.clearRecordedEvents();
113 debugModule.initialAsyncTimers.forEach(function(timer) {
114 var func = timer.func;
115 func();
116 });
117 }
119 function stepTo(index) {
120 if (!debugModule.getPaused()) {
121 debugModule.setPaused(true);
122 resetProgram();
123 }
125 if (index < 0 || index >= recordedEvents.length) {
126 throw "Index out of bounds: " + index;
127 }
129 if (index < eventCounter) {
130 resetProgram();
131 eventCounter = 0;
132 }
134 assert(index >= eventCounter, "index must be bad");
135 while (eventCounter < index) {
136 var nextEvent = recordedEvents[eventCounter];
137 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
139 eventCounter += 1;
140 }
141 assert(eventCounter == index, "while loop didnt' work");
142 }
144 function getMaxSteps() {
145 return recordedEvents.length - 1;
146 }
148 function doPlayback(eventList) {
149 var x = eventList.shift();
150 var time = x[2];
151 runtime.notify(x[0], x[1], x[2]);
153 if (eventList.length > 0) {
154 var delta = eventList[0][2] - time;
155 setTimeout(function() { doPlayback(eventList); }, delta);
156 }
157 }
159 function redrawGraphics() {
160 var graphicsNode = debugModule.moduleInstance.main.kids[0];
161 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
162 }
164 var elmDebugger = {
165 reset: resetProgram,
166 restart: restartProgram,
167 getMaxSteps: getMaxSteps,
168 stepTo: stepTo
169 };
171 return elmDebugger;
174function Point(x, y) {
175 this.x = x;
176 this.y = y;
178 this.translate = function(x, y) {
179 return new Point(this.x + x, this.y + y);
180 }
182 this.equals = function(p) {
183 return this.x == p.x && this.y == p.y;
184 }
187function tracePathInit(runtime) {
188 var List = Elm.List.make(runtime);
189 var tracePathCanvas = createCanvas();
190 var tracePositions = {};
192 function findPositions(currentScene) {
193 var positions = {};
194 function processElement(elem, offset) {
195 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
196 {
197 List.map(F2(processForm)(offset))(elem.element.model.forms);
198 }
199 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
200 {
201 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
202 }
203 }
205 function processForm(offset, form) {
206 if (form.form.ctor == "FElement")
207 {
208 processElement(form.form._0, offset.translate(form.x, -form.y));
209 }
210 }
212 processElement(currentScene, new Point(0, 0));
213 return positions;
214 }
216 function appendPositions(positions) {
217 for (var id in positions) {
218 var pos = positions[id];
219 if (tracePositions.hasOwnProperty(id)) {
220 var points = tracePositions[id];
221 if (!pos.equals(points[points.length-1])) {
222 points.push(pos);
223 }
224 }
225 else {
226 tracePositions[id] = [pos];
227 }
228 }
229 }
231 function graphicsUpdate(currentScene) {
232 var ctx = tracePathCanvas.getContext('2d');
233 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
234 ctx.fillText("Debugger trace path", 20, 20);
236 ctx.save();
237 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
238 appendPositions(findPositions(currentScene));
239 for (var id in tracePositions)
240 {
241 ctx.beginPath();
242 var points = tracePositions[id];
243 for (var i=0; i < points.length; i++)
244 {
245 var p = points[i];
246 if (i == 0) {
247 ctx.moveTo(p.x, p.y);
248 }
249 else {
250 ctx.lineTo(p.x, p.y);
251 }
252 }
253 }
254 ctx.lineWidth = 1;
255 ctx.strokeStyle = "black";
256 ctx.stroke();
258 ctx.restore();
259 }
261 function clearTraces() {
262 tracePositions = {};
263 }
265 return {
266 graphicsUpdate: graphicsUpdate,
267 canvas: tracePathCanvas,
268 clearTraces: clearTraces
269 }
273function createCanvas() {
274 var c = document.createElement('canvas');
275 c.width = window.innerWidth;
276 c.height = window.innerHeight;
277 c.style.position = "absolute";
278 c.style.top = "0";
279 c.style.left = "0";
280 return c;
283function assert(bool, msg) {
284 if (!bool) {
285 throw "Assertion error: " + msg;
286 }
289function saveNodeValues(allNodes) {
290 var nodeValues = [];
292 allNodes.forEach(function(node) {
293 nodeValues.push({ value: node.value, id: node.id });
294 });
296 return nodeValues;
297};
300function restoreNodeValues(allNodes, nodeStates) {
301 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
302 for (var i=0; i < allNodes.length; i++) {
303 var node = allNodes[i];
304 var state = nodeStates[i];
305 assert(node.id == state.id, "the nodes moved position");
307 node.value = state.value;
308 }
311function flattenNodes(nodes) {
312 var treeReduce = function(prev, cur, i, arr) {
313 var flatKids = cur.kids.reduce(treeReduce, []);
314 flatKids.push(cur);
315 return prev.concat(flatKids);
316 }
318 return nodes.reduce(treeReduce, []);
319};
321}());