diff --git a/doc/global.html b/doc/global.html index 397dc3ba..5e83fc0d 100644 --- a/doc/global.html +++ b/doc/global.html @@ -54,6 +54,121 @@

Methods

+
+ + + +

findPartialIntersections()

+ + + + + +
+

Recursive function to calculate intersection and complement paths in 2 given +pathsets at a given depth +Parameters:

+ +
+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ request/complement.js, line 127 +
+ + + + + + + +
+ + + + + + + + + + + + + + + +
+ +
diff --git a/doc/request_GetRequestV2.js.html b/doc/request_GetRequestV2.js.html index 86e34b54..06d583a4 100644 --- a/doc/request_GetRequestV2.js.html +++ b/doc/request_GetRequestV2.js.html @@ -133,13 +133,16 @@

/** * Attempts to add paths to the outgoing request. If there are added * paths then the request callback will be added to the callback list. + * Handles adding partial paths as well * - * @returns {Array} - the remaining paths in the request. + * @returns {Array} - whether new requested paths were inserted in this + * request, the remaining paths that could not be added, + * and disposable for the inserted requested paths. */ - add: function(requested, optimized, callback) { + add: function(requested, optimized, depthDifferences, callback) { // uses the length tree complement calculator. var self = this; - var complementTuple = complement(requested, optimized, self._pathMap); + var complementTuple = complement(requested, optimized, depthDifferences, self._pathMap); var optimizedComplement; var requestedComplement; @@ -154,10 +157,9 @@

var inserted = false; var disposable = false; - // If the out paths is less than the passed in paths, then there - // has been an intersection and the complement has been returned. - // Therefore, this can be deduped across requests. - if (optimizedComplement.length < optimized.length) { + // If we found an intersection, then just add new callback + // as one of the dependents of that request + if (complementTuple && complementTuple[0].length) { inserted = true; var idx = self._callbacks.length; self._callbacks[idx] = callback; diff --git a/doc/request_RequestQueueV2.js.html b/doc/request_RequestQueueV2.js.html index 052201c0..7334783b 100644 --- a/doc/request_RequestQueueV2.js.html +++ b/doc/request_RequestQueueV2.js.html @@ -60,7 +60,7 @@

* @param {Array} optimizedPaths - * @param {Function} cb - */ - get: function(requestedPaths, optimizedPaths, cb) { + get: function(requestedPaths, optimizedPaths, depthDifferences, cb) { var self = this; var disposables = []; var count = 0; @@ -81,7 +81,7 @@

// if possible. if (request.sent) { var results = request.add( - rRemainingPaths, oRemainingPaths, refCountCallback); + rRemainingPaths, oRemainingPaths, depthDifferences, refCountCallback); // Checks to see if the results were successfully inserted // into the outgoing results. Then our paths will be reduced diff --git a/doc/request_complement.js.html b/doc/request_complement.js.html index d40a4387..37bdcf2e 100644 --- a/doc/request_complement.js.html +++ b/doc/request_complement.js.html @@ -17,54 +17,217 @@

var hasIntersection = require("falcor-path-utils").hasIntersection;
 var arraySlice = require("./../support/array-slice");
+var arrayConcat = require("./../support/array-concat");
+var iterateKeySet = require("falcor-path-utils").iterateKeySet;
 
 /**
- * creates the complement of the requested and optimized paths
- * based on the provided tree.
+ * Figures out what paths in requested pathsets can be
+ * deduped based on existing optimized path tree provided.
  *
- * If there is no complement then this is just a glorified
- * array copy.
+ * ## no deduping possible:
+ *
+ * if no existing requested sub tree at all for path,
+ * just add the entire path to complement.
+ *
+ * ## fully deduped:
+ *
+ * if required path is a complete subset of given sub tree,
+ * just add the entire path to intersection
+ *
+ * ## partial deduping:
+ *
+ * if some part of path, when ranges are expanded, is a subset
+ * of given sub tree, then add only that part to intersection,
+ * and all other parts of this path to complement
+ *
+ * To keep `depth` argument be a valid index for optimized path (`oPath`),
+ * either requested or optimized path is sent in pre-initialized with
+ * some items so that their remaining length matches exactly, keeping
+ * remaining ranges in those pathsets 1:1 in correspondence
+ *
+ * Note that positive `depthDiff` value means that requested path is
+ * longer than optimized path, and we need to pre-initialize current
+ * requested path with that many offset items, so that their remaining
+ * length matches. Similarly, negative `depthDiff` value means that
+ * optimized path is longer, and we pre-initialize optimized path with
+ * those many items. Note that because of the way requested and
+ * optimized paths are accumulated from what user requested in model.get
+ * (see onMissing.js), it is not possible for the pre-initialized paths
+ * to have any ranges in them.
+ *
+ * `intersectionData` is:
+ * [ requestedIntersection, optimizedComplement, requestedComplement ]
+ * where `requestedIntersection` is matched requested paths that can be
+ * deduped, `optimizedComplement` is missing optimized paths, and
+ * `requestedComplement` is requested counterparts of those missing
+ * optimized paths
  */
-module.exports = function complement(requested, optimized, tree) {
+module.exports = function complement(requested, optimized, depthDifferences, tree) {
     var optimizedComplement = [];
     var requestedComplement = [];
     var requestedIntersection = [];
     var intersectionLength = -1, complementLength = -1;
-    var intersectionFound = false;
 
     for (var i = 0, len = optimized.length; i < len; ++i) {
-        // If this does not intersect then add it to the output.
-        var path = optimized[i];
-        var subTree = tree[path.length];
+        var oPath = optimized[i];
+        var rPath = requested[i];
+        var depthDiff = depthDifferences[i];
+        var subTree = tree[oPath.length];
+
+        // no deduping possible
+        if (!subTree) {
+            optimizedComplement[++complementLength] = oPath;
+            requestedComplement[complementLength] = rPath;
+            continue;
+        }
+        // fully deduped
+        if (hasIntersection(subTree, oPath, 0)) {
+            requestedIntersection[++intersectionLength] = rPath;
+            continue;
+        }
 
-        // If there is no subtree to look into or there is no intersection.
-        if (!subTree || !hasIntersection(subTree, path, 0)) {
+        // partial deduping
+        var intersectionData = findPartialIntersections(
+            rPath,
+            oPath,
+            subTree,
+            depthDiff < 0 ? -depthDiff : 0,
+            depthDiff > 0 ? arraySlice(rPath, 0, depthDiff) : [],
+            depthDiff < 0 ? arraySlice(oPath, 0, -depthDiff) : [],
+            depthDiff);
+        for (var j = 0, jLen = intersectionData[0].length; j < jLen; ++j) {
+            requestedIntersection[++intersectionLength] = intersectionData[0][j];
+        }
+        for (var k = 0, kLen = intersectionData[1].length; k < kLen; ++k) {
+            optimizedComplement[++complementLength] = intersectionData[1][k];
+            requestedComplement[complementLength] = intersectionData[2][k];
+        }
+    }
 
-            if (intersectionFound) {
-                optimizedComplement[++complementLength] = path;
-                requestedComplement[complementLength] = requested[i];
-            }
-        } else {
-            // If there has been no intersection yet and
-            // i is bigger than 0 (meaning we have had only complements)
-            // then we need to update our complements to match the current
-            // reality.
-            if (!intersectionFound && i > 0) {
-                requestedComplement = arraySlice(requested, 0, i);
-                optimizedComplement = arraySlice(optimized, 0, i);
+    if (!requestedIntersection.length) {
+        return null;
+    }
+    return [requestedIntersection, optimizedComplement, requestedComplement];
+};
+
+/**
+ * Recursive function to calculate intersection and complement paths in 2 given
+ * pathsets at a given depth
+ * Parameters:
+ *  - `requestedPath`: full requested path (can include ranges)
+ *  - `optimizedPath`: corresponding optimized path (can include ranges)
+ *  - `currentTree`: path map for in-flight request, against which to dedupe
+ *  - `depth`: index of optimized path that we are trying to match with `currentTree`
+ *  - `rCurrentPath`: current accumulated requested path by previous recursive
+ *                    iterations. Could also have been pre-initialized as stated
+ *                    above.
+ *                    This path cannot contain ranges, instead contains a key
+ *                    from the range, representing one of the individual paths
+ *                    in `requestedPath` pathset
+ *  - `oCurrentPath`: corresponding accumulated optimized path, to be matched
+ *                    with `currentTree`. Could have been pre-initialized.
+ *                    Cannot contain ranges, instead contains a key from the
+ *                    range at given `depth` in `optimizedPath`
+ *  - `depthDiff`: difference in length between `requestedPath` and `optimizedPath`
+ *
+ *  Example scenario:
+ *      - requestedPath: ['lolomo', 0, 0, 'tags', { from: 0, to: 2 }]
+ *      - optimizedPath: ['videosById', 11, 'tags', { from: 0, to: 2 }]
+ *      - currentTree: { videosById: 11: { tags: { 0: null, 1: null }}}
+ *      // since requested path is longer, optimized path index starts from depth 0
+ *      // and accumulated requested path starts pre-initialized (rCurrentPath)
+ *      - depth: 0
+ *      - rCurrentPath: ['lolomo']
+ *      - oCurrentPath: []
+ *      - depthDiff: 1
+ */
+function findPartialIntersections(requestedPath, optimizedPath, currentTree, depth, rCurrentPath, oCurrentPath, depthDiff) {
+    var intersections = [];
+    var rComplementPaths = [];
+    var oComplementPaths = [];
+    // iterate over optimized path, looking for deduping opportunities
+    for (; depth < optimizedPath.length; ++depth) {
+        var key = optimizedPath[depth];
+        var keyType = typeof key;
+
+        // if range key is found, start inner loop to iterate over all keys in range
+        // and add intersections and complements from each iteration separately.
+        // range keys branch-out like this, providing individual deduping
+        // opportunities for each inner key
+        if (key && keyType === "object") {
+            var note = {};
+            var innerKey = iterateKeySet(key, note);
+
+            while (!note.done) {
+                var nextTree = currentTree[innerKey];
+                if (nextTree === undefined) {
+                    // if no next sub tree exists for an inner key, it's a dead-end
+                    // and we can add this to complement paths
+                    var oPath = oCurrentPath.concat(
+                        innerKey,
+                        arraySlice(
+                            optimizedPath,
+                            depth + 1));
+                    oComplementPaths[oComplementPaths.length] = oPath;
+                    var rPath = rCurrentPath.concat(
+                        innerKey,
+                        arraySlice(
+                            requestedPath,
+                            depth + 1 + depthDiff));
+                    rComplementPaths[rComplementPaths.length] = rPath;
+                } else if (depth === optimizedPath.length - 1) {
+                    // reaching the end of optimized path means that we found a
+                    // corresponding node in the path map tree every time,
+                    // so add current path to successful intersections
+                    intersections[intersections.length] = arrayConcat(rCurrentPath, [innerKey]);
+                } else {
+                    // otherwise keep trying to find further partial deduping
+                    // opportunities in the remaining path!
+                    var intersectionData = findPartialIntersections(
+                        requestedPath,
+                        optimizedPath,
+                        nextTree,
+                        depth + 1,
+                        arrayConcat(rCurrentPath, [innerKey]),
+                        arrayConcat(oCurrentPath, [innerKey]),
+                        depthDiff);
+                    for (var j = 0, jLen = intersectionData[0].length; j < jLen; ++j) {
+                        intersections[intersections.length] = intersectionData[0][j];
+                    }
+                    for (var k = 0, kLen = intersectionData[1].length; k < kLen; ++k) {
+                        oComplementPaths[oComplementPaths.length] = intersectionData[1][k];
+                        rComplementPaths[rComplementPaths.length] = intersectionData[2][k];
+                    }
+                }
+                innerKey = iterateKeySet(key, note);
             }
+            break;
+        }
 
-            requestedIntersection[++intersectionLength] = requested[i];
-            intersectionFound = true;
+        // for simple keys, we don't need to branch out. looping over `depth`
+        // here instead of recursion, for performance
+        currentTree = currentTree[key];
+        oCurrentPath[oCurrentPath.length] = optimizedPath[depth];
+        rCurrentPath[rCurrentPath.length] = requestedPath[depth + depthDiff];
+
+        if (currentTree === undefined) {
+            // if dead-end, add this to complements
+            oComplementPaths[oComplementPaths.length] =
+                arrayConcat(oCurrentPath, arraySlice(optimizedPath, depth + 1));
+            rComplementPaths[rComplementPaths.length] =
+                arrayConcat(rCurrentPath, arraySlice(requestedPath, depth + depthDiff + 1));
+            break;
+        } else if (depth === optimizedPath.length - 1) {
+            // if reach end of optimized path successfully, add to intersections
+            intersections[intersections.length] = rCurrentPath;
         }
+        // otherwise keep going
     }
 
-    if (!intersectionFound) {
-        return null;
-    }
+    // return accumulated intersection and complement pathsets
+    return [intersections, oComplementPaths, rComplementPaths];
+}
 
-    return [requestedIntersection, optimizedComplement, requestedComplement ];
-};