blob: e74ba895620b3eebfa18c24d9f3a1c360f396ddb [file] [log] [blame]
/*
* MVKObjectPool.h
*
* Copyright (c) 2015-2022 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 MVKObjectPoolCounts {
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;
};