on 92898ac help sign in
elk/runtime/Debugger.js
266 lines 6.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.debuggerInit = function() {
11 Elm.Debugger = debuggerInit(wrappedModule, runtime);
12 };
13 return wrappedModule.moduleInstance;
14 }
15 };
16};
18function debugModule(module, runtime) {
19 var programPaused = false;
20 var recordedEvents = [];
21 var eventCounter = 0;
22 var asyncTimers = [];
24 function wrapNotify(id, v) {
25 var timestep = Date.now();
27 if (programPaused) {
28 // ignore notify because we are stepping
29 }
30 else {
31 runtime.notify(id, v, timestep);
32 recordEvent(id, v, timestep);
33 }
34 };
36 function wrapRunDelayed(func, delayMs) {
37 return safeSetTimeout(func, delayMs);
38 };
40 function safeSetTimeout(func, delayMs) {
41 var timerId = setTimeout(func, delayMs);
42 asyncTimers.push({ func:func, delayMs:delayMs, timerId:timerId });
43 return timerId;
44 }
46 function recordEvent(id, v, timestep) {
47 recordedEvents.push({ id:id, value:v, timestep:timestep });
48 }
50 function clearAsyncEvents() {
51 asyncTimers.forEach(function(timer) {
52 clearTimeout(timer.timerId);
53 });
54 }
56 function clearRecordedEvents() {
57 recordedEvents = [];
58 }
60 function getRecordedEvents() {
61 return recordedEvents.slice();
62 }
64 function setPaused(v) {
65 programPaused = v;
66 }
68 function getPaused() {
69 return programPaused;
70 }
72 var wrappedRuntime = {
73 notify: wrapNotify,
74 runDelayed: wrapRunDelayed,
75 node: runtime.node,
76 display: runtime.display,
77 id: runtime.id,
78 addListener: runtime.addListener,
79 inputs: runtime.inputs
80 };
82 var moduleInstance = module.make(wrappedRuntime);
83 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
85 return {
86 moduleInstance: moduleInstance,
87 moduleNodes: moduleNodes,
88 initialNodeValues: saveNodeValues(moduleNodes),
89 initialAsyncTimers: asyncTimers.slice(),
90 // API functions
91 clearAsyncEvents: clearAsyncEvents,
92 clearRecordedEvents: clearRecordedEvents,
93 getRecordedEvents: getRecordedEvents,
94 getPaused: getPaused,
95 setPaused: setPaused
96 };
99function debuggerInit(debugModule, runtime) {
100 var Signal = Elm.Signal.make(runtime);
101 var List = Elm.List.make(runtime);
102 var graphicsNode = debugModule.moduleInstance.main.kids[0];
104 var tracePathCanvas = document.createElement('canvas');
105 tracePathCanvas.width = window.innerWidth;
106 tracePathCanvas.height = window.innerHeight;
107 tracePathCanvas.style.position = "absolute";
108 tracePathCanvas.style.top = "0";
109 tracePathCanvas.style.left = "0";
110 runtime.node.parentNode.appendChild(tracePathCanvas);
112 function graphicsUpdate(currentScene) {
113 var ctx = tracePathCanvas.getContext('2d');
114 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
115 ctx.fillText("Debugger trace path", 20, 20);
117 function drawElement(elem) {
118 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
119 {
120 List.map(drawForm)(elem.element.model.forms);
121 }
122 else if (elem.element.ctor == "Image")
123 {
124 var x = 0;
125 var y = 0;
126 ctx.beginPath();
127 ctx.arc(x, y, 2, 0, 2*Math.PI);
128 ctx.fillStyle = "grey";
129 ctx.fill();
130 ctx.lineWidth = 1;
131 ctx.strokeStype = "black";
132 ctx.stroke();
133 }
134 }
136 function drawForm(form) {
137 ctx.save();
138 ctx.scale(form.scale, form.scale);
139 ctx.rotate(form.theta);
140 ctx.translate(form.x, -form.y);
141 if (form.form.ctor == "FElement")
142 {
143 drawElement(form.form._0);
144 }
146 ctx.restore();
147 }
149 ctx.save();
150 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
151 drawElement(currentScene);
152 ctx.restore();
153 }
155 var tracePathNode = A2(Signal.lift, graphicsUpdate, graphicsNode);
157 function resetProgram() {
158 debugModule.clearAsyncEvents();
159 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
160 redrawGraphics();
161 }
163 function restartProgram() {
164 resetProgram();
165 debugModule.clearRecordedEvents();
166 debugModule.initialAsyncTimers.forEach(function(timer) {
167 var func = timer.func;
168 func();
169 });
170 }
172 function stepTo(index) {
173 if (!debugModule.getPaused()) {
174 debugModule.setPaused(true);
175 resetProgram();
176 }
178 if (index < 0 || index >= recordedEvents.length) {
179 throw "Index out of bounds: " + index;
180 }
182 if (index < eventCounter) {
183 resetProgram();
184 eventCounter = 0;
185 }
187 assert(index >= eventCounter, "index must be bad");
188 while (eventCounter < index) {
189 var nextEvent = recordedEvents[eventCounter];
190 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
192 eventCounter += 1;
193 }
194 assert(eventCounter == index, "while loop didnt' work");
195 }
197 function getMaxSteps() {
198 return recordedEvents.length - 1;
199 }
201 function doPlayback(eventList) {
202 var x = eventList.shift();
203 var time = x[2];
204 runtime.notify(x[0], x[1], x[2]);
206 if (eventList.length > 0) {
207 var delta = eventList[0][2] - time;
208 setTimeout(function() { doPlayback(eventList); }, delta);
209 }
210 }
212 function redrawGraphics() {
213 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
214 }
216 var elmDebugger = {
217 reset: resetProgram,
218 restart: restartProgram,
219 getMaxSteps: getMaxSteps,
220 stepTo: stepTo
221 };
223 return elmDebugger;
228function assert(bool, msg) {
229 if (!bool) {
230 throw "Assertion error: " + msg;
231 }
234function saveNodeValues(allNodes) {
235 var nodeValues = [];
237 allNodes.forEach(function(node) {
238 nodeValues.push({ value: node.value, id: node.id });
239 });
241 return nodeValues;
242};
245function restoreNodeValues(allNodes, nodeStates) {
246 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
247 for (var i=0; i < allNodes.length; i++) {
248 var node = allNodes[i];
249 var state = nodeStates[i];
250 assert(node.id == state.id, "the nodes moved position");
252 node.value = state.value;
253 }
256function flattenNodes(nodes) {
257 var treeReduce = function(prev, cur, i, arr) {
258 var flatKids = cur.kids.reduce(treeReduce, []);
259 flatKids.push(cur);
260 return prev.concat(flatKids);
261 }
263 return nodes.reduce(treeReduce, []);
264};
266}());