| /* |
| * MVKObjectPool.h |
| * |
| * Copyright (c) 2015-2020 The Brenwill Workshop Ltd. (http://www.brenwill.com) |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| #pragma once |
| |
| #include "MVKBaseObject.h" |
| #include <mutex> |
| |
| |
| #pragma mark - |
| #pragma mark MVKLinkableMixin |
| |
| /** |
| * Instances of sublcasses of this mixin can participate in a typed linked list or pool. |
| * A simple implementation of the CRTP (https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). |
| */ |
| template <class T> |
| class MVKLinkableMixin { |
| |
| public: |
| |
| /** |
| * When participating in a linked list or pool, this is a reference to the next instance |
| * in the list or pool. This value should only be managed and set by the list or pool. |
| */ |
| T* _next = nullptr; |
| |
| protected: |
| friend T; |
| MVKLinkableMixin() {}; |
| }; |
| |
| |
| #pragma mark - |
| #pragma mark MVKObjectPool |
| |
| /** Track pool stats. */ |
| typedef struct { |
| uint64_t created = 0; |
| uint64_t alive = 0; |
| uint64_t resident = 0; |
| } MVKObjectPoolCounts; |
| |
| /** |
| * Manages a pool of instances of a particular object type. |
| * |
| * The objects managed by this pool should derive from MVKLinkableMixin, or otherwise |
| * support a public member variable named "_next", of the same object type, which is |
| * used by this pool to create a linked list of objects. |
| * |
| * When this pool is destroyed, any objects contained in the pool are also destroyed. |
| * |
| * This pool includes member functions for managing resources in either a thread-safe, |
| * or somewhat faster, but not-thread-safe manner. |
| * |
| * An instance of this pool can be configured to either manage a pool of objects, |
| * or simply allocate a new object instance on each request and destroy the object |
| * when it is released back to the pool. |
| */ |
| template <class T> |
| class MVKObjectPool : public MVKBaseObject { |
| |
| public: |
| |
| /** |
| * Acquires and returns the next available object from the pool, creating it if necessary. |
| * |
| * If this instance was configured to use pooling, the object is removed from the pool |
| * until it is returned back to the pool. If this instance was configured NOT to use |
| * pooling, the object is created anew on each request, and will be deleted when |
| * returned back to the pool. |
| * |
| * This method is not thread-safe. For a particular pool instance, all calls to |
| * aquireObject() and returnObject() must be made from the same thread. |
| */ |
| T* acquireObject() { |
| T* obj = nullptr; |
| if (_isPooling) { obj = nextObject(); } |
| if ( !obj ) { |
| obj = newObject(); |
| _counts.created++; |
| _counts.alive++; |
| } |
| |
| return obj; |
| } |
| |
| /** |
| * Returns the specified object back to the pool. |
| * |
| * If this instance was configured to use pooling, the returned object is added back |
| * into the pool. If this instance was configured NOT to use pooling, the returned |
| * object is simply deleted. |
| * |
| * This method is not thread-safe. For a particular pool instance, all calls to |
| * aquireObject() and returnObject() must be made from the same thread. |
| */ |
| void returnObject(T* obj) { |
| if ( !obj ) { return; } |
| |
| if (_isPooling) { |
| if (_tail) { _tail->_next = obj; } |
| obj->_next = nullptr; |
| _tail = obj; |
| if ( !_head ) { _head = obj; } |
| _counts.resident++; |
| } else { |
| destroyObject(obj); |
| } |
| } |
| |
| /** A thread-safe version of the acquireObject() function. */ |
| T* acquireObjectSafely() { |
| std::lock_guard<std::mutex> lock(_lock); |
| return acquireObject(); |
| } |
| |
| /** A thread-safe version of the returnObject() function. */ |
| void returnObjectSafely(T* obj) { |
| std::lock_guard<std::mutex> lock(_lock); |
| returnObject(obj); |
| } |
| |
| /** Clears all the objects from this pool, destroying each one. This method is thread-safe. */ |
| void clear() { |
| std::lock_guard<std::mutex> lock(_lock); |
| while ( T* obj = nextObject() ) { destroyObject(obj); } |
| } |
| |
| /** Returns the current counts. */ |
| MVKObjectPoolCounts getCounts() { return _counts; } |
| |
| /** |
| * Configures this instance to either use pooling, or not, depending on the |
| * value of isPooling, which defaults to true if not indicated explicitly. |
| */ |
| MVKObjectPool(bool isPooling = true) : _isPooling(isPooling) {} |
| |
| ~MVKObjectPool() override { clear(); } |
| |
| protected: |
| |
| /** |
| * Removes and returns the first object in this pool, or returns null if this pool |
| * contains no objects. This differs from the acquireObject() function, which creates |
| * and return a new instance if this pool is empty. This method is not thread-safe. |
| */ |
| T* nextObject() { |
| T* obj = _head; |
| if (obj) { |
| _head = (T*)obj->_next; // Will be null for last object in pool |
| if ( !_head ) { _tail = nullptr; } // If last, also clear tail |
| obj->_next = nullptr; // Objects in the wild should never think they are still part of this pool |
| _counts.resident--; |
| } |
| return obj; |
| } |
| |
| /** Returns a new instance of the type of object managed by this pool. */ |
| virtual T* newObject() = 0; |
| |
| /** Destroys the object. */ |
| void destroyObject(T* obj) { |
| obj->destroy(); |
| _counts.alive--; |
| } |
| |
| std::mutex _lock; |
| T* _head = nullptr; |
| T* _tail = nullptr; |
| bool _isPooling; |
| MVKObjectPoolCounts _counts; |
| }; |
| |