on ecc6c14 help sign in
elk/runtime/Debugger.js
331 lines 7.9 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 eventCounter = 0;
31 var asyncCallbacks = [];
33 function wrapNotify(id, v) {
34 var timestep = Date.now();
36 if (programPaused) {
37 // ignore notify because we are stepping
38 }
39 else {
40 runtime.notify(id, v, timestep);
41 recordEvent(id, v, timestep);
42 }
43 };
45 function wrapRunDelayed(func, delayMs) {
46 return safeSetTimeout(func, delayMs);
47 };
49 function safeSetTimeout(func, delayMs) {
50 var timerId = setTimeout(func, delayMs);
51 asyncCallbacks.push({ func:func, delayMs:delayMs, timerId:timerId });
52 return timerId;
53 }
55 function recordEvent(id, v, timestep) {
56 recordedEvents.push({ id:id, value:v, timestep:timestep });
57 }
59 function clearAsyncCallbacks() {
60 asyncCallbacks.forEach(function(timer) {
61 clearTimeout(timer.timerId);
62 });
63 }
65 function clearRecordedEvents() {
66 recordedEvents = [];
67 }
69 function getRecordedEvents() {
70 return recordedEvents.slice();
71 }
73 function setPaused(v) {
74 programPaused = v;
75 }
77 function getPaused() {
78 return programPaused;
79 }
81 var wrappedRuntime = {
82 notify: wrapNotify,
83 runDelayed: wrapRunDelayed,
84 node: runtime.node,
85 display: runtime.display,
86 id: runtime.id,
87 addListener: runtime.addListener,
88 inputs: runtime.inputs
89 };
91 var moduleInstance = module.make(wrappedRuntime);
92 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
94 return {
95 moduleInstance: moduleInstance,
96 moduleNodes: moduleNodes,
97 initialNodeValues: saveNodeValues(moduleNodes),
98 initialAsyncCallbacks: asyncCallbacks.slice(),
99 // API functions
100 clearAsyncCallbacks: clearAsyncCallbacks,
101 clearRecordedEvents: clearRecordedEvents,
102 getRecordedEvents: getRecordedEvents,
103 getPaused: getPaused,
104 setPaused: setPaused
105 };
108function debuggerInit(debugModule, runtime) {
109 var Signal = Elm.Signal.make(runtime);
111 var tracePath = tracePathInit(runtime);
112 var tracePathNode = A2(Signal.lift, tracePath.graphicsUpdate, debugModule.moduleInstance.main);
113 runtime.node.parentNode.appendChild(tracePath.canvas);
115 function resetProgram() {
116 debugModule.clearAsyncCallbacks();
117 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
118 redrawGraphics();
119 }
121 function restartProgram() {
122 resetProgram();
123 debugModule.clearRecordedEvents();
124 debugModule.initialAsyncCallbacks.forEach(function(timer) {
125 var func = timer.func;
126 func();
127 });
128 }
130 function stepTo(index) {
131 if (!debugModule.getPaused()) {
132 debugModule.setPaused(true);
133 resetProgram();
134 }
136 if (index < 0 || index >= recordedEvents.length) {
137 throw "Index out of bounds: " + index;
138 }
140 if (index < eventCounter) {
141 resetProgram();
142 eventCounter = 0;
143 }
145 assert(index >= eventCounter, "index must be bad");
146 while (eventCounter < index) {
147 var nextEvent = recordedEvents[eventCounter];
148 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
150 eventCounter += 1;
151 }
152 assert(eventCounter == index, "while loop didnt' work");
153 }
155 function getMaxSteps() {
156 return recordedEvents.length - 1;
157 }
159 function doPlayback(eventList) {
160 var x = eventList.shift();
161 var time = x[2];
162 runtime.notify(x[0], x[1], x[2]);
164 if (eventList.length > 0) {
165 var delta = eventList[0][2] - time;
166 setTimeout(function() { doPlayback(eventList); }, delta);
167 }
168 }
170 function redrawGraphics() {
171 var graphicsNode = debugModule.moduleInstance.main.kids[0];
172 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
173 }
175 var elmDebugger = {
176 reset: resetProgram,
177 restart: restartProgram,
178 getMaxSteps: getMaxSteps,
179 stepTo: stepTo
180 };
182 return elmDebugger;
185function Point(x, y) {
186 this.x = x;
187 this.y = y;
189 this.translate = function(x, y) {
190 return new Point(this.x + x, this.y + y);
191 }
193 this.equals = function(p) {
194 return this.x == p.x && this.y == p.y;
195 }
198function tracePathInit(runtime) {
199 var List = Elm.List.make(runtime);
200 var tracePathCanvas = createCanvas();
201 var tracePositions = {};
203 function findPositions(currentScene) {
204 var positions = {};
205 function processElement(elem, offset) {
206 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
207 {
208 List.map(F2(processForm)(offset))(elem.element.model.forms);
209 }
210 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
211 {
212 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
213 }
214 }
216 function processForm(offset, form) {
217 if (form.form.ctor == "FElement")
218 {
219 processElement(form.form._0, offset.translate(form.x, -form.y));
220 }
221 }
223 processElement(currentScene, new Point(0, 0));
224 return positions;
225 }
227 function appendPositions(positions) {
228 for (var id in positions) {
229 var pos = positions[id];
230 if (tracePositions.hasOwnProperty(id)) {
231 var points = tracePositions[id];
232 if (!pos.equals(points[points.length-1])) {
233 points.push(pos);
234 }
235 }
236 else {
237 tracePositions[id] = [pos];
238 }
239 }
240 }
242 function graphicsUpdate(currentScene) {
243 var ctx = tracePathCanvas.getContext('2d');
244 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
246 ctx.save();
247 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
248 appendPositions(findPositions(currentScene));
249 for (var id in tracePositions)
250 {
251 ctx.beginPath();
252 var points = tracePositions[id];
253 for (var i=0; i < points.length; i++)
254 {
255 var p = points[i];
256 if (i == 0) {
257 ctx.moveTo(p.x, p.y);
258 }
259 else {
260 ctx.lineTo(p.x, p.y);
261 }
262 }
263 }
264 ctx.lineWidth = 1;
265 ctx.strokeStyle = "black";
266 ctx.stroke();
268 ctx.restore();
269 }
271 function clearTraces() {
272 tracePositions = {};
273 }
275 return {
276 graphicsUpdate: graphicsUpdate,
277 canvas: tracePathCanvas,
278 clearTraces: clearTraces
279 }
283function createCanvas() {
284 var c = document.createElement('canvas');
285 c.width = window.innerWidth;
286 c.height = window.innerHeight;
287 c.style.position = "absolute";
288 c.style.top = "0";
289 c.style.left = "0";
290 return c;
293function assert(bool, msg) {
294 if (!bool) {
295 throw "Assertion error: " + msg;
296 }
299function saveNodeValues(allNodes) {
300 var nodeValues = [];
302 allNodes.forEach(function(node) {
303 nodeValues.push({ value: node.value, id: node.id });
304 });
306 return nodeValues;
307};
310function restoreNodeValues(allNodes, nodeStates) {
311 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
312 for (var i=0; i < allNodes.length; i++) {
313 var node = allNodes[i];
314 var state = nodeStates[i];
315 assert(node.id == state.id, "the nodes moved position");
317 node.value = state.value;
318 }
321function flattenNodes(nodes) {
322 var treeReduce = function(prev, cur, i, arr) {
323 var flatKids = cur.kids.reduce(treeReduce, []);
324 flatKids.push(cur);
325 return prev.concat(flatKids);
326 }
328 return nodes.reduce(treeReduce, []);
329};
331}());