on master help sign in
Merge remote-tracking branch 'laszlopandy/elm-debugger' into debugger
c689fefEvan Czaplicki
libraries/Debug.elm+9−2
@@ -2,9 +2,10 @@ module Debug where
2{-| This library is for investigating bugs or performance problems. It should
2{-| This library is for investigating bugs or performance problems. It should
3*not* be used in production code.
3*not* be used in production code.
4
4
5−@docs log
5+@docs log, trace
6-}
6-}
7
7
8+import Graphics.Element (Element)
8import Native.Debug
9import Native.Debug
9
10
10{-| Log a tagged value on the developer console, and then return the value.
11{-| Log a tagged value on the developer console, and then return the value.
@@ -16,4 +17,10 @@ Notice that `log` is not a pure function! It should *only* be used for
16investigating bugs or performance problems.
17investigating bugs or performance problems.
17-}
18-}
18log : String -> a -> a
19log : String -> a -> a
19−log = Native.Debug.log
20+log = Native.Debug.log
21+
22+{-| Trace an Element over time in the reactive debugger.
23+-}
24+trace : String -> Element -> Element
25+trace = Native.Debug.tracePath
26+
libraries/Native/Debug.js+11−3
@@ -3,6 +3,8 @@ Elm.Native.Debug.make = function(elm) {
3 elm.Native = elm.Native || {};
3 elm.Native = elm.Native || {};
4 elm.Native.Debug = elm.Native.Debug || {};
4 elm.Native.Debug = elm.Native.Debug || {};
5 if (elm.Native.Debug.values) return elm.Native.Debug.values;
5 if (elm.Native.Debug.values) return elm.Native.Debug.values;
6+ if ('values' in Elm.Native.Debug)
7+ return elm.Native.Debug.values = Elm.Native.Debug.values;
6
8
7 var show = Elm.Native.Show.make(elm).show;
9 var show = Elm.Native.Show.make(elm).show;
8
10
@@ -17,8 +19,14 @@ Elm.Native.Debug.make = function(elm) {
17 return value;
19 return value;
18 }
20 }
19
21
22+ function tracePath(debugId, elem) {
23+ elem.props.debugTracePathId = debugId;
24+ return elem;
25+ }
26+
20− return elm.Native.Debug.values = {
27+ Elm.Native.Debug.values = {
28+ tracePath: F2(tracePath),
21− log: F2(log)
29+ log: F2(log),
22 };
30 };
23−
31+ return elm.Native.Debug.values = Elm.Native.Debug.values;
24};
32};
libraries/Native/Show.js+1−1
@@ -17,7 +17,7 @@ Elm.Native.Show.make = function(elm) {
17 } else if (type === "boolean") {
17 } else if (type === "boolean") {
18 return v ? "True" : "False";
18 return v ? "True" : "False";
19 } else if (type === "number") {
19 } else if (type === "number") {
20− return v+"";
20+ return v.toFixed(2).replace(/\.0+$/g, '');
21 } else if ((v instanceof String) && v.isChar) {
21 } else if ((v instanceof String) && v.isChar) {
22 return "'" + addSlashes(v) + "'";
22 return "'" + addSlashes(v) + "'";
23 } else if (type === "string") {
23 } else if (type === "string") {
libraries/Native/Signal/Keyboard.js+6−3
@@ -17,9 +17,10 @@ Elm.Native.Keyboard.make = function(elm) {
17 var NList = Elm.Native.List.make(elm);
17 var NList = Elm.Native.List.make(elm);
18 var Utils = Elm.Native.Utils.make(elm);
18 var Utils = Elm.Native.Utils.make(elm);
19
19
20+ var NS = Elm.Native.Signal.make(elm);
20− var downEvents = Signal.constant(0);
21+ var downEvents = NS.input(0);
21− var upEvents = Signal.constant(0);
22+ var upEvents = NS.input(0);
22− var blurEvents = Signal.constant(0);
23+ var blurEvents = NS.input(0);
23
24
24 elm.addListener([downEvents.id], document, 'keydown', function down(e) {
25 elm.addListener([downEvents.id], document, 'keydown', function down(e) {
25 elm.notify(downEvents.id, e.keyCode);
26 elm.notify(downEvents.id, e.keyCode);
@@ -34,6 +35,8 @@ Elm.Native.Keyboard.make = function(elm) {
34 });
35 });
35
36
36 function KeyMerge(down, up, blur) {
37 function KeyMerge(down, up, blur) {
38+ this.nodeType = "merge";
39+
37 var args = [down,up,blur];
40 var args = [down,up,blur];
38 this.id = Utils.guid();
41 this.id = Utils.guid();
39 // Ignore starting values here
42 // Ignore starting values here
libraries/Native/Signal/Signal.js+23−1
@@ -17,6 +17,8 @@ Elm.Native.Signal.make = function(elm) {
17 }
17 }
18
18
19 function Input(base) {
19 function Input(base) {
20+ this.nodeType = "input";
21+
20 this.id = Utils.guid();
22 this.id = Utils.guid();
21 this.value = base;
23 this.value = base;
22 this.kids = [];
24 this.kids = [];
@@ -31,6 +33,8 @@ Elm.Native.Signal.make = function(elm) {
31 }
33 }
32
34
33 function LiftN(update, args) {
35 function LiftN(update, args) {
36+ this.nodeType = "lift";
37+
34 this.id = Utils.guid();
38 this.id = Utils.guid();
35 this.value = update();
39 this.value = update();
36 this.kids = [];
40 this.kids = [];
@@ -86,6 +90,8 @@ Elm.Native.Signal.make = function(elm) {
86 }
90 }
87
91
88 function Foldp(step, state, input) {
92 function Foldp(step, state, input) {
93+ this.nodeType = "foldp";
94+
89 this.id = Utils.guid();
95 this.id = Utils.guid();
90 this.value = state;
96 this.value = state;
91 this.kids = [];
97 this.kids = [];
@@ -104,6 +110,8 @@ Elm.Native.Signal.make = function(elm) {
104 }
110 }
105
111
106 function DropIf(pred,base,input) {
112 function DropIf(pred,base,input) {
113+ this.nodeType = "dropIf";
114+
107 this.id = Utils.guid();
115 this.id = Utils.guid();
108 this.value = pred(input.value) ? base : input.value;
116 this.value = pred(input.value) ? base : input.value;
109 this.kids = [];
117 this.kids = [];
@@ -116,6 +124,8 @@ Elm.Native.Signal.make = function(elm) {
116 }
124 }
117
125
118 function DropRepeats(input) {
126 function DropRepeats(input) {
127+ this.nodeType = "dropRepeats";
128+
119 this.id = Utils.guid();
129 this.id = Utils.guid();
120 this.value = input.value;
130 this.value = input.value;
121 this.kids = [];
131 this.kids = [];
@@ -133,6 +143,9 @@ Elm.Native.Signal.make = function(elm) {
133 }
143 }
134
144
135 function SampleOn(s1,s2) {
145 function SampleOn(s1,s2) {
146+ this.nodeType = "sampleOn";
147+ this.indirectParent = s1;
148+
136 this.id = Utils.guid();
149 this.id = Utils.guid();
137 this.value = s2.value;
150 this.value = s2.value;
138 this.kids = [];
151 this.kids = [];
@@ -157,6 +170,8 @@ Elm.Native.Signal.make = function(elm) {
157 function sampleOn(s1,s2) { return new SampleOn(s1,s2); }
170 function sampleOn(s1,s2) { return new SampleOn(s1,s2); }
158
171
159 function delay(t,s) {
172 function delay(t,s) {
173+ this.nodeType = "delay";
174+
160 var delayed = new Input(s.value);
175 var delayed = new Input(s.value);
161 var firstEvent = true;
176 var firstEvent = true;
162 function update(v) {
177 function update(v) {
@@ -168,6 +183,8 @@ Elm.Native.Signal.make = function(elm) {
168 }
183 }
169
184
170 function Merge(s1,s2) {
185 function Merge(s1,s2) {
186+ this.nodeType = "merge";
187+
171 this.id = Utils.guid();
188 this.id = Utils.guid();
172 this.value = s1.value;
189 this.value = s1.value;
173 this.kids = [];
190 this.kids = [];
@@ -199,7 +216,12 @@ Elm.Native.Signal.make = function(elm) {
199 function merges(ss) { return A2(foldr1, F2(merge), ss); }
216 function merges(ss) { return A2(foldr1, F2(merge), ss); }
200
217
201 return elm.Native.Signal.values = {
218 return elm.Native.Signal.values = {
219+ input: function(v) { return new Input(v); },
202− constant : function(v) { return new Input(v); },
220+ constant : function(v) {
221+ var s = new Input(v);
222+ s.nodeType = "constant";
223+ return s;
224+ },
203 lift : F2(lift ),
225 lift : F2(lift ),
204 lift2 : F3(lift2),
226 lift2 : F3(lift2),
205 lift3 : F4(lift3),
227 lift3 : F4(lift3),
libraries/Native/Signal/Time.js+8−6
@@ -13,7 +13,7 @@ Elm.Native.Time.make = function(elm) {
13 function fpsWhen(desiredFPS, isOn) {
13 function fpsWhen(desiredFPS, isOn) {
14 var msPerFrame = 1000 / desiredFPS;
14 var msPerFrame = 1000 / desiredFPS;
15 var prev = Date.now(), curr = prev, diff = 0, wasOn = true;
15 var prev = Date.now(), curr = prev, diff = 0, wasOn = true;
16− var ticker = Signal.constant(diff);
16+ var ticker = NS.input(diff);
17 function tick(zero) { return function() {
17 function tick(zero) { return function() {
18 curr = Date.now();
18 curr = Date.now();
19 diff = zero ? 0 : curr - prev;
19 diff = zero ? 0 : curr - prev;
@@ -24,7 +24,7 @@ Elm.Native.Time.make = function(elm) {
24 var timeoutID = 0;
24 var timeoutID = 0;
25 function f(isOn, t) {
25 function f(isOn, t) {
26 if (isOn) {
26 if (isOn) {
27− timeoutID = setTimeout(tick(!wasOn && isOn), msPerFrame);
27+ timeoutID = elm.runDelayed(tick(!wasOn && isOn), msPerFrame);
28 } else if (wasOn) {
28 } else if (wasOn) {
29 clearTimeout(timeoutID);
29 clearTimeout(timeoutID);
30 }
30 }
@@ -35,10 +35,12 @@ Elm.Native.Time.make = function(elm) {
35 }
35 }
36
36
37 function every(t) {
37 function every(t) {
38− var clock = Signal.constant(Date.now());
38+ var clock = NS.input(Date.now());
39− setInterval(function() {
39+ var id = elm.runDelayed(function tellTime() {
40− elm.notify(clock.id, Date.now());
40+ if (elm.notify(clock.id, Date.now())) {
41+ elm.runDelayed(tellTime, t);
42+ }
41− }, t);
43+ }, t);
42 return clock;
44 return clock;
43 }
45 }
44
46
libraries/Native/Signal/Window.js+2−1
@@ -6,6 +6,7 @@ Elm.Native.Window.make = function(elm) {
6 if (elm.Native.Window.values) return elm.Native.Window.values;
6 if (elm.Native.Window.values) return elm.Native.Window.values;
7
7
8 var Signal = Elm.Signal.make(elm);
8 var Signal = Elm.Signal.make(elm);
9+ var NS = Elm.Native.Signal.make(elm);
9 var Tuple2 = Elm.Native.Utils.make(elm).Tuple2;
10 var Tuple2 = Elm.Native.Utils.make(elm).Tuple2;
10
11
11 function getWidth() { return elm.node.clientWidth; }
12 function getWidth() { return elm.node.clientWidth; }
@@ -16,7 +17,7 @@ Elm.Native.Window.make = function(elm) {
16 return elm.node.clientHeight;
17 return elm.node.clientHeight;
17 }
18 }
18
19
19− var dimensions = Signal.constant(Tuple2(getWidth(), getHeight()));
20+ var dimensions = NS.input(Tuple2(getWidth(), getHeight()));
20 dimensions.defaultNumberOfKids = 2;
21 dimensions.defaultNumberOfKids = 2;
21
22
22 // Do not move width and height into Elm. By setting the default number of kids,
23 // Do not move width and height into Elm. By setting the default number of kids,
runtime/Debugger.jsnew file+457
@@ -0,0 +1,457 @@
1+
2+(function() {
3+'use strict';
4+
5+Elm.Debugger = null;
6+Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
7+ return {
8+ make: function(runtime) {
9+ var wrappedModule = debugModule(module, runtime);
10+ Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
11+ dispatchElmDebuggerInit();
12+ return wrappedModule.moduleInstance;
13+ }
14+ };
15+};
16+
17+function 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+ }
29+}
30+
31+function debugModule(module, runtime) {
32+ var programPaused = false;
33+ var recordedEvents = [];
34+ var asyncCallbacks = [];
35+
36+ function wrapNotify(id, v) {
37+ var timestep = Date.now();
38+
39+ if (programPaused) {
40+ // ignore async events generated while playing back
41+ // or user events while program is paused
42+ return false;
43+ }
44+ else {
45+ var changed = runtime.notify(id, v, timestep);
46+ recordEvent(id, v, timestep);
47+ if (parent.window) {
48+ parent.window.postMessage("elmNotify", window.location.origin);
49+ }
50+ return changed;
51+ }
52+ };
53+
54+ function wrapRunDelayed(func, delayMs) {
55+ var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
56+ var timerId = setTimeout(function() {
57+ cbObj.executed = true;
58+ func();
59+ }, delayMs);
60+ cbObj.timerId = timerId;
61+ asyncCallbacks.push(cbObj);
62+ return timerId;
63+ }
64+
65+ function recordEvent(id, v, timestep) {
66+ recordedEvents.push({ id:id, value:v, timestep:timestep });
67+ }
68+
69+ function clearAsyncCallbacks() {
70+ asyncCallbacks.forEach(function(timer) {
71+ if (!timer.executed) {
72+ clearTimeout(timer.timerId);
73+ }
74+ });
75+ }
76+
77+ function clearRecordedEvents() {
78+ recordedEvents = [];
79+ }
80+
81+ function getRecordedEventsLength() {
82+ return recordedEvents.length;
83+ }
84+
85+ function getRecordedEventAt(i) {
86+ return recordedEvents[i];
87+ }
88+
89+ function copyRecordedEvents() {
90+ return recordedEvents.slice();
91+ }
92+
93+ function loadRecordedEvents(events) {
94+ recordedEvents = events.slice();
95+ }
96+
97+ function setPaused(v) {
98+ programPaused = v;
99+ if (programPaused) {
100+ clearAsyncCallbacks();
101+ tracePath.stopRecording();
102+ }
103+ else {
104+ // executeCallbacks should only be called when continuing, not when restarting.
105+ //executeCallbacks(asyncCallbacks, false);
106+ tracePath.startRecording();
107+ }
108+ }
109+
110+ function getPaused() {
111+ return programPaused;
112+ }
113+
114+ // runtime is the prototype of wrappedRuntime
115+ // so we can access all runtime properties too
116+ var wrappedRuntime = Object.create(runtime);
117+ wrappedRuntime.notify = wrapNotify;
118+ wrappedRuntime.runDelayed = wrapRunDelayed;
119+
120+ var assignedPropTracker = Object.create(wrappedRuntime);
121+ var moduleInstance = module.make(assignedPropTracker);
122+ // The main module stores imported modules onto the runtime.
123+ // To ensure only one instance of each module is created,
124+ // we assign them back on the original runtime object.
125+ Object.keys(assignedPropTracker).forEach(function(key) {
126+ runtime[key] = assignedPropTracker[key];
127+ });
128+
129+ var moduleNodes = flattenNodes(wrappedRuntime.inputs);
130+ var tracePath = tracePathInit(runtime, moduleInstance.main);
131+
132+ return {
133+ moduleInstance: moduleInstance,
134+ moduleNodes: moduleNodes,
135+ initialNodeValues: saveNodeValues(moduleNodes),
136+ initialAsyncCallbacks: asyncCallbacks.slice(),
137+ // API functions
138+ clearAsyncCallbacks: clearAsyncCallbacks,
139+ clearRecordedEvents: clearRecordedEvents,
140+ getRecordedEventsLength: getRecordedEventsLength,
141+ getRecordedEventAt: getRecordedEventAt,
142+ copyRecordedEvents: copyRecordedEvents,
143+ loadRecordedEvents: loadRecordedEvents,
144+ getPaused: getPaused,
145+ setPaused: setPaused,
146+ tracePath: tracePath
147+ };
148+}
149+
150+function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
151+ var eventCounter = 0;
152+
153+ function resetProgram() {
154+ debugModule.clearAsyncCallbacks();
155+ restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
156+ redrawGraphics();
157+ }
158+
159+ function restartProgram() {
160+ resetProgram();
161+ debugModule.tracePath.clearTraces();
162+ debugModule.clearRecordedEvents();
163+ debugModule.setPaused(false);
164+ executeCallbacks(debugModule.initialAsyncCallbacks, true);
165+ }
166+
167+ function pauseProgram() {
168+ debugModule.setPaused(true);
169+ eventCounter = debugModule.getRecordedEventsLength();
170+ }
171+
172+ function continueProgram() {
173+ if (debugModule.getPaused())
174+ {
175+ debugModule.setPaused(false);
176+ }
177+ }
178+
179+ function stepTo(index) {
180+ if (!debugModule.getPaused()) {
181+ debugModule.setPaused(true);
182+ resetProgram();
183+ }
184+
185+ if (index < 0 || index > getMaxSteps()) {
186+ throw "Index out of bounds: " + index;
187+ }
188+
189+ if (index < eventCounter) {
190+ resetProgram();
191+ eventCounter = 0;
192+ }
193+
194+ assert(index >= eventCounter, "index must be bad");
195+ while (eventCounter < index) {
196+ var nextEvent = debugModule.getRecordedEventAt(eventCounter);
197+ runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
198+
199+ eventCounter += 1;
200+ }
201+ assert(eventCounter == index, "while loop didn't work");
202+ }
203+
204+ function getMaxSteps() {
205+ return debugModule.getRecordedEventsLength();
206+ }
207+
208+ function doPlayback(eventList) {
209+ var x = eventList.shift();
210+ var time = x[2];
211+ runtime.notify(x[0], x[1], x[2]);
212+
213+ if (eventList.length > 0) {
214+ var delta = eventList[0][2] - time;
215+ setTimeout(function() { doPlayback(eventList); }, delta);
216+ }
217+ }
218+
219+ function redrawGraphics() {
220+ var graphicsNode = debugModule.moduleInstance.main.kids[0];
221+ graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
222+ }
223+
224+ function getHotSwapState() {
225+ var counter = eventCounter;
226+ if (!debugModule.getPaused()) {
227+ counter = getMaxSteps();
228+ }
229+ return {
230+ recordedEvents: debugModule.copyRecordedEvents(),
231+ eventCounter: counter
232+ };
233+ }
234+
235+ function dispose() {
236+ var parentNode = runtime.node.parentNode;
237+ parentNode.removeChild(debugModule.tracePath.canvas);
238+ parentNode.removeChild(runtime.node);
239+ }
240+
241+ if (hotSwapState) {
242+ debugModule.setPaused(true);
243+ debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
244+
245+ // draw new trace path
246+ debugModule.tracePath.startRecording();
247+ stepTo(getMaxSteps());
248+ debugModule.tracePath.stopRecording();
249+
250+ stepTo(hotSwapState.eventCounter);
251+ }
252+
253+ runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
254+
255+ var elmDebugger = {
256+ restart: restartProgram,
257+ pause: pauseProgram,
258+ kontinue: continueProgram,
259+ getMaxSteps: getMaxSteps,
260+ stepTo: stepTo,
261+ getPaused: debugModule.getPaused,
262+ getHotSwapState: getHotSwapState,
263+ dispose: dispose,
264+ allNodes: debugModule.moduleNodes,
265+ mainNode: debugModule.moduleInstance.main
266+ };
267+
268+ return elmDebugger;
269+}
270+
271+function Point(x, y) {
272+ this.x = x;
273+ this.y = y;
274+
275+ this.translate = function(x, y) {
276+ return new Point(this.x + x, this.y + y);
277+ }
278+
279+ this.equals = function(p) {
280+ return this.x == p.x && this.y == p.y;
281+ }
282+}
283+
284+function tracePathInit(runtime, mainNode) {
285+ var List = Elm.List.make(runtime);
286+ var Signal = Elm.Signal.make(runtime);
287+ var tracePathNode = A2(Signal.lift, graphicsUpdate, mainNode);
288+ var tracePathCanvas = createCanvas();
289+ var tracePositions = {};
290+ var recordingTraces = true;
291+
292+ function findPositions(currentScene) {
293+ var positions = {};
294+ function processElement(elem, offset) {
295+ if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
296+ {
297+ List.map(F2(processForm)(offset))(elem.element.model.forms);
298+ }
299+ else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
300+ {
301+ positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
302+ }
303+ }
304+
305+ function processForm(offset, form) {
306+ if (form.form.ctor == "FElement")
307+ {
308+ processElement(form.form._0, offset.translate(form.x, -form.y));
309+ }
310+ }
311+
312+ processElement(currentScene, new Point(0, 0));
313+ return positions;
314+ }
315+
316+ function appendPositions(positions) {
317+ for (var id in positions) {
318+ var pos = positions[id];
319+ if (tracePositions.hasOwnProperty(id)) {
320+ var points = tracePositions[id];
321+ if (!pos.equals(points[points.length-1])) {
322+ points.push(pos);
323+ }
324+ }
325+ else {
326+ tracePositions[id] = [pos];
327+ }
328+ }
329+ }
330+
331+ function graphicsUpdate(currentScene) {
332+ if (!recordingTraces) {
333+ return;
334+ }
335+
336+ var ctx = tracePathCanvas.getContext('2d');
337+ ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
338+
339+ ctx.save();
340+ ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
341+ appendPositions(findPositions(currentScene));
342+ for (var id in tracePositions)
343+ {
344+ ctx.beginPath();
345+ var points = tracePositions[id];
346+ for (var i=0; i < points.length; i++)
347+ {
348+ var p = points[i];
349+ if (i == 0) {
350+ ctx.moveTo(p.x, p.y);
351+ }
352+ else {
353+ ctx.lineTo(p.x, p.y);
354+ }
355+ }
356+ ctx.lineWidth = 1;
357+ ctx.strokeStyle = "#888a85";
358+ ctx.stroke();
359+
360+ for (var i=0; i < points.length; i++)
361+ {
362+ var p = points[i];
363+ ctx.beginPath();
364+ ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
365+ ctx.fillStyle = "#2e3436";
366+ ctx.fill();
367+ }
368+ }
369+
370+ ctx.restore();
371+ }
372+
373+ function clearTraces() {
374+ tracePositions = {};
375+ }
376+
377+ function stopRecording() {
378+ recordingTraces = false;
379+ }
380+
381+ function startRecording() {
382+ recordingTraces = true;
383+ }
384+
385+ return {
386+ graphicsUpdate: graphicsUpdate,
387+ canvas: tracePathCanvas,
388+ clearTraces: clearTraces,
389+ stopRecording: stopRecording,
390+ startRecording: startRecording
391+ }
392+}
393+
394+
395+function executeCallbacks(callbacks, reexecute) {
396+ callbacks.forEach(function(timer) {
397+ if (reexecute || !timer.executed) {
398+ var func = timer.func;
399+ func();
400+ }
401+ });
402+}
403+
404+function createCanvas() {
405+ var c = document.createElement('canvas');
406+ c.width = window.innerWidth;
407+ c.height = window.innerHeight;
408+ c.style.position = "absolute";
409+ c.style.top = "0";
410+ c.style.left = "0";
411+ return c;
412+}
413+
414+function assert(bool, msg) {
415+ if (!bool) {
416+ throw "Assertion error: " + msg;
417+ }
418+}
419+
420+function saveNodeValues(allNodes) {
421+ var nodeValues = [];
422+
423+ allNodes.forEach(function(node) {
424+ nodeValues.push({ value: node.value, id: node.id });
425+ });
426+
427+ return nodeValues;
428+};
429+
430+
431+function restoreNodeValues(allNodes, nodeStates) {
432+ assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
433+ for (var i=0; i < allNodes.length; i++) {
434+ var node = allNodes[i];
435+ var state = nodeStates[i];
436+ assert(node.id == state.id, "the nodes moved position");
437+
438+ node.value = state.value;
439+ }
440+}
441+
442+function flattenNodes(nodes) {
443+ var nodesById = {};
444+
445+ function addAllToDict(node) {
446+ nodesById[node.id] = node;
447+ node.kids.forEach(addAllToDict);
448+ }
449+ nodes.forEach(addAllToDict);
450+
451+ var allNodes = Object.keys(nodesById).sort().map(function(key) {
452+ return nodesById[key];
453+ });
454+ return allNodes;
455+};
456+
457+}());
runtime/Init.js+10−1
@@ -84,6 +84,11 @@ function init(display, container, module, ports, moduleToReplace) {
84 return newElm;
84 return newElm;
85 }
85 }
86
86
87+ function dispose() {
88+ removeListeners(listeners);
89+ inputs = [];
90+ }
91+
87 var Module = {};
92 var Module = {};
88 var reportAnyErrors = function() {};
93 var reportAnyErrors = function() {};
89 try {
94 try {
@@ -110,7 +115,11 @@ function init(display, container, module, ports, moduleToReplace) {
110 }
115 }
111
116
112 reportAnyErrors();
117 reportAnyErrors();
118+ return {
119+ swap:swap,
113− return { swap:swap, ports:elm.ports.outgoing };
120+ ports:elm.ports.outgoing,
121+ dispose:dispose
122+ };
114};
123};
115
124
116function checkPorts(elm) {
125function checkPorts(elm) {