cleaning up (1)

This commit is contained in:
DadaMonad
2014-12-14 17:00:02 +00:00
parent 9b582fc795
commit 7696864841
50 changed files with 9302 additions and 6 deletions

View File

@@ -0,0 +1,63 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script src="../change_summary.js"></script>
<script src="constrain.js"></script>
<script src="persist.js"></script>
<script src="mdv-object-observe/include.js"></script>
<h1>Circles</h1>
<div data-controller="CircleController">
<template iterate>
<div style="border: 1px solid black; margin: 8px">
<table>
<tr><td>radius:</td><td><input type="number" value="{{ radius }}"></td></tr>
<tr><td>area:</td><td><input type="number" value="{{ area }}"></td></tr>
<tr><td>circumference:</td><td><input type="number" value="{{ circumference }}"></td></tr>
</table>
<button data-action="click:delete">Delete</button>
</div>
</template>
<button data-action="click:add">New</button>
</div>
<script>
function Circle(radius) {
// circumference = 2*PI*radius
constrain(this, {
radius: function() { return this.circumference / (2*Math.PI); },
circumference: function() { return 2 * Math.PI * this.radius; }
});
// area = PI*r^2'
constrain(this, {
area: function() { return Math.PI * Math.pow(this.radius, 2); },
radius: function() { return Math.sqrt(this.area / Math.PI); }
});
if (radius)
this.radius = radius;
}
function CircleController(elm) {
this.circles = elm.model = persistDB.retrieve(Circle);
}
CircleController.prototype = {
delete: function(circle) {
var index = this.circles.indexOf(circle);
this.circles.splice(index, 1);
},
add: function() {
this.circles.push(new Circle());
}
}
</script>

View File

@@ -0,0 +1,29 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<h1>The world's simplest constraint-solver</h1>
<script src="constrain.js"></script>
<script>
function Circle(radius) {
// circumference = 2*PI*radius
constrain(this, {
radius: function() { return this.circumference / (2*Math.PI); },
circumference: function() { return 2 * Math.PI * this.radius; }
});
// area = PI*r^2'
constrain(this, {
area: function() { return Math.PI * Math.pow(this.radius, 2); },
radius: function() { return Math.sqrt(this.area / Math.PI); }
});
if (radius)
this.radius = radius;
}
</script>

View File

