2016-07-27 10:51:30 +00:00
|
|
|
"use strict";
|
|
|
|
// Property-based "dataflow"
|
|
|
|
|
|
|
|
var Immutable = require("immutable");
|
|
|
|
var MapSet = require("./mapset.js");
|
|
|
|
|
|
|
|
function Graph() {
|
|
|
|
this.edgesForward = Immutable.Map();
|
|
|
|
this.edgesReverse = Immutable.Map();
|
|
|
|
this.damagedNodes = Immutable.Set();
|
|
|
|
this.currentSubjectId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Graph.prototype.withSubject = function (subjectId, f) {
|
|
|
|
var oldSubjectId = this.currentSubjectId;
|
|
|
|
this.currentSubjectId = subjectId;
|
|
|
|
var result;
|
|
|
|
try {
|
|
|
|
result = f();
|
|
|
|
} catch (e) {
|
|
|
|
this.currentSubjectId = oldSubjectId;
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
this.currentSubjectId = oldSubjectId;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
Graph.prototype.recordObservation = function (objectId) {
|
|
|
|
if (this.currentSubjectId) {
|
|
|
|
this.edgesForward = MapSet.add(this.edgesForward, objectId, this.currentSubjectId);
|
|
|
|
this.edgesReverse = MapSet.add(this.edgesReverse, this.currentSubjectId, objectId);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Graph.prototype.recordDamage = function (objectId) {
|
|
|
|
this.damagedNodes = this.damagedNodes.add(objectId);
|
|
|
|
};
|
|
|
|
|
|
|
|
Graph.prototype.forgetSubject = function (subjectId) {
|
|
|
|
var self = this;
|
|
|
|
var subjectObjects = self.edgesReverse.get(subjectId) || Immutable.Set();
|
|
|
|
self.edgesReverse = self.edgesReverse.remove(subjectId);
|
|
|
|
subjectObjects.forEach(function (objectId) {
|
|
|
|
self.edgesForward = MapSet.remove(self.edgesForward, objectId, subjectId);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
Graph.prototype.repairDamage = function (repairNode) {
|
|
|
|
var self = this;
|
|
|
|
var repairedThisRound = Immutable.Set();
|
|
|
|
while (true) {
|
|
|
|
var workSet = self.damagedNodes;
|
|
|
|
self.damagedNodes = Immutable.Set();
|
|
|
|
|
|
|
|
var alreadyDamaged = workSet.intersect(repairedThisRound);
|
|
|
|
if (!alreadyDamaged.isEmpty()) {
|
|
|
|
console.warn('Cyclic dependencies involving', alreadyDamaged);
|
|
|
|
}
|
|
|
|
|
|
|
|
workSet = workSet.subtract(repairedThisRound);
|
|
|
|
repairedThisRound = repairedThisRound.union(workSet);
|
|
|
|
|
|
|
|
if (workSet.isEmpty()) break;
|
|
|
|
|
|
|
|
workSet.forEach(function (objectId) {
|
|
|
|
var subjects = self.edgesForward.get(objectId) || Immutable.Set();
|
|
|
|
subjects.forEach(function (subjectId) {
|
|
|
|
self.forgetSubject(subjectId);
|
|
|
|
self.withSubject(subjectId, function () {
|
|
|
|
repairNode(subjectId);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-06 23:57:47 +00:00
|
|
|
Graph.prototype.defineObservableProperty = function (obj, prop, value, maybeOptions) {
|
2016-07-27 10:51:30 +00:00
|
|
|
var graph = this;
|
2016-08-06 23:57:47 +00:00
|
|
|
var options = typeof maybeOptions === 'undefined' ? {} : maybeOptions;
|
2016-08-07 19:33:09 +00:00
|
|
|
var objectId = options.objectId || '__' + prop;
|
2016-07-27 10:51:30 +00:00
|
|
|
Object.defineProperty(obj, prop, {
|
|
|
|
configurable: true,
|
|
|
|
enumerable: true,
|
|
|
|
get: function () {
|
|
|
|
graph.recordObservation(objectId);
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
set: function (newValue) {
|
|
|
|
if (!options.noopGuard || !options.noopGuard(value, newValue)) {
|
|
|
|
graph.recordDamage(objectId);
|
|
|
|
value = newValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-08-08 01:40:00 +00:00
|
|
|
graph.recordDamage(objectId);
|
2016-07-27 10:51:30 +00:00
|
|
|
return objectId;
|
|
|
|
};
|
|
|
|
|
2016-08-06 23:57:29 +00:00
|
|
|
Graph.newScope = function (o) {
|
|
|
|
function O() {}
|
|
|
|
O.prototype = o;
|
|
|
|
return new O();
|
|
|
|
};
|
|
|
|
|
2016-07-27 10:51:30 +00:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
module.exports.Graph = Graph;
|