diff --git a/include/crucible/pool.h b/include/crucible/pool.h new file mode 100644 index 0000000..ccb634b --- /dev/null +++ b/include/crucible/pool.h @@ -0,0 +1,185 @@ +#ifndef CRUCIBLE_POOL_H +#define CRUCIBLE_POOL_H + +#include +#include +#include +#include + +namespace crucible { + using namespace std; + + /// Storage for reusable anonymous objects that are too expensive to create and/or destroy frequently + + template + class Pool { + public: + using Ptr = shared_ptr; + using Generator = function; + using Checker = function; + + ~Pool(); + Pool(Generator f = Generator(), Checker checkin = Checker(), Checker checkout = Checker()); + + /// Function to create new objects when Pool is empty + void generator(Generator f); + + /// Optional function called when objects exit the pool (user handle is created and returned to user) + void checkout(Checker f); + + /// Optional function called when objects enter the pool (last user handle is destroyed) + void checkin(Checker f); + + /// Pool() returns a handle to an object of type shared_ptr + Ptr operator()(); + + /// Destroy all objects in Pool that are not in use + void clear(); + + private: + using ListType = list; + struct ListRep { + ListType m_list; + mutex m_mutex; + bool m_destroyed = false; + Checker m_checkin; + ListRep(Checker checkin); + }; + struct Handle { + shared_ptr m_list_rep; + Ptr m_ret_ptr; + Handle(shared_ptr list_rep, Ptr ret_ptr); + ~Handle(); + }; + + Generator m_fn; + Checker m_checkout; + shared_ptr m_list_rep; + }; + + template + Pool::ListRep::ListRep(Checker checkin) : + m_checkin(checkin) + { + } + + template + Pool::Pool(Generator f, Checker checkin, Checker checkout) : + m_fn(f), + m_checkout(checkout), + m_list_rep(make_shared(checkin)) + { + } + + template + Pool::~Pool() + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_destroyed = true; + m_list_rep->m_list.clear(); + } + + template + Pool::Handle::Handle(shared_ptr list_rep, Ptr ret_ptr) : + m_list_rep(list_rep), + m_ret_ptr(ret_ptr) + { + } + + template + Pool::Handle::~Handle() + { + unique_lock lock(m_list_rep->m_mutex); + + // Checkin prepares the object for storage and reuse. + // Neither of those will happen if there is no Pool. + // If the Pool was destroyed, just let m_ret_ptr expire. + if (!m_list_rep->m_destroyed) { + + // If a checkin function is defined, call it + auto checkin = m_list_rep->m_checkin; + if (checkin) { + lock.unlock(); + checkin(m_ret_ptr); + lock.lock(); + } + + // Place object back in pool + m_list_rep->m_list.push_front(m_ret_ptr); + + } + } + + template + typename Pool::Ptr + Pool::operator()() + { + Ptr rv; + + // Do we have an object in the pool we can return instead? + unique_lock lock(m_list_rep->m_mutex); + if (m_list_rep->m_list.empty()) { + // No, release cache lock and call the function + lock.unlock(); + + // Create new value + rv = m_fn(); + } else { + rv = m_list_rep->m_list.front(); + m_list_rep->m_list.pop_front(); + + // Release lock so we don't deadlock with Handle destructor + lock.unlock(); + } + + // rv now points to a new T object that is not in the list. + THROW_CHECK0(runtime_error, rv); + + // Construct a shared_ptr for Handle which will refcount the Handle objects + // and reinsert the T into the Pool when the last Handle is destroyed. + auto hv = make_shared(m_list_rep, rv); + + // If a checkout function is defined, call it + if (m_checkout) { + m_checkout(rv); + } + + // T an alias shared_ptr for the T using Handle's refcount. + return Ptr(hv, rv.get()); + } + + template + void + Pool::generator(Generator func) + { + unique_lock lock(m_list_rep->m_mutex); + m_fn = func; + } + + template + void + Pool::checkin(Checker func) + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_checkin = func; + } + + template + void + Pool::checkout(Checker func) + { + unique_lock lock(m_list_rep->m_mutex); + m_checkout = func; + } + + template + void + Pool::clear() + { + unique_lock lock(m_list_rep->m_mutex); + m_list_rep->m_list.clear(); + } + +} + +#endif // POOL_H