Merge branch 'deps-radical' into devel

This commit is contained in:
David Greenspan
2013-03-21 14:12:49 -07:00
13 changed files with 218 additions and 247 deletions

View File

@@ -2399,7 +2399,7 @@ whether they are being accessed reactively or not.
It's very rare to need to access `currentComputation` directly. The
current computation is used implicitly by
[`Deps.active`](#deps_active) (which tests whether there is one),
[`Deps.depend`](#deps_depend) (which registers that it depends on a
[`dependency.depend()`](#dependency_depend) (which registers that it depends on a
dependency), and [`Deps.onInvalidate`](#deps_oninvalidate) (which
registers a callback with it).
@@ -2417,15 +2417,6 @@ computations that need rerunning. This means that if an `afterFlush`
function invalidates a computation, that computation will be rerun
before any other `afterFlush` functions are called.
{{> api_box deps_depend }}
`Deps.depend` is used in reactive data source implementations as the
primary way to record the fact that a Dependency is being accessed from
some computation. If there is a current computation, it becomes a
dependent of the Dependency, while the Dependency becomes a dependency of
the computation. Calls
[`dependency.addDependent()`](#dependency_adddependent).
<h2 id="deps_computation"><span>Deps.Computation</span></h2>
A Computation object represents code that is repeatedly rerun in
@@ -2539,10 +2530,10 @@ accompanied by a Dependency object that tracks the computations that depend
on it, as in this example:
var weather = "sunny";
var weatherDeps = new Deps.Dependency;
var weatherDep = new Deps.Dependency;
var getWeather = function () {
Deps.depend(weatherDeps);
weatherDep.depend()
return weather;
};
@@ -2550,12 +2541,12 @@ on it, as in this example:
weather = w;
// (could add logic here to only call changed()
// if the new value is different from the old)
weatherDeps.changed();
weatherDep.changed();
};
This example implements a weather data source with a simple getter and
setter. The getter records that the current computation depends on
the `weatherDeps` dependency using `Deps.depend`, while the setter
the `weatherDep` dependency using `depend()`, while the setter
signals the dependency to invalidate all dependent computations by
calling `changed()`.
@@ -2571,7 +2562,7 @@ current computation is made to depend on an internal Dependency that
does not change if the weather goes from, say, "rainy" to "cloudy".
Conceptually, the only two things a Dependency can do are gain a
dependent (typically via `Deps.depend`) and change.
dependent and change.
A Dependency's dependent computations are always valid (they have
`invalidated === false`). If a dependent is invalidated at any time,
@@ -2580,14 +2571,10 @@ removed.
{{> api_box dependency_changed }}
{{> api_box dependency_adddependent }}
{{> api_box dependency_depend }}
In almost all cases, you want to declare a dependency of the current
computation and should use `Deps.depend(dependency)` instead,
which calls `dependency.addDependent(null)`.
Computations added as dependents of the Dependency will be removed
immediately if they are ever invalidated or stopped.
`dep.depend()` is used in reactive data source implementations to record
the fact that `dep` is being accessed from the current computation.
{{> api_box dependency_hasdependents }}

View File

@@ -812,18 +812,6 @@ Template.api.deps_afterflush = {
]
};
Template.api.deps_depend = {
id: "deps_depend",
name: "Deps.depend(dependency)",
locus: "Client",
descr: ["Declares that the current computation depends on `dependency`. The current computation, if there is one, becomes a dependent of `dependency`, meaning it will be invalidated and rerun the next time `dependency` changes.", "Returns `true` if this results in `dependency` gaining a new dependent (or `false` if this relationship already exists or there is no current computation)."],
args: [
{name: "dependency",
type: "Deps.Dependency",
descr: "The dependency for this computation to depend on."}
]
};
Template.api.computation_stop = {
id: "computation_stop",
name: "<em>computation</em>.stop()",
@@ -878,15 +866,15 @@ Template.api.dependency_changed = {
descr: ["Invalidate all dependent computations immediately and remove them as dependents."]
};
Template.api.dependency_adddependent = {
id: "dependency_adddependent",
name: "<em>dependency</em>.addDependent(computation)",
Template.api.dependency_depend = {
id: "dependency_depend",
name: "<em>dependency</em>.depend([fromComputation])",
locus: "Client",
descr: ["Adds `computation` as a dependent of this Dependency, recording the fact that the computation depends on this Dependency.", "Returns true if the computation was not already a dependent of this Dependency."],
descr: ["Declares that the current computation (or `fromComputation` if given) depends on `dependency`. The computation will be invalidated the next time `dependency` changes.", "If there is no current computation and `depend()` is called with no arguments, it does nothing and returns false.", "Returns true if the computation is a new dependent of `dependency` rather than an existing one."],
args: [
{name: "computation",
{name: "fromComputation",
type: "Deps.Computation",
descr: "The computation to add, or `null` to use the current computation (in which case there must be a current computation)."}
descr: "An optional computation declared to depend on `dependency` instead of the current computation."}
]
};

View File

@@ -251,7 +251,6 @@ var toc = [
"Deps.currentComputation",
"Deps.onInvalidate",
"Deps.afterFlush",
"Deps.depend",
"Deps.Computation", [
{instance: "computation", name: "stop", id: "computation_stop"},
{instance: "computation", name: "invalidate", id: "computation_invalidate"},
@@ -262,7 +261,7 @@ var toc = [
],
"Deps.Dependency", [
{instance: "dependency", name: "changed", id: "dependency_changed"},
{instance: "dependency", name: "addDependent", id: "dependency_adddependent"},
{instance: "dependency", name: "depend", id: "dependency_depend"},
{instance: "dependency", name: "hasDependents", id: "dependency_hasdependents"}
]
],

View File

@@ -15,7 +15,7 @@ Accounts._setLoggingIn = function (x) {
}
};
Meteor.loggingIn = function () {
Deps.depend(loggingInDeps);
loggingInDeps.depend();
return loggingIn;
};
@@ -194,6 +194,6 @@ Accounts.loginServicesConfigured = function () {
return true;
// not yet complete, save the context for invalidation once we are.
Deps.depend(loginServicesConfiguredDeps);
loginServicesConfiguredDeps.depend();
return false;
};

View File

@@ -1,199 +1,198 @@
Deps = {};
Deps.active = false;
Deps.currentComputation = null;
Deps = {};
Deps.active = false;
Deps.currentComputation = null;
var setCurrentComputation = function (c) {
Deps.currentComputation = c;
Deps.active = !! c;
};
var setCurrentComputation = function (c) {
Deps.currentComputation = c;
Deps.active = !! c;
};
var _debugFunc = function () {
// lazy evaluation because `Meteor` does not exist right away
return (typeof Meteor !== "undefined" ? Meteor._debug :
((typeof console !== "undefined") && console.log ? console.log :
function () {}));
};
var _debugFunc = function () {
// lazy evaluation because `Meteor` does not exist right away
return (typeof Meteor !== "undefined" ? Meteor._debug :
((typeof console !== "undefined") && console.log ? console.log :
function () {}));
};
var nextId = 1;
// computations whose callbacks we should call at flush time
var pendingComputations = [];
// `true` if a Deps.flush is scheduled, or if we are in Deps.flush now
var willFlush = false;
// `true` if we are in Deps.flush now
var inFlush = false;
// `true` if we are computing a computation now, either first time
// or recompute. This matches Deps.active unless we are inside
// Deps.nonreactive, which nullfies currentComputation even though
// an enclosing computation may still be running.
var inCompute = false;
var nextId = 1;
// computations whose callbacks we should call at flush time
var pendingComputations = [];
// `true` if a Deps.flush is scheduled, or if we are in Deps.flush now
var willFlush = false;
// `true` if we are in Deps.flush now
var inFlush = false;
// `true` if we are computing a computation now, either first time
// or recompute. This matches Deps.active unless we are inside
// Deps.nonreactive, which nullfies currentComputation even though
// an enclosing computation may still be running.
var inCompute = false;
var afterFlushCallbacks = [];
var afterFlushCallbacks = [];
var requireFlush = function () {
if (! willFlush) {
setTimeout(Deps.flush, 0);
willFlush = true;
}
};
var requireFlush = function () {
if (! willFlush) {
setTimeout(Deps.flush, 0);
willFlush = true;
}
};
// Deps.Computation constructor is visible but private
// (throws an error if you try to call it)
var constructingComputation = false;
// Deps.Computation constructor is visible but private
// (throws an error if you try to call it)
var constructingComputation = false;
Deps.Computation = function (f, parent) {
if (! constructingComputation)
throw new Error(
"Deps.Computation constructor is private; use Deps.autorun");
constructingComputation = false;
Deps.Computation = function (f, parent) {
if (! constructingComputation)
throw new Error(
"Deps.Computation constructor is private; use Deps.autorun");
constructingComputation = false;
var self = this;
self.stopped = false;
self.invalidated = false;
self.firstRun = true;
self._id = nextId++;
self._onInvalidateCallbacks = [];
// the plan is at some point to use the parent relation
// to constrain the order that computations are processed
self._parent = parent;
self._func = f;
self._recomputing = false;
try {
self._compute();
} finally {
self.firstRun = false;
}
};
_.extend(Deps.Computation.prototype, {
onInvalidate: function (f) {
var self = this;
if (typeof f !== 'function')
throw new Error("onInvalidate requires a function");
var g = function () {
Deps.nonreactive(function () {
f(self);
});
};
if (self.invalidated)
g();
else
self._onInvalidateCallbacks.push(g);
},
invalidate: function () {
var self = this;
if (! self.invalidated) {
// if we're currently in _recompute(), don't enqueue
// ourselves, since we'll rerun immediately anyway.
if (! self._recomputing && ! self.stopped) {
requireFlush();
pendingComputations.push(this);
}
self.invalidated = true;
// callbacks can't add callbacks, because
// self.invalidated === true.
for(var i = 0, f; f = self._onInvalidateCallbacks[i]; i++)
f(); // already bound with self as argument
self._onInvalidateCallbacks = [];
}
},
stop: function () {
if (! this.stopped) {
this.stopped = true;
this.invalidate();
}
},
_compute: function () {
var self = this;
self.stopped = false;
self.invalidated = false;
self.firstRun = true;
self._id = nextId++;
self._onInvalidateCallbacks = [];
// the plan is at some point to use the parent relation
// to constrain the order that computations are processed
self._parent = parent;
self._func = f;
self._recomputing = false;
var previous = Deps.currentComputation;
setCurrentComputation(self);
var previousInCompute = inCompute;
inCompute = true;
try {
self._compute();
self._func(self);
} finally {
self.firstRun = false;
setCurrentComputation(previous);
inCompute = false;
}
};
},
_.extend(Deps.Computation.prototype, {
_recompute: function () {
var self = this;
onInvalidate: function (f) {
var self = this;
if (typeof f !== 'function')
throw new Error("onInvalidate requires a function");
var g = function () {
Deps.nonreactive(function () {
f(self);
});
};
if (self.invalidated)
g();
else
self._onInvalidateCallbacks.push(g);
},
invalidate: function () {
var self = this;
if (! self.invalidated) {
// if we're currently in _recompute(), don't enqueue
// ourselves, since we'll rerun immediately anyway.
if (! self._recomputing && ! self.stopped) {
requireFlush();
pendingComputations.push(this);
}
self.invalidated = true;
// callbacks can't add callbacks, because
// self.invalidated === true.
for(var i = 0, f; f = self._onInvalidateCallbacks[i]; i++)
f(); // already bound with self as argument
self._onInvalidateCallbacks = [];
}
},
stop: function () {
if (! this.stopped) {
this.stopped = true;
this.invalidate();
}
},
_compute: function () {
var self = this;
self.invalidated = false;
var previous = Deps.currentComputation;
setCurrentComputation(self);
var previousInCompute = inCompute;
inCompute = true;
self._recomputing = true;
while (self.invalidated && ! self.stopped) {
try {
self._func(self);
} finally {
setCurrentComputation(previous);
inCompute = false;
self._compute();
} catch (e) {
_debugFunc()("Exception from Deps recompute:", e.stack || e.message);
}
},
_recompute: function () {
var self = this;
self._recomputing = true;
while (self.invalidated && ! self.stopped) {
try {
self._compute();
} catch (e) {
_debugFunc()("Exception from Deps recompute:", e.stack || e.message);
}
// If _compute() invalidated us, we run again immediately.
// A computation that invalidates itself indefinitely is an
// infinite loop, of course.
//
// We could put an iteration counter here and catch run-away
// loops.
}
self._recomputing = false;
// If _compute() invalidated us, we run again immediately.
// A computation that invalidates itself indefinitely is an
// infinite loop, of course.
//
// We could put an iteration counter here and catch run-away
// loops.
}
});
self._recomputing = false;
}
});
Deps.Dependency = function () {
this._dependentsById = {};
};
Deps.Dependency = function () {
this._dependentsById = {};
};
_.extend(Deps.Dependency.prototype, {
// Adds `computation` to this set if it is not already
// present. Returns true if `computation` is a new member of the set.
// If no argument, defaults to currentComputation (which is required to
// exist in this case).
addDependent: function (computation) {
if (! computation) {
if (! Deps.active)
throw new Error(
"Dependency.addDependent() called with no currentComputation");
_.extend(Deps.Dependency.prototype, {
// Adds `computation` to this set if it is not already
// present. Returns true if `computation` is a new member of the set.
// If no argument, defaults to currentComputation, or does nothing
// if there is no currentComputation.
depend: function (computation) {
if (! computation) {
if (! Deps.active)
return false;
computation = Deps.currentComputation;
}
var self = this;
var id = computation._id;
if (! (id in self._dependentsById)) {
self._dependentsById[id] = computation;
computation.onInvalidate(function () {
delete self._dependentsById[id];
});
return true;
}
return false;
},
changed: function () {
var self = this;
for (var id in self._dependentsById)
self._dependentsById[id].invalidate();
},
hasDependents: function () {
var self = this;
for(var id in self._dependentsById)
return true;
return false;
computation = Deps.currentComputation;
}
});
var self = this;
var id = computation._id;
if (! (id in self._dependentsById)) {
self._dependentsById[id] = computation;
computation.onInvalidate(function () {
delete self._dependentsById[id];
});
return true;
}
return false;
},
changed: function () {
var self = this;
for (var id in self._dependentsById)
self._dependentsById[id].invalidate();
},
hasDependents: function () {
var self = this;
for(var id in self._dependentsById)
return true;
return false;
}
});
_.extend(Deps, {
flush: function () {
// Nested flush could plausibly happen if, say, a flush causes
_.extend(Deps, {
flush: function () {
// Nested flush could plausibly happen if, say, a flush causes
// DOM mutation, which causes a "blur" event, which runs an
// app event handler that calls Deps.flush. At the moment
// Spark blocks event handlers during DOM mutation anyway,
@@ -297,13 +296,6 @@
Deps.currentComputation.onInvalidate(f);
},
depend: function (v) {
if (! Deps.active)
return false;
return v.addDependent();
},
afterFlush: function (f) {
afterFlushCallbacks.push(f);
requireFlush();

View File

@@ -2,7 +2,7 @@ Tinytest.add('deps - run', function (test) {
var d = new Deps.Dependency;
var x = 0;
var handle = Deps.autorun(function (handle) {
Deps.depend(d);
d.depend();
++x;
});
test.equal(x, 1);
@@ -26,7 +26,7 @@ Tinytest.add('deps - run', function (test) {
test.equal(x, 3);
Deps.autorun(function (internalHandle) {
Deps.depend(d);
d.depend();
++x;
if (x == 6)
internalHandle.stop();
@@ -63,22 +63,22 @@ Tinytest.add("deps - nested run", function (test) {
var buf = "";
var c1 = Deps.autorun(function () {
Deps.depend(a);
a.depend();
buf += 'a';
Deps.autorun(function () {
Deps.depend(b);
b.depend();
buf += 'b';
Deps.autorun(function () {
Deps.depend(c);
c.depend();
buf += 'c';
var c2 = Deps.autorun(function () {
Deps.depend(d);
d.depend();
buf += 'd';
Deps.autorun(function () {
Deps.depend(e);
e.depend();
buf += 'e';
Deps.autorun(function () {
Deps.depend(f);
f.depend();
buf += 'f';
});
});

View File

@@ -495,7 +495,7 @@ _.extend(Meteor._LivedataConnection.prototype, {
if (!_.has(self._subscriptions, id))
return false;
var record = self._subscriptions[id];
record.readyDeps && Deps.depend(record.readyDeps);
record.readyDeps && record.readyDeps.depend();
return record.ready;
}
};
@@ -787,7 +787,7 @@ _.extend(Meteor._LivedataConnection.prototype, {
userId: function () {
var self = this;
if (self._userIdDeps)
Deps.depend(self._userIdDeps);
self._userIdDeps.depend();
return self._userId;
},

View File

@@ -374,7 +374,7 @@ LocalCollection.Cursor.prototype._depend = function (changers) {
if (Deps.active) {
var v = new Deps.Dependency;
Deps.depend(v);
v.depend();
var notifyChange = _.bind(v.changed, v);
var options = {_suppress_initial: true};

View File

@@ -15,6 +15,11 @@ Meteor.autosubscribe = Deps.autorun;
Meteor.flush = Deps.flush;
Meteor.autorun = Deps.autorun;
// Deps API that briefly existed in 0.5.9
Deps.depend = function (d) {
return d.depend();
};
// Instead of the "random" package with Random.id(), we used to have this
// Meteor.uuid() implementing the RFC 4122 v4 UUID.
Meteor.uuid = function () {

View File

@@ -51,7 +51,7 @@ Session = _.extend({}, {
get: function (key) {
var self = this;
self._ensureKey(key);
Deps.depend(self.keyDeps[key]);
self.keyDeps[key].depend();
return parse(self.keys[key]);
},
@@ -83,7 +83,7 @@ Session = _.extend({}, {
if (! _.has(self.keyValueDeps[key], serializedValue))
self.keyValueDeps[key][serializedValue] = new Deps.Dependency;
var isNew = Deps.depend(self.keyValueDeps[key][serializedValue]);
var isNew = self.keyValueDeps[key][serializedValue].depend();
if (isNew) {
Deps.onInvalidate(function () {
// clean up [key][serializedValue] if it's now empty, so we don't

View File

@@ -139,7 +139,7 @@ _.extend(Meteor._Stream.prototype, {
status: function () {
var self = this;
if (self.status_listeners)
Deps.depend(self.status_listeners);
self.status_listeners.depend();
return self.current_status;
},

View File

@@ -23,7 +23,7 @@ ReactiveVar = function(initialValue) {
};
ReactiveVar.prototype.get = function() {
Deps.depend(this._deps);
this._deps.depend();
return this._value;
};

View File

@@ -26,12 +26,12 @@ Meteor.startup(function () {
});
Template.progressBar.running = function () {
Deps.depend(countDeps);
countDeps.depend();
return passedCount + failedCount < totalCount;
};
Template.progressBar.percentPass = function () {
Deps.depend(countDeps);
countDeps.depend();
if (totalCount === 0)
return 0;
return 100*passedCount/totalCount;
@@ -46,14 +46,14 @@ Template.progressBar.passedCount = function () {
};
Template.progressBar.percentFail = function () {
Deps.depend(countDeps);
countDeps.depend();
if (totalCount === 0)
return 0;
return 100*failedCount/totalCount;
};
Template.progressBar.anyFail = function () {
Deps.depend(countDeps);
countDeps.depend();
return failedCount > 0;
};
@@ -95,12 +95,12 @@ Template.test_group.events({
});
Template.test_table.running = function() {
Deps.depend(resultsDeps);
resultsDeps.depend();
return running;
};
Template.test_table.passed = function() {
Deps.depend(resultsDeps);
resultsDeps.depend();
// walk whole tree to look for failed tests
var walk = function (groups) {
@@ -129,7 +129,7 @@ Template.test_table.passed = function() {
Template.test_table.total_test_time = function() {
Deps.depend(resultsDeps);
resultsDeps.depend();
// walk whole tree to get all tests
var walk = function (groups) {
@@ -152,11 +152,11 @@ Template.test_table.total_test_time = function() {
Template.test_table.data = function() {
Deps.depend(resultsDeps);
resultsDeps.depend();
return resultTree;
};
Template.test_table.failedTests = function() {
Deps.depend(resultsDeps);
resultsDeps.depend();
return failedTests;
};