@@ -0,0 +1,403 @@
/*
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function(global) {
/* This is a very simple version of the QuickPlan algorithm for solving
* mutli-variable contraints. (http://www.cs.utk.edu/~bvz/quickplan.html)
* The implementation varies from the standard described approach in a few ways:
*
* -There is no notion of constraint heirarchy. Here, all constraints are
* considered REQUIRED.
*
* -There is no "improvement" phase where rejected constraints are added back
* in an attempt to find a "better solution"
*
* -In place of the above two, a heuristic is used to pick the "weakest"
* free constraint to remove. A function, "stayFunc" is passed to the
* Variable class and is expected to return a priority value for the variable
* 0 being highest and 1, 2, 3, etc... being lower.
*
* I suspect these variations result in there being no guarentee of choosing the
* optimal solution, but it does seem to work well for the examples I've tested.
* Note also that the DeltaBlue planner can be used in a similar pattern,
* but it only supports single variable assignment.
*
* Note also that this is hacky and thrown together. Don't expect it to work
* much at all =-).
*/
function Map() {
this.map_ = new global.Map;
this.keys_ = [];
}
Map.prototype = {
get: function(key) {
return this.map_.get(key);
},
set: function(key, value) {
if (!this.map_.has(key))
this.keys_.push(key);
return this.map_.set(key, value);
},
has: function(key) {
return this.map_.has(key);
},
delete: function(key) {
this.keys_.splice(this.keys_.indexOf(key), 1);
this.map_.delete(key);
},
keys: function() {
return this.keys_.slice();
}
}
function Variable(property, stayFunc) {
this.property = property;
this.stayFunc = stayFunc || function() {
//console.log("Warning: using default stay func");
return 0;
};
this.methods = [];
};
Variable.prototype = {
addMethod: function(method) {
this.methods.push(method);
},
removeMethod: function(method) {
this.methods.splice(this.methods.indexOf(method), 1);
},
isFree: function() {
return this.methods.length <= 1;
},
get stayPriority() {
return this.stayFunc(this.property);
}
}
function Method(opts) {
opts = opts || {};
this.name = opts.name || 'function() { ... }';
this.outputs = opts.outputs || [];
this.f = opts.f || function() {
console.log('Warning: using default execution function');
};
};
Method.prototype = {
planned_: false,
variables_: [],
set planned(planned) {
this.planned_ = planned;
if (this.planned_) {
if (this.variables_) {
// Remove this method from all variables.
this.variables_.forEach(function(variable) {
variable.removeMethod(this);
}, this);
}
this.variables_ = null;
} else {
this.variables_ = null;
// Get & add this method to all variables.
if (this.constraint && this.constraint.planner) {
this.variables_ = this.outputs.map(function(output) {
var variable = this.constraint.planner.getVariable(output);
variable.addMethod(this);
return variable;
}, this);
}
}
},
get planned() {
return this.planned_;
},
isFree: function() {
// Return true only if all variables are free.
var variables = this.variables_;
for (var i = variables.length - 1; i >= 0; i--) {
if (!variables[i].isFree())
return false;
}
return true;
},
weakerOf: function(other) {
if (!other) {
return this;
}
// Prefer a method that assigns to fewer variables.
if (this.variables_.length != other.variables_.length) {
return this.variables_.length < other.variables_.length ? this : other;
}
// Note: A weaker stay priority is a higher number.
return this.getStayPriority() >= other.getStayPriority() ? this : other;
},
getStayPriority: function() {
// This returns the strongest (lowest) stay priority of this method's
// output variables.
return retval = this.variables_.reduce(function(min, variable) {
return Math.min(min, variable.stayPriority);
}, Infinity);
},
execute: function() {
console.log(JSON.stringify(this.outputs) + ' <= ' + this.name);
this.f();
}
};
function Constraint(methods, when) {
this.methods = methods;
this.when = when;
};
Constraint.prototype = {
executionMethod_: null,
set executionMethod(executionMethod) {
this.executionMethod_ = executionMethod;
var planned = !!this.executionMethod_;
this.methods.forEach(function(method) {
method.constraint = this;
method.planned = planned;
}, this);
},
get executionMethod() {
return this.executionMethod_;
},
getWeakestFreeMethod: function() {
var methods = this.methods;
var weakest = null;
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (method.isFree())
weakest = method.weakerOf(weakest);
}
return weakest;
},
execute: function() {
this.executionMethod.execute();
}
};
function Planner(object) {
this.object = object;
this.properties = {};
this.priority = []
var self = this;
this.stayFunc = function(property) {
if (self.object[property] === undefined)
return Infinity;
var index = self.priority.indexOf(property);
return index >= 0 ? index : Infinity;
}
Object.observe(this.object, internalCallback);
};
Planner.prototype = {
plan_: null,
deliverChanged: function(changeRecords) {
var needsResolve = false;
changeRecords.forEach(function(change) {
var property = change.name;
if (!(property in this.properties))
return;
var index = this.priority.indexOf(property);
if (index >= 0)
this.priority.splice(this.priority.indexOf(property), 1);
this.priority.unshift(property);
needsResolve = true;
}, this);
if (!needsResolve)
return;
console.log('Resolving: ' + Object.getPrototypeOf(changeRecords[0].object).constructor.name);
Object.unobserve(this.object, internalCallback);
this.execute();
console.log('...Done: ' + JSON.stringify(this.object));
Object.observe(this.object, internalCallback);
},
addConstraint: function(methods) {
methods.forEach(function(method) {
method.outputs.forEach(function(output) {
this.properties[output] = true;
}, this);
}, this);
var constraint = new Constraint(methods);
this.constraints = this.constraints || [];
if (this.constraints.indexOf(constraint) < 0) {
this.plan_ = null;
this.constraints.push(constraint);
}
return constraint;
},
removeConstraint: function(constraint) {
var index = this.constraints.indexOf(constraint);
if (index >= 0) {
this.plan_ = null;
var removed = this.constraints.splice(index, 1)[0];
}
return constraint;
},
getVariable: function(property) {
var index = this.properties_.indexOf(property);
if (index >= 0) {
return this.variables_[index];
}
this.properties_.push(property);
var variable = new Variable(property, this.stayFunc);
this.variables_.push(variable);
return variable;
},
get plan() {
if (this.plan_) {
return this.plan_;
}
this.plan_ = [];
this.properties_ = [];
this.variables_ = [];
var unplanned = this.constraints.filter(function(constraint) {
// Note: setting executionMethod must take place after setting planner.
if (constraint.when && !constraint.when()) {
// Conditional and currenty disabled => not in use.
constraint.planner = null;
constraint.executionMethod = null;
return false;
} else {
// In use.
constraint.planner = this;
constraint.executionMethod = null;
return true;
}
}, this);
while (unplanned.length > 0) {
var method = this.chooseNextMethod(unplanned);
if (!method) {
throw "Cycle detected";
}
var nextConstraint = method.constraint;
unplanned.splice(unplanned.indexOf(nextConstraint), 1);
this.plan_.unshift(nextConstraint);
nextConstraint.executionMethod = method;
}
return this.plan_;
},
chooseNextMethod: function(constraints) {
var weakest = null;
for (var i = 0; i < constraints.length; i++) {
var current = constraints[i].getWeakestFreeMethod();
weakest = current ? current.weakerOf(weakest) : weakest;
}
return weakest;
},
run: function() {
this.execute();
},
execute: function() {
this.plan_ = null;
this.executing = true;
this.plan.forEach(function(constraint) {
constraint.execute();
});
this.executing = false;
}
}
var planners = new WeakMap;
function internalCallback(changeRecords) {
var changeMap = new Map;
changeRecords.forEach(function(change) {
if (!planners.has(change.object))
return;
var changes = changeMap.get(change.object);
if (!changes) {
changeMap.set(change.object, [change]);
return;
}
changes.push(change);
});
changeMap.keys().forEach(function(object) {
planners.get(object).deliverChanged(changeMap.get(object));
});
}
// Register callback to assign delivery order.
var register = {};
Object.observe(register, internalCallback);
Object.unobserve(register, internalCallback);
global.constrain = function(obj, methodFunctions) {
var planner = planners.get(obj);
if (!planner) {
planner = new Planner(obj);
planners.set(obj, planner);
}
planner.addConstraint(Object.keys(methodFunctions).map(function(property) {
var func = methodFunctions[property];
return new Method({
name: func.toString(),
outputs: [ property ],
f: function() { obj[property] = func.apply(obj); }
});
}));
}
})(this);

View File

@@ -0,0 +1,13 @@
<!--
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<h1>The worlds simplest persistence system</h1>
<script src="../change_summary.js"></script>
<script src="persist.js"></script>

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/
(function(global) {
function Set() {
this.set_ = new global.Set;
this.keys_ = [];
}
Set.prototype = {
add: function(key) {
if (!this.set_.has(key))
this.keys_.push(key);
return this.set_.add(key);
},
has: function(key) {
return this.set_.has(key);
},
delete: function(key) {
this.keys_.splice(this.keys_.indexOf(key), 1);
this.set_.delete(key);
},
keys: function() {
return this.keys_.slice();
}
}
var dbName = 'PersistObserved';
var version;
var db;
var storeNames = {};
function constructorName(objOrFunction) {
if (typeof objOrFunction == 'function')
return objOrFunction.name;
else
return Object.getPrototypeOf(objOrFunction).constructor.name;
}
function getKeyPath(constructor) {
return constructor.keyPath || 'id';
}
function onerror(e) {
console.log('Error: ' + e);
};
var postOpen = [];
function openDB() {
var request = webkitIndexedDB.open(dbName);
request.onerror = onerror;
request.onsuccess = function(e) {
db = e.target.result;
version = db.version || 0;
for (var i = 0; i < db.objectStoreNames.length; i++)
storeNames[db.objectStoreNames.item(i)] = true;
postOpen.forEach(function(action) {
action();
});
};
}
function handleChanged(changeRecords) {
changeRecords.forEach(function(change) {
persist(change.object);
});
}
var observer = new ChangeSummary(function(summaries) {
storeChanges = {};
function getChange(obj) {
var change = storeChanges[constructorName(obj)];
if (change)
return change;
change = {
keyPath: getKeyPath(obj),
needsAdd: new Set,
needsSave: new Set,
needsDelete: new Set
};
storeChanges[storeName] = change;
return change;
}
summaries.forEach(function(summary) {
if (!Array.isArray(summary.object)) {
getChange(summary.object).needsSave.add(summary.object);
return;
}
summary.arraySplices.forEach(function(splice) {
for (var i = 0; i < splice.removed.length; i++) {
var obj = splice.removed[i];
var change = getChange(obj);
if (change.needsAdd.has(obj))
change.needsAdd.delete(obj);
else
change.needsDelete.add(obj);
}
for (var i = splice.index; i < splice.index + splice.addedCount; i++) {
var obj = summary.object[i];
var change = getChange(obj);
if (change.needsDelete.has(obj))
change.needsDelete.delete(obj);
else
change.needsAdd.add(obj);
}
});
});
var storeNames = Object.keys(storeChanges);
console.log('Persisting: ' + JSON.stringify(storeNames));
var trans = db.transaction(storeNames, "readwrite");
trans.onerror = onerror;
trans.oncomplete = function() {
console.log('...complete');
}
storeNames.forEach(function(storeName) {
var change = storeChanges[storeName];
var store = trans.objectStore(storeName);
change.needsDelete.keys().forEach(function(obj) {
var request = store.delete(obj[change.keyPath]);
request.onerror = onerror;
request.onsuccess = function(e) {
console.log(' deleted: ' + JSON.stringify(obj));
delete obj[keyPath];
observer.unobserve(obj);
if (change.needsSave.has(obj))
change.needsSave.delete(obj);
};
});
change.needsSave.keys().forEach(function(obj) {
var request = store.put(obj);
request.onerror = onerror;
request.onsuccess = function(e) {
console.log(' saved: ' + JSON.stringify(obj));
};
});
change.needsAdd.keys().forEach(function(obj) {
obj[keyPath] = ++maxIds[storeName];
var request = store.put(obj);
request.onerror = onerror;
request.onsuccess = function(e) {
console.log(' created: ' + JSON.stringify(obj));
observer.observe(obj);
};
});
});
});
var maxIds = {};
global.persistDB = {};
global.persistDB.retrieve = function(constructor) {
var results = [];
var instance = new constructor();
keyPath = constructor.keyPath || 'id';
storeName = constructor.name;
maxIds[storeName] = maxIds[storeName] || 0;
function doRetrieve() {
console.log("Retrieving: " + storeName);
var trans = db.transaction([storeName]);
var store = trans.objectStore(storeName);
var keyRange = webkitIDBKeyRange.lowerBound(0);
var request = store.openCursor(keyRange);
request.onerror = onerror;
request.onsuccess = function(e) {
var result = e.target.result;
if (!!result == false) {
observer.observePropertySet(results);
console.log('...complete');
return;
}
var object = result.value;
maxIds[storeName] = Math.max(maxIds[storeName], object[keyPath]);
object.__proto__ = instance;
constructor.apply(object);
results.push(object);
observer.observe(object);
console.log(' => ' + JSON.stringify(object));
result.continue();
};
}
function createStore() {
console.log('Creating store: ' + storeName);
version++;
var request = db.setVersion(version);
request.onerror = onerror;
request.onsuccess = function(e) {
var store = db.createObjectStore(storeName, { keyPath: keyPath });
storeNames[storeName] = true;
e.target.transaction.oncomplete = doRetrieve;
};
}
var action = function() {
if (storeName in storeNames)
doRetrieve()
else
createStore();
}
if (db)
action();
else
postOpen.push(action);
return results;
};
openDB();
})(this);