Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ref-implicitdecoration' into dev…
Browse files Browse the repository at this point in the history
…elop

Conflicts:
	autowiring/AutoPacket.h
  • Loading branch information
codemercenary committed Jul 31, 2014
2 parents 6cb6832 + 9ca76a6 commit 34d9afd
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 100 deletions.
2 changes: 1 addition & 1 deletion autowiring/AutoFilterDescriptor.h
Expand Up @@ -448,4 +448,4 @@ namespace std {
return (size_t) subscriber.GetAutoFilter()->ptr();
}
};
}
}
80 changes: 65 additions & 15 deletions autowiring/AutoPacket.h
Expand Up @@ -7,12 +7,13 @@
#include "is_shared_ptr.h"
#include "ObjectPool.h"
#include "is_any.h"
#include <sstream>
#include <typeinfo>
#include MEMORY_HEADER
#include TYPE_INDEX_HEADER
#include STL_UNORDERED_MAP
#include EXCEPTION_PTR_HEADER

struct SatCounter;
class AutoPacketFactory;
class AutoPacketProfiler;
struct AutoFilterDescriptor;
Expand All @@ -37,12 +38,12 @@ class AutoPacket:
{
private:
AutoPacket(const AutoPacket& rhs) = delete;
AutoPacket(AutoPacketFactory& factory);
AutoPacket(AutoPacketFactory& factory, const std::shared_ptr<Object>& outstanding);

public:
~AutoPacket(void);

static ObjectPool<AutoPacket> CreateObjectPool(AutoPacketFactory& factory);
static ObjectPool<AutoPacket> CreateObjectPool(AutoPacketFactory& factory, const std::shared_ptr<Object>& outstanding);

private:
// A back-link to the previously issued packet in the packet sequence. May potentially be null,
Expand All @@ -57,16 +58,50 @@ class AutoPacket:
typedef std::unordered_map<std::type_index, DecorationDisposition> t_decorationMap;
t_decorationMap m_decorations;

// Outstanding count local and remote holds:
std::shared_ptr<Object> m_outstanding;
const std::shared_ptr<Object>& m_outstandingRemote;

/// <summary>
/// Last change call with unsatisfied optional arguments
/// Resets satisfaction counters and decoration status.
/// </summary>
void ResolveOptions(void);
/// <remarks>
/// Is it expected that AutoPacketFactory will call methods in the following order:
/// AutoPacket(); //Construction in ObjectPool
/// Initialize(); //Issued from ObjectPool
/// Decorate();
/// ... //More Decorate calls
/// Finalize(); //Returned to ObjectPool
/// Initialize();
/// ... //More Issue & Return cycles
/// ~AutoPacket(); //Destruction in ObjectPool
/// Reset() must be called before the body of Initialize() in order to begin in the
/// correct state. It must also be called after the body of Finalize() in order to
/// avoid holding shared_ptr references.
/// Therefore Reset() is called at the conclusion of both AutoPacket() and Finalize().
/// </remarks>
void Reset(void);

/// <summary>
/// Resets counters, then decrements subscribers requiring AutoPacket argument.
/// Decrements subscribers requiring AutoPacket argument then calls all initializing subscribers.
/// </summary>
/// <remarks>
/// Initialize is called when a packet is issued by the AutoPacketFactory.
/// It is not called when the Packet is created since that could result in
/// spurious calls when no packet is issued.
/// </remarks>
void Initialize(void);

/// <summary>
/// Last chance call with unsatisfied optional arguments.
/// </summary>
/// <remarks>
/// This is called when the packet is returned to the AutoPacketFactory.
/// It is not called when the Packet is destroyed, since that could result in
/// suprious calles when no packet is issued.
/// </remarks>
void Finalize(void);

/// <summary>
/// Marks the specified entry as being unsatisfiable
/// </summary>
Expand Down Expand Up @@ -138,8 +173,12 @@ class AutoPacket:
template<class T>
const T& Get(void) const {
const T* retVal;
if(!Get(retVal))
throw_rethrowable autowiring_error("Attempted to obtain a value which was not decorated on this packet");
if(!Get(retVal)) {
std::stringstream ss;
ss << "Attempted to obtain a type " << typeid(retVal).name()
<< " which was not decorated on this packet";
throw std::runtime_error(ss.str());
}
return *retVal;
}

Expand Down Expand Up @@ -214,11 +253,18 @@ class AutoPacket:
{
std::lock_guard<std::mutex> lk(m_lock);
auto& entry = m_decorations[typeid(type)];
if(entry.satisfied)
throw std::runtime_error("Cannot decorate this packet with type T, the requested decoration already exists");
if(entry.isCheckedOut)
throw std::runtime_error("Cannot check out this decoration, it's already checked out elsewhere");

if (entry.satisfied) {
std::stringstream ss;
ss << "Cannot decorate this packet with type " << typeid(*ptr).name()
<< ", the requested decoration already exists";
throw std::runtime_error(ss.str());
}
if(entry.isCheckedOut) {
std::stringstream ss;
ss << "Cannot check out decoration of type " << typeid(*ptr).name()
<< ", it is already checked out elsewhere";
throw std::runtime_error(ss.str());
}
entry.isCheckedOut = true;
entry.wasCheckedOut = true;
m_decorations[typeid(type)].m_decoration = ptr;
Expand Down Expand Up @@ -318,8 +364,12 @@ class AutoPacket:
std::lock_guard<std::mutex> lk(m_lock);
for(size_t i = 0; i < sizeof...(Ts); i++) {
pTypeSubs[i] = &m_decorations[*sc_typeInfo[i]];
if(pTypeSubs[i]->wasCheckedOut)
throw std::runtime_error("Cannot perform immediate decoration with type T, the requested decoration already exists");
if(pTypeSubs[i]->wasCheckedOut) {
std::stringstream ss;
ss << "Cannot perform immediate decoration with type " << sc_typeInfo[i]->name()
<< ", the requested decoration already exists";
throw std::runtime_error(ss.str());
}

// Mark the entry as appropriate:
pTypeSubs[i]->satisfied = true;
Expand Down
3 changes: 3 additions & 0 deletions autowiring/AutoPacketFactory.h
Expand Up @@ -119,4 +119,7 @@ class AutoPacketFactory:
/// satisfaction graph
/// </summary>
std::shared_ptr<AutoPacket> NewPacket(void);

/// <returns>the number of outstanding AutoPackets</returns>
size_t GetOutstanding(void) const { return m_packets.GetOutstanding(); }
};
84 changes: 46 additions & 38 deletions autowiring/ObjectPool.h
Expand Up @@ -16,7 +16,10 @@ T* DefaultCreate(void) {
}

template<typename T>
void DefaultReset(T&){}
void DefaultInitialize(T&){}

template<typename T>
void DefaultFinalize(T&){}

/// <summary>
/// Allows the management of a pool of objects based on an embedded factory
Expand All @@ -43,25 +46,28 @@ class ObjectPool
size_t limit = ~0,
size_t maxPooled = ~0,
const std::function<T*()>& alloc = &DefaultCreate<T>,
const std::function<void(T&)>& rx = &DefaultReset<T>
const std::function<void(T&)>& initial = &DefaultInitialize<T>,
const std::function<void(T&)>& final = &DefaultFinalize<T>
) :
m_monitor(std::make_shared<ObjectPoolMonitor>(this)),
m_poolVersion(0),
m_maxPooled(maxPooled),
m_limit(limit),
m_outstanding(0),
m_rx(rx),
m_alloc(alloc)
m_alloc(alloc),
m_initial(initial),
m_final(final)
{}

/// <param name="limit">The maximum number of objects this pool will allow to be outstanding at any time</param>
ObjectPool(
const std::function<T*()>& alloc,
const std::function<void(T&)>& rx = &DefaultReset<T>,
const std::function<void(T&)>& initial = &DefaultInitialize<T>,
const std::function<void(T&)>& final = &DefaultFinalize<T>,
size_t limit = ~0,
size_t maxPooled = ~0
) :
ObjectPool(limit, maxPooled, alloc, rx)
ObjectPool(limit, maxPooled, alloc, initial, final)
{}

ObjectPool(ObjectPool&& rhs)
Expand Down Expand Up @@ -89,31 +95,43 @@ class ObjectPool
// time the ClearCachedEntities method is called, and causes entities which might be trying
// to return to the pool to instead free themselves.
size_t m_poolVersion;
std::vector<std::shared_ptr<T>> m_objs;
std::vector<std::unique_ptr<T>> m_objs;

size_t m_maxPooled;
size_t m_limit;
size_t m_outstanding;

// Resetter:
std::function<void(T&)> m_rx;
// Resetters:
std::function<void(T&)> m_initial;
std::function<void(T&)> m_final;

// Allocator:
std::function<T*()> m_alloc;

/// <summary>
/// Creates a shared pointer to wrap the specified object
/// Creates a shared pointer to wrap the specified object while it is issued
/// </summary>
/// <remarks>
/// The Initialize is applied immediate when Wrap is called.
/// The Finalize function will be applied is in the shared_ptr destructor.
/// </remarks>
std::shared_ptr<T> Wrap(T* pObj) {
// Initialize the issued object
m_initial(*pObj);

// Fill the shared pointer with the object we created, and ensure that we override
// the destructor so that the object is returned to the pool when it falls out of
// scope.
size_t poolVersion = m_poolVersion;
auto monitor = m_monitor;
std::function<void(T&)> final = m_final;

return std::shared_ptr<T>(
pObj,
[poolVersion, monitor](T* ptr) {
[poolVersion, monitor, final](T* ptr) {
// Finalize object before destruction or return to pool
final(*ptr);

// Default behavior will be to destroy the pointer
std::unique_ptr<T> unique(ptr);

Expand All @@ -132,6 +150,7 @@ class ObjectPool
}

void Return(size_t poolVersion, std::unique_ptr<T>& unique) {
// ASSERT: Object has already been finalized
// Always decrement the count when an object is no longer outstanding
assert(m_outstanding);
m_outstanding--;
Expand All @@ -143,9 +162,8 @@ class ObjectPool
// Object pool needs to be capable of accepting another object as an input
m_objs.size() < m_maxPooled
) {
// Reset the object and put it back in the pool:
m_rx(*unique);
m_objs.push_back(Wrap(unique.release()));
// Return the object to the pool:
m_objs.emplace_back(std::move(unique));
}

// If the new outstanding count is less than or equal to the limit, wake up any waiters:
Expand All @@ -170,13 +188,14 @@ class ObjectPool
lk.unlock();

// We failed to recover an object, create a new one:
return Wrap(m_alloc());
auto obj = Wrap(m_alloc());
return obj;
}

// Remove, return:
auto obj = m_objs.back();
m_objs.pop_back();
return obj;
// Transition from pooled to issued:
std::shared_ptr<T> iObj = Wrap(m_objs.back().release()); // Takes ownership
m_objs.pop_back(); // Removes non-referencing object
return iObj;
}

public:
Expand Down Expand Up @@ -207,19 +226,13 @@ class ObjectPool
/// </remarks>
void ClearCachedEntities(void) {
// Declare this first, so it's freed last:
std::vector<std::shared_ptr<T>> objs;
std::vector<std::unique_ptr<T>> objs;

// Move all of our objects into a local variable which we can then free at our leisure. This allows us to
// perform destruction outside of the scope of a lock, preventing any deadlocks that might occur inside
// the shared_ptr cleanup lambda.
// Default destructor is using in object pool, so it is safe to destroy all
// in pool while holding lock.
std::lock_guard<std::mutex> lk(*m_monitor);
m_poolVersion++;

// Swap the cached object collection with an empty one
std::swap(objs, m_objs);

// Technically, all of these entities are now outstanding. Update accordingly.
m_outstanding += objs.size();
m_objs.clear();
}

/// <summary>
Expand All @@ -233,20 +246,14 @@ class ObjectPool
void SetMaximumPooledEntities(size_t maxPooled) {
m_maxPooled = maxPooled;
for(;;) {
std::shared_ptr<T> prior;
std::lock_guard<std::mutex> lk(*m_monitor);

// Space check:
if(m_objs.size() <= m_maxPooled)
// Managed to get the size down sufficiently, we can continue:
return;

// Removing an entry from the cache, must increase the outstanding count at this point
m_outstanding++;

// Funny syntax needed to ensure destructors run while we aren't holding any locks. The prior
// shared_ptr will be reset after the lock is released, guaranteeing the desired ordering.
prior = m_objs.back();
// Remove unique pointer
m_objs.pop_back();
}
}
Expand Down Expand Up @@ -402,12 +409,13 @@ class ObjectPool
std::swap(m_monitor, rhs.m_monitor);

m_poolVersion = rhs.m_poolVersion;
m_objs = rhs.m_objs;
m_maxPooled = rhs.m_maxPooled;
m_limit = rhs.m_limit;
m_outstanding = rhs.m_outstanding;
std::swap(m_rx, rhs.m_rx);
std::swap(m_objs, rhs.m_objs);
std::swap(m_alloc, rhs.m_alloc);
std::swap(m_initial, rhs.m_initial);
std::swap(m_final, rhs.m_final);

// Now we can take ownership of this monitor object:
m_monitor->SetOwner(this);
Expand Down

0 comments on commit 34d9afd

Please sign in to comment.