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 |