2014-12-14 17:00:02 +00:00

310 lines
7.1 KiB
JavaScript

// Copyright 2012 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(global) {
"use strict";
function ArraySet() {
this.entries = [];
}
ArraySet.prototype = {
add: function(key) {
if (this.entries.indexOf(key) >= 0)
return;
this.entries.push(key);
},
delete: function(key) {
var i = this.entries.indexOf(key);
if (i < 0)
return;
this.entries.splice(i, 1);
},
first: function() {
return this.entries[0];
},
get size() {
return this.entries.length;
}
};
function UIDSet() {
this.entries = {};
this.size = 0;
}
UIDSet.prototype = {
add: function(key) {
if (this.entries[key.__UID__] !== undefined)
return;
this.entries[key.__UID__] = key;
this.size++;
},
delete: function(key) {
if (this.entries[key.__UID__] === undefined)
return;
this.entries[key.__UID__] = undefined;
this.size--;
}
};
function Heap(scoreFunction, populate) {
this.scoreFunction = scoreFunction;
this.content = populate || [];
if (this.content.length)
this.build();
}
Heap.prototype = {
get size() {
return this.content.length;
},
build: function() {
var lastNonLeaf = Math.floor(this.content.length / 2) - 1;
for (var i = lastNonLeaf; i >= 0; i--)
this.sinkDown(i);
},
push: function(element) {
this.content.push(element);
this.bubbleUp(this.content.length - 1);
},
pop: function() {
var result = this.content[0];
var end = this.content.pop();
if (this.content.length) {
this.content[0] = end;
this.sinkDown(0);
}
return result;
},
delete: function(element) {
var len = this.content.length;
for (var i = 0; i < len; i++) {
if (this.content[i] == element) {
var end = this.content.pop();
if (i != len - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node)) this.bubbleUp(i);
else this.sinkDown(i);
}
return;
}
}
},
bubbleUp: function(n) {
var element = this.content[n];
while (n > 0) {
var parentN = Math.floor((n + 1) / 2) - 1,
parent = this.content[parentN];
if (this.scoreFunction(element) <= this.scoreFunction(parent))
break;
this.content[parentN] = element;
this.content[n] = parent;
n = parentN;
}
},
sinkDown: function(n) {
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
do {
var child2N = (n + 1) * 2
var child1N = child2N - 1;
var swap = null;
var swapScore = elemScore;
if (child1N < length) {
var child1 = this.content[child1N],
child1Score = this.scoreFunction(child1);
if (child1Score > elemScore) {
swap = child1N;
swapScore = child1Score;
}
}
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score > swapScore)
swap = child2N;
}
if (swap != null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
} while (swap != null);
}
};
function Variable(stayFunc) {
this.stayFunc = stayFunc;
this.methods = new ArraySet;
};
Variable.prototype = {
freeMethod: function() {
return this.methods.first();
}
}
function Method(constraint, variable) {
this.constraint = constraint;
this.variable = variable;
};
function Constraint(planner) {
this.planner = planner;
this.methods = [];
};
Constraint.prototype = {
addMethod: function(variable) {
var method = new Method(this, variable);
this.methods.push(method);
method.__UID__ = this.planner.methodUIDCounter++;
return method;
},
reset: function() {
this.methods.forEach(function(method) {
method.variable.methods.add(method);
});
},
remove: function() {
this.methods.forEach(function(method) {
method.variable.methods.delete(method);
});
}
};
function Planner() {
this.variables = [];
this.constraints = [];
this.variableUIDCounter = 1;
this.methodUIDCounter = 1;
};
Planner.prototype = {
addVariable: function(stayFunc) {
var variable = new Variable(stayFunc);
variable.__UID__ = this.variableUIDCounter++;
this.variables.push(variable);
return variable;
},
addConstraint: function() {
var constraint = new Constraint(this);
this.constraints.push(constraint);
return constraint;
},
removeConstraint: function(constraint) {
var index = this.constraints.indexOf(constraint);
if (index < 0)
return;
constraint.remove();
this.constraints.splice(index, 1);
this.constraints.forEach(function(constraint) {
constraint.reset();
});
this.variables = this.variables.filter(function(variable) {
return variable.methods.size;
});
},
getPlan: function() {
this.variables.forEach(function(variable) {
variable.priority = variable.stayFunc();
});
this.constraints.forEach(function(constraint) {
constraint.reset();
});
var methods = [];
var free = [];
var overconstrained = new UIDSet;
this.variables.forEach(function(variable) {
var methodCount = variable.methods.size;
if (methodCount > 1)
overconstrained.add(variable);
else if (methodCount == 1)
free.push(variable);
});
free = new Heap(function(variable) {
return variable.priority;
}, free);
while (free.size) {
var lowest;
do {
lowest = free.pop();
} while (free.size && !lowest.methods.size);
if (!lowest.methods.size)
break;
var method = lowest.freeMethod();
var constraint = method.constraint;
constraint.remove();
constraint.methods.forEach(function(method) {
var variable = method.variable;
if (variable.methods.size == 1) {
overconstrained.delete(variable);
free.push(variable);
}
});
methods.push(method);
}
if (overconstrained.size)
return undefined;
return methods.reverse();
}
}
global.Planner = Planner;
})(this);