Fast implementation of QuadTree
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 17 Mar 2014 10:38:34 +0000 (10:38 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 17 Mar 2014 10:38:34 +0000 (10:38 +0000)
Rewritten to avoid memory allocations.

BUG=skia:2242
R=tomhudson@google.com, mtklein@google.com, reed@google.com, robertphillips@google.com

Author: iancottrell@google.com

Review URL: https://codereview.chromium.org/187233002

git-svn-id: http://skia.googlecode.com/svn/trunk@13826 2bbb7eff-a529-9590-31e7-b0007b416f81

bench/QuadTreeBench.cpp
gyp/tests.gypi
src/core/SkQuadTree.cpp
src/core/SkQuadTree.h
src/core/SkQuadTreePicture.cpp
src/core/SkTInternalSList.h [new file with mode: 0644]
src/core/SkTObjectPool.h [new file with mode: 0644]
tests/BBoxHierarchyTest.cpp
tests/ObjectPoolTest.cpp [new file with mode: 0644]
tests/SListTest.cpp [new file with mode: 0644]

index f1d5bcf..4204e91 100644 (file)
@@ -180,33 +180,37 @@ static inline SkIRect make_random_rects(SkRandom& rand, int index, int numRects)
 
 DEF_BENCH(
     return SkNEW_ARGS(BBoxBuildBench, ("XYordered", &make_XYordered_rects,
-                      SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxQueryBench, ("XYordered", &make_XYordered_rects,
-                      BBoxQueryBench::kRandom_QueryType, SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      BBoxQueryBench::kRandom_QueryType,
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxBuildBench, ("YXordered", &make_YXordered_rects,
-                      SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxQueryBench, ("YXordered", &make_YXordered_rects,
-                      BBoxQueryBench::kRandom_QueryType, SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      BBoxQueryBench::kRandom_QueryType,
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxBuildBench, ("random", &make_random_rects,
-                      SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxQueryBench, ("random", &make_random_rects,
-                      BBoxQueryBench::kRandom_QueryType, SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      BBoxQueryBench::kRandom_QueryType,
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxBuildBench, ("concentric", &make_concentric_rects_increasing,
-                      SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
 DEF_BENCH(
     return SkNEW_ARGS(BBoxQueryBench, ("concentric", &make_concentric_rects_increasing,
-                      BBoxQueryBench::kRandom_QueryType, SkQuadTree::Create(QUAD_TREE_BOUNDS)));
+                      BBoxQueryBench::kRandom_QueryType,
+                      SkNEW_ARGS(SkQuadTree, (QUAD_TREE_BOUNDS))));
 )
index 80bd380..cd0e111 100644 (file)
     '../tests/MessageBusTest.cpp',
     '../tests/MetaDataTest.cpp',
     '../tests/MipMapTest.cpp',
+    '../tests/ObjectPoolTest.cpp',
     '../tests/OSPathTest.cpp',
     '../tests/OnceTest.cpp',
     '../tests/PDFPrimitivesTest.cpp',
     '../tests/ShaderImageFilterTest.cpp',
     '../tests/ShaderOpacityTest.cpp',
     '../tests/SkBase64Test.cpp',
+    '../tests/SListTest.cpp',
     '../tests/SmallAllocatorTest.cpp',
     '../tests/SortTest.cpp',
     '../tests/SrcOverTest.cpp',
index 97f86cf..8531823 100644 (file)
 #include <stdio.h>
 #include <vector>
 
-class SkQuadTree::QuadTreeNode {
-public:
-    struct Data {
-        Data(const SkIRect& bounds, void* data) : fBounds(bounds), fInnerBounds(bounds), fData(data) {}
-        SkIRect fBounds;
-        SkIRect fInnerBounds;
-        void* fData;
-    };
-
-    QuadTreeNode(const SkIRect& bounds)
-     : fBounds(bounds)
-     , fTopLeft(NULL)
-     , fTopRight(NULL)
-     , fBottomLeft(NULL)
-     , fBottomRight(NULL)
-     , fCanSubdivide((fBounds.width() * fBounds.height()) > 0) {}
-
-    ~QuadTreeNode() {
-        clear();
-    }
-
-    void clear() {
-        SkDELETE(fTopLeft);
-        fTopLeft = NULL;
-        SkDELETE(fTopRight);
-        fTopRight = NULL;
-        SkDELETE(fBottomLeft);
-        fBottomLeft = NULL;
-        SkDELETE(fBottomRight);
-        fBottomRight = NULL;
-        fData.reset();
-    }
-
-    const SkIRect& getBounds() const { return fBounds; }
-
-    // Insert data into the QuadTreeNode
-    bool insert(Data& data) {
-        // Ignore objects which do not belong in this quad tree
-        return data.fInnerBounds.intersect(fBounds) && doInsert(data);
-    }
-
-    // Find all data which appear within a range
-    void queryRange(const SkIRect& range, SkTDArray<void*>* dataInRange) const {
-        // Automatically abort if the range does not collide with this quad
-        if (!SkIRect::Intersects(fBounds, range)) {
-            return; // nothing added to the list
-        }
+static const int kSplitThreshold = 8;
+static const int kMinDimensions = 128;
 
-        // Check objects at this quad level
-        for (int i = 0; i < fData.count(); ++i) {
-            if (SkIRect::Intersects(fData[i].fBounds, range)) {
-                dataInRange->push(fData[i].fData);
-            }
-        }
+SkQuadTree::SkQuadTree(const SkIRect& bounds)
+    : fEntryCount(0)
+    , fRoot(NULL) {
+    SkASSERT((bounds.width() * bounds.height()) > 0);
+    fRoot = fNodePool.acquire();
+    fRoot->fBounds = bounds;
+}
 
-        // Terminate here, if there are no children
-        if (!hasChildren()) {
-            return;
-        }
+SkQuadTree::~SkQuadTree() {
+}
 
-        // Otherwise, add the data from the children
-        fTopLeft->queryRange(range, dataInRange);
-        fTopRight->queryRange(range, dataInRange);
-        fBottomLeft->queryRange(range, dataInRange);
-        fBottomRight->queryRange(range, dataInRange);
+SkQuadTree::Node* SkQuadTree::pickChild(Node* node,
+                                        const SkIRect& bounds) const {
+    // is it entirely to the left?
+    int index = 0;
+    if (bounds.fRight < node->fSplitPoint.fX) {
+        // Inside the left side
+    } else if(bounds.fLeft >= node->fSplitPoint.fX) {
+        // Inside the right side
+        index |= 1;
+    } else {
+        // Not inside any children
+        return NULL;
     }
-
-    int getDepth(int i = 1) const {
-        if (hasChildren()) {
-            int depthTL = fTopLeft->getDepth(++i);
-            int depthTR = fTopRight->getDepth(i);
-            int depthBL = fBottomLeft->getDepth(i);
-            int depthBR = fBottomRight->getDepth(i);
-            return SkTMax(SkTMax(depthTL, depthTR), SkTMax(depthBL, depthBR));
-        }
-        return i;
+    if (bounds.fBottom < node->fSplitPoint.fY) {
+        // Inside the top side
+    } else if(bounds.fTop >= node->fSplitPoint.fY) {
+        // Inside the bottom side
+        index |= 2;
+    } else {
+        // Not inside any children
+        return NULL;
     }
+    return node->fChildren[index];
+}
 
-    void rewindInserts(SkBBoxHierarchyClient* client) {
-        for (int i = fData.count() - 1; i >= 0; --i) {
-            if (client->shouldRewind(fData[i].fData)) {
-                fData.remove(i);
-            }
-        }
-        if (hasChildren()) {
-            fTopLeft->rewindInserts(client);
-            fTopRight->rewindInserts(client);
-            fBottomLeft->rewindInserts(client);
-            fBottomRight->rewindInserts(client);
+void SkQuadTree::insert(Node* node, Entry* entry) {
+    // does it belong in a child?
+    if (NULL != node->fChildren[0]) {
+        Node* child = pickChild(node, entry->fBounds);
+        if (NULL != child) {
+            insert(child, entry);
+        } else {
+            node->fEntries.push(entry);
         }
+        return;
     }
-
-private:
-    // create four children which fully divide this quad into four quads of equal area
-    void subdivide() {
-        if (!hasChildren() && fCanSubdivide) {
-            SkIPoint center = SkIPoint::Make(fBounds.centerX(), fBounds.centerY());
-            fTopLeft = SkNEW_ARGS(QuadTreeNode, (SkIRect::MakeLTRB(
-                fBounds.fLeft, fBounds.fTop, center.fX, center.fY)));
-            fTopRight = SkNEW_ARGS(QuadTreeNode, (SkIRect::MakeLTRB(
-                center.fX, fBounds.fTop, fBounds.fRight, center.fY)));
-            fBottomLeft = SkNEW_ARGS(QuadTreeNode, (SkIRect::MakeLTRB(
-                fBounds.fLeft, center.fY, center.fX, fBounds.fBottom)));
-            fBottomRight = SkNEW_ARGS(QuadTreeNode, (SkIRect::MakeLTRB(
-                center.fX, center.fY, fBounds.fRight, fBounds.fBottom)));
-
-            // If any of the data can fit entirely into a subregion, move it down now
-            for (int i = fData.count() - 1; i >= 0; --i) {
-                // If the data fits entirely into one of the 4 subregions, move that data
-                // down to that subregion.
-                if (fTopLeft->doInsert(fData[i]) ||
-                    fTopRight->doInsert(fData[i]) ||
-                    fBottomLeft->doInsert(fData[i]) ||
-                    fBottomRight->doInsert(fData[i])) {
-                    fData.remove(i);
-                }
-            }
-        }
+    // No children yet, add to this node
+    node->fEntries.push(entry);
+    // should I split?
+    if (node->fEntries.getCount() < kSplitThreshold) {
+        return;
     }
 
-    bool doInsert(const Data& data) {
-        if (!fBounds.contains(data.fInnerBounds)) {
-            return false;
-        }
+    if ((node->fBounds.width() < kMinDimensions) ||
+        (node->fBounds.height() < kMinDimensions)) {
+        return;
+    }
 
-        if (fData.count() > kQuadTreeNodeCapacity) {
-            subdivide();
-        }
+    // Build all the children
+    node->fSplitPoint = SkIPoint::Make(node->fBounds.centerX(),
+                                       node->fBounds.centerY());
+    for(int index=0; index<kChildCount; ++index) {
+        node->fChildren[index] = fNodePool.acquire();
+    }
+    node->fChildren[0]->fBounds = SkIRect::MakeLTRB(
+        node->fBounds.fLeft,    node->fBounds.fTop,
+        node->fSplitPoint.fX,   node->fSplitPoint.fY);
+    node->fChildren[1]->fBounds = SkIRect::MakeLTRB(
+        node->fSplitPoint.fX,   node->fBounds.fTop,
+        node->fBounds.fRight,   node->fSplitPoint.fY);
+    node->fChildren[2]->fBounds = SkIRect::MakeLTRB(
+        node->fBounds.fLeft,    node->fSplitPoint.fY,
+        node->fSplitPoint.fX,   node->fBounds.fBottom);
+    node->fChildren[3]->fBounds = SkIRect::MakeLTRB(
+        node->fSplitPoint.fX,   node->fSplitPoint.fY,
+        node->fBounds.fRight,   node->fBounds.fBottom);
+    // reinsert all the entries of this node to allow child trickle
+    SkTInternalSList<Entry> entries;
+    entries.pushAll(&node->fEntries);
+    while(!entries.isEmpty()) {
+        insert(node, entries.pop());
+    }
+}
 
-        // If there is space in this quad tree, add the object here
-        // If this quadtree can't be subdivided, we have no choice but to add it here
-        if ((fData.count() <= kQuadTreeNodeCapacity) || !fCanSubdivide) {
-            if (fData.isEmpty()) {
-                fData.setReserve(kQuadTreeNodeCapacity);
-            }
-            fData.push(data);
-        } else if (!fTopLeft->doInsert(data) && !fTopRight->doInsert(data) &&
-                   !fBottomLeft->doInsert(data) && !fBottomRight->doInsert(data)) {
-            // Can't be pushed down to children ? keep it here
-            fData.push(data);
+void SkQuadTree::search(Node* node, const SkIRect& query,
+                        SkTDArray<void*>* results) const {
+    for (Entry* entry = node->fEntries.head(); NULL != entry;
+        entry = entry->getSListNext()) {
+        if (SkIRect::IntersectsNoEmptyCheck(entry->fBounds, query)) {
+            results->push(entry->fData);
         }
-
-        return true;
     }
-
-    bool hasChildren() const {
-        return (NULL != fTopLeft);
+    if (NULL == node->fChildren[0]) {
+        return;
+    }
+    // fast quadrant test
+    bool left = true;
+    bool right = true;
+    if (query.fRight < node->fSplitPoint.fX) {
+        right = false;
+    } else if(query.fLeft >= node->fSplitPoint.fX) {
+        left = false;
+    }
+    bool top = true;
+    bool bottom = true;
+    if (query.fBottom < node->fSplitPoint.fY) {
+        bottom = false;
+    } else if(query.fTop >= node->fSplitPoint.fY) {
+        top = false;
+    }
+    // search all the active quadrants
+    if (top && left) {
+        search(node->fChildren[0], query, results);
+    }
+    if (top && right) {
+        search(node->fChildren[1], query, results);
+    }
+    if (bottom && left) {
+        search(node->fChildren[2], query, results);
+    }
+    if (bottom && right) {
+        search(node->fChildren[3], query, results);
     }
-
-    // Arbitrary constant to indicate how many elements can be stored in this quad tree node
-    enum { kQuadTreeNodeCapacity = 4 };
-
-    // Bounds of this quad tree
-    SkIRect fBounds;
-
-    // Data in this quad tree node
-    SkTDArray<Data> fData;
-
-    // Children
-    QuadTreeNode* fTopLeft;
-    QuadTreeNode* fTopRight;
-    QuadTreeNode* fBottomLeft;
-    QuadTreeNode* fBottomRight;
-
-    // Whether or not this node can have children
-    bool fCanSubdivide;
-};
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkQuadTree* SkQuadTree::Create(const SkIRect& bounds) {
-    return new SkQuadTree(bounds);
 }
 
-SkQuadTree::SkQuadTree(const SkIRect& bounds)
-    : fCount(0)
-    , fRoot(SkNEW_ARGS(QuadTreeNode, (bounds))) {
-    SkASSERT((bounds.width() * bounds.height()) > 0);
+void SkQuadTree::clear(Node* node) {
+    // first clear the entries of this node
+    fEntryPool.releaseAll(&node->fEntries);
+    // recurse into and clear all child nodes
+    for(int index=0; index<kChildCount; ++index) {
+        Node* child = node->fChildren[index];
+        node->fChildren[index] = NULL;
+        if (NULL != child) {
+            clear(child);
+            fNodePool.release(child);
+        }
+    }
 }
 
-SkQuadTree::~SkQuadTree() {
-    SkDELETE(fRoot);
+int SkQuadTree::getDepth(Node* node) const {
+    int maxDepth = 0;
+    if (NULL != node->fChildren[0]) {
+        for(int index=0; index<kChildCount; ++index) {
+            maxDepth = SkMax32(maxDepth, getDepth(node->fChildren[index]));
+        }
+    }
+    return maxDepth + 1;
 }
 
 void SkQuadTree::insert(void* data, const SkIRect& bounds, bool) {
@@ -199,27 +168,54 @@ void SkQuadTree::insert(void* data, const SkIRect& bounds, bool) {
         SkASSERT(false);
         return;
     }
-
-    QuadTreeNode::Data quadTreeData(bounds, data);
-    fRoot->insert(quadTreeData);
-    ++fCount;
+    Entry* entry = fEntryPool.acquire();
+    entry->fData = data;
+    entry->fBounds = bounds;
+    ++fEntryCount;
+    if (fRoot->fEntries.isEmpty() && (NULL == fRoot->fChildren[0])) {
+        fDeferred.push(entry);
+    } else {
+        insert(fRoot, entry);
+    }
 }
 
 void SkQuadTree::search(const SkIRect& query, SkTDArray<void*>* results) {
+    SkASSERT(fDeferred.isEmpty());
     SkASSERT(NULL != results);
-    fRoot->queryRange(query, results);
+    if (SkIRect::Intersects(fRoot->fBounds, query)) {
+        search(fRoot, query, results);
+    }
 }
 
 void SkQuadTree::clear() {
-    fCount = 0;
-    fRoot->clear();
+    fEntryCount = 0;
+    clear(fRoot);
 }
 
 int SkQuadTree::getDepth() const {
-    return fRoot->getDepth();
+    return getDepth(fRoot);
 }
 
 void SkQuadTree::rewindInserts() {
     SkASSERT(fClient);
-    fRoot->rewindInserts(fClient);
+     // Currently only supports deferred inserts
+    SkASSERT(fRoot->fEntries.isEmpty() && fRoot->fChildren[0] == NULL);
+    SkTInternalSList<Entry> entries;
+    entries.pushAll(&fDeferred);
+    while(!entries.isEmpty()) {
+        Entry* entry = entries.pop();
+        if (fClient->shouldRewind(entry->fData)) {
+            entry->fData = NULL;
+            fEntryPool.release(entry);
+            --fEntryCount;
+        } else {
+            fDeferred.push(entry);
+        }
+    }
+}
+
+void SkQuadTree::flushDeferredInserts() {
+    while(!fDeferred.isEmpty()) {
+        insert(fRoot, fDeferred.pop());
+    }
 }
index a261749..a3b47bd 100644 (file)
 #include "SkRect.h"
 #include "SkTDArray.h"
 #include "SkBBoxHierarchy.h"
+#include "SkTInternalSList.h"
+#include "SkTObjectPool.h"
 
 /**
- * An QuadTree implementation. In short, it is a tree containing a hierarchy of bounding rectangles
+ * A QuadTree implementation. In short, it is a tree containing a hierarchy of bounding rectangles
  * in which each internal node has exactly four children.
  *
  * For more details see:
@@ -25,9 +27,13 @@ public:
     SK_DECLARE_INST_COUNT(SkQuadTree)
 
     /**
-     * Create a new QuadTree
+     * Quad tree constructor.
+     * @param bounds The bounding box for the root of the quad tree.
+     *               giving the quad tree bounds that fall outside the root
+     *               bounds may result in pathological but correct behavior.
      */
-    static SkQuadTree* Create(const SkIRect& bounds);
+    SkQuadTree(const SkIRect& bounds);
+
     virtual ~SkQuadTree();
 
     /**
@@ -43,7 +49,7 @@ public:
     /**
      * If any inserts have been deferred, this will add them into the tree
      */
-    virtual void flushDeferredInserts() SK_OVERRIDE {}
+    virtual void flushDeferredInserts() SK_OVERRIDE;
 
     /**
      * Given a query rectangle, populates the passed-in array with the elements it intersects
@@ -60,19 +66,44 @@ public:
     /**
      * This gets the insertion count (rather than the node count)
      */
-    virtual int getCount() const SK_OVERRIDE { return fCount; }
+    virtual int getCount() const SK_OVERRIDE { return fEntryCount; }
 
     virtual void rewindInserts() SK_OVERRIDE;
 
 private:
-    class QuadTreeNode;
-
-    SkQuadTree(const SkIRect& bounds);
-
-    // This is the count of data elements (rather than total nodes in the tree)
-    int fCount;
-
-    QuadTreeNode* fRoot;
+    struct Entry {
+        Entry() : fData(NULL) {}
+        SkIRect fBounds;
+        void* fData;
+        SK_DECLARE_INTERNAL_SLIST_INTERFACE(Entry);
+    };
+
+    static const int kChildCount = 4;
+
+    struct Node {
+        Node() {
+            for (int index=0; index<kChildCount; ++index) {
+                fChildren[index] = NULL;
+            }
+        }
+        SkTInternalSList<Entry> fEntries;
+        SkIRect fBounds;
+        SkIPoint fSplitPoint; // Only valid if the node has children.
+        Node* fChildren[kChildCount];
+        SK_DECLARE_INTERNAL_SLIST_ADAPTER(Node, fChildren[0]);
+    };
+
+    SkTObjectPool<Entry> fEntryPool;
+    SkTObjectPool<Node> fNodePool;
+    int fEntryCount;
+    Node* fRoot;
+    SkTInternalSList<Entry> fDeferred;
+
+    Node* pickChild(Node* node, const SkIRect& bounds) const;
+    void insert(Node* node, Entry* entry);
+    void search(Node* node, const SkIRect& query, SkTDArray<void*>* results) const;
+    void clear(Node* node);
+    int getDepth(Node* node) const;
 
     typedef SkBBoxHierarchy INHERITED;
 };
index 354b904..091f440 100644 (file)
@@ -10,5 +10,5 @@
 #include "SkQuadTree.h"
 
 SkBBoxHierarchy* SkQuadTreePicture::createBBoxHierarchy() const {
-    return SkQuadTree::Create(fBounds);
+    return SkNEW_ARGS(SkQuadTree, (fBounds));
 }
diff --git a/src/core/SkTInternalSList.h b/src/core/SkTInternalSList.h
new file mode 100644 (file)
index 0000000..4695e5a
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkTInternalSList_DEFINED
+#define SkTInternalSList_DEFINED
+
+#include "SkTInternalLList.h" // for SkPtrWrapper
+
+/**
+ * This macro creates the methods required by the SkTInternalSList class.
+ * It should be instantiated in the private block of the class you want to put
+ * into an SkTInternalSList.
+ * For most use cases you should use SK_DECLARE_INTERNAL_SLIST_INTERFACE and not
+ * this macro. If you care about the field name, or want to re-use an existing
+ * field, then you can use this macro to declare the methods pointing to a
+ * specific field.
+ * Unlike SK_DECLARE_INTERNAL_SLIST_INTERFACE this does not declare the field
+ * itself.
+ * It also makes SkTInternalSList<ClassName> a friend to give it access to the
+ * methods.
+ */
+#define SK_DECLARE_INTERNAL_SLIST_ADAPTER(ClassName, field)     \
+    ClassName* getSListNext() {                                 \
+        return this->field;                                     \
+    }                                                           \
+    void setSListNext(ClassName* next) {                        \
+        this->field = next;                                     \
+    }                                                           \
+    friend class SkTInternalSList<ClassName>
+
+/**
+ * This macro declares an fSListNext that auto initializes to NULL and then
+ * uses SK_DECLARE_INTERNAL_SLIST_ADAPTER to add the methods needed by
+ * SkTInternalSList.
+ * It should be instantiated in the private block of the class you want to put
+ * into an SkTInternalSList.
+ */
+#define SK_DECLARE_INTERNAL_SLIST_INTERFACE(ClassName)          \
+    SK_DECLARE_INTERNAL_SLIST_ADAPTER(ClassName, fSListNext);   \
+    SkPtrWrapper<ClassName> fSListNext
+
+/**
+ * An implementation of an intrusive singly linked list.
+ * The type T must have a methods getSListNext and setSListNext that are visible
+ * to the list. The easiest way to do this is with
+ * SK_DECLARE_INTERNAL_SLIST_INTERFACE.
+ * The list does not maintain ownership of any of its elements, or ever delete
+ * them.
+ */
+template<typename T> class SkTInternalSList {
+public:
+    SkTInternalSList() : fHead(NULL), fCount(0) {}
+
+    /**
+     * Push an item onto the head of the list.
+     * This method is *not* thread safe.
+     */
+    void push(T* entry) {
+        SkASSERT(entry->getSListNext() == NULL);
+        entry->setSListNext(fHead);
+        fHead = entry;
+        ++fCount;
+    }
+
+    /**
+     * Takes all the items from another list and pushes them into this list.
+     * No ordering guarantees are made, the other list will be emptied.
+     * This method is *not* thread safe.
+     */
+    void pushAll(SkTInternalSList<T>* other) {
+        if (this->isEmpty()) {
+            this->swap(other);
+            return;
+        }
+        while (!other->isEmpty()) {
+            this->push(other->pop());
+        }
+    }
+
+    /**
+     * Pop an item from the head of the list.
+     * Returns NULL if the list is empty.
+     * This method is *not* thread safe.
+     */
+    T* pop() {
+        if (NULL == fHead) {
+            return NULL;
+        }
+        T* result = fHead;
+        fHead = result->getSListNext();
+        result->setSListNext(NULL);
+        --fCount;
+        return result;
+    }
+
+    T* head() const {
+        return fHead;
+    }
+
+    /**
+     * Returns true if the list has no elements.
+     */
+    bool isEmpty() const {
+        return NULL == fHead;
+    }
+
+    /**
+     * Swaps the contents of this list with another one.
+     * This method is *not* thread safe.
+     */
+    void swap(SkTInternalSList<T>* other) {
+        SkTSwap(fHead, other->fHead);
+        SkTSwap(fCount, other->fCount);
+    }
+
+    /**
+     * Returns the count of elements in the list.
+     */
+    int getCount() const {
+        return fCount;
+    }
+private:
+    T* fHead;
+    int fCount;
+};
+
+
+#endif
diff --git a/src/core/SkTObjectPool.h b/src/core/SkTObjectPool.h
new file mode 100644 (file)
index 0000000..01e8e8e
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkFreeList_DEFINED
+#define SkFreeList_DEFINED
+
+#include "SkTInternalSList.h"
+
+/**
+ * An implementation of a self growing pool of objects.
+ * It maintains a pool of fully initialized objects. If an attempt is made to
+ * acquire one, and there are none left, it makes some more.
+ * It does not automatically reclaim them, they have to be given back to it.
+ * Constructors will be called on objects allocated by the pool at allocation
+ * time.
+ * All allocated objects will be destroyed and memory will be reclaimed when
+ * the pool is destroyed, so the pool must survive longer than you are using
+ * any item taken from it.
+ */
+template<typename T, int numItemsPerBlock = 4096/sizeof(T)> class SkTObjectPool {
+public:
+    SkTObjectPool() {}
+    ~SkTObjectPool() {
+        while (!fBlocks.isEmpty()) {
+            SkDELETE(fBlocks.pop());
+        }
+    }
+
+    /**
+     * Get an item from the pool.
+     * If the pool has no free items, it will allocate and construct some more.
+     * The returned item is only valid as long as the pool has not been
+     * destroyed, at that point all memory allocated by grow will have been
+     * reclaimed.
+     * This method is *not* thread safe.
+     */
+    T* acquire() {
+        if (fAvailable.isEmpty()) {
+            grow();
+        }
+        return fAvailable.pop();
+    }
+
+    /**
+     * Release an item into the pool.
+     * The item does not have to have come from the pool, but if it did not
+     * it must have a lifetime greater than the pool does.
+     * This method is *not* thread safe.
+     */
+    void release(T* entry) {
+        fAvailable.push(entry);
+    }
+
+    /**
+     * Takes all the items from an SkTInternalSList and adds them back to this
+     * pool. The other list will be left empty.
+     */
+    void releaseAll(SkTInternalSList<T>* other) {
+        fAvailable.pushAll(other);
+    }
+
+    /**
+     * Returns the number of items immediately available without having to
+     * construct any new ones.
+     */
+    int available() const { return fAvailable.getCount(); }
+
+    /**
+     * Returns the number of blocks of items the pool has allocated so far.
+     */
+    int blocks() const { return fBlocks.getCount(); }
+
+private:
+    /**
+     * The type for a new block of entries for the list.
+     */
+    struct Block {
+        T entries[numItemsPerBlock];
+        SK_DECLARE_INTERNAL_SLIST_INTERFACE(Block);
+    };
+    SkTInternalSList<Block> fBlocks;
+    SkTInternalSList<T> fAvailable;
+
+    /**
+     * When the free list runs out of items, this method is called to allocate
+     * a new block of them.
+     * It calls the constructors and then pushes the nodes into the available
+     * list.
+     */
+    void grow() {
+        Block* block = SkNEW(Block);
+        fBlocks.push(block);
+        for(int index = 0; index < numItemsPerBlock; ++index) {
+            fAvailable.push(&block->entries[index]);
+        }
+    }
+
+};
+
+#endif
index a562da2..662cc37 100644 (file)
@@ -13,7 +13,7 @@
 
 static const size_t RTREE_MIN_CHILDREN = 6;
 static const size_t RTREE_MAX_CHILDREN = 11;
-static const size_t QUADTREE_MIN_CHILDREN = 4;
+static const size_t QUADTREE_MIN_CHILDREN = 0;
 static const size_t QUADTREE_MAX_CHILDREN = 0; // No hard limit for quadtree
 
 static const int NUM_RECTS = 200;
@@ -126,7 +126,9 @@ static void tree_test_main(SkBBoxHierarchy* tree, int minChildren, int maxChildr
         REPORTER_ASSERT(reporter, 0 == tree->getCount());
 
         // Then try immediate inserts
-        for (int i = 0; i < NUM_RECTS; ++i) {
+        tree->insert(rects[0].data, rects[0].rect);
+        tree->flushDeferredInserts();
+        for (int i = 1; i < NUM_RECTS; ++i) {
             tree->insert(rects[i].data, rects[i].rect);
         }
         run_queries(reporter, rand, rects, *tree);
@@ -138,7 +140,9 @@ static void tree_test_main(SkBBoxHierarchy* tree, int minChildren, int maxChildr
         REPORTER_ASSERT(reporter, 0 == tree->getCount());
 
         // And for good measure try immediate inserts, but in reversed order
-        for (int i = NUM_RECTS - 1; i >= 0; --i) {
+        tree->insert(rects[NUM_RECTS - 1].data, rects[NUM_RECTS - 1].rect);
+        tree->flushDeferredInserts();
+        for (int i = NUM_RECTS - 2; i >= 0; --i) {
             tree->insert(rects[i].data, rects[i].rect);
         }
         run_queries(reporter, rand, rects, *tree);
@@ -166,14 +170,14 @@ DEF_TEST(BBoxHierarchy, reporter) {
 
     // QuadTree
     {
-        SkQuadTree* quadtree = SkQuadTree::Create(
-            SkIRect::MakeLTRB(-MAX_SIZE, -MAX_SIZE, MAX_SIZE, MAX_SIZE));
+        SkQuadTree* quadtree = SkNEW_ARGS(SkQuadTree, (
+            SkIRect::MakeLTRB(-MAX_SIZE, -MAX_SIZE, MAX_SIZE, MAX_SIZE)));
         SkAutoUnref au(quadtree);
         tree_test_main(quadtree, QUADTREE_MIN_CHILDREN, QUADTREE_MAX_CHILDREN, reporter);
 
         // QuadTree that orders input rectangles on deferred insert.
-        SkQuadTree* unsortedQuadTree = SkQuadTree::Create(
-            SkIRect::MakeLTRB(-MAX_SIZE, -MAX_SIZE, MAX_SIZE, MAX_SIZE));
+        SkQuadTree* unsortedQuadTree = SkNEW_ARGS(SkQuadTree, (
+            SkIRect::MakeLTRB(-MAX_SIZE, -MAX_SIZE, MAX_SIZE, MAX_SIZE)));
         SkAutoUnref auo(unsortedQuadTree);
         tree_test_main(unsortedQuadTree, QUADTREE_MIN_CHILDREN, QUADTREE_MAX_CHILDREN, reporter);
     }
diff --git a/tests/ObjectPoolTest.cpp b/tests/ObjectPoolTest.cpp
new file mode 100644 (file)
index 0000000..404448e
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTObjectPool.h"
+#include "SkTObjectPool.h"
+#include "Test.h"
+
+class PoolEntry {
+public:
+private:
+    SK_DECLARE_INTERNAL_SLIST_INTERFACE(PoolEntry);
+};
+
+static const int kNumItemsPerBlock = 3;
+typedef SkTObjectPool<PoolEntry, kNumItemsPerBlock> ObjectPoolType;
+
+static bool verifyPool(skiatest::Reporter* reporter,
+                       const ObjectPoolType& pool,
+                       const char* stage,
+                       int available, int blocks) {
+    if (available != pool.available()) {
+        ERRORF(reporter, "%s - Pool available is %d not %d",
+               stage, pool.available(), available);
+        return false;
+    }
+    if (blocks != pool.blocks()) {
+        ERRORF(reporter, "%s - Pool blocks is %d not %d",
+               stage, pool.blocks(), blocks);
+        return false;
+    }
+    return true;
+}
+
+static const int kNumToAcquire = kNumItemsPerBlock * 5;
+static void testObjectPool(skiatest::Reporter* reporter) {
+    ObjectPoolType pool;
+    SkTInternalSList<PoolEntry> used;
+    verifyPool(reporter, pool, "empty", 0, 0);
+    for (int index = 0; index < kNumToAcquire; ++index) {
+        used.push(pool.acquire());
+        int blocks = (index / kNumItemsPerBlock) + 1;
+        int available = (blocks * kNumItemsPerBlock) - (index + 1);
+        if (!verifyPool(reporter, pool, "acquire", available, blocks)) {
+            return;
+        }
+    }
+    int available = pool.available();
+    int blocks = pool.blocks();
+    for (int index = 0; index < kNumToAcquire / 2; ++index) {
+        pool.release(used.pop());
+        ++available;
+        if (!verifyPool(reporter, pool, "release", available, blocks)) {
+            return;
+        }
+    }
+    available += used.getCount();
+    pool.releaseAll(&used);
+    REPORTER_ASSERT(reporter, used.isEmpty());
+    verifyPool(reporter, pool, "releaseAll", available, blocks);
+}
+
+DEF_TEST(ObjectPool, reporter) {
+    testObjectPool(reporter);
+}
diff --git a/tests/SListTest.cpp b/tests/SListTest.cpp
new file mode 100644 (file)
index 0000000..72a8281
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTInternalSList.h"
+#include "Test.h"
+
+class SListEntry {
+public:
+    SListEntry* next() { return getSListNext(); }
+private:
+    SK_DECLARE_INTERNAL_SLIST_INTERFACE(SListEntry);
+};
+
+static bool verifyEmptyList(skiatest::Reporter* reporter,
+                            const SkTInternalSList<SListEntry>& list,
+                            const char* stage) {
+
+    if (!list.isEmpty()) {
+        ERRORF(reporter, "%s - List not empty", stage);
+        return false;
+    }
+    if (0 != list.getCount()) {
+        ERRORF(reporter, "%s - List count is not zero, %d instead", stage, list.getCount());
+        return false;
+    }
+    if (NULL != list.head()) {
+        ERRORF(reporter, "%s - List has elements when empty", stage);
+        return false;
+    }
+    return true;
+}
+
+static bool verifyList(skiatest::Reporter* reporter,
+                       const SkTInternalSList<SListEntry>& list,
+                       const char* stage,
+                       SListEntry* start, int count, int step = 1) {
+    SListEntry* next = list.head();
+    if (list.getCount() != count) {
+        ERRORF(reporter, "%s - List was too short, %d instead of %d", stage, list.getCount(), count);
+        return false;
+    }
+    int index = 0;
+    for(SListEntry* value = start; index < count; value += step, ++index) {
+        if (NULL == next) {
+            ERRORF(reporter, "%s - List too short, should be %d", stage, count);
+            return false;
+        }
+        if (next!= value) {
+            ERRORF(reporter, "%s - List entries at index %d of %d don't match", stage, index, count);
+            return false;
+        }
+        next = next->next();
+    }
+    if (NULL != next) {
+        ERRORF(reporter, "%s - List too long, should be %d", stage, count);
+        return false;
+    }
+    return true;
+}
+
+static void testTInternalSList(skiatest::Reporter* reporter) {
+    // Build a test array of data
+    static const int testArraySize = 10;
+    SListEntry testArray[testArraySize];
+    // Basic add remove tests
+    SkTInternalSList<SListEntry> list;
+    verifyEmptyList(reporter, list, "start");
+    // Push values in, testing on the way
+    for (int index = 0; index < testArraySize; ++index) {
+        list.push(&testArray[index]);
+        if (!verifyList(reporter, list, "push", &testArray[index], index+1, -1)) {
+            return;
+        }
+    }
+    // Now remove them again
+    for (int index = testArraySize - 1; index >= 0; --index) {
+        REPORTER_ASSERT(reporter, &testArray[index] == list.pop());
+        if ((index != 0) &&
+            !verifyList(reporter, list, "pop", &testArray[index-1], index, -1)) {
+            return;
+        }
+    }
+    verifyEmptyList(reporter, list, "end");
+    // Move between list tests
+    for (int index = 0; index < testArraySize; ++index) {
+        list.push(&testArray[index]);
+    }
+    verifyList(reporter, list, "swap", &testArray[testArraySize-1], testArraySize, -1);
+    SkTInternalSList<SListEntry> other;
+    // Check swap moves the list over unchanged
+    other.swap(&list);
+    verifyEmptyList(reporter, list, "swap");
+    verifyList(reporter, other, "swap", &testArray[testArraySize-1], testArraySize, -1);
+    // Check pushAll optimizes to a swap when one of the is empty
+    list.pushAll(&other);
+    verifyList(reporter, list, "pushAll-empty", &testArray[testArraySize-1], testArraySize, -1);
+    verifyEmptyList(reporter, other, "pushAll-empty");
+    // Check pushAll when non empty works
+    other.push(list.pop());
+    other.pushAll(&list);
+    verifyEmptyList(reporter, list, "pushAll");
+    verifyList(reporter, other, "pushAll", &testArray[0], testArraySize, 1);
+}
+
+DEF_TEST(SList, reporter) {
+    testTInternalSList(reporter);
+}