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/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..fa25903a8 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++") @@ -223,9 +189,9 @@ if(NOT AUTOWIRING_IS_EMBEDDED) COMPONENT autowiring FILES_MATCHING PATTERN "*.h" ) - - # Install autoboost headers - if(autowiring_INSTALL_AUTOBOOST) + + # Install autoboost headers on ARM, which still requires them + if(autowiring_BUILD_ARM) install( DIRECTORY ${PROJECT_SOURCE_DIR}/contrib/autoboost/autoboost DESTINATION include diff --git a/Doxyfile b/Doxyfile index 8bea9e696..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.2" +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/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 be79fb5a8..a56f1a8f9 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; + } } /// @@ -431,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/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 69d76ef92..01fc6f7bd 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 /// @@ -184,15 +189,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..c8a695be6 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. /// @@ -331,6 +383,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 { + T* retVal = std::shared_ptr::get(); + 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/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/autowiring/CoreContext.h b/autowiring/CoreContext.h index 9cb862fba..c1cf9c06d 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. @@ -161,7 +167,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 @@ -295,11 +302,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(...) {} @@ -325,7 +328,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 +418,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 +658,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. /// @@ -1144,6 +1184,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/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index b5629d179..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" @@ -24,9 +25,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)), @@ -36,12 +37,14 @@ struct CoreObjectDescriptor { pFilter(autowiring::fast_pointer_cast(value)), 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())) + [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; - } // "T" not found in event registry return false; }() @@ -63,12 +66,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 +101,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/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/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/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; }; 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/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/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) { 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/publicDoxyfile.conf b/publicDoxyfile.conf index a5dfcb4b8..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.2 +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/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 diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index 123f9ebbc..4d697cd8d 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(){} @@ -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}, @@ -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; 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/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/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/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/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 651dd54fa..2a27d24b6 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); @@ -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/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); 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/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)); + } +} diff --git a/src/autowiring/test/AutoSignalTest.cpp b/src/autowiring/test/AutoSignalTest.cpp new file mode 100644 index 000000000..7cba85b91 --- /dev/null +++ b/src/autowiring/test/AutoSignalTest.cpp @@ -0,0 +1,124 @@ +// 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"; +} + +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"; +} diff --git a/src/autowiring/test/AutowiringTest.cpp b/src/autowiring/test/AutowiringTest.cpp index 48181d0b0..b12d71489 100644 --- a/src/autowiring/test/AutowiringTest.cpp +++ b/src/autowiring/test/AutowiringTest.cpp @@ -178,3 +178,15 @@ 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"; +} + +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 diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index ef348d204..56ef33af5 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -5,11 +5,15 @@ set(AutowiringTest_SRCS AutoConstructTest.cpp AutoFilterCollapseRulesTest.cpp AutoFilterDiagnosticsTest.cpp + AutoFilterFunctionTest.cpp + AutoFilterSequencing.cpp + AutoFilterTest.cpp AutoInjectableTest.cpp AutoPacketTest.cpp AutoPacketFactoryTest.cpp AutoParameterTest.cpp AutoRestarterTest.cpp + AutoSignalTest.cpp AutowiringTest.cpp AutowiringUtilitiesTest.cpp BasicThreadTest.cpp @@ -66,14 +70,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 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 diff --git a/version.cmake b/version.cmake index 116908e6e..006a6503f 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.4.4) +set(autowiring_VERSION 0.5.0)