From f9e94f7a6e89a02ab178c28cb014afff2b5c4d69 Mon Sep 17 00:00:00 2001 From: Shashi Gowda Date: Thu, 12 Jun 2014 19:16:18 +0530 Subject: [PATCH] Update signal graph nodes in topological order. --- libraries/Native/Show.js | 2 +- libraries/Native/Signal/Keyboard.js | 49 ++++-------- libraries/Native/Signal/Signal.js | 95 ++++++++-------------- runtime/Init.js | 57 ++++++++++++- runtime/Utils.js | 119 ++++++++++++++++++++++++++++ 5 files changed, 223 insertions(+), 99 deletions(-) diff --git a/libraries/Native/Show.js b/libraries/Native/Show.js index 26d5c4792..84e7264de 100644 --- a/libraries/Native/Show.js +++ b/libraries/Native/Show.js @@ -77,7 +77,7 @@ Elm.Native.Show.make = function(elm) { return v.ctor + output; } } - if (type === 'object' && 'recv' in v) return ''; + if (type === 'object' && 'update' in v) return ''; return ""; }; diff --git a/libraries/Native/Signal/Keyboard.js b/libraries/Native/Signal/Keyboard.js index e78d5e241..985c1f54f 100644 --- a/libraries/Native/Signal/Keyboard.js +++ b/libraries/Native/Signal/Keyboard.js @@ -5,14 +5,6 @@ Elm.Native.Keyboard.make = function(elm) { elm.Native.Keyboard = elm.Native.Keyboard || {}; if (elm.Native.Keyboard.values) return elm.Native.Keyboard.values; - // Duplicated from Native.Signal - function send(node, timestep, changed) { - var kids = node.kids; - for (var i = kids.length; i--; ) { - kids[i].recv(timestep, changed, node.id); - } - } - var Signal = Elm.Signal.make(elm); var NList = Elm.Native.List.make(elm); var Utils = Elm.Native.Utils.make(elm); @@ -39,35 +31,28 @@ Elm.Native.Keyboard.make = function(elm) { // Ignore starting values here this.value = NList.Nil this.kids = []; - + var n = args.length; var count = 0; var isChanged = false; - this.recv = function(timestep, changed, parentID) { - ++count; - if (changed) { - // We know this a change must only be one of the following cases - if (parentID === down.id && !(NList.member(down.value)(this.value))) { - isChanged = true; - this.value = NList.Cons(down.value, this.value); - } - if (parentID === up.id) { - isChanged = true; - var notEq = function(kc) { return kc !== up.value }; - this.value = NList.filter(notEq)(this.value); - } - if (parentID === blur.id) { - isChanged = true; - this.value = NList.Nil; - } - } - if (count == n) { - send(this, timestep, isChanged); - isChanged = false; - count = 0; + this.update = function(timestep, parentID) { + // We know this a change must only be one of the following cases + if (parentID === down.id && !(NList.member(down.value)(this.value))) { + this.value = NList.Cons(down.value, this.value); + return true; + } + if (parentID === up.id) { + var notEq = function(kc) { return kc !== up.value }; + this.value = NList.filter(notEq)(this.value); + return true; + } + if (parentID === blur.id) { + this.value = NList.Nil; + return true; } - }; + return false; + } for (var i = n; i--; ) { args[i].kids.push(this); } diff --git a/libraries/Native/Signal/Signal.js b/libraries/Native/Signal/Signal.js index a8f86a6ff..3453b26c2 100644 --- a/libraries/Native/Signal/Signal.js +++ b/libraries/Native/Signal/Signal.js @@ -9,24 +9,11 @@ Elm.Native.Signal.make = function(elm) { var Utils = Elm.Native.Utils.make(elm); var foldr1 = Elm.List.make(elm).foldr1; - function send(node, timestep, changed) { - var kids = node.kids; - for (var i = kids.length; i--; ) { - kids[i].recv(timestep, changed, node.id); - } - } - function Input(base) { this.id = Utils.guid(); this.value = base; this.kids = []; this.defaultNumberOfKids = 0; - this.recv = function(timestep, eid, v) { - var changed = eid === this.id; - if (changed) { this.value = v; } - send(this, timestep, changed); - return changed; - }; elm.inputs.push(this); } @@ -39,15 +26,9 @@ Elm.Native.Signal.make = function(elm) { var count = 0; var isChanged = false; - this.recv = function(timestep, changed, parentID) { - ++count; - if (changed) { isChanged = true; } - if (count == n) { - if (isChanged) { this.value = update(); } - send(this, timestep, isChanged); - isChanged = false; - count = 0; - } + this.update = function(timestep, parentID) { + this.value = update(); + return true; }; for (var i = n; i--; ) { args[i].kids.push(this); } } @@ -90,11 +71,9 @@ Elm.Native.Signal.make = function(elm) { this.value = state; this.kids = []; - this.recv = function(timestep, changed, parentID) { - if (changed) { - this.value = A2( step, input.value, this.value ); - } - send(this, timestep, changed); + this.update = function(timestep, parentID) { + this.value = A2( step, input.value, this.value ); + return true; }; input.kids.push(this); } @@ -107,10 +86,13 @@ Elm.Native.Signal.make = function(elm) { this.id = Utils.guid(); this.value = pred(input.value) ? base : input.value; this.kids = []; - this.recv = function(timestep, changed, parentID) { - var chng = changed && !pred(input.value); - if (chng) { this.value = input.value; } - send(this, timestep, chng); + this.update = function(timestep, parentID) { + if (!pred(input.value)) { + this.value = input.value; + return true; + } else { + return false; + } }; input.kids.push(this); } @@ -119,11 +101,14 @@ Elm.Native.Signal.make = function(elm) { this.id = Utils.guid(); this.value = input.value; this.kids = []; - this.recv = function(timestep, changed, parentID) { - var chng = changed && !Utils.eq(this.value,input.value); - if (chng) { this.value = input.value; } - send(this, timestep, chng); - }; + this.update = function(timestep, parentID) { + if (!Utils.eq(this.value,input.value)) { + this.value = input.value; + return true; + } else { + return false; + } + } input.kids.push(this); } @@ -140,18 +125,11 @@ Elm.Native.Signal.make = function(elm) { var count = 0; var isChanged = false; - this.recv = function(timestep, changed, parentID) { - if (parentID === s1.id) isChanged = changed; - ++count; - if (count == 2) { - if (isChanged) { this.value = s2.value; } - send(this, timestep, isChanged); - count = 0; - isChanged = false; - } - }; + this.update = function(timestep, parentID) { + this.value = s2.value; + return true; + } s1.kids.push(this); - s2.kids.push(this); } function sampleOn(s1,s2) { return new SampleOn(s1,s2); } @@ -171,26 +149,19 @@ Elm.Native.Signal.make = function(elm) { this.id = Utils.guid(); this.value = s1.value; this.kids = []; + this.parentRank = {}; + this.parentRank[s1.id] = 1; + this.parentRank[s2.id] = 2; var next = null; var count = 0; var isChanged = false; - this.recv = function(timestep, changed, parentID) { - ++count; - if (changed) { - isChanged = true; - if (parentID == s2.id && next === null) { next = s2.value; } - if (parentID == s1.id) { next = s1.value; } - } - - if (count == 2) { - if (isChanged) { this.value = next; next = null; } - send(this, timestep, isChanged); - isChanged = false; - count = 0; - } - }; + this.update = function(timestep, parentID) { + if (s1.id == parentID) this.value = s1.value; + if (s2.id == parentID) this.value = s2.value; + return true; + } s1.kids.push(this); s2.kids.push(this); } diff --git a/runtime/Init.js b/runtime/Init.js index c002828f0..77db03816 100644 --- a/runtime/Init.js +++ b/runtime/Init.js @@ -32,6 +32,46 @@ function init(display, container, module, ports, moduleToReplace) { var inputs = []; var updateInProgress = false; + function propagate(startNode, timestep) { + var parentMap = {}; + var heap = new ElmRuntime.BinaryHeap(function (i) { return i.node.id; }); + for (var i = 0, l = startNode.kids.length; i < l; i++) { + var node = startNode.kids[i]; + if (node.parentRank) { + parentMap[node.id] = startNode.id; + } + heap.push({node: node, parent: startNode.id}); + } + + var prev = null; + while (heap.size() > 0) { + var next = heap.pop(), node = next.node; + if (node.id == prev) { + continue; + } + var goOn = false; + if (node.parentRank) { + if (parentMap[next.node.id]) { + goOn = node.update(timestep, parentMap[node.id]); + } + } else { + goOn = node.update(timestep, next.parent); + } + if (goOn) { + for (var i = 0, l = node.kids.length; i < l; i++) { + var child = node.kids[i]; + if (child.parentRank) { + if (child.parentRank[parentMap[child.id]] > child.parentRank[node.id]) { + parentMap[child.id] = node.id; + } + } + heap.push({node: child, parent: node.id}); + } + } + prev = node.id; + } + } + function notify(id, v) { if (updateInProgress) { throw new Error( @@ -40,10 +80,17 @@ function init(display, container, module, ports, moduleToReplace) { 'Definitely report this to \n'); } updateInProgress = true; - var timestep = Date.now(); + var timestep = Date.now(), input = null; + for (var i = inputs.length; i--; ) { - inputs[i].recv(timestep, id, v); + if (inputs[i].id == id) { + input = inputs[i]; + break; + } } + + input.value = v; + propagate(input, timestep); updateInProgress = false; } @@ -105,7 +152,9 @@ function init(display, container, module, ports, moduleToReplace) { // rerender scene if graphics are enabled. if (typeof graphicsNode !== 'undefined') { - graphicsNode.recv(0, true, 0); + if (graphicsNode.update(0, 0)) { + propagate(graphicsNode, 0); + } } } @@ -210,7 +259,7 @@ function initGraphics(elm, Module) { // make sure the signal graph is actually a signal & extract the visual model var Signal = Elm.Signal.make(elm); - if (!('recv' in signalGraph)) { + if (!('update' in signalGraph)) { signalGraph = Signal.constant(signalGraph); } var currentScene = signalGraph.value; diff --git a/runtime/Utils.js b/runtime/Utils.js index e95fb1982..ed5caf0a8 100644 --- a/runtime/Utils.js +++ b/runtime/Utils.js @@ -51,4 +51,123 @@ if (win.requestAnimationFrame && win.cancelAnimationFrame) { ElmRuntime.draw = function(callback) { callback(); }; } +// Binary Heap implementation from +// http://eloquentjavascript.net/appendix2.html +(function (ElmRuntime) { + +function BinaryHeap(scoreFunction){ + this.content = []; + this.scoreFunction = scoreFunction; +} + +BinaryHeap.prototype = { + push: function(element) { + // Add the new element to the end of the array. + this.content.push(element); + // Allow it to bubble up. + this.bubbleUp(this.content.length - 1); + }, + + pop: function() { + // Store the first element so we can return it later. + var result = this.content[0]; + // Get the element at the end of the array. + var end = this.content.pop(); + // If there are any elements left, put the end element at the + // start, and let it sink down. + if (this.content.length > 0) { + this.content[0] = end; + this.sinkDown(0); + } + return result; + }, + + remove: function(node) { + var length = this.content.length; + // To remove a value, we must search through the array to find + // it. + for (var i = 0; i < length; i++) { + if (this.content[i] != node) continue; + // When it is found, the process seen in 'pop' is repeated + // to fill up the hole. + var end = this.content.pop(); + // If the element we popped was the one we needed to remove, + // we're done. + if (i == length - 1) break; + // Otherwise, we replace the removed element with the popped + // one, and allow it to float up or sink down as appropriate. + this.content[i] = end; + this.bubbleUp(i); + this.sinkDown(i); + break; + } + }, + + size: function() { + return this.content.length; + }, + + bubbleUp: function(n) { + // Fetch the element that has to be moved. + var element = this.content[n], score = this.scoreFunction(element); + // When at 0, an element can not go up any further. + while (n > 0) { + // Compute the parent element's index, and fetch it. + var parentN = Math.floor((n + 1) / 2) - 1, + parent = this.content[parentN]; + // If the parent has a lesser score, things are in order and we + // are done. + if (score >= this.scoreFunction(parent)) + break; + + // Otherwise, swap the parent with the current element and + // continue. + this.content[parentN] = element; + this.content[n] = parent; + n = parentN; + } + }, + + sinkDown: function(n) { + // Look up the target element and its score. + var length = this.content.length, + element = this.content[n], + elemScore = this.scoreFunction(element); + + while(true) { + // Compute the indices of the child elements. + var child2N = (n + 1) * 2, child1N = child2N - 1; + // This is used to store the new position of the element, + // if any. + var swap = null; + // If the first child exists (is inside the array)... + if (child1N < length) { + // Look it up and compute its score. + var child1 = this.content[child1N], + child1Score = this.scoreFunction(child1); + // If the score is less than our element's, we need to swap. + if (child1Score < elemScore) + swap = child1N; + } + // Do the same checks for the other child. + if (child2N < length) { + var child2 = this.content[child2N], + child2Score = this.scoreFunction(child2); + if (child2Score < (swap == null ? elemScore : child1Score)) + swap = child2N; + } + + // No need to swap further, we are done. + if (swap == null) break; + + // Otherwise, swap and continue. + this.content[n] = this.content[swap]; + this.content[swap] = element; + n = swap; + } + } +} +ElmRuntime.BinaryHeap = BinaryHeap; +})(ElmRuntime); + }());