From b91b7092b4cd918e24942f50ab1bfff9bdd76d51 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 25 Feb 2015 09:18:42 -0800 Subject: [PATCH 01/18] Simplification of the TypeIdentifier concept There's no need to construct `TypeIdentifier` instances as shared pointers because there is no mutable data stored here. Instead, make use of the underlying interface directly in all places where GetTypeIdentifier would have been used. --- autowiring/CoreObjectDescriptor.h | 6 ++---- autowiring/EventRegistry.h | 30 +++++++++--------------------- autowiring/TypeIdentifier.h | 23 ++++++++--------------- autowiring/TypeRegistry.h | 21 ++++++--------------- src/autonet/AutoNetServerImpl.cpp | 6 +++--- src/autonet/AutoNetServerImpl.hpp | 2 +- 6 files changed, 29 insertions(+), 59 deletions(-) diff --git a/autowiring/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index b5629d179..eba7251be 100644 --- a/autowiring/CoreObjectDescriptor.h +++ b/autowiring/CoreObjectDescriptor.h @@ -37,11 +37,9 @@ struct CoreObjectDescriptor { pBoltBase(autowiring::fast_pointer_cast(value)), receivesEvents( [this]{ - for (auto evt = g_pFirstEventEntry; evt; evt = evt->pFlink) { - auto identifier = evt->NewTypeIdentifier(); - if (identifier->IsSameAs(pCoreObject.get())) + for (auto evt = g_pFirstEventEntry; evt; evt = evt->pFlink) + if (evt->IsSameAs(pCoreObject.get())) return true; - } // "T" not found in event registry return false; }() diff --git a/autowiring/EventRegistry.h b/autowiring/EventRegistry.h index 61271b97e..ad701e2e3 100644 --- a/autowiring/EventRegistry.h +++ b/autowiring/EventRegistry.h @@ -6,7 +6,9 @@ class JunctionBoxBase; -struct EventRegistryEntry { +struct EventRegistryEntry: + public TypeIdentifierBase +{ EventRegistryEntry(const std::type_info& ti); // Next entry in the list: @@ -15,20 +17,10 @@ struct EventRegistryEntry { // Type of this entry: const std::type_info& ti; - /// - /// The runtime type information corresponding to this entry - /// - virtual const std::type_info& GetTypeInfo(void) const = 0; - /// /// Constructor method, used to generate a new junction box /// virtual std::shared_ptr NewJunctionBox(void) const = 0; - - /// - /// Used to create a type identifier value, for use with AutoNet - /// - virtual std::shared_ptr NewTypeIdentifier(void) const = 0; }; template @@ -38,20 +30,16 @@ struct EventRegistryEntryT: EventRegistryEntryT(void): EventRegistryEntry(typeid(T)) {} - - virtual const std::type_info& GetTypeInfo(void) const override { return typeid(T); } - + virtual const std::type_info& GetTypeInfo(void) const override { return typeid(T); } + virtual std::shared_ptr NewJunctionBox(void) const override { - return std::static_pointer_cast( - std::make_shared>() - ); + return std::make_shared>(); } - std::shared_ptr NewTypeIdentifier(void) const override { - return std::static_pointer_cast( - std::make_shared>() - ); + // true if "obj" is an event receiver for T + bool IsSameAs(const CoreObject* obj) const override { + return !!dynamic_cast(obj); } }; diff --git a/autowiring/TypeIdentifier.h b/autowiring/TypeIdentifier.h index 73db0b5f9..e41898711 100644 --- a/autowiring/TypeIdentifier.h +++ b/autowiring/TypeIdentifier.h @@ -5,20 +5,13 @@ // Checks if an Object* listens to a event T; struct TypeIdentifierBase { - virtual bool IsSameAs(const CoreObject* obj) = 0; - virtual const std::type_info& Type() = 0; -}; + /// + /// The runtime type information corresponding to this identifier + /// + virtual const std::type_info& GetTypeInfo(void) const = 0; -template - struct TypeIdentifier: -public TypeIdentifierBase -{ - // true if "obj" is an event receiver for T - bool IsSameAs(const CoreObject* obj) override { - return !!dynamic_cast(obj); - } - - const std::type_info& Type() override { - return typeid(T); - } + /// + /// True if this type identifier can cast the specified CoreObject + /// + virtual bool IsSameAs(const CoreObject* obj) const = 0; }; diff --git a/autowiring/TypeRegistry.h b/autowiring/TypeRegistry.h index 8295aacd3..6ad9be58a 100644 --- a/autowiring/TypeRegistry.h +++ b/autowiring/TypeRegistry.h @@ -10,7 +10,9 @@ namespace autowiring { void InjectCurrent(void); } -struct TypeRegistryEntry { +struct TypeRegistryEntry: + public TypeIdentifierBase +{ TypeRegistryEntry(const std::type_info& ti); // Next entry in the list: @@ -19,16 +21,6 @@ struct TypeRegistryEntry { // Type of this entry: const std::type_info& ti; - /// - /// The runtime type information corresponding to this entry - /// - virtual const std::type_info& GetTypeInfo(void) const = 0; - - /// - /// Used to create a type identifier value, for use with AutoNet - /// - virtual std::shared_ptr NewTypeIdentifier(void) const = 0; - /// /// Returns true if an injection is possible on the described type /// @@ -57,10 +49,9 @@ struct TypeRegistryEntryT: virtual const std::type_info& GetTypeInfo(void) const override { return typeid(T); } - std::shared_ptr NewTypeIdentifier(void) const override { - return std::static_pointer_cast( - std::make_shared>() - ); + // true if "obj" is an event receiver for T + bool IsSameAs(const CoreObject* obj) const override { + return !!dynamic_cast(obj); } bool CanInject(void) const override { diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index 123f9ebbc..ad36d30fb 100644 --- a/src/autonet/AutoNetServerImpl.cpp +++ b/src/autonet/AutoNetServerImpl.cpp @@ -57,7 +57,7 @@ AutoNetServerImpl::AutoNetServerImpl(std::unique_ptr&& transpo // Generate list of all events from event registry for(auto event = g_pFirstEventEntry; event; event = event->pFlink) - m_EventTypes.insert(event->NewTypeIdentifier()); + m_EventTypes.insert(event); } AutoNetServerImpl::~AutoNetServerImpl(){} @@ -233,8 +233,8 @@ void AutoNetServerImpl::NewObject(CoreContext& ctxt, const CoreObjectDescriptor& { Json::array listenerTypes; for(const auto& event : m_EventTypes) { - if(event->IsSameAs(object.pCoreObject.get())) - listenerTypes.push_back(autowiring::demangle(event->Type())); + if (object.receivesEvents) + listenerTypes.push_back(autowiring::demangle(event->GetTypeInfo())); } if(!listenerTypes.empty()) diff --git a/src/autonet/AutoNetServerImpl.hpp b/src/autonet/AutoNetServerImpl.hpp index 132a0dc85..c8b98dd3b 100644 --- a/src/autonet/AutoNetServerImpl.hpp +++ b/src/autonet/AutoNetServerImpl.hpp @@ -145,7 +145,7 @@ class AutoNetServerImpl: std::map m_ContextPtrs; // All event types - std::set> m_EventTypes; + std::set m_EventTypes; // All ContextMembers std::map> m_AllTypes; From 9feda809afddf074d92a2132e07e4987da2d8c87 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 25 Feb 2015 11:12:50 -0800 Subject: [PATCH 02/18] AutoPacket::Get must throw exceptions for multiply present decorations This is because this variant assumes that there are zero or one decorations present on the packet; when this assumption is violated, we shouldn't simply return control to the caller as though there are no decorations present; rather, this exceptional case should be handled expressly as it indicates a violation of convention has taken place. --- autowiring/AutoPacket.h | 41 +++++++++++++++++++++----- src/autowiring/AutoPacket.cpp | 6 ++++ src/autowiring/test/AutoPacketTest.cpp | 21 ++++++++++++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/autowiring/AutoPacket.h b/autowiring/AutoPacket.h index be79fb5a8..722584b40 100644 --- a/autowiring/AutoPacket.h +++ b/autowiring/AutoPacket.h @@ -161,6 +161,11 @@ class AutoPacket: /// static void ThrowNotDecoratedException(const DecorationKey& key); + /// + /// Throws a formatted runtime error corresponding to the case where a decoration was demanded and more than one such decoration was present + /// + static void ThrowMultiplyDecoratedException(const DecorationKey& key); + public: /// /// The number of distinct decoration types on this packet @@ -210,11 +215,20 @@ class AutoPacket: /// template bool Get(const T*& out, int tshift=0) const { - const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), false, tshift)); + DecorationKey key(auto_id::key(), false, tshift); + const DecorationDisposition* pDisposition = GetDisposition(key); if (pDisposition) { - if (pDisposition->m_decorations.size() == 1) { + switch (pDisposition->m_decorations.size()) { + case 0: + // No shared pointer decorations available, we have to try something else + break; + case 1: + // Single decoration, we can do what the user is asking out = static_cast(pDisposition->m_decorations[0]->ptr()); return true; + default: + ThrowMultiplyDecoratedException(key); + break; } // Second-chance satisfaction with an immediate @@ -238,18 +252,31 @@ class AutoPacket: /// /// This specialization cannot be used to obtain a decoration which has been attached to this packet via /// DecorateImmediate. + /// + /// This method will throw an exception if the requested decoration is multiply present on the packet /// template bool Get(const std::shared_ptr*& out, int tshift=0) const { // Decoration must be present and the shared pointer itself must also be present - const DecorationDisposition* pDisposition = GetDisposition(DecorationKey(auto_id::key(), true, tshift)); - if (!pDisposition || pDisposition->m_decorations.size() != 1) { + DecorationKey key(auto_id::key(), true, tshift); + const DecorationDisposition* pDisposition = GetDisposition(key); + if (!pDisposition) { out = nullptr; return false; } - - out = &pDisposition->m_decorations[0]->as_unsafe(); - return true; + switch (pDisposition->m_decorations.size()) { + case 0: + // Simple non-availability, trivial return + out = nullptr; + return false; + case 1: + // Single decoration available, we can return here + out = &pDisposition->m_decorations[0]->as_unsafe(); + return true; + default: + ThrowMultiplyDecoratedException(key); + return false; + } } /// diff --git a/src/autowiring/AutoPacket.cpp b/src/autowiring/AutoPacket.cpp index c2b8ab395..18ce30c67 100644 --- a/src/autowiring/AutoPacket.cpp +++ b/src/autowiring/AutoPacket.cpp @@ -324,6 +324,12 @@ void AutoPacket::ThrowNotDecoratedException(const DecorationKey& key) { throw std::runtime_error(ss.str()); } +void AutoPacket::ThrowMultiplyDecoratedException(const DecorationKey& key) { + std::stringstream ss; + ss << "Attempted to obtain a type " << autowiring::demangle(key.ti) << " which was decorated more than once on this packet"; + throw std::runtime_error(ss.str()); +} + size_t AutoPacket::GetDecorationTypeCount(void) const { std::lock_guard lk(m_lock); diff --git a/src/autowiring/test/AutoPacketTest.cpp b/src/autowiring/test/AutoPacketTest.cpp index 0bc1638b3..653255518 100644 --- a/src/autowiring/test/AutoPacketTest.cpp +++ b/src/autowiring/test/AutoPacketTest.cpp @@ -42,4 +42,23 @@ TEST_F(AutoPacketTest, FactoryCallTest) { ); ASSERT_TRUE(bCalled); -} \ No newline at end of file +} + +TEST_F(AutoPacketTest, MultipleDecorateGetFailures) { + // Decorate with two integer types: + *factory += [](int& arg) { arg = 0; }; + *factory += [](int& arg) { arg = 1; }; + + // Now issue the packet: + auto packet = factory->NewPacket(); + + // Any type of "Get" call made on int should fail now + { + const int* out; + ASSERT_ANY_THROW(packet->Get(out)); + } + { + const std::shared_ptr* out; + ASSERT_ANY_THROW(packet->Get(out)); + } +} From 6119e1b8ac79d69ddd86af22a4312678dd4d7864 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 19 Feb 2015 10:18:51 -0800 Subject: [PATCH 03/18] Reintroducing CoreContext::Add This method allows users to explicitly register a type in a context for discovery without making that type fully present in the context as a concrete type. The specified interface will not be the subject of event receipt nor will it participate in `AutoFilter` networks. `CoreContext::Add` is the logical counterpart to `CoreContext::Snoop`. --- autowiring/CoreContext.h | 42 ++++- src/autowiring/CoreContext.cpp | 197 ++++++++++++++++-------- src/autowiring/test/CoreContextTest.cpp | 11 ++ 3 files changed, 188 insertions(+), 62 deletions(-) diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 9cb862fba..7a5ca3fb0 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -161,7 +161,8 @@ class CoreContext: // The first deferrable autowiring which requires this type, if one exists: DeferrableAutowiring* pFirst; - // A back reference to the concrete type from which this memo was generated: + // A back reference to the concrete type from which this memo was generated. This field may be null + // if there is no corresponding concrete type. const CoreObjectDescriptor* pObjTraits; // Once this memo entry is satisfied, this will contain the AnySharedPointer instance that performs @@ -325,7 +326,22 @@ class CoreContext: /// \internal /// - /// Invokes all deferred autowiring fields, generally called after a new member has been added + /// Satisfies all slots associated with the passed memo entry and causes finalizers to be invoked + /// + /// + /// The passed lock will be unlocked when this function returns + /// + void SatisfyAutowiring(std::unique_lock& lk, MemoEntry& entry); + + /// \internal + /// + /// Updates slots related to a single autowired field + /// + void UpdateDeferredElement(std::unique_lock&& lk, MemoEntry& entry); + + /// \internal + /// + /// Updates all deferred autowiring fields, generally called after a new member has been added /// void UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry); @@ -400,6 +416,12 @@ class CoreContext: /// void AddInternal(const CoreObjectDescriptor& traits); + /// \internal + /// + /// Internal specific interface introduction routine + /// + void AddInternal(const AnySharedPointer& ptr); + /// \internal /// /// Scans the memo collection for the specified entry, or adds a deferred resolution marker if resolution was not possible @@ -634,6 +656,22 @@ class CoreContext: EnableInternal((T*)nullptr, (Boltable*)nullptr); } + /// + /// Introduces the specified pointer to this context explicitly + /// + /// The interface to make available in this context + /// + /// Add is similar in behavior to Inject, except that the passed pointer is not treated as a concrete + /// type. This means that other interfaces implemented by ptr will not be available for autowiring + /// unless explicitly made discoverable by another call to Add. + /// + /// It is an error to add a type which is already autowirable in a context. + /// + template + void Add(const std::shared_ptr& ptr) { + AddInternal(AnySharedPointer(ptr)); + } + /// /// Injects the specified types into this context. /// diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 651dd54fa..c49976108 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -307,6 +307,19 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { GetGlobal()->Invoke(&AutowiringEvents::NewObject)(*this, traits); } +void CoreContext::AddInternal(const AnySharedPointer& ptr) { + std::unique_lock lk(m_stateBlock->m_lock); + + // Verify that this type isn't already satisfied + MemoEntry& entry = m_typeMemos[ptr->type()]; + if (entry.m_value) + throw autowiring_error("This interface is already present in the context"); + + // Now we can satisfy it: + entry.m_value = ptr; + UpdateDeferredElement(std::move(lk), entry); +} + void CoreContext::FindByType(AnySharedPointer& reference) const { std::lock_guard lk(m_stateBlock->m_lock); FindByTypeUnsafe(reference); @@ -736,76 +749,145 @@ void CoreContext::BroadcastContextCreationNotice(const std::type_info& sigil) co m_pParent->BroadcastContextCreationNotice(sigil); } -void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry) { - // Collection of satisfiable lists: - std::vector satisfiable; - - // Notify any autowired field whose autowiring was deferred. We do this by processing each entry - // in the entire type memos collection. These entries are keyed on the type of the memo, and the - // value is a linked list of trees of deferred autowiring instances that will need to be called - // if the corresponding memo type has been satisfied. - // - // A tree data structure is used, here, specifically because there are cases where child nodes - // on a tree should only be called if and only if the root node is still present. For instance, - // creating an Autowired field adds a tree to this list with the root node referring to the - // Autowired field itself, and then invoking Autowired::NotifyWhenAutowired attaches a child to - // this tree. If the Autowired instance is destroyed, the lambda registered for notification is - // also removed at the same time. - // - // Each connected nonroot deferrable autowiring is referred to as a "dependant chain". +void CoreContext::SatisfyAutowiring(std::unique_lock& lk, MemoEntry& entry) { + std::vector requiresFinalize; + + // Now we need to take on the responsibility of satisfying this deferral. We will do this by + // nullifying the flink, and by ensuring that the memo is satisfied at the point where we + // release the lock. std::stack stk; - for(auto& cur : m_typeMemos) { - MemoEntry& value = cur.second; + stk.push(entry.pFirst); + entry.pFirst = nullptr; + + // Depth-first search + while (!stk.empty()) { + auto top = stk.top(); + stk.pop(); + + for (DeferrableAutowiring* pCur = top; pCur; pCur = pCur->GetFlink()) { + pCur->SatisfyAutowiring(entry.m_value); + + // See if there's another chain we need to process: + auto child = pCur->ReleaseDependentChain(); + if (child) + stk.push(child); + + // Not everyone needs to be finalized. The entities that don't require finalization + // are identified by an empty strategy, and we just skip them. + auto strategy = pCur->GetStrategy(); + if (strategy) + requiresFinalize.push_back(strategy); + } + } - if(value.m_value) - // This entry is already satisfied, no need to process it - continue; + lk.unlock(); - // Determine whether the current candidate element satisfies the autowiring we are considering. - // This is done internally via a dynamic cast on the interface type for which this polymorphic - // base type was constructed. - if(!value.m_value->try_assign(entry.pCoreObject)) + // Run through everything else and finalize it all: + for (const auto& cur : requiresFinalize) + cur->Finalize(); +} + +void CoreContext::UpdateDeferredElement(std::unique_lock&& lk, MemoEntry& entry) { + // Satisfy what needs to be satisfied: + SatisfyAutowiring(lk, entry); + + // Give children a chance to also update their deferred elements: + lk.lock(); + for (const auto& weak_child : m_children) { + // Hold reference to prevent this iterator from becoming invalidated: + auto ctxt = weak_child.lock(); + if (!ctxt) continue; - // Success, assign the traits - value.pObjTraits = &entry; - - // Now we need to take on the responsibility of satisfying this deferral. We will do this by - // nullifying the flink, and by ensuring that the memo is satisfied at the point where we - // release the lock. - stk.push(value.pFirst); - value.pFirst = nullptr; - - // Finish satisfying the remainder of the chain while we hold the lock: - while(!stk.empty()) { - auto top = stk.top(); - stk.pop(); - - for(DeferrableAutowiring* pNext = top; pNext; pNext = pNext->GetFlink()) { - pNext->SatisfyAutowiring(value.m_value); - - // See if there's another chain we need to process: - auto child = pNext->ReleaseDependentChain(); - if(child) - stk.push(child); - - // Not everyone needs to be finalized. The entities that don't require finalization - // are identified by an empty strategy, and we just skip them. - auto strategy = pNext->GetStrategy(); - if(strategy) - satisfiable.push_back(strategy); + // Reverse lock before satisfying children: + lk.unlock(); + ctxt->UpdateDeferredElement( + std::unique_lock(ctxt->m_stateBlock->m_lock), + entry + ); + lk.lock(); + } + lk.unlock(); +} + +void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, const CoreObjectDescriptor& entry) { + { + // Collection of items needing finalization: + std::vector delayedFinalize; + + // Notify any autowired field whose autowiring was deferred. We do this by processing each entry + // in the entire type memos collection. These entries are keyed on the type of the memo, and the + // value is a linked list of trees of deferred autowiring instances that will need to be called + // if the corresponding memo type has been satisfied. + // + // A tree data structure is used, here, specifically because there are cases where child nodes + // on a tree should only be called if and only if the root node is still present. For instance, + // creating an Autowired field adds a tree to this list with the root node referring to the + // Autowired field itself, and then invoking Autowired::NotifyWhenAutowired attaches a child to + // this tree. If the Autowired instance is destroyed, the lambda registered for notification is + // also removed at the same time. + // + // Each connected nonroot deferrable autowiring is referred to as a "dependant chain". + std::stack stk; + for (auto& cur : m_typeMemos) { + MemoEntry& value = cur.second; + + if (value.m_value) + // This entry is already satisfied, no need to process it + continue; + + // Determine whether the current candidate element satisfies the autowiring we are considering. + // This is done internally via a dynamic cast on the interface type for which this polymorphic + // base type was constructed. + if (!value.m_value->try_assign(entry.pCoreObject)) + continue; + + // Success, assign the traits + value.pObjTraits = &entry; + + // Now we need to take on the responsibility of satisfying this deferral. We will do this by + // nullifying the flink, and by ensuring that the memo is satisfied at the point where we + // release the lock. + stk.push(value.pFirst); + value.pFirst = nullptr; + + // Finish satisfying the remainder of the chain while we hold the lock: + while (!stk.empty()) { + auto top = stk.top(); + stk.pop(); + + for (DeferrableAutowiring* pNext = top; pNext; pNext = pNext->GetFlink()) { + pNext->SatisfyAutowiring(value.m_value); + + // See if there's another chain we need to process: + auto child = pNext->ReleaseDependentChain(); + if (child) + stk.push(child); + + // Not everyone needs to be finalized. The entities that don't require finalization + // are identified by an empty strategy, and we just skip them. + auto strategy = pNext->GetStrategy(); + if (strategy) + delayedFinalize.push_back(strategy); + } } } + lk.unlock(); + + // Run through everything else and finalize it all: + for (const auto& cur : delayedFinalize) + cur->Finalize(); } // Give children a chance to also update their deferred elements: - for(const auto& weak_child : m_children) { + lk.lock(); + for (const auto& weak_child : m_children) { // Hold reference to prevent this iterator from becoming invalidated: auto ctxt = weak_child.lock(); if(!ctxt) continue; - // Reverse lock before satisfying children: + // Reverse lock before handing off control: lk.unlock(); ctxt->UpdateDeferredElements( std::unique_lock(ctxt->m_stateBlock->m_lock), @@ -813,11 +895,6 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons ); lk.lock(); } - lk.unlock(); - - // Run through everything else and finalize it all: - for(const auto& cur : satisfiable) - cur->Finalize(); } void CoreContext::AddEventReceiver(const JunctionBoxEntry& entry) { diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index 953d797e1..779257299 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -350,3 +350,14 @@ TEST_F(CoreContextTest, InitiateMultipleChildren) { outerCtxt->SignalShutdown(true); } } + +class CoreContextAddTestClass {}; + +TEST_F(CoreContextTest, CoreContextAdd) { + auto myClass = std::make_shared(); + AutoCurrentContext ctxt; + ctxt->Add(myClass); + + Autowired mc; + ASSERT_TRUE(mc.IsAutowired()) << "Manually registered interface was not detected as expected"; +} \ No newline at end of file From 7073ba77caef4e077650efb1a76f4d6ff7862947 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 26 Feb 2015 14:30:06 -0800 Subject: [PATCH 04/18] CoreObjectDescriptor should be copyable --- autowiring/CoreObjectDescriptor.h | 30 +++++++++++++++--------------- src/autonet/AutoNetServerImpl.cpp | 2 +- src/autowiring/CoreContext.cpp | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/autowiring/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index b5629d179..b52ac7fbc 100644 --- a/autowiring/CoreObjectDescriptor.h +++ b/autowiring/CoreObjectDescriptor.h @@ -24,9 +24,9 @@ struct CoreObjectDescriptor { template CoreObjectDescriptor(const std::shared_ptr& value, T*) : - type(typeid(T)), - actual_type(typeid(*value)), - stump(SlotInformationStump::s_stump), + type(&typeid(T)), + actual_type(&typeid(*value)), + stump(&SlotInformationStump::s_stump), value(value), subscriber(MakeAutoFilterDescriptor(value)), pCoreObject(autowiring::fast_pointer_cast(value)), @@ -63,12 +63,12 @@ struct CoreObjectDescriptor { {} // The type of the passed pointer - const std::type_info& type; + const std::type_info* type; // The "actual type" used by Autowiring. This type may differ from CoreObjectDescriptor::type in cases // where a type unifier is used, or if the concrete type is defined in an external module--for // instance, by a class factory. - const std::type_info& actual_type; + const std::type_info* actual_type; /// /// Used to obtain a list of slots defined on this type, for reflection purposes @@ -98,23 +98,23 @@ struct CoreObjectDescriptor { /// /// The linked list is guaranteed to be in reverse-sorted order /// - const SlotInformationStumpBase& stump; + const SlotInformationStumpBase* stump; // A holder to store the original shared pointer, to ensure that type information propagates // correctly on the right-hand side of our map - const AnySharedPointer value; + AnySharedPointer value; // The packet subscriber introduction method, if appropriate: - const AutoFilterDescriptor subscriber; + AutoFilterDescriptor subscriber; // There are a lot of interfaces we support, here they all are: - const std::shared_ptr pCoreObject; - const std::shared_ptr pContextMember; - const std::shared_ptr pCoreRunnable; - const std::shared_ptr pBasicThread; - const std::shared_ptr pFilter; - const std::shared_ptr pBoltBase; + std::shared_ptr pCoreObject; + std::shared_ptr pContextMember; + std::shared_ptr pCoreRunnable; + std::shared_ptr pBasicThread; + std::shared_ptr pFilter; + std::shared_ptr pBoltBase; // Does this type receive events? - const bool receivesEvents; + bool receivesEvents; }; diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index 123f9ebbc..4b4b2636d 100644 --- a/src/autonet/AutoNetServerImpl.cpp +++ b/src/autonet/AutoNetServerImpl.cpp @@ -184,7 +184,7 @@ void AutoNetServerImpl::NewObject(CoreContext& ctxt, const CoreObjectDescriptor& // Add slots for this object { Json::array slots; - for(auto slot = object.stump.pHead; slot; slot = slot->pFlink) { + for(auto slot = object.stump->pHead; slot; slot = slot->pFlink) { slots.push_back(Json::object{ {"id", autowiring::demangle(slot->type)}, {"autoRequired", slot->autoRequired}, diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 651dd54fa..fce6abd30 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -195,7 +195,7 @@ const std::type_info& CoreContext::GetAutoTypeId(const AnySharedPointer& ptr) co throw autowiring_error("Attempted to obtain the true type of a shared pointer that was not a member of this context"); const CoreObjectDescriptor* pObjTraits = q->second.pObjTraits; - return pObjTraits->type; + return *pObjTraits->type; } std::shared_ptr CoreContext::IncrementOutstandingThreadCount(void) { @@ -246,7 +246,7 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { // concrete type defined in another context or potentially a unifier type. Creating a slot here // is also undesirable because the complete type is not available and we can't create a dynaimc // caster to identify when this slot gets satisfied. - auto q = m_typeMemos.find(traits.actual_type); + auto q = m_typeMemos.find(*traits.actual_type); if(q != m_typeMemos.end()) { auto& v = q->second; if(*v.m_value == traits.pCoreObject) @@ -290,7 +290,7 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { } // Subscribers, if applicable: - const auto& stump = traits.stump; + const auto& stump = *traits.stump; if(!traits.subscriber.empty()) { AddPacketSubscriber(traits.subscriber); From 4c04e735e3bba3daf7a084443b0da618769d0794 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 26 Feb 2015 15:08:35 -0800 Subject: [PATCH 05/18] Adding a double-check for operator-> and operator* An attempt to dereference an Autowired or AutowiredFast field should result in an exception, rather than just crashing due to a null pointer dereference. --- autowiring/AutowirableSlot.h | 12 ++++++++++-- autowiring/Autowired.h | 14 ++++++++++++++ src/autowiring/test/AutowiringTest.cpp | 6 ++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 69d76ef92..105166b3e 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -184,15 +184,23 @@ class AutowirableSlot: // where we can guarantee that the type will be completely defined, because the user is about // to make use of this type. (void) autowiring::fast_pointer_cast_initializer::sc_init; - return get(); + + auto retVal = get(); + if (!retVal) + throw autowiring_error("Attempted to dereference a null autowired field"); + return retVal; } T& operator*(void) const { + auto retVal = get(); + if (!retVal) + throw autowiring_error("Attempted to dereference a null autowired field"); + // We have to initialize here, in the operator context, because we don't actually know if the // user will be making use of this type. (void) autowiring::fast_pointer_cast_initializer::sc_init; - return *get(); + return *retVal; } using AnySharedPointer::operator=; diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index ec13671c7..2e6ccc1ca 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -331,6 +331,20 @@ class AutowiredFast: return std::shared_ptr::get(); } + T* operator->(void) const { + auto retVal = std::shared_ptr::operator->(); + if (!retVal) + throw autowiring_error("Attempted to dereference a null autowired field"); + return retVal; + } + + T& operator*(void) const { + auto retVal = std::shared_ptr::operator*(); + if (!retVal) + throw autowiring_error("Attempted to dereference a null autowired field"); + return retVal; + } + bool IsAutowired(void) const { return std::shared_ptr::get() != nullptr; } }; diff --git a/src/autowiring/test/AutowiringTest.cpp b/src/autowiring/test/AutowiringTest.cpp index 48181d0b0..e3bf38555 100644 --- a/src/autowiring/test/AutowiringTest.cpp +++ b/src/autowiring/test/AutowiringTest.cpp @@ -178,3 +178,9 @@ TEST_F(AutowiringTest, StaticNewWithArgs) { ctxt->SignalShutdown(true); } } + +TEST_F(AutowiringTest, NullDereferenceAttempt) { + Autowired co; + ASSERT_ANY_THROW(*co) << "A dereference attempt on a CoreObject did not throw an exception as expected"; + ASSERT_ANY_THROW(co->one) << "A dereference attempt on a CoreObject did not throw an exception as expected"; +} \ No newline at end of file From 65d37c22471e116b718e9468e4f6747f361abc13 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 2 Mar 2015 09:52:39 -0800 Subject: [PATCH 06/18] Bumped version number --- version.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.cmake b/version.cmake index 116908e6e..a2e8c8864 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.4.4) +set(autowiring_VERSION 0.4.5) From 14c8553bed964913007f8d4f51c644c29902705a Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 2 Mar 2015 09:42:48 -0800 Subject: [PATCH 07/18] Correcting accidental copy This statement actually takes a copy of the return value of operator*, make use `std::shared_ptr::get` to disambiguate. --- autowiring/Autowired.h | 6 +++--- src/autowiring/test/AutowiringTest.cpp | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index 2e6ccc1ca..19381b854 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -332,17 +332,17 @@ class AutowiredFast: } T* operator->(void) const { - auto retVal = std::shared_ptr::operator->(); + auto* retVal = std::shared_ptr::operator->(); if (!retVal) throw autowiring_error("Attempted to dereference a null autowired field"); return retVal; } T& operator*(void) const { - auto retVal = std::shared_ptr::operator*(); + T* retVal = std::shared_ptr::get(); if (!retVal) throw autowiring_error("Attempted to dereference a null autowired field"); - return retVal; + return *retVal; } bool IsAutowired(void) const { return std::shared_ptr::get() != nullptr; } diff --git a/src/autowiring/test/AutowiringTest.cpp b/src/autowiring/test/AutowiringTest.cpp index e3bf38555..b12d71489 100644 --- a/src/autowiring/test/AutowiringTest.cpp +++ b/src/autowiring/test/AutowiringTest.cpp @@ -183,4 +183,10 @@ TEST_F(AutowiringTest, NullDereferenceAttempt) { Autowired co; ASSERT_ANY_THROW(*co) << "A dereference attempt on a CoreObject did not throw an exception as expected"; ASSERT_ANY_THROW(co->one) << "A dereference attempt on a CoreObject did not throw an exception as expected"; +} + +TEST_F(AutowiringTest, FastNullDereferenceAttempt) { + AutowiredFast co; + ASSERT_ANY_THROW(*co) << "A dereference attempt on a CoreObject did not throw an exception as expected"; + ASSERT_ANY_THROW(co->one) << "A dereference attempt on a CoreObject did not throw an exception as expected"; } \ No newline at end of file From c527dabd67ec1a945dccc325b7a7a8ead8a9f1a1 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 2 Mar 2015 10:07:22 -0800 Subject: [PATCH 08/18] Correcting 64-bit Windows unit test failure Issue has to do with layout changes in the `_Packaged_task` structure. Added an additional static assertion to catch these layout changes if they occur on other build configurations in the future. --- autowiring/auto_future_win.h | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/autowiring/auto_future_win.h b/autowiring/auto_future_win.h index 041d9d125..1fda66f6e 100644 --- a/autowiring/auto_future_win.h +++ b/autowiring/auto_future_win.h @@ -4,20 +4,39 @@ #include namespace autowiring { + #pragma pack(push, _CRT_PACKING) + template class _Packaged_state_unwrap: - public std::_Associated_state<_Ret*> + public std::_Associated_state<_Ret> { public: std::function<_Ret(_ArgTypes...)> _Fn; }; + template + class _Packaged_state_unwrap<_Ret&, _ArgTypes...>: + public std::_Associated_state<_Ret*> + { + public: + std::function<_Ret&(_ArgTypes...)> _Fn; + }; + + template + class _Packaged_state_unwrap: + public std::_Associated_state + { + public: + std::function _Fn; + }; + class _Task_async_state_unwrap: public std::_Packaged_state { public: ::Concurrency::task _Task; }; + #pragma pack(pop) /// /// Platform-specific operation appending routine @@ -36,6 +55,11 @@ namespace autowiring { auto* packagedState = static_cast*>(deferredAsync); auto* unwrap = reinterpret_cast<_Packaged_state_unwrap*>(packagedState); + static_assert( + sizeof(*packagedState) == sizeof(*unwrap), + "Size of unwrapped version differs from underlying version, internal API has changed" + ); + // New function which consists of a call to the original then a call to the continuation unwrap->_Fn = std::bind( [] (const std::function& orig, const Fn& fn) { From 93d7daf8fe13171175e8bae2d24f950c82634632 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 5 Mar 2015 14:12:06 -0800 Subject: [PATCH 09/18] Fix doxygen version number --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index 8bea9e696..108cb6b2b 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = Autowiring # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = "0.4.2" +PROJECT_NUMBER = "0.4.5" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer From 26ba6dad3193402e5cfba2338510b63371f1d1a3 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 5 Mar 2015 14:02:21 -0800 Subject: [PATCH 10/18] Eliminate libstdc++ support from CMakeLists The time has finally come to eliminate support for libstdc++ once and for all. We will start by short-circuiting all preprocessor blocks and macros that make references to libcxx. Then comes the slow, arduous process of fixing all of the stupid backflips that we had to make to classes such as `ObjectPool` to make them compatible with libstdc++. libstdc++ is dead. Long live libc++. --- AutowiringConfig.h.in | 8 ----- CMakeLists.txt | 49 ++------------------------- autowiring-configVersion.cmake.in | 27 ++++++--------- autowiring/AutoPacket.h | 8 ----- autowiring/C++11/cpp11.h | 6 +--- src/autonet/CMakeLists.txt | 5 --- src/autowiring/AutoPacketInternal.cpp | 5 --- src/autowiring/test/CMakeLists.txt | 11 ++---- 8 files changed, 17 insertions(+), 102 deletions(-) diff --git a/AutowiringConfig.h.in b/AutowiringConfig.h.in index 43d230fc6..686ac171b 100644 --- a/AutowiringConfig.h.in +++ b/AutowiringConfig.h.in @@ -10,11 +10,3 @@ // Building for ARM? #cmakedefine01 autowiring_BUILD_ARM - -// Are we linking with C++11 STL? -#cmakedefine01 autowiring_USE_LIBCXX -#if autowiring_USE_LIBCXX -#define AUTOWIRING_USE_LIBCXX 1 -#else -#define AUTOWIRING_USE_LIBCXX 0 -#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index c80ba66b6..df6a547ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,33 +40,6 @@ if(EXTERNAL_LIBRARY_DIR) list(APPEND CMAKE_INCLUDE_PATH ${EXTERNAL_LIBRARY_DIR}) endif() -if(APPLE) - # Offer option for autowiring_USE_LIBCXX - # Check for existing definition of autowiring_USE_LIBCXX - if(NOT DEFINED autowiring_USE_LIBCXX) - option(autowiring_USE_LIBCXX "Build Autowiring using c++11" ON) - else() - if(NOT autowiring_USE_LIBCXX) - message("Parent project has set autowiring_USE_LIBCXX = OFF -> Build Autowiring using c++98") - endif() - endif() - - # Install autoboost when using libstdc++ - if(autowiring_USE_LIBCXX) - set(autowiring_INSTALL_AUTOBOOST OFF) - else() - set(autowiring_INSTALL_AUTOBOOST ON) - endif() -else() - # Always use libc++ on other platforms - set(autowiring_USE_LIBCXX ON) - - # Don't install autoboost unless otherwise specified - if(NOT DEFINED autowiring_INSTALL_AUTOBOOST) - set(autowiring_INSTALL_AUTOBOOST OFF) - endif() -endif() - if(CMAKE_COMPILER_IS_GNUCC) if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8") message(FATAL_ERROR "GCC version 4.8 minimum is required to build Autowiring") @@ -86,16 +59,9 @@ endif() # Clang needs special additional flags to build with C++11 if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - # Apple needs us to tell it that we're using libc++, or it will try to use libstdc++ instead - if(autowiring_USE_LIBCXX) - message(STATUS "AppleClang C++11") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") - else() - message(STATUS "AppleClang C++11 with libstdc++") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libstdc++") - endif() + message(STATUS "AppleClang C++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") message(STATUS "Clang C++11") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") @@ -224,15 +190,6 @@ if(NOT AUTOWIRING_IS_EMBEDDED) FILES_MATCHING PATTERN "*.h" ) - # Install autoboost headers - if(autowiring_INSTALL_AUTOBOOST) - install( - DIRECTORY ${PROJECT_SOURCE_DIR}/contrib/autoboost/autoboost - DESTINATION include - COMPONENT autowiring - ) - endif() - # Targets file is needed in order to describe how to link Autowiring to the rest of the system install(EXPORT AutowiringTargets FILE AutowiringTargets.cmake COMPONENT autowiring NAMESPACE Autowiring:: DESTINATION cmake CONFIGURATIONS ${CMAKE_CONFIGURATION_TYPES}) diff --git a/autowiring-configVersion.cmake.in b/autowiring-configVersion.cmake.in index 64bca57a4..02552c97a 100644 --- a/autowiring-configVersion.cmake.in +++ b/autowiring-configVersion.cmake.in @@ -12,9 +12,6 @@ if(autowiring_DEBUG) message(STATUS "Installed CMAKE_SIZEOF_VOID_P: @CMAKE_SIZEOF_VOID_P@") message(STATUS "Configured CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}") - - message(STATUS "Installed using autowiring_USE_LIBCXX: @autowiring_USE_LIBCXX@") - message(STATUS "Configured using autowiring_USE_LIBCXX: ${autowiring_USE_LIBCXX}") endif() # If the customer has an override architecture requirement, use that @@ -73,26 +70,22 @@ endforeach() # Determine whether the user's request (either implied or explicit) for libstdc++ can # be met by this verison of Autowiring if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") - # If this value isn't defined, then we assume the user's request is "on" - if(NOT DEFINED autowiring_USE_LIBCXX) - SET(autowiring_USE_LIBCXX ON) + # Require that the user either omit autowiring_USE_LIBCXX or leave it off + if(DEFINED autowiring_USE_LIBCXX) + if(autowiring_USE_LIBCXX) + message(WARNING "Autowiring no longer supports libstdc++, autowiring_USE_LIBCXX is therefore a deprecated flag") + else() + message(STATUS "Autowiring no longer supports libstdc++") + set(PACKAGE_VERSION_COMPATIBLE FALSE) + set(PACKAGE_VERSION_UNSUITABLE TRUE) + return() + endif() endif() if(autowiring_DEBUG) message(STATUS "Installed autowiring_USE_LIBCXX: @autowiring_USE_LIBCXX@") message(STATUS "Configured autowiring_USE_LIBCXX: ${autowiring_USE_LIBCXX}") endif() - - # Our built version must be the same as the requested version. If it's not, then we are - # not a match for the user's request - if((NOT ${autowiring_USE_LIBCXX} AND @autowiring_USE_LIBCXX@) OR (${autowiring_USE_LIBCXX} AND NOT @autowiring_USE_LIBCXX@)) - if(autowiring_DEBUG) - message("User C++ runtime library request incompatible with locally built version") - endif() - set(PACKAGE_VERSION_COMPATIBLE FALSE) - set(PACKAGE_VERSION_UNSUITABLE TRUE) - return() - endif() endif() if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION) diff --git a/autowiring/AutoPacket.h b/autowiring/AutoPacket.h index 722584b40..a56f1a8f9 100644 --- a/autowiring/AutoPacket.h +++ b/autowiring/AutoPacket.h @@ -458,18 +458,10 @@ class AutoPacket: } // Now trigger a rescan to hit any deferred, unsatisfiable entries: -#if autowiring_USE_LIBCXX for (const std::type_info* ti : {&auto_id::key(), &auto_id::key()...}) { MarkUnsatisfiable(DecorationKey(*ti, true, 0)); MarkUnsatisfiable(DecorationKey(*ti, false, 0)); } -#else - bool dummy[] = { - (MarkUnsatisfiable(DecorationKey(auto_id::key(), false, 0)), false), - (MarkUnsatisfiable(DecorationKey(auto_id::key(), false, 0)), false)... - }; - (void)dummy; -#endif }), PulseSatisfaction(pTypeSubs, 1 + sizeof...(Ts)); } diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index 8efa94198..842f0ead4 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -131,11 +131,7 @@ #endif #if TYPE_TRAITS_AVAILABLE - #if defined(_MSC_VER) || defined(_LIBCPP_VERSION) - #define TYPE_TRAITS_HEADER - #else - #define TYPE_TRAITS_HEADER - #endif + #define TYPE_TRAITS_HEADER #else #define TYPE_TRAITS_HEADER #endif diff --git a/src/autonet/CMakeLists.txt b/src/autonet/CMakeLists.txt index 66f863e53..a43d7430d 100644 --- a/src/autonet/CMakeLists.txt +++ b/src/autonet/CMakeLists.txt @@ -1,8 +1,3 @@ -if(NOT WIN32 AND NOT autowiring_USE_LIBCXX) - message("Cannot build Autonet, requires proper C++11 support") - return() -endif() - add_googletest(test) include_directories( ${PROJECT_SOURCE_DIR}/contrib/autoboost diff --git a/src/autowiring/AutoPacketInternal.cpp b/src/autowiring/AutoPacketInternal.cpp index 525aaddcb..a3e6a8183 100644 --- a/src/autowiring/AutoPacketInternal.cpp +++ b/src/autowiring/AutoPacketInternal.cpp @@ -43,12 +43,7 @@ void AutoPacketInternal::Initialize(bool isFirstPacket) { call->GetCall()(call->GetAutoFilter(), *this); // First-call indicated by argumument type AutoPacket&: -#if autowiring_USE_LIBCXX for (bool is_shared : {false, true}) { -#else - for (int num = 0; num < 2; ++num) { - bool is_shared = (bool)num; -#endif std::unique_lock lk(m_lock); // Don't modify the decorations set if nobody expects an AutoPacket input diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index ef348d204..d29d60c23 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -5,6 +5,9 @@ set(AutowiringTest_SRCS AutoConstructTest.cpp AutoFilterCollapseRulesTest.cpp AutoFilterDiagnosticsTest.cpp + AutoFilterFunctionTest.cpp + AutoFilterSequencing.cpp + AutoFilterTest.cpp AutoInjectableTest.cpp AutoPacketTest.cpp AutoPacketFactoryTest.cpp @@ -66,14 +69,6 @@ add_windows_sources( AutoFutureTest.cpp ) -if(autowiring_USE_LIBCXX) - set(AutowiringTest_SRCS ${AutowiringTest_SRCS} - AutoFilterFunctionTest.cpp - AutoFilterSequencing.cpp - AutoFilterTest.cpp - ) -endif() - set(AutowiringFixture_SRCS HasForwardOnlyType.hpp HasForwardOnlyType.cpp From 3b6ed0bbb1829d7e05c65f38d8eefa1b32d4d15d Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 5 Mar 2015 14:20:41 -0800 Subject: [PATCH 11/18] Add a version number linter We have to copy the version number in (currently) three different places, and it's easy to miss one. This ensures that these three spots all report the same number. --- .travis.yml | 3 +++ publicDoxyfile.conf | 2 +- scripts/version_number_updated.sh | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100755 scripts/version_number_updated.sh diff --git a/.travis.yml b/.travis.yml index 90e1375b3..11e1363b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ before_install: # Enforce Leap Motion copyright notice - ./scripts/copyright_check.sh + # Verify that our version numbers all line up + - ./scripts/version_number_updated.sh + # g++4.8.1 - if [ "$CXX" == "g++" ]; then sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test diff --git a/publicDoxyfile.conf b/publicDoxyfile.conf index a5dfcb4b8..c5bfd81e9 100644 --- a/publicDoxyfile.conf +++ b/publicDoxyfile.conf @@ -38,7 +38,7 @@ PROJECT_NAME = Autowiring # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.4.2 +PROJECT_NUMBER = 0.4.5 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/scripts/version_number_updated.sh b/scripts/version_number_updated.sh new file mode 100755 index 000000000..80c9c4d1b --- /dev/null +++ b/scripts/version_number_updated.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# +# Ensure that the version.cmake version number is the same everywhere +# + +# Get the version number from version.cmake first +if ! version=$(grep -oE "[0-9]+.[0-9]+.[0-9]+" version.cmake); then + echo "Version number not found in version.cmake" + exit 1 +fi + +# Verify that this identical version number appears in our doxygen files +if ! grep $version Doxyfile; then + echo "Expected to find version $version in Doxyfile" + exit 1 +fi +if ! grep $version publicDoxyfile.conf; then + echo "Expected to find version $version in publicDoxyfile.conf" + exit 1 +fi From 748ea01270fa24ce29d79878f6f2b1d64d1749c6 Mon Sep 17 00:00:00 2001 From: Jimmy Nguyen Date: Mon, 9 Mar 2015 11:08:24 -0700 Subject: [PATCH 12/18] Fixing the AutowiringEvents by manually checking the CoreObjectDescriptor since it may have also been manually added in in the JunctionBoxManager constructor --- autowiring/CoreObjectDescriptor.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/autowiring/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index c276b3b37..95fa92242 100644 --- a/autowiring/CoreObjectDescriptor.h +++ b/autowiring/CoreObjectDescriptor.h @@ -2,6 +2,7 @@ #pragma once #include "AnySharedPointer.h" #include "AutoFilterDescriptor.h" +#include "AutowiringEvents.h" #include "BoltBase.h" #include "ContextMember.h" #include "CoreRunnable.h" @@ -36,7 +37,11 @@ struct CoreObjectDescriptor { pFilter(autowiring::fast_pointer_cast(value)), pBoltBase(autowiring::fast_pointer_cast(value)), receivesEvents( - [this]{ + [this, value]{ + // Because we manually added AutowiringEvents to the JunctionBoxManager, check here also + if (autowiring::fast_pointer_cast(value)) + return true; + for (auto evt = g_pFirstEventEntry; evt; evt = evt->pFlink) if (evt->IsSameAs(pCoreObject.get())) return true; From be8a45f2319e1c5bbe904087215db69a25338357 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 12 Mar 2015 02:38:14 -0700 Subject: [PATCH 13/18] Simplifying the SlotInformationStackLocation concept --- autowiring/CreationRules.h | 2 +- autowiring/SlotInformation.h | 52 +++++++++++++----------------- src/autowiring/SlotInformation.cpp | 33 +++++++------------ 3 files changed, 36 insertions(+), 51 deletions(-) diff --git a/autowiring/CreationRules.h b/autowiring/CreationRules.h index 24ca00965..5ad53712e 100644 --- a/autowiring/CreationRules.h +++ b/autowiring/CreationRules.h @@ -111,7 +111,7 @@ struct crh { try { // Push a new stack location so that all constructors from here know the injected type under construction SlotInformationStackLocation loc( - &SlotInformationStump::s_stump, + SlotInformationStump::s_stump, pSpace, sizeof(TActual) ); diff --git a/autowiring/SlotInformation.h b/autowiring/SlotInformation.h index c49a82e97..0c3d765a9 100644 --- a/autowiring/SlotInformation.h +++ b/autowiring/SlotInformation.h @@ -110,46 +110,49 @@ SlotInformationStump SlotInformationStump::s_stump; /// class SlotInformationStackLocation { public: - SlotInformationStackLocation(const SlotInformationStackLocation& rhs) = delete; - SlotInformationStackLocation(SlotInformationStackLocation&& rhs) = delete; - /// /// Registers a new stack location on the current thread, used to provide slot reflection services in Autowiring /// - SlotInformationStackLocation(SlotInformationStumpBase* pStump, const void* pObj = nullptr, size_t extent = 0); + SlotInformationStackLocation(SlotInformationStumpBase& stump, const void* pObj = nullptr, size_t extent = 0); ~SlotInformationStackLocation(void); -private: - // The prior stack location, made current when this slot goes out of scope - SlotInformationStackLocation* m_pPrior; - // The pointer location where the stump will be stored: - SlotInformationStumpBase* m_pStump; + SlotInformationStumpBase& stump; + + // Information about the object being constructed while this stack location is valid: + const void* const pObj; + const size_t extent; + + // The prior stack location, made current when this slot goes out of scope + SlotInformationStackLocation& prior; +private: // Current slot information: SlotInformation* m_pCur; // Most recent AutoFilter descriptor link: AutoFilterDescriptorStubLink* m_pLastLink; - // Information about the object being constructed while this stack location is valid: - const void* m_pObj; - size_t m_extent; - public: /// /// True if the passed pointer is inside of the object currently under construction at this stack location /// - bool Encloses(const void* ptr) { - return m_pObj < ptr && ptr < (char*) m_pObj + m_extent; + bool Encloses(const void* ptr) const { + return pObj < ptr && ptr < (char*) pObj + extent; } - /// - /// The slot information stump for this stack location - /// - SlotInformationStumpBase* GetStump(void) const { - return m_pStump; + /// + /// The offset of the specified pointer from the beginning of the object under construction + /// + /// The object whose offset is to be found + /// + /// The return value is undefined if the passed pointer is not within the object under construction. This + /// function is intend to be used during field construction to find the offset of the passed field within + /// its enclosing type. + /// + size_t Offset(const void* ptr) const { + return (char*) ptr - (char*) pObj; } /// @@ -157,11 +160,6 @@ class SlotInformationStackLocation { /// static SlotInformationStackLocation* CurrentStackLocation(void); - /// - /// Returns the stump in the current stack location, or null - /// - static SlotInformationStumpBase* CurrentStump(void); - /// /// Registers the named slot with the current stack location /// @@ -171,8 +169,4 @@ class SlotInformationStackLocation { /// Registers a NewAutoFilter with this SlotInformation /// static void RegisterSlot(const AutoFilterDescriptorStub& stub); - - // Operator overloads: - void operator=(SlotInformationStackLocation&& rhs) = delete; - void operator=(const SlotInformationStackLocation& rhs) = delete; }; diff --git a/src/autowiring/SlotInformation.cpp b/src/autowiring/SlotInformation.cpp index ae8b6080f..a6b069df4 100644 --- a/src/autowiring/SlotInformation.cpp +++ b/src/autowiring/SlotInformation.cpp @@ -9,13 +9,13 @@ // Special file-level allocation with a no-op dtor, because all stack locations are stack-allocated static autowiring::thread_specific_ptr tss([](SlotInformationStackLocation*) {}); -SlotInformationStackLocation::SlotInformationStackLocation(SlotInformationStumpBase* pStump, const void* pObj, size_t extent) : - m_pPrior(tss.get()), - m_pStump(pStump), +SlotInformationStackLocation::SlotInformationStackLocation(SlotInformationStumpBase& stump, const void* pObj, size_t extent) : + prior(*tss), + stump(stump), m_pCur(nullptr), m_pLastLink(nullptr), - m_pObj(pObj), - m_extent(extent) + pObj(pObj), + extent(extent) { tss.reset(this); } @@ -33,35 +33,26 @@ void UpdateOrCascadeDelete(T* ptr, const T*& dest) { } SlotInformationStackLocation::~SlotInformationStackLocation(void) { - if(!m_pStump) - // Rvalue moved, end here - return; - // Replace the prior stack location, we were pushed - tss.reset(m_pPrior); + tss.reset(&prior); - UpdateOrCascadeDelete(m_pCur, m_pStump->pHead); - UpdateOrCascadeDelete(m_pLastLink, m_pStump->pFirstAutoFilter); + UpdateOrCascadeDelete(m_pCur, stump.pHead); + UpdateOrCascadeDelete(m_pLastLink, stump.pFirstAutoFilter); // Unconditionally update to true, no CAS needed - m_pStump->bInitialized = true; + stump.bInitialized = true; } SlotInformationStackLocation* SlotInformationStackLocation::CurrentStackLocation(void) { return tss.get(); } -SlotInformationStumpBase* SlotInformationStackLocation::CurrentStump(void) { - // Trivial null defaulting: - return tss.get() ? tss->m_pStump : nullptr; -} - void SlotInformationStackLocation::RegisterSlot(DeferrableAutowiring* pDeferrable) { if(!tss.get()) // Nothing to do, this slot entry is missing return; - if(tss->m_pStump->bInitialized) + if(tss->stump.bInitialized) // No reason to continue, stump already initialized return; @@ -73,13 +64,13 @@ void SlotInformationStackLocation::RegisterSlot(DeferrableAutowiring* pDeferrabl tss->m_pCur, pDeferrable->GetType(), reinterpret_cast(pDeferrable) - - reinterpret_cast(tss->m_pObj), + reinterpret_cast(tss->pObj), false ); } void SlotInformationStackLocation::RegisterSlot(const AutoFilterDescriptorStub& stub) { - if(!tss.get() || tss->m_pStump->bInitialized) + if(!tss.get() || tss->stump.bInitialized) return; tss->m_pLastLink = new AutoFilterDescriptorStubLink(stub, tss->m_pLastLink); From bc02f47b959299975f06dc15b3d03b687878862b Mon Sep 17 00:00:00 2001 From: Graham Tremper Date: Sun, 8 Mar 2015 15:46:30 -0700 Subject: [PATCH 14/18] Simplify idiom for calling function on each element of a parameter pack --- autowiring/CoreContext.h | 6 +----- autowiring/MicroBolt.h | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 7a5ca3fb0..908c26ef0 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -296,11 +296,7 @@ class CoreContext: // Enables a boltable class template void EnableInternal(T*, Boltable*) { - bool dummy[] = { - false, // Ensure non-zero array size - (AutoRequireMicroBolt(), false)... - }; - (void) dummy; + [](...){}((AutoRequireMicroBolt(),false)...); } void EnableInternal(...) {} diff --git a/autowiring/MicroBolt.h b/autowiring/MicroBolt.h index 4e93356da..6b3b3a27b 100644 --- a/autowiring/MicroBolt.h +++ b/autowiring/MicroBolt.h @@ -32,11 +32,7 @@ struct MicroBolt: // multiple calls to Inject() if a matching context // is created during traversal. const auto ctxt = CoreContext::CurrentContext(); - const bool dummy [] = { - LoopInject(ctxt)..., - false - }; - (void)dummy; + [](...){}(LoopInject(ctxt)...); } void ContextCreated(void) override; }; From 8613503cae0e6efce0d0e8945a470c76f86f478e Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 12 Mar 2015 02:39:24 -0700 Subject: [PATCH 15/18] Adding a slots/signals concept --- autowiring/AutowirableSlot.h | 5 + autowiring/Autowired.h | 54 ++++++- autowiring/CoreContext.h | 36 +++++ autowiring/auto_signal.h | 216 +++++++++++++++++++++++++ src/autowiring/CMakeLists.txt | 2 + src/autowiring/auto_signal.cpp | 35 ++++ src/autowiring/test/AutoSignalTest.cpp | 69 ++++++++ src/autowiring/test/CMakeLists.txt | 1 + 8 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 autowiring/auto_signal.h create mode 100644 src/autowiring/auto_signal.cpp create mode 100644 src/autowiring/test/AutoSignalTest.cpp diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 69d76ef92..5778b02bb 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -84,6 +84,11 @@ class DeferrableAutowiring: m_pFlink = pFlink; } + /// + /// The context corresponding to this slot, if it hasn't already expired + /// + std::shared_ptr GetContext(void) const { return m_context.lock(); } + /// /// The type on which this deferred slot is bound /// diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index ec13671c7..67e86dbb1 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "auto_signal.h" #include "AutowirableSlot.h" #include "Decompose.h" #include "GlobalCoreContext.h" @@ -120,11 +121,15 @@ class Autowired: } ~Autowired(void) { + // Unlink all signal entries + for (auto& unlinkEntry : m_unlinkEntries) + *unlinkEntry.relay -= unlinkEntry.node; + if(m_pFirstChild == this) // Tombstoned, nothing to do: return; - // Need to ensure that nobody tries to autowire us while we are tearing down: + // Need to ensure that nobody tries to fill us while we are tearing down: this->CancelAutowiring(); // And now we destroy our deferrable autowiring collection: @@ -139,6 +144,23 @@ class Autowired: // which will be the first member registered via NotifyWhenAutowired. std::atomic*> m_pFirstChild; + struct unlink_entry + { + unlink_entry( + autowiring::signal_relay* relay, + autowiring::internal::signal_node_base* node + ) : + relay(relay), + node(node) + {} + + autowiring::signal_relay* relay; + autowiring::internal::signal_node_base* node; + }; + + // The set of all nodes that will have to be unlinked when this field is torn down + std::vector m_unlinkEntries; + public: operator const std::shared_ptr&(void) const { return @@ -156,7 +178,37 @@ class Autowired: operator T*(void) const { return this->operator const std::shared_ptr&().get(); } + + template + struct signal_relay { + signal_relay(Autowired& owner, autowiring::signal_relay_t& relay) : + owner(owner), + relay(relay) + {} + + private: + Autowired& owner; + autowiring::signal_relay_t& relay; + + public: + void operator+=(std::function&& fn) { + owner.m_unlinkEntries.push_back( + unlink_entry( + &relay, + relay += std::move(fn) + ) + ); + } + }; + template + signal_relay operator()(autowiring::signal T::*handler) { + auto ctxt = AutowirableSlot::GetContext(); + if (!ctxt) + throw std::runtime_error("Attempted to attach a signal to an Autowired field in a context that was already destroyed"); + return {*this, ctxt->RelayForType(handler)}; + } + /// /// Assigns a lambda function to be called when the dependency for this slot is autowired. /// diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 9cb862fba..d65477d78 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -1,6 +1,7 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "AnySharedPointer.h" +#include "auto_signal.h" #include "AutoFilterDescriptor.h" #include "AutowirableSlot.h" #include "AutowiringEvents.h" @@ -52,6 +53,11 @@ class CoreContextT; template class JunctionBox; +namespace autowiring { + template + struct signal_relay_t; +} + /// \file /// CoreContext definitions. @@ -1144,6 +1150,36 @@ class CoreContext: /// void CancelAutowiringNotification(DeferrableAutowiring* pDeferrable); + /// + /// A slots-and-signals type relay for a specific type + /// + template + autowiring::signal_relay_t& RelayForType(autowiring::signal T::*handler) { + typedef typename SelectTypeUnifier::type TActual; + + // Get the table first + auto registration = Inject(); + + // Find the basis offset between T and TActual. This is the address of the first member of T + // relative to the base of TActual. + size_t basis = + reinterpret_cast( + static_cast( + reinterpret_cast(1) + ) + ) - 1; + + // Find the offset and return the relay + return + static_cast&>( + *registration->GetSignalRelay( + typeid(TActual), + basis + + reinterpret_cast(&(static_cast(nullptr)->*handler)) + ) + ); + } + /// /// Utility debug method for writing a snapshot of this context to the specified output stream /// diff --git a/autowiring/auto_signal.h b/autowiring/auto_signal.h new file mode 100644 index 000000000..5f7823e32 --- /dev/null +++ b/autowiring/auto_signal.h @@ -0,0 +1,216 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include +#include +#include +#include +#include + +namespace autowiring { + struct signal_relay_registration_table; +} + +/// +/// Implements an asynchronous signal concept as an AutoFired alternative +/// +namespace autowiring { + template + struct signal; + + struct signal_relay; + + namespace internal { + // Linked list entry + struct signal_node_base { + signal_node_base(void) : + pFlink(nullptr), + pBlink(nullptr) + {} + + virtual ~signal_node_base(void) {} + + // Forward and backward linkages: + signal_node_base* pFlink; + signal_node_base* pBlink; + }; + + // Holds a reference to one of the signal holders + template + struct signal_node: + signal_node_base + { + signal_node(const signal_node& rhs) = delete; + + signal_node(std::function&& fn) : + fn(std::move(fn)) + {} + + const std::function fn; + }; + + + struct signal_registration_base { + signal_registration_base(void); + + // Associates signal entries with their corresponding relays + std::unordered_map> entries; + }; + + /// + /// Associates offsets in type T with signals in that type + /// + template + struct signal_registration: + signal_registration_base + {}; + + /// + /// Obtains the signal relay for the specified member type: + /// + std::shared_ptr ObtainRelay(void* pMember); + } + + /// + /// Associates abstract offsets with signal relays + /// + /// + /// One of these registration tables exists per context. + /// + struct signal_relay_registration_table { + struct registration { + registration(const std::type_info* ti, size_t offset) : + ti(ti), + offset(offset) + {} + + const std::type_info* ti; + size_t offset; + + bool operator==(const registration& rhs) const { + return *ti == *rhs.ti && offset == rhs.offset; + } + }; + + struct registration_hash { + size_t operator()(const registration& reg) const { + return reg.ti->hash_code() + reg.offset; + } + }; + + // Lock for the table: + std::mutex lock; + + /// + /// Associates an offset in the relay registration with a signal relay + /// + /// + /// This map assumes that users have some other way of inferring the types on the key side. + /// + std::unordered_map, registration_hash> table; + + /// + /// Creates or returns the relay at the specified offset + /// + std::shared_ptr GetSignalRelay(const std::type_info& ti, size_t offset); + }; + + /// + /// Stores a signal + /// + struct signal_relay + { + public: + signal_relay(void) : + pHead(nullptr) + {} + + ~signal_relay(void) { + // Standard linked list cleaup + internal::signal_node_base* next = nullptr; + for (auto cur = pHead; cur; cur = next) { + next = cur->pFlink; + delete cur; + } + } + + protected: + // First entry on the list: + internal::signal_node_base* pHead; + + public: + void operator-=(internal::signal_node_base* rhs) { + // Clear linkage + if (rhs->pBlink) + rhs->pBlink->pFlink = rhs->pFlink; + if (rhs->pFlink) + rhs->pFlink->pBlink = rhs->pBlink; + + // If we're the head pointer then unlink + if (rhs == pHead) + pHead = rhs->pFlink; + + // Fully unlinked, delete + delete rhs; + } + }; + + /// + /// Descriptor type used to add entries that will be constructed in a signal relay + /// + template + struct signal_relay_t: + signal_relay + { + internal::signal_node* GetHead(void) const { + return static_cast*>(pHead); + } + + /// + /// Attaches the specified handler to this signal + /// + internal::signal_node* operator+=(std::function&& fn) { + // Standard singly linked list insert: + auto retVal = new internal::signal_node(std::move(fn)); + retVal->pFlink = pHead; + if (pHead) + pHead->pBlink = retVal; + pHead = retVal; + return retVal; + } + }; + + /// + /// A signal registration entry, for use as an embedded member variable of a context member. + /// + template + struct signal + { + public: + signal(void) : + m_relay( + std::static_pointer_cast>( + internal::ObtainRelay(this) + ) + ) + {} + + private: + const std::shared_ptr> m_relay; + + public: + internal::signal_node* operator+=(std::function&& fn) { return *m_relay += std::move(fn); } + void operator-=(internal::signal_node* node) { return *m_relay -= node; } + + /// + /// Raises the signal and invokes all attached handlers + /// + void operator()(Args... args) const { + for ( + auto cur = m_relay->GetHead(); + cur; + cur = static_cast(cur->pFlink) + ) + cur->fn(args...); + } + }; +} diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index f502fdc18..dc9031e6c 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -16,6 +16,8 @@ set(Autowiring_SRCS at_exit.h auto_id.h auto_future.h + auto_signal.h + auto_signal.cpp AutoConfig.cpp AutoConfig.h AutoConfigManager.cpp diff --git a/src/autowiring/auto_signal.cpp b/src/autowiring/auto_signal.cpp new file mode 100644 index 000000000..29465cd0b --- /dev/null +++ b/src/autowiring/auto_signal.cpp @@ -0,0 +1,35 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "auto_signal.h" +#include "Autowired.h" +#include "SlotInformation.h" + +using namespace autowiring; +using namespace autowiring::internal; + +signal_registration_base::signal_registration_base(void) { + +} + +std::shared_ptr signal_relay_registration_table::GetSignalRelay(const std::type_info& ti, size_t offset) +{ + std::lock_guard lk(lock); + auto& retVal = table[registration(&ti, offset)]; + if (!retVal) + retVal = std::make_shared(); + return retVal; +} + +std::shared_ptr autowiring::internal::ObtainRelay(void* pMember) +{ + auto location = SlotInformationStackLocation::CurrentStackLocation(); + if (!location || !location->Encloses(pMember)) + // Signal is being constructed outside of an autowired member, we need to create a default relay + return std::make_shared(); + + // Signal is being constructed as a member, the registration table should know where the relay is + return AutoRequired()->GetSignalRelay( + location->stump.ti, + location->Offset(pMember) + ); +} \ No newline at end of file diff --git a/src/autowiring/test/AutoSignalTest.cpp b/src/autowiring/test/AutoSignalTest.cpp new file mode 100644 index 000000000..71fadacc8 --- /dev/null +++ b/src/autowiring/test/AutoSignalTest.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include + +class AutoSignalTest: + public testing::Test +{}; + +class RaisesASignal { +public: + autowiring::signal signal; +}; + +TEST_F(AutoSignalTest, SimpleSignalTest) { + autowiring::signal signal; + + bool handler_called = false; + int val = 0; + + // Register a handler directly on the signal: + auto* registration = + signal += [&](int v) { + handler_called = true; + val = v; + }; + + // Trivially raise the signal: + signal(101); + ASSERT_TRUE(handler_called) << "Handler was not called on a stack-allocated signal"; + ASSERT_EQ(101, val) << "Handler did not correctly copy a passed value"; + + // Unregister and verify we can still raise the signal: + signal -= registration; + + handler_called = false; + signal(102); + ASSERT_FALSE(handler_called) << "Handler was called after being unregistered"; +} + +TEST_F(AutoSignalTest, SignalWithAutowiring) { + bool handler_called = false; + int val = 202; + + { + Autowired ras; + + // Register a signal handler: + ras(&RaisesASignal::signal) += [&](int v) { + handler_called = true; + val = v; + }; + + // Inject type type after the signal has been registered + AutoRequired(); + + // Now raise the signal, see what happens: + ras->signal(55); + + // Verify that the handler got called with the correct value: + ASSERT_TRUE(handler_called) << "Signal handler was not invoked"; + ASSERT_EQ(55, val) << "Signal handler not called with the correct parameter as expected"; + } + + // Raise the signal again, this should not cause anything to break: + Autowired ras; + handler_called = false; + ras->signal(99); + ASSERT_FALSE(handler_called) << "A handler was unexpectedly called after it should have been destroyed"; +} diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index ef348d204..005793884 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -10,6 +10,7 @@ set(AutowiringTest_SRCS AutoPacketFactoryTest.cpp AutoParameterTest.cpp AutoRestarterTest.cpp + AutoSignalTest.cpp AutowiringTest.cpp AutowiringUtilitiesTest.cpp BasicThreadTest.cpp From a3fb0ccb0fadad6856bc4b4237db7e1dfdc60090 Mon Sep 17 00:00:00 2001 From: Jimmy Nguyen Date: Fri, 13 Mar 2015 10:15:49 -0700 Subject: [PATCH 16/18] Adding tests for multiple slots and raising a signal within a slot --- src/autowiring/test/AutoSignalTest.cpp | 55 ++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/autowiring/test/AutoSignalTest.cpp b/src/autowiring/test/AutoSignalTest.cpp index 71fadacc8..7cba85b91 100644 --- a/src/autowiring/test/AutoSignalTest.cpp +++ b/src/autowiring/test/AutoSignalTest.cpp @@ -67,3 +67,58 @@ TEST_F(AutoSignalTest, SignalWithAutowiring) { ras->signal(99); ASSERT_FALSE(handler_called) << "A handler was unexpectedly called after it should have been destroyed"; } + +TEST_F(AutoSignalTest, MultipleSlotsTest) { + autowiring::signal signal; + + bool handler_called1 = false; + bool handler_called2 = false; + + auto* registration1 = + signal += [&] { + handler_called1 = true; + }; + + // Registration 2 + signal += [&] { + handler_called2 = true; + }; + + // Trivially raise the signal: + signal(); + ASSERT_TRUE(handler_called1) << "Handler 1 was not called on a stack-allocated signal"; + ASSERT_TRUE(handler_called2) << "Handler 2 was not called on a stack-allocated signal"; + + // Unregister the first signal and reset the variables + signal -= registration1; + + handler_called1 = false; + handler_called2 = false; + + // Verify that registration 2 can still receive the signals + signal(); + ASSERT_FALSE(handler_called1) << "Handler 1 was called after being unregistered"; + ASSERT_TRUE(handler_called2) << "Handler 2 was inadvertently unregistered"; +} + +TEST_F(AutoSignalTest, RaiseASignalWithinASlotTest) { + autowiring::signal signal1; + autowiring::signal signal2; + + bool handler_called1 = false; + bool handler_called2 = false; + + signal1 += [&] { + handler_called1 = true; + signal2(); + }; + + signal2 += [&] { + handler_called2 = true; + }; + + // Trivially raise the signal: + signal1(); + ASSERT_TRUE(handler_called1) << "Handler 1 was not called on a stack-allocated signal"; + ASSERT_TRUE(handler_called2) << "Handler 2 was not called on a stack-allocated signal"; +} From 356394b47f918524b367bbdd0482cb38dde4fecf Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 13 Mar 2015 11:33:30 -0700 Subject: [PATCH 17/18] Bump version number --- Doxyfile | 2 +- publicDoxyfile.conf | 2 +- version.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doxyfile b/Doxyfile index 108cb6b2b..38bd60e9f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -32,7 +32,7 @@ PROJECT_NAME = Autowiring # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = "0.4.5" +PROJECT_NUMBER = "0.5.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer diff --git a/publicDoxyfile.conf b/publicDoxyfile.conf index c5bfd81e9..c56e74b41 100644 --- a/publicDoxyfile.conf +++ b/publicDoxyfile.conf @@ -38,7 +38,7 @@ PROJECT_NAME = Autowiring # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 0.4.5 +PROJECT_NUMBER = 0.5.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/version.cmake b/version.cmake index a2e8c8864..006a6503f 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.4.5) +set(autowiring_VERSION 0.5.0) From 91e795c5c547ae4756943b592067ecd4c729a91b Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 13 Mar 2015 16:22:54 -0700 Subject: [PATCH 18/18] We still have to install Autoboost headers on ARM --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index df6a547ca..fa25903a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,6 +189,15 @@ if(NOT AUTOWIRING_IS_EMBEDDED) COMPONENT autowiring FILES_MATCHING PATTERN "*.h" ) + + # Install autoboost headers on ARM, which still requires them + if(autowiring_BUILD_ARM) + install( + DIRECTORY ${PROJECT_SOURCE_DIR}/contrib/autoboost/autoboost + DESTINATION include + COMPONENT autowiring + ) + endif() # Targets file is needed in order to describe how to link Autowiring to the rest of the system install(EXPORT AutowiringTargets FILE AutowiringTargets.cmake COMPONENT autowiring NAMESPACE Autowiring:: DESTINATION cmake CONFIGURATIONS ${CMAKE_CONFIGURATION_TYPES})