Index: gpu/command_buffer/common/id_allocator.cc |
diff --git a/gpu/command_buffer/common/id_allocator.cc b/gpu/command_buffer/common/id_allocator.cc |
index 61d2c4a91135381921171a4d1fd0f418fbf77d06..29ec66ae2d92443a8832efd17d794283a1825b23 100644 |
--- a/gpu/command_buffer/common/id_allocator.cc |
+++ b/gpu/command_buffer/common/id_allocator.cc |
@@ -6,155 +6,401 @@ |
#include "gpu/command_buffer/common/id_allocator.h" |
+#include <limits> |
+ |
#include "base/logging.h" |
namespace gpu { |
-IdAllocator::IdAllocator() {} |
+// IdAllocator stores the list of allocated ids in an AVL tree. The |
+// tree node |IdAllocator::Node| represents maximal closed interval |
+// (min, max) of allocated ids. The tree is ordered by the range |
+// minimum value. |
-IdAllocator::~IdAllocator() {} |
+struct IdAllocator::Node { |
+ ResourceId min; |
+ ResourceId max; |
+ Node* left; |
+ Node* right; |
+ int height; |
-ResourceId IdAllocator::AllocateID() { |
- ResourceId id; |
- ResourceIdSet::iterator iter = free_ids_.begin(); |
- if (iter != free_ids_.end()) { |
- id = *iter; |
- } else { |
- id = LastUsedId() + 1; |
- if (!id) { |
- // We wrapped around to 0. |
- id = FindFirstUnusedId(); |
- } |
+ Node(ResourceId min, ResourceId max) |
+ : min(min), max(max), left(NULL), right(NULL), height(0) {} |
+}; |
+ |
+IdAllocator::IdAllocator() : root_(NULL) { |
+} |
+ |
+IdAllocator::~IdAllocator() { |
+ if (root_ != NULL) { |
+ Destroy(root_); |
} |
- MarkAsUsed(id); |
- return id; |
+} |
+ |
+ResourceId IdAllocator::AllocateID() { |
+ return AllocateIDRange(1); |
} |
ResourceId IdAllocator::AllocateIDAtOrAbove(ResourceId desired_id) { |
- ResourceId id; |
- ResourceIdSet::iterator iter = free_ids_.lower_bound(desired_id); |
- if (iter != free_ids_.end()) { |
- id = *iter; |
- } else if (LastUsedId() < desired_id) { |
- id = desired_id; |
- } else { |
- id = LastUsedId() + 1; |
- if (!id) { |
- // We wrapped around to 0. |
- id = FindFirstUnusedId(); |
- } |
+ if (desired_id == 0) { |
+ desired_id = 1; |
+ } |
+ |
+ if (root_ == NULL) { |
+ root_ = Insert(desired_id, desired_id, root_); |
+ return desired_id; |
+ } |
+ |
+ ResourceId result = 0; |
+ root_ = AddRange(1, |
+ &result, |
+ desired_id, |
+ std::numeric_limits<ResourceId>::max(), |
+ root_, |
+ NULL, |
+ NULL); |
+ |
+ if (result == 0) { |
+ root_ = AddRange(1, |
+ &result, |
+ 1, |
+ std::numeric_limits<ResourceId>::max(), |
+ root_, |
+ NULL, |
+ NULL); |
} |
- MarkAsUsed(id); |
- return id; |
+ |
+ return result; |
} |
ResourceId IdAllocator::AllocateIDRange(uint32_t range) { |
- DCHECK(range > 0); |
- if (range == 1) { |
- return AllocateID(); |
- } |
- |
- ResourceId first_id = kInvalidResource; |
- |
- { |
- // Try to re-use ids from free_ids. |
- ResourceIdSet::const_iterator iter = free_ids_.begin(); |
- ResourceIdSet::const_iterator end = free_ids_.end(); |
- const ResourceIdSet::size_type size = free_ids_.size(); |
- if (iter != end && size >= range) { |
- ResourceId first = *iter; |
- ResourceIdSet::size_type needed_size = range; |
- ResourceId prev = first; |
- ++iter; |
- for (; iter != end && size >= needed_size; ++iter) { |
- if (prev + 1 == *iter) { |
- if (first + range - 1 == *iter) { |
- first_id = first; |
- break; |
- } |
- } else { |
- needed_size += prev - first + 1; |
- first = *iter; |
- } |
- prev = *iter; |
- } |
- } |
+ if (root_ == NULL) { |
+ root_ = Insert(1, range, NULL); |
+ return 1; |
} |
- if (first_id == kInvalidResource) { |
- // Allocate after the last id. |
- ResourceId first = LastUsedId() + 1; |
- if (first != 0) { |
- ResourceId last = first + range - 1; |
- if (first < last) { |
- first_id = first; |
- } |
+ ResourceId result = 0; |
+ root_ = AddRange(range, |
+ &result, |
+ 1, |
+ std::numeric_limits<ResourceId>::max(), |
+ root_, |
+ NULL, |
+ NULL); |
+ return result; |
+} |
+ |
+bool IdAllocator::MarkAsUsed(ResourceId id) { |
+ if (root_ == NULL) { |
+ root_ = Insert(id, id, NULL); |
+ return true; |
+ } |
+ |
+ ResourceId result = 0; |
+ root_ = AddRange(1, &result, id, id, root_, NULL, NULL); |
+ return result != 0; |
+} |
+ |
+void IdAllocator::FreeID(ResourceId id) { |
+ FreeIDRange(id, 1); |
+} |
+ |
+void IdAllocator::FreeIDRange(ResourceId first_id, uint32_t range) { |
+ if (root_ == NULL) { |
+ return; |
+ } |
+ |
+ root_ = RemoveRange(first_id, first_id + range - 1u, root_); |
+ DCHECK(root_ == NULL || root_->min > 0); |
+} |
+ |
+bool IdAllocator::InUse(ResourceId id) const { |
+ if (id == 0) { |
+ return false; |
+ } |
+ |
+ Node* pos = root_; |
+ while (pos != NULL) { |
+ if (id >= pos->min && id <= pos->max) { |
+ return true; |
+ } |
+ |
+ if (id < pos->min) { |
+ pos = pos->left; |
+ } else { |
+ pos = pos->right; |
} |
} |
- if (first_id == kInvalidResource) { |
- // Scan the gaps between used ids. |
- ResourceId prev = 0; |
- for (auto id : used_ids_) { |
- if (id - prev > range) { |
- first_id = prev + 1; |
- break; |
- } |
- prev = id; |
+ return false; |
+} |
+ |
+inline int IdAllocator::GetHeight(Node* node) { |
+ return node ? node->height : 0; |
+} |
+ |
+inline void IdAllocator::UpdateHeight(Node* node) { |
+ node->height = std::max(GetHeight(node->left), GetHeight(node->right)) + 1; |
+} |
+ |
+inline int IdAllocator::GetBalance(Node* node) { |
+ return node ? GetHeight(node->left) - GetHeight(node->right) : 0; |
+} |
+ |
+inline IdAllocator::Node* IdAllocator::RotateLeft(Node* node) { |
+ Node* new_root = node->right; |
+ node->right = new_root->left; |
+ new_root->left = node; |
+ UpdateHeight(node); |
+ UpdateHeight(new_root); |
+ return new_root; |
+} |
+ |
+inline IdAllocator::Node* IdAllocator::RotateRight(Node* node) { |
+ Node* new_root = node->left; |
+ node->left = new_root->right; |
+ new_root->right = node; |
+ UpdateHeight(node); |
+ UpdateHeight(new_root); |
+ return new_root; |
+} |
+ |
+IdAllocator::Node* IdAllocator::Balance(Node* node) { |
+ UpdateHeight(node); |
+ int balance = GetBalance(node); |
+ if (balance > 1) { |
+ if (GetBalance(node->left) >= 0) { |
+ return RotateRight(node); |
} |
+ node->left = RotateLeft(node->left); |
+ return RotateRight(node); |
} |
- if (first_id != kInvalidResource) { |
- for (uint32 ii = 0; ii < range; ++ii) { |
- MarkAsUsed(first_id + ii); |
+ if (balance < -1) { |
+ if (GetBalance(node->right) <= 0) { |
+ return RotateLeft(node); |
} |
+ node->right = RotateRight(node->right); |
+ return RotateLeft(node); |
} |
- return first_id; |
+ return node; |
} |
-bool IdAllocator::MarkAsUsed(ResourceId id) { |
- DCHECK(id); |
- free_ids_.erase(id); |
- std::pair<ResourceIdSet::iterator, bool> result = used_ids_.insert(id); |
- return result.second; |
+// Finds the largest node of |max_subtree| and stores its |
+// contents to |to_replace|. Deletes the found node. |
+IdAllocator::Node* IdAllocator::ReplaceMax(Node* to_replace, |
+ Node* max_subtree) { |
+ if (max_subtree->right != NULL) { |
+ max_subtree->right = ReplaceMax(to_replace, max_subtree->right); |
+ return Balance(max_subtree); |
+ } |
+ |
+ to_replace->min = max_subtree->min; |
+ to_replace->max = max_subtree->max; |
+ Node* result = max_subtree->left; |
+ delete max_subtree; |
+ return result; |
} |
-void IdAllocator::FreeID(ResourceId id) { |
- if (id) { |
- used_ids_.erase(id); |
- free_ids_.insert(id); |
+IdAllocator::Node* IdAllocator::Delete(Node* node) { |
+ if (node->left != NULL && node->right != NULL) { |
+ node->left = ReplaceMax(node, node->left); |
+ return Balance(node); |
} |
+ |
+ Node* result = node->left != NULL ? node->left : node->right; |
+ delete node; |
+ return result; |
} |
-void IdAllocator::FreeIDRange(ResourceId first_id, uint32 range) { |
- for (uint32 ii = 0; ii < range; ++ii) { |
- FreeID(first_id + ii); |
+IdAllocator::Node* IdAllocator::Insert(ResourceId min, |
+ ResourceId max, |
+ Node* pos) { |
+ if (pos == NULL) { |
+ return new Node(min, max); |
} |
-} |
-bool IdAllocator::InUse(ResourceId id) const { |
- return id == kInvalidResource || used_ids_.find(id) != used_ids_.end(); |
+ if (min < pos->min) { |
+ pos->left = Insert(min, max, pos->left); |
+ } else { |
+ pos->right = Insert(min, max, pos->right); |
+ } |
+ |
+ return Balance(pos); |
} |
-ResourceId IdAllocator::LastUsedId() const { |
- if (used_ids_.empty()) { |
- return 0u; |
+// Allocates |range| amount of ids and stores the first id allocated |
+// in |first_id_out|. In case of failed allocation, |first_id_out| |
+// remains 0. The allocated id range will start at the smallest possible |
+// value between closed range [|first_id_min|, |first_id_max|]. |
+ |
+// Works by recursing through the tree. When taking the left branch |
+// (searching the smaller id space), |right_limit| is updated. When taking |
+// the right branch (searching the larger id space), |left_limit| is updated. |
+// When the id space is found, uses |left_limit| or |right_limit| to identify |
+// whether the new id range needs to be merged at the end of the limiter or the |
+// current position in the tree. Additional option the special case where passed |
+// in |
+// |first_id_min| causes a new range to be created. |
+IdAllocator::Node* IdAllocator::AddRange(uint32_t range, |
+ ResourceId* first_id_out, |
+ ResourceId first_id_min, |
+ ResourceId first_id_max, |
+ Node* node, |
+ Node* left_limit, |
+ Node* right_limit) { |
+ DCHECK(*first_id_out == 0); |
+ |
+ ResourceId new_range_max = first_id_min + range - 1u; |
+ if (new_range_max < first_id_min) { |
+ return node; |
+ } |
+ |
+ if (node->min > new_range_max) { |
+ if (node->left != NULL) { |
+ node->left = AddRange(range, |
+ first_id_out, |
+ first_id_min, |
+ first_id_max, |
+ node->left, |
+ left_limit, |
+ node); |
+ if (*first_id_out != 0) { |
+ return Balance(node); |
+ } |
+ } else { |
+ *first_id_out = first_id_min; |
+ bool merges_node = node->min - new_range_max == 1u; |
+ bool merges_left_limit = |
+ left_limit != NULL && (first_id_min - left_limit->max == 1u); |
+ if (merges_node && merges_left_limit) { |
+ left_limit->max = node->max; |
+ return Delete(node); |
+ } else if (merges_node) { |
+ node->min = first_id_min; |
+ return node; |
+ } else if (merges_left_limit) { |
+ left_limit->max = new_range_max; |
+ return node; |
+ } |
+ return Insert(first_id_min, new_range_max, node); |
+ } |
+ } |
+ |
+ if (first_id_min <= node->max) { |
+ first_id_min = node->max + 1u; |
+ if (first_id_min < node->max) { |
+ return node; |
+ } |
+ } |
+ |
+ if (first_id_min > first_id_max) { |
+ return node; |
+ } |
+ |
+ if (node->right != NULL) { |
+ node->right = AddRange(range, |
+ first_id_out, |
+ first_id_min, |
+ first_id_max, |
+ node->right, |
+ node, |
+ right_limit); |
+ if (*first_id_out != 0) { |
+ return Balance(node); |
+ } |
+ return node; |
+ } |
+ |
+ new_range_max = first_id_min + range - 1u; |
+ if (new_range_max < first_id_min) { |
+ return node; |
+ } |
+ |
+ if (right_limit != NULL) { |
+ if (new_range_max < right_limit->min) { |
+ *first_id_out = first_id_min; |
+ if (right_limit->min - new_range_max == 1u) { |
+ right_limit->min = node->min; |
+ return Delete(node); |
+ } |
+ node->max = new_range_max; |
+ return node; |
+ } |
} else { |
- return *used_ids_.rbegin(); |
+ *first_id_out = first_id_min; |
+ if (first_id_min - node->max == 1u) { |
+ node->max = new_range_max; |
+ return node; |
+ } |
+ return Insert(first_id_min, new_range_max, node); |
} |
+ |
+ return node; |
} |
-ResourceId IdAllocator::FindFirstUnusedId() const { |
- ResourceId id = 1; |
- for (ResourceIdSet::const_iterator it = used_ids_.begin(); |
- it != used_ids_.end(); ++it) { |
- if ((*it) != id) { |
- return id; |
+IdAllocator::Node* IdAllocator::RemoveRange(ResourceId first_id, |
+ ResourceId last_id, |
+ Node* pos) { |
+ if (pos == NULL) { |
+ return NULL; |
+ } |
+ |
+ if (first_id < pos->min) { |
+ pos->left = RemoveRange(first_id, std::min(pos->min, last_id), pos->left); |
+ } |
+ |
+ if (last_id > pos->max) { |
+ pos->right = RemoveRange(std::max(pos->max, first_id), last_id, pos->right); |
+ } |
+ |
+ if (first_id <= pos->min) { |
+ if (last_id >= pos->min) { |
+ ResourceId old_max = pos->max; |
+ if (last_id < old_max) { |
+ pos->min = last_id + 1u; |
+ pos->max = old_max; |
+ } else { |
+ pos = Delete(pos); |
+ } |
+ } |
+ } else if (first_id <= pos->max) { |
+ ResourceId old_max = pos->max; |
+ pos->max = first_id - 1u; |
+ if (last_id < old_max) { |
+ pos = Insert(last_id + 1u, old_max, pos); |
+ } |
+ } |
+ |
+ if (pos == NULL) { |
+ return NULL; |
+ } |
+ |
+ // The nodes |pos->left| and |pos->right| are both |
+ // balanced. However, |pos| is potentially unbalanced. Since |
+ // multiple operations might be done during update of |pos->left| and |
+ // |pos->right|, we must balance |pos| multiple times. |
+ while (true) { |
+ int balance = GetBalance(pos); |
+ if (balance >= -1 && balance <= 1) { |
+ break; |
} |
- ++id; |
+ pos = Balance(pos); |
} |
- return id; |
+ |
+ return pos; |
+} |
+ |
+void IdAllocator::Destroy(Node* pos) { |
+ do { |
+ if (pos->left != NULL) { |
+ Destroy(pos->left); |
+ } |
+ Node* right = pos->right; |
+ delete pos; |
+ pos = right; |
+ } while (pos != NULL); |
} |
} // namespace gpu |