| OLD | NEW |
| (Empty) |
| 1 // Copyright 2012 Google Inc. | |
| 2 // | |
| 3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 // you may not use this file except in compliance with the License. | |
| 5 // You may obtain a copy of the License at | |
| 6 // | |
| 7 // http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 // | |
| 9 // Unless required by applicable law or agreed to in writing, software | |
| 10 // distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 // See the License for the specific language governing permissions and | |
| 13 // limitations under the License. | |
| 14 | |
| 15 (function(global) { | |
| 16 | |
| 17 "use strict"; | |
| 18 | |
| 19 function ArraySet() { | |
| 20 this.entries = []; | |
| 21 } | |
| 22 | |
| 23 ArraySet.prototype = { | |
| 24 add: function(key) { | |
| 25 if (this.entries.indexOf(key) >= 0) | |
| 26 return; | |
| 27 | |
| 28 this.entries.push(key); | |
| 29 }, | |
| 30 | |
| 31 delete: function(key) { | |
| 32 var i = this.entries.indexOf(key); | |
| 33 if (i < 0) | |
| 34 return; | |
| 35 | |
| 36 this.entries.splice(i, 1); | |
| 37 }, | |
| 38 | |
| 39 first: function() { | |
| 40 return this.entries[0]; | |
| 41 }, | |
| 42 | |
| 43 get size() { | |
| 44 return this.entries.length; | |
| 45 } | |
| 46 }; | |
| 47 | |
| 48 function UIDSet() { | |
| 49 this.entries = {}; | |
| 50 this.size = 0; | |
| 51 } | |
| 52 | |
| 53 UIDSet.prototype = { | |
| 54 add: function(key) { | |
| 55 if (this.entries[key.__UID__] !== undefined) | |
| 56 return; | |
| 57 | |
| 58 this.entries[key.__UID__] = key; | |
| 59 this.size++; | |
| 60 }, | |
| 61 | |
| 62 delete: function(key) { | |
| 63 if (this.entries[key.__UID__] === undefined) | |
| 64 return; | |
| 65 | |
| 66 this.entries[key.__UID__] = undefined; | |
| 67 this.size--; | |
| 68 } | |
| 69 }; | |
| 70 | |
| 71 function Heap(scoreFunction, populate) { | |
| 72 this.scoreFunction = scoreFunction; | |
| 73 this.content = populate || []; | |
| 74 if (this.content.length) | |
| 75 this.build(); | |
| 76 } | |
| 77 | |
| 78 Heap.prototype = { | |
| 79 get size() { | |
| 80 return this.content.length; | |
| 81 }, | |
| 82 | |
| 83 build: function() { | |
| 84 var lastNonLeaf = Math.floor(this.content.length / 2) - 1; | |
| 85 for (var i = lastNonLeaf; i >= 0; i--) | |
| 86 this.sinkDown(i); | |
| 87 }, | |
| 88 | |
| 89 push: function(element) { | |
| 90 this.content.push(element); | |
| 91 this.bubbleUp(this.content.length - 1); | |
| 92 }, | |
| 93 | |
| 94 pop: function() { | |
| 95 var result = this.content[0]; | |
| 96 var end = this.content.pop(); | |
| 97 if (this.content.length) { | |
| 98 this.content[0] = end; | |
| 99 this.sinkDown(0); | |
| 100 } | |
| 101 return result; | |
| 102 }, | |
| 103 | |
| 104 delete: function(element) { | |
| 105 var len = this.content.length; | |
| 106 for (var i = 0; i < len; i++) { | |
| 107 if (this.content[i] == element) { | |
| 108 var end = this.content.pop(); | |
| 109 if (i != len - 1) { | |
| 110 this.content[i] = end; | |
| 111 if (this.scoreFunction(end) < this.scoreFunction(node)) this.bubbleU
p(i); | |
| 112 else this.sinkDown(i); | |
| 113 } | |
| 114 return; | |
| 115 } | |
| 116 } | |
| 117 }, | |
| 118 | |
| 119 bubbleUp: function(n) { | |
| 120 var element = this.content[n]; | |
| 121 while (n > 0) { | |
| 122 var parentN = Math.floor((n + 1) / 2) - 1, | |
| 123 parent = this.content[parentN]; | |
| 124 | |
| 125 if (this.scoreFunction(element) <= this.scoreFunction(parent)) | |
| 126 break; | |
| 127 | |
| 128 this.content[parentN] = element; | |
| 129 this.content[n] = parent; | |
| 130 n = parentN; | |
| 131 } | |
| 132 }, | |
| 133 | |
| 134 sinkDown: function(n) { | |
| 135 var length = this.content.length, | |
| 136 element = this.content[n], | |
| 137 elemScore = this.scoreFunction(element); | |
| 138 | |
| 139 do { | |
| 140 var child2N = (n + 1) * 2 | |
| 141 var child1N = child2N - 1; | |
| 142 | |
| 143 var swap = null; | |
| 144 var swapScore = elemScore; | |
| 145 | |
| 146 if (child1N < length) { | |
| 147 var child1 = this.content[child1N], | |
| 148 child1Score = this.scoreFunction(child1); | |
| 149 if (child1Score > elemScore) { | |
| 150 swap = child1N; | |
| 151 swapScore = child1Score; | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 if (child2N < length) { | |
| 156 var child2 = this.content[child2N], | |
| 157 child2Score = this.scoreFunction(child2); | |
| 158 if (child2Score > swapScore) | |
| 159 swap = child2N; | |
| 160 } | |
| 161 | |
| 162 if (swap != null) { | |
| 163 this.content[n] = this.content[swap]; | |
| 164 this.content[swap] = element; | |
| 165 n = swap; | |
| 166 } | |
| 167 } while (swap != null); | |
| 168 } | |
| 169 }; | |
| 170 | |
| 171 function Variable(stayFunc) { | |
| 172 this.stayFunc = stayFunc; | |
| 173 this.methods = new ArraySet; | |
| 174 }; | |
| 175 | |
| 176 Variable.prototype = { | |
| 177 freeMethod: function() { | |
| 178 return this.methods.first(); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 function Method(constraint, variable) { | |
| 183 this.constraint = constraint; | |
| 184 this.variable = variable; | |
| 185 }; | |
| 186 | |
| 187 function Constraint(planner) { | |
| 188 this.planner = planner; | |
| 189 this.methods = []; | |
| 190 }; | |
| 191 | |
| 192 Constraint.prototype = { | |
| 193 addMethod: function(variable) { | |
| 194 var method = new Method(this, variable); | |
| 195 this.methods.push(method); | |
| 196 method.__UID__ = this.planner.methodUIDCounter++; | |
| 197 return method; | |
| 198 }, | |
| 199 | |
| 200 reset: function() { | |
| 201 this.methods.forEach(function(method) { | |
| 202 method.variable.methods.add(method); | |
| 203 }); | |
| 204 }, | |
| 205 | |
| 206 remove: function() { | |
| 207 this.methods.forEach(function(method) { | |
| 208 method.variable.methods.delete(method); | |
| 209 }); | |
| 210 } | |
| 211 }; | |
| 212 | |
| 213 function Planner() { | |
| 214 this.variables = []; | |
| 215 this.constraints = []; | |
| 216 this.variableUIDCounter = 1; | |
| 217 this.methodUIDCounter = 1; | |
| 218 }; | |
| 219 | |
| 220 Planner.prototype = { | |
| 221 addVariable: function(stayFunc) { | |
| 222 var variable = new Variable(stayFunc); | |
| 223 variable.__UID__ = this.variableUIDCounter++; | |
| 224 this.variables.push(variable); | |
| 225 return variable; | |
| 226 }, | |
| 227 | |
| 228 addConstraint: function() { | |
| 229 var constraint = new Constraint(this); | |
| 230 this.constraints.push(constraint); | |
| 231 return constraint; | |
| 232 }, | |
| 233 | |
| 234 removeConstraint: function(constraint) { | |
| 235 var index = this.constraints.indexOf(constraint); | |
| 236 if (index < 0) | |
| 237 return; | |
| 238 | |
| 239 constraint.remove(); | |
| 240 this.constraints.splice(index, 1); | |
| 241 | |
| 242 this.constraints.forEach(function(constraint) { | |
| 243 constraint.reset(); | |
| 244 }); | |
| 245 | |
| 246 this.variables = this.variables.filter(function(variable) { | |
| 247 return variable.methods.size; | |
| 248 }); | |
| 249 }, | |
| 250 | |
| 251 getPlan: function() { | |
| 252 this.variables.forEach(function(variable) { | |
| 253 variable.priority = variable.stayFunc(); | |
| 254 }); | |
| 255 | |
| 256 this.constraints.forEach(function(constraint) { | |
| 257 constraint.reset(); | |
| 258 }); | |
| 259 | |
| 260 var methods = []; | |
| 261 var free = []; | |
| 262 var overconstrained = new UIDSet; | |
| 263 | |
| 264 this.variables.forEach(function(variable) { | |
| 265 var methodCount = variable.methods.size; | |
| 266 | |
| 267 if (methodCount > 1) | |
| 268 overconstrained.add(variable); | |
| 269 else if (methodCount == 1) | |
| 270 free.push(variable); | |
| 271 }); | |
| 272 | |
| 273 free = new Heap(function(variable) { | |
| 274 return variable.priority; | |
| 275 }, free); | |
| 276 | |
| 277 while (free.size) { | |
| 278 var lowest; | |
| 279 do { | |
| 280 lowest = free.pop(); | |
| 281 } while (free.size && !lowest.methods.size); | |
| 282 | |
| 283 if (!lowest.methods.size) | |
| 284 break; | |
| 285 | |
| 286 var method = lowest.freeMethod(); | |
| 287 var constraint = method.constraint; | |
| 288 | |
| 289 constraint.remove(); | |
| 290 constraint.methods.forEach(function(method) { | |
| 291 var variable = method.variable; | |
| 292 if (variable.methods.size == 1) { | |
| 293 overconstrained.delete(variable); | |
| 294 free.push(variable); | |
| 295 } | |
| 296 }); | |
| 297 | |
| 298 methods.push(method); | |
| 299 } | |
| 300 | |
| 301 if (overconstrained.size) | |
| 302 return undefined; | |
| 303 | |
| 304 return methods.reverse(); | |
| 305 } | |
| 306 } | |
| 307 | |
| 308 global.Planner = Planner; | |
| 309 })(this); | |
| OLD | NEW |