on b624ffa help sign in
elk/runtime/Debugger.js
337 lines 8.1 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 notify because we are stepping
37 }
38 else {
39 runtime.notify(id, v, timestep);
40 recordEvent(id, v, timestep);
41 }
42 };
44 function wrapRunDelayed(func, delayMs) {
45 return safeSetTimeout(func, delayMs);
46 };
48 function safeSetTimeout(func, delayMs) {
49 var timerId = setTimeout(func, delayMs);
50 asyncCallbacks.push({ func:func, delayMs:delayMs, timerId:timerId });
51 return timerId;
52 }
54 function recordEvent(id, v, timestep) {
55 recordedEvents.push({ id:id, value:v, timestep:timestep });
56 }
58 function clearAsyncCallbacks() {
59 asyncCallbacks.forEach(function(timer) {
60 clearTimeout(timer.timerId);
61 });
62 }
64 function clearRecordedEvents() {
65 recordedEvents = [];
66 }
68 function getRecordedEventsLength() {
69 return recordedEvents.length;
70 }
72 function getRecordedEventAt(i) {
73 return recordedEvents[i];
74 }
76 function setPaused(v) {
77 programPaused = v;
78 }
80 function getPaused() {
81 return programPaused;
82 }
84 var wrappedRuntime = {
85 notify: wrapNotify,
86 runDelayed: wrapRunDelayed,
87 node: runtime.node,
88 display: runtime.display,
89 id: runtime.id,
90 addListener: runtime.addListener,
91 inputs: runtime.inputs
92 };
94 var moduleInstance = module.make(wrappedRuntime);
95 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
97 return {
98 moduleInstance: moduleInstance,
99 moduleNodes: moduleNodes,
100 initialNodeValues: saveNodeValues(moduleNodes),
101 initialAsyncCallbacks: asyncCallbacks.slice(),
102 // API functions
103 clearAsyncCallbacks: clearAsyncCallbacks,
104 clearRecordedEvents: clearRecordedEvents,
105 getRecordedEventsLength: getRecordedEventsLength,
106 getRecordedEventAt: getRecordedEventAt,
107 getPaused: getPaused,
108 setPaused: setPaused,
109 tracePath: tracePathInit(runtime)
110 };
113function debuggerInit(debugModule, runtime) {
114 var Signal = Elm.Signal.make(runtime);
116 var tracePathNode = A2(Signal.lift, debugModule.tracePath.graphicsUpdate, debugModule.moduleInstance.main);
117 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
119 var eventCounter = 0;
121 function resetProgram() {
122 debugModule.clearAsyncCallbacks();
123 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
124 redrawGraphics();
125 }
127 function restartProgram() {
128 resetProgram();
129 debugModule.clearRecordedEvents();
130 debugModule.initialAsyncCallbacks.forEach(function(timer) {
131 var func = timer.func;
132 func();
133 });
134 }
136 function stepTo(index) {
137 if (!debugModule.getPaused()) {
138 debugModule.setPaused(true);
139 resetProgram();
140 }
142 if (index < 0 || index > getMaxSteps()) {
143 throw "Index out of bounds: " + index;
144 }
146 if (index < eventCounter) {
147 resetProgram();
148 eventCounter = 0;
149 }
151 assert(index >= eventCounter, "index must be bad");
152 while (eventCounter < index) {
153 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
154 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
156 eventCounter += 1;
157 }
158 assert(eventCounter == index, "while loop didn't work");
159 }
161 function getMaxSteps() {
162 return debugModule.getRecordedEventsLength() - 1;
163 }
165 function doPlayback(eventList) {
166 var x = eventList.shift();
167 var time = x[2];
168 runtime.notify(x[0], x[1], x[2]);
170 if (eventList.length > 0) {
171 var delta = eventList[0][2] - time;
172 setTimeout(function() { doPlayback(eventList); }, delta);
173 }
174 }
176 function redrawGraphics() {
177 var graphicsNode = debugModule.moduleInstance.main.kids[0];
178 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
179 }
181 var elmDebugger = {
182 reset: resetProgram,
183 restart: restartProgram,
184 getMaxSteps: getMaxSteps,
185 stepTo: stepTo
186 };
188 return elmDebugger;
191function Point(x, y) {
192 this.x = x;
193 this.y = y;
195 this.translate = function(x, y) {
196 return new Point(this.x + x, this.y + y);
197 }
199 this.equals = function(p) {
200 return this.x == p.x && this.y == p.y;
201 }
204function tracePathInit(runtime) {
205 var List = Elm.List.make(runtime);
206 var tracePathCanvas = createCanvas();
207 var tracePositions = {};
209 function findPositions(currentScene) {
210 var positions = {};
211 function processElement(elem, offset) {
212 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
213 {
214 List.map(F2(processForm)(offset))(elem.element.model.forms);
215 }
216 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
217 {
218 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
219 }
220 }
222 function processForm(offset, form) {
223 if (form.form.ctor == "FElement")
224 {
225 processElement(form.form._0, offset.translate(form.x, -form.y));
226 }
227 }
229 processElement(currentScene, new Point(0, 0));
230 return positions;
231 }
233 function appendPositions(positions) {
234 for (var id in positions) {
235 var pos = positions[id];
236 if (tracePositions.hasOwnProperty(id)) {
237 var points = tracePositions[id];
238 if (!pos.equals(points[points.length-1])) {
239 points.push(pos);
240 }
241 }
242 else {
243 tracePositions[id] = [pos];
244 }
245 }
246 }
248 function graphicsUpdate(currentScene) {
249 var ctx = tracePathCanvas.getContext('2d');
250 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
252 ctx.save();
253 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
254 appendPositions(findPositions(currentScene));
255 for (var id in tracePositions)
256 {
257 ctx.beginPath();
258 var points = tracePositions[id];
259 for (var i=0; i < points.length; i++)
260 {
261 var p = points[i];
262 if (i == 0) {
263 ctx.moveTo(p.x, p.y);
264 }
265 else {
266 ctx.lineTo(p.x, p.y);
267 }
268 }
269 }
270 ctx.lineWidth = 1;
271 ctx.strokeStyle = "black";
272 ctx.stroke();
274 ctx.restore();
275 }
277 function clearTraces() {
278 tracePositions = {};
279 }
281 return {
282 graphicsUpdate: graphicsUpdate,
283 canvas: tracePathCanvas,
284 clearTraces: clearTraces
285 }
289function createCanvas() {
290 var c = document.createElement('canvas');
291 c.width = window.innerWidth;
292 c.height = window.innerHeight;
293 c.style.position = "absolute";
294 c.style.top = "0";
295 c.style.left = "0";
296 return c;
299function assert(bool, msg) {
300 if (!bool) {
301 throw "Assertion error: " + msg;
302 }
305function saveNodeValues(allNodes) {
306 var nodeValues = [];
308 allNodes.forEach(function(node) {
309 nodeValues.push({ value: node.value, id: node.id });
310 });
312 return nodeValues;
313};
316function restoreNodeValues(allNodes, nodeStates) {
317 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
318 for (var i=0; i < allNodes.length; i++) {
319 var node = allNodes[i];
320 var state = nodeStates[i];
321 assert(node.id == state.id, "the nodes moved position");
323 node.value = state.value;
324 }
327function flattenNodes(nodes) {
328 var treeReduce = function(prev, cur, i, arr) {
329 var flatKids = cur.kids.reduce(treeReduce, []);
330 flatKids.push(cur);
331 return prev.concat(flatKids);
332 }
334 return nodes.reduce(treeReduce, []);
335};
337}());