Comparing version 0.6.1 to 0.6.2
@@ -16,3 +16,3 @@ "use strict"; | ||
function Entry(fn, args) { | ||
function Entry(fn, key, args) { | ||
this.parents = new Set; | ||
@@ -26,7 +26,12 @@ this.childValues = new Map; | ||
reset(this, fn, args); | ||
reset(this, fn, key, args); | ||
++Entry.count; | ||
} | ||
function reset(entry, fn, args) { | ||
Entry.count = 0; | ||
function reset(entry, fn, key, args) { | ||
entry.fn = fn; | ||
entry.key = key; | ||
entry.args = args; | ||
@@ -38,11 +43,16 @@ entry.value = UNKNOWN_VALUE; | ||
entry.recomputing = false; | ||
// Optional callback that will be invoked when entry.parents becomes | ||
// empty. The Entry object is given as the first parameter. If the | ||
// callback returns true, then this entry can be removed from the graph | ||
// and safely recycled into the entryPool. | ||
entry.reportOrphan = null; | ||
} | ||
Entry.acquire = function (fn, args) { | ||
Entry.acquire = function (fn, key, args) { | ||
var entry = entryPool.pop(); | ||
if (entry) { | ||
reset(entry, fn, args); | ||
reset(entry, fn, key, args); | ||
return entry; | ||
} | ||
return new Entry(fn, args); | ||
return new Entry(fn, key, args); | ||
}; | ||
@@ -52,3 +62,4 @@ | ||
assert(entry.parents.size === 0); | ||
forgetChildren(entry); | ||
assert(entry.childValues.size === 0); | ||
assert(entry.dirtyChildren === null); | ||
entryPool.push(entry); | ||
@@ -65,6 +76,26 @@ } | ||
Ep.recompute = function recompute() { | ||
rememberParent(this); | ||
if (! rememberParent(this) && | ||
maybeReportOrphan(this)) { | ||
// The recipient of the entry.reportOrphan callback decided to dispose | ||
// of this orphan entry by calling entry.dispos(), which recycles it | ||
// into the entryPool, so we don't need to (and should not) proceed | ||
// with the recomputation. | ||
return; | ||
} | ||
return recomputeIfDirty(this); | ||
}; | ||
// If the given entry has a reportOrphan method, and no remaining parents, | ||
// call entry.reportOrphan and return true iff it returns true. The | ||
// reportOrphan function should return true to indicate entry.dispose() | ||
// has been called, and the entry has been removed from any other caches | ||
// (see index.js for the only current example). | ||
function maybeReportOrphan(entry) { | ||
var report = entry.reportOrphan; | ||
return typeof report === "function" && | ||
entry.parents.size === 0 && | ||
report(entry) === true; | ||
} | ||
Ep.setDirty = function setDirty() { | ||
@@ -74,8 +105,4 @@ if (this.dirty) return; | ||
this.value = UNKNOWN_VALUE; | ||
// Since we're explicitly setting this.dirty = true, we won't need to | ||
// examine any of our children in recomputeIfDirty, so we can go ahead | ||
// and forget them. | ||
forgetChildren(this); | ||
reportDirty(this); | ||
// We can also go ahead and unsubscribe, since any further dirty | ||
// We can go ahead and unsubscribe here, since any further dirty | ||
// notifications we receive will be redundant, and unsubscribing may | ||
@@ -88,3 +115,3 @@ // free up some resources, e.g. file watchers. | ||
var entry = this; | ||
forgetChildren(entry); | ||
forgetChildren(entry).forEach(maybeReportOrphan); | ||
unsubscribe(entry); | ||
@@ -104,4 +131,4 @@ | ||
entry.parents.forEach(function (parent) { | ||
// Causes parent to forget all of its children, including entry. | ||
parent.setDirty(); | ||
forgetChild(parent, entry); | ||
}); | ||
@@ -168,8 +195,13 @@ | ||
function reportCleanChild(entry, child) { | ||
var cv = entry.childValues; | ||
// Must have called rememberChild(child) before calling | ||
// reportCleanChild(parent, child). | ||
assert(entry.childValues.has(child)); | ||
assert(cv.has(child)); | ||
assert(! mightBeDirty(child)); | ||
if (entry.childValues.get(child) !== child.value) { | ||
var childValue = cv.get(child); | ||
if (childValue === UNKNOWN_VALUE) { | ||
cv.set(child, child.value); | ||
} else if (childValue !== child.value) { | ||
entry.setDirty(); | ||
@@ -259,3 +291,6 @@ } | ||
forgetChildren(entry); | ||
// Since this recomputation is likely to re-remember some of this | ||
// entry's children, we forget our children here but do not call | ||
// maybeReportOrphan until after the recomputation finishes. | ||
var originalChildren = forgetChildren(entry); | ||
@@ -290,8 +325,18 @@ var local = getLocal(); | ||
// Now that we've had a chance to re-remember any children that were | ||
// involved in the recomputation, we can safely report any orphan | ||
// children that remain. | ||
originalChildren.forEach(maybeReportOrphan); | ||
return entry.value; | ||
} | ||
// Removes all children from this entry and returns an array of the | ||
// removed children. | ||
function forgetChildren(entry) { | ||
var children = []; | ||
entry.childValues.forEach(function (value, child) { | ||
forgetChild(entry, child); | ||
children.push(child); | ||
}); | ||
@@ -302,2 +347,4 @@ | ||
assert(entry.dirtyChildren === null); | ||
return children; | ||
} | ||
@@ -304,0 +351,0 @@ |
@@ -6,2 +6,3 @@ "use strict"; | ||
var Entry = require("./entry.js").Entry; | ||
var getLocal = require("./local.js").get; | ||
@@ -33,2 +34,7 @@ function defaultMakeCacheKey() { | ||
// If this wrapped function is disposable, then its creator does not | ||
// care about its return value, and it should be removed from the cache | ||
// immediately when it no longer has any parents that depend on it. | ||
var disposable = !! options.disposable; | ||
var cache = new Cache({ | ||
@@ -41,3 +47,20 @@ max: options.max, | ||
function reportOrphan(entry) { | ||
if (disposable) { | ||
// Triggers the entry.dispose() call above. | ||
cache.delete(entry.key); | ||
return true; | ||
} | ||
} | ||
function optimistic() { | ||
if (disposable && ! getLocal().currentParentEntry) { | ||
// If there's no current parent computation, and this wrapped | ||
// function is disposable (meaning we don't care about entry.value, | ||
// just dependency tracking), then we can short-cut everything else | ||
// in this function, because entry.recompute() is going to recycle | ||
// the entry object without recomputing anything, anyway. | ||
return; | ||
} | ||
var key = options.makeCacheKey.apply(null, arguments); | ||
@@ -55,7 +78,17 @@ if (! key) { | ||
} else { | ||
cache.set(key, entry = Entry.acquire(fn, args)); | ||
cache.set(key, entry = Entry.acquire(fn, key, args)); | ||
entry.subscribe = options.subscribe; | ||
if (disposable) { | ||
entry.reportOrphan = reportOrphan; | ||
} | ||
} | ||
return entry.recompute(); | ||
var value = entry.recompute(); | ||
// If options.disposable is truthy, the caller of wrap is telling us | ||
// they don't care about the result of entry.recompute(), so we should | ||
// avoid returning the value, so it won't be accidentally used. | ||
if (! disposable) { | ||
return value; | ||
} | ||
} | ||
@@ -62,0 +95,0 @@ |
{ | ||
"name": "optimism", | ||
"version": "0.6.1", | ||
"version": "0.6.2", | ||
"author": "Ben Newman <ben@benjamn.com>", | ||
@@ -5,0 +5,0 @@ "description": "Composable reactive caching with efficient invalidation.", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18752
505