diff --git a/CMakeLists.txt b/CMakeLists.txt index fa25903a8..610b45fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,9 @@ endif() message(STATUS "Compiler version ${CMAKE_CXX_COMPILER_VERSION}") -# Always use c++11 compiler +# Always use c++11 compiler with hidden visibility if(NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fvisibility=hidden") endif() # Clang needs special additional flags to build with C++11 diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 09b0a5b45..79ebbaad4 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -117,38 +117,3 @@ ====================================================== -1. Jared Deckard -Github account: deckar01 -Email: jared.deckard@gmail.com - -2. Stu Kabakoff -Github account: sakabako -Email: sakabako@gmail.com - -3. Yoshihiro Iwanaga -Github account: iwanaga -Email: iwanaga.blackie@gmail.com - -4. ARJUNKUMAR KRISHNAMOORTHY -Github account: tk120404 -Email: Arjunkumartk@gmail.com - -5. Rob Witoff -Github account: witoff -Email: leap@pspct.com - -6. Richard Pearson -Github account: catdevnull -Email: github@catdevnull.co.uk - -7. Ben Nortier -Github account: bjnortier -Email: bjnortier@gmail.com - -8. Andrew Kennedy -Github account: akenn -Email: andrew@akenn.org - -9. Victor Norgren -Github account: logotype -Email: victor@logotype.se diff --git a/Doxyfile b/Doxyfile index e73312580..eb0d4511c 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.5.1" +PROJECT_NUMBER = "0.5.2" # 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/AutoConfig.h b/autowiring/AutoConfig.h index cc7b5c405..11625ba41 100644 --- a/autowiring/AutoConfig.h +++ b/autowiring/AutoConfig.h @@ -1,93 +1,179 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "AutoConfigBase.h" #include "Autowired.h" -#include "AutoConfigManager.h" #include "ConfigRegistry.h" -#include -#include TYPE_INDEX_HEADER - -struct AnySharedPointer; - -/// \internal /// -/// Utility base type for configuration members -/// -class AutoConfigBase -{ -public: - AutoConfigBase(const std::type_info& tiName); - - // Key used to identify this config value - const std::string m_key; -}; - +/// The type underlying the AutoConfig System. +/// Represents a unique type created by the combination of the type and a set of sigils. +/// Responsible for tracking changes to the underlying value and triggering signals, +/// making sure values are inherited correctly from enclosing contexts, and providing +/// a primitive polymorphic get/set interface (void*) /// -/// Register an attribute with the AutoConfig system. For example -/// -/// AutoConfig m_myVal; -/// defines the key "MyNamespace.MyKey" -/// -/// The Namespace field is optional, so -/// AutoConfig m_myVal; -/// defines the key "MyKey" -/// -/// AutoConfig values can be set from the AutoConfigManager. The string key -/// is used as the identifier for the value -/// + template -class AutoConfig: - public AutoConfigBase +class AutoConfigVar: + public AutoConfigVarBase { public: - static_assert(sizeof...(TKey) >= 1, "Must provide a key and optionally at least one namespace"); + static_assert(sizeof...(TKey) >= 1, "Must provide a key and optionally a set of namespaces"); template - AutoConfig(t_Arg &&arg, t_Args&&... args) : - AutoConfigBase(typeid(ConfigTypeExtractor)) + AutoConfigVar(t_Arg &&arg, t_Args&&... args) : + AutoConfigVarBase(typeid(ConfigTypeExtractor), true), + m_value(std::forward(arg), std::forward(args)...) { // Register with config registry - (void)RegConfig::r; + (void)AutoConfigVar::RegistryEntry; - if (!IsConfigured()){ - m_manager->Set(m_key, T(std::forward(arg), std::forward(args)...)); + if (RegistryEntry.m_hasValidator) { + if (!RegistryEntry.validatorInternal()(m_value)) { + throw autowiring_error("Cannot construct AutoConfigVar with a value that fails validation"); + } } + + onChangedSignal(*this); } - AutoConfig() : - AutoConfigBase(typeid(ConfigTypeExtractor)) + AutoConfigVar() : + AutoConfigVarBase(typeid(ConfigTypeExtractor)), + m_value() { // Register with config registry - (void)RegConfig::r; - } + (void)AutoConfigVar::RegistryEntry; + + const auto ctxt = m_context.lock(); + if (!ctxt) + return; -protected: - AutoRequired m_manager; + //This will wind up being recursive + auto parent = ctxt->GetParentContext(); + if (parent != nullptr) { + auto parentVar = parent->template Inject>(); -public: - const T& operator*() const { - return *m_manager->Get(m_key).template as().get(); - } + //Only copy the value if it's initalized. Base::AutoInit will take care of + //the various listing notifications. + if (parentVar->IsConfigured()) { + m_isConfigured = true; + m_value = parentVar->m_value; + } - const T* operator->(void) const { - return m_manager->Get(m_key)->template as().get(); + m_parentRegistration = *parentVar += [this](const T& val){ + RunValidation(val); + SetInternal(val); + }; + } } +public: + + operator const T&() const { return m_value; } + const T* operator->() const { return &m_value; } + void operator=(const T& newValue) { - return m_manager->Set(m_key, newValue); + RunValidation(newValue); + + if (m_parentRegistration) { + auto parent_ctxt = m_context.lock()->GetParentContext(); + AutowiredFast> parentVar(parent_ctxt); + *parentVar -= m_parentRegistration; + m_parentRegistration = nullptr; + OnSetLocally(); + } + + SetInternal(newValue); } - /// - /// True if this configurable field has been satisfied with a value - /// - bool IsConfigured(void) const { - return m_manager->IsConfigured(m_key); + void Get(void* pValue) const override { *reinterpret_cast(pValue) = m_value; } + void Set(const void* pValue) override { *this = *reinterpret_cast(pValue); } + + void SetParsed(const std::string& value) override { + *this = RegistryEntry.template parseInternal(value); } - + // Add a callback for when this config value changes - void operator+=(std::function&& fx) { - m_manager->AddCallback(m_key, [fx](const AnySharedPointer& val){ - fx(*val.template as().get()); - }); + t_OnChangedSignal::registration_t* operator+=(std::function&& fx) { + return onChangedSignal += [fx](const AutoConfigVarBase& var){ + fx(reinterpret_cast*>(&var)->m_value); + }; + } + + void operator-=(t_OnChangedSignal::registration_t* node) { onChangedSignal -= node; } + +private: + T m_value; + + void RunValidation(const T& val) { + if (RegistryEntry.m_hasValidator) { + if (!RegistryEntry.validatorInternal()(val)) { + throw autowiring_error("Validator rejected set for config value"); + } + } + } + + void SetInternal(const T& val) { + m_isConfigured = true; + m_value = val; + onChangedSignal(*this); + } + +public: + static std::shared_ptr Inject(const std::shared_ptr& ctxt, const void* value) { + if (!value) + return ctxt->Inject>(); + else + return ctxt->Inject>(*reinterpret_cast(value)); } + + static const ConfigRegistryEntryT RegistryEntry; }; + +template +const ConfigRegistryEntryT AutoConfigVar::RegistryEntry(&AutoConfigVar::Inject); + + +/// +/// Register an attribute with the AutoConfig system. For example +/// +/// AutoConfig m_myVal; +/// defines the key "MyNamespace.MyKey" +/// +/// The Namespace field is optional, so +/// AutoConfig m_myVal; +/// defines the key "MyKey" +/// +/// AutoConfig values can also be set from the AutoConfigListing in the same context. The string key +/// is used as the identifier for the value. +/// +template +class AutoConfig : public AutoRequired> { +public: + typedef AutoConfigVar t_Var; + + using AutoRequired::operator*; + + AutoConfig(const std::shared_ptr& ctxt = CoreContext::CurrentContext()) : + AutoRequired(ctxt) + { + } + + AutoConfig(T&& initialValue, const std::shared_ptr& ctxt = CoreContext::CurrentContext()) : + AutoRequired(ctxt, std::move(initialValue)) + { + if (!(*this)->IsLocal()) { + **this = std::move(initialValue); + } + } + + template + explicit AutoConfig(t_Arg&& arg, t_Args&&... args) : + AutoRequired(CoreContext::CurrentContext(), std::forward(arg), std::forward(args)...) + { + //If we wind up being a reference to an existing value, we may still want to set it... + if (!(*this)->IsLocal()) { + **this = T(std::forward(arg), std::forward(args)...); + } + } + +}; \ No newline at end of file diff --git a/autowiring/AutoConfigBase.h b/autowiring/AutoConfigBase.h new file mode 100644 index 000000000..0fbb06dfe --- /dev/null +++ b/autowiring/AutoConfigBase.h @@ -0,0 +1,43 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include "ContextMember.h" +#include "auto_signal.h" + +#include +#include "C++11/cpp11.h" +#include TYPE_INDEX_HEADER + +struct AnySharedPointer; + +/// \internal +/// +/// Utility base type & interface for configuration members +/// +class AutoConfigVarBase : public ContextMember +{ +public: + AutoConfigVarBase(const std::type_info& tiName, bool configured = false); + void AutoInit(); + + // Key used to identify this config value + const std::string m_key; + + // True if this config was set at all + bool IsConfigured() const { return m_isConfigured; } + bool IsInherited() const { return m_parentRegistration != nullptr; } + //True if the config was set from within this context (isn't inherited) + bool IsLocal() const { return IsConfigured() && !IsInherited(); } + + typedef autowiring::signal t_OnChangedSignal; + t_OnChangedSignal onChangedSignal; + + virtual void Get(void* pValue) const = 0; + virtual void Set(const void* pValue) = 0; + virtual void SetParsed(const std::string& value) = 0; + +protected: + void OnSetLocally(); + + bool m_isConfigured; + t_OnChangedSignal::registration_t* m_parentRegistration; +}; diff --git a/autowiring/AutoConfigManager.h b/autowiring/AutoConfigListing.h similarity index 61% rename from autowiring/AutoConfigManager.h rename to autowiring/AutoConfigListing.h index 03a34a5ac..e143644cd 100644 --- a/autowiring/AutoConfigManager.h +++ b/autowiring/AutoConfigListing.h @@ -10,48 +10,37 @@ #include MUTEX_HEADER #include MEMORY_HEADER -struct AnySharedPointer; +class AutoConfigVarBase; -class AutoConfigManager: +class AutoConfigListing: public ContextMember { public: - AutoConfigManager(); - virtual ~AutoConfigManager(); + AutoConfigListing(); + virtual ~AutoConfigListing(); - // Callback function type - typedef std::function t_callback; + // Callback signal type + typedef autowiring::signal onAddSignal_t; - typedef std::function t_add_callback; - // Validator function type typedef std::function t_validator; private: - // local map of the config registry + // Local map of the config registry static const std::unordered_map s_registry; - // map of validators registered for a key - static const std::unordered_map> s_validators; + // Validators for keys that have them. + static const std::unordered_map s_validators; - // lock for all members + // Lock for all members std::mutex m_lock; - // Values of AutoConfigs in this context - std::unordered_map m_values; - - // Set of keys for values set from this context - std::unordered_set m_setHere; + // Map of AutoConfigs in this context + std::unordered_map> m_values; - // list of keys for values set from this context in order of creation. + // list of keys set locally from this context in order of creation. std::vector m_orderedKeys; - // map of callbacks registered for a key - std::unordered_map> m_callbacks; - - // list of callbacks registered for keys which exist in this context. - std::list m_addCallbacks; - // Exception throwers: void ThrowKeyNotFoundException(const std::string& key) const; void ThrowTypeMismatchException(const std::string& key, const std::type_info& ti) const; @@ -74,10 +63,10 @@ class AutoConfigManager: /// This method will throw an exception if the specified name cannot be found as a configurable value /// in the application, or if the specified value type does not match the type expected by this field /// - AnySharedPointer& Get(const std::string& key); + std::shared_ptr Get(const std::string& key); /// - /// Assigns the specified value to an AnySharedPointer slot + /// Assigns the specified value to an AutoConfig with a given key. Creates one if it doesn't exist. /// /// /// This method will throw an exception if the specified name cannot be found as a configurable value @@ -92,15 +81,9 @@ class AutoConfigManager: if (!s_registry.find(key)->second->verifyType(typeid(T))) ThrowTypeMismatchException(key, typeid(T)); - // Set value in this AutoConfigManager - SetRecursive(key, AnySharedPointer(std::make_shared(value))); + SetInternal(key, &value); } - /// - /// Overload for c-style string. Converts to std::string - /// - void Set(const std::string& key, const char* value); - /// /// Coerces the string representation of the specified field to the correct value type /// @@ -113,20 +96,25 @@ class AutoConfigManager: bool SetParsed(const std::string& key, const std::string& value); // Add a callback for when key is changed in this context - void AddCallback(const std::string& key, t_callback&& fx); + void AddOnChanged(const std::string& key, std::function&& fx); // Add a callback for when a key is set in this context. Is immediately called on all - // currently existing values in the order they were created - void AddCallback(t_add_callback&& fx); + // currently existing values in the order they were created. + onAddSignal_t::registration_t* AddCallback(onAddSignal_t::function_t&& fx); - // Returns a list of all keys which have been set from this context + // Returns a list of all keys which have been set from this context. const std::vector& GetLocalKeys() const { return m_orderedKeys; } private: - // Handles setting a value recursivly to all child contexts - void SetRecursive(const std::string& key, AnySharedPointer value); - // Set a value in this manager, call callbacks // Must hold m_lock when calling this - void SetInternal(const std::string& key, const AnySharedPointer& value); + void SetInternal(const std::string& key, const void* value); + + std::shared_ptr GetOrConstruct(const std::string& key, const void* value); + + onAddSignal_t m_onAddedSignal; + + friend class AutoConfigVarBase; + void NotifyConfigAdded(const std::shared_ptr& cfg); + void NotifySetLocally(const std::shared_ptr& cfg); }; diff --git a/autowiring/AutoFilterDescriptor.h b/autowiring/AutoFilterDescriptor.h index ddfd2b67f..d0e596242 100644 --- a/autowiring/AutoFilterDescriptor.h +++ b/autowiring/AutoFilterDescriptor.h @@ -1,6 +1,7 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "AnySharedPointer.h" +#include "altitude.h" #include "auto_arg.h" #include "AutoFilterDescriptorInput.h" #include "CallExtractor.h" @@ -18,6 +19,7 @@ class Deferred; struct AutoFilterDescriptorStub { AutoFilterDescriptorStub(void) : m_pType(nullptr), + m_altitude(autowiring::altitude::Standard), m_pArgs(nullptr), m_deferred(false), m_arity(0), @@ -25,14 +27,8 @@ struct AutoFilterDescriptorStub { m_pCall(nullptr) {} - AutoFilterDescriptorStub(const AutoFilterDescriptorStub& rhs) : - m_pType(rhs.m_pType), - m_pArgs(rhs.m_pArgs), - m_deferred(rhs.m_deferred), - m_arity(rhs.m_arity), - m_requiredCount(rhs.m_requiredCount), - m_pCall(rhs.m_pCall) - {} + AutoFilterDescriptorStub(const AutoFilterDescriptorStub&) = default; + AutoFilterDescriptorStub& operator=(const AutoFilterDescriptorStub&) = default; /// /// Constructs a new packet subscriber entry based on the specified call extractor and call pointer @@ -46,8 +42,9 @@ struct AutoFilterDescriptorStub { /// is required to carry information about the type of the proper member function to be called; t_extractedCall is /// required to be instantiated by the caller and point to the AutoFilter proxy routine. /// - AutoFilterDescriptorStub(const std::type_info* pType, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) : + AutoFilterDescriptorStub(const std::type_info* pType, autowiring::altitude altitude, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) : m_pType(pType), + m_altitude(altitude), m_pArgs(pArgs), m_deferred(deferred), m_arity(0), @@ -67,6 +64,9 @@ struct AutoFilterDescriptorStub { // Type of the subscriber itself const std::type_info* m_pType; + // Altitude--controls when the filter gets called + autowiring::altitude m_altitude; + // This subscriber's argument types // NOTE: This is a reference to a static generated list, // therefore it MUST be const and MUST be shallow-copied. @@ -93,6 +93,7 @@ struct AutoFilterDescriptorStub { public: // Accessor methods: + autowiring::altitude GetAltitude(void) const { return m_altitude; } const std::type_info* GetType() const { return m_pType; } size_t GetArity(void) const { return m_arity; } size_t GetRequiredCount(void) const { return m_requiredCount; } @@ -137,18 +138,8 @@ struct AutoFilterDescriptor: AutoFilterDescriptorStub { AutoFilterDescriptor(void) {} - - AutoFilterDescriptor(const AutoFilterDescriptor& rhs) : - AutoFilterDescriptorStub(rhs), - m_autoFilter(rhs.m_autoFilter) - {} - - AutoFilterDescriptor& operator=(const AutoFilterDescriptor& rhs) { - AutoFilterDescriptorStub::operator = (rhs); - m_autoFilter = rhs.m_autoFilter; - return *this; - } - + AutoFilterDescriptor(const AutoFilterDescriptor&) = default; + AutoFilterDescriptor& operator=(const AutoFilterDescriptor&) = default; AutoFilterDescriptor(AutoFilterDescriptor&& rhs) : AutoFilterDescriptorStub(std::move(rhs)), m_autoFilter(std::move(rhs.m_autoFilter)) @@ -178,6 +169,10 @@ struct AutoFilterDescriptor: std::static_pointer_cast::type>(subscriber) ), &typeid(T), + autowiring::altitude_of< + T, + CallExtractor::deferred ? autowiring::altitude::Dispatch : autowiring::altitude::Standard + >::value, Decompose::template Enumerate::types, CallExtractor::deferred, &CallExtractor::template Call<&T::AutoFilter> @@ -191,10 +186,11 @@ struct AutoFilterDescriptor: /// Recipients added in this way cannot receive piped data, since they are anonymous. /// template - AutoFilterDescriptor(Fn fn): + AutoFilterDescriptor(Fn fn, autowiring::altitude altitude = autowiring::altitude::Standard): AutoFilterDescriptor( AnySharedPointer(std::make_shared(std::forward(fn))), &typeid(Fn), + altitude, CallExtractor::template Enumerate::types, false, &CallExtractor::template Call<&Fn::operator()> @@ -219,13 +215,13 @@ struct AutoFilterDescriptor: /// /// The caller is responsible for decomposing the desired routine into the target AutoFilter call /// - AutoFilterDescriptor(const AnySharedPointer& autoFilter, const std::type_info* pType, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) : - AutoFilterDescriptorStub(pType, pArgs, deferred, pCall), + AutoFilterDescriptor(const AnySharedPointer& autoFilter, const std::type_info* pType, autowiring::altitude altitude, const AutoFilterDescriptorInput* pArgs, bool deferred, t_extractedCall pCall) : + AutoFilterDescriptorStub(pType, altitude, pArgs, deferred, pCall), m_autoFilter(autoFilter) {} template - AutoFilterDescriptor(RetType(*pfn)(Args...)): + AutoFilterDescriptor(RetType(*pfn)(Args...), autowiring::altitude altitude = autowiring::altitude::Standard) : AutoFilterDescriptor( // Token shared pointer, used to provide a pointer to pfn because we can't // capture it in a template processing context. Hopefully this can be changed @@ -239,7 +235,7 @@ struct AutoFilterDescriptor: // The remainder is fairly straightforward &typeid(pfn), - + altitude, CallExtractor::template Enumerate::types, false, CallExtractor::Call @@ -278,9 +274,11 @@ struct AutoFilterDescriptor: /// Default for std library sorting of unique elements /// bool operator<(const AutoFilterDescriptor& rhs) const { - if (m_pCall < rhs.m_pCall) - return true; - return m_autoFilter < rhs.m_autoFilter; + // This filter is "logically prior" to the right-hand side if this filter has a HIGHER altitude + // than the one on the right-hand side + return + std::tie(m_altitude, m_pCall, m_autoFilter) < + std::tie(rhs.m_altitude, rhs.m_pCall, rhs.m_autoFilter); } /// diff --git a/autowiring/AutoPacketFactory.h b/autowiring/AutoPacketFactory.h index e40b30042..ed12d85c1 100644 --- a/autowiring/AutoPacketFactory.h +++ b/autowiring/AutoPacketFactory.h @@ -7,7 +7,7 @@ #include "TypeRegistry.h" #include CHRONO_HEADER #include TYPE_TRAITS_HEADER -#include STL_UNORDERED_SET +#include class AutoPacketFactory; class DispatchQueue; @@ -41,7 +41,7 @@ class AutoPacketFactory: std::shared_ptr m_nextPacket; // Collection of known subscribers - typedef std::unordered_set> t_autoFilterSet; + typedef std::set t_autoFilterSet; t_autoFilterSet m_autoFilters; // Accumulators used to compute statistics about AutoPacket lifespan. @@ -89,32 +89,70 @@ class AutoPacketFactory: /// /// Registers the passed subscriber, if it defines a method called AutoFilter /// + /// The descriptor for the AutoFilter to be added + /// rhs /// /// This method is idempotent /// - void AddSubscriber(const AutoFilterDescriptor& rhs); + const AutoFilterDescriptor& AddSubscriber(const AutoFilterDescriptor& rhs); /// /// Convenience override of AddSubscriber /// + /// A shared pointer to a type which has an AutoFilter routine on it + /// + /// For this call to be valid, T::AutoFilter must be defined and must be a compliant AutoFilter + /// template - void AddSubscriber(const std::shared_ptr& rhs) { - AddSubscriber(AutoFilterDescriptor(rhs)); + AutoFilterDescriptor AddSubscriber(const std::shared_ptr& rhs) { + return AddSubscriber(AutoFilterDescriptor(rhs)); + } + + /// + /// Removes the designated AutoFilter from this factory + /// + /// The AutoFilter to be removed + /// + /// This method will not retroactively modify packets that have already been issued with the specified + /// AutoFilter on it. Only packets that are issued after this method returns will lack the presence of + /// the autoFilter described by the parameter. + /// + void RemoveSubscriber(const AutoFilterDescriptor& autoFilter); + + struct AutoPacketFactoryExpression { + AutoPacketFactoryExpression(AutoPacketFactory& factory, autowiring::altitude altitude): + factory(factory), + altitude(altitude) + {} + + AutoPacketFactory& factory; + autowiring::altitude altitude; + + template + AutoFilterDescriptor operator,(Fx&& fx) { + return factory.AddSubscriber(AutoFilterDescriptor(std::forward(fx), altitude)); + } + }; + + AutoPacketFactoryExpression operator+=(autowiring::altitude altitude) { + return AutoPacketFactoryExpression(*this, altitude); } /// /// Convienance overload of operator+= to add a subscriber from a lambda /// + /// + /// This method provides a way to attach a lambda function directly to the factory + /// template - void operator+=(Fx&& fx) { - AddSubscriber(AutoFilterDescriptor(std::forward(fx))); + AutoFilterDescriptor operator+= (Fx&& fx) { + return AddSubscriber(AutoFilterDescriptor(std::forward(fx))); } /// - /// Removes the designated AutoFilter from this factory + /// Overloaded counterpart to RemoveSubscriber /// - /// The AutoFilter to be removed - void RemoveSubscriber(const AutoFilterDescriptor& autoFilter); + void operator-=(const AutoFilterDescriptor& desc); protected: /// diff --git a/autowiring/AutoParameter.h b/autowiring/AutoParameter.h index b5ae2cef8..c8c6480ac 100644 --- a/autowiring/AutoParameter.h +++ b/autowiring/AutoParameter.h @@ -7,7 +7,7 @@ struct AutoParam{}; /// -/// Register an AutoParameter with "AutoParam" namespace in AutoConfigManager. +/// Register an AutoParameter with "AutoParam" namespace in AutoConfigListing. /// In addition to being the lookup string, the Key also implements: /// - static constexpr T Default() /// - (optional) static bool Validate(const T&) @@ -66,7 +66,7 @@ class AutoParameter: /// Base class for providing an easy way to specify just a default value /// /// -/// Because the class names are used as keys to be stored in the AutoConfigManager, these classes cannot be used +/// Because the class names are used as keys to be stored in the AutoConfigListing, these classes cannot be used /// directly and must be subclassed: /// /// struct MyDefaultKey : DefaultKey {}; @@ -88,7 +88,7 @@ struct DefaultKey /// Base class for providing an easy way to default, min and max values /// /// -/// Because the class names are used as keys to be stored in the AutoConfigManager, these classes cannot be used +/// Because the class names are used as keys to be stored in the AutoConfigListing, these classes cannot be used /// directly and must be subclassed: /// /// struct MyDefaultMinMaxKey : DefaultMinMaxKey {}; diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index c8a695be6..fd7c1e22a 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -160,6 +160,8 @@ class Autowired: // The set of all nodes that will have to be unlinked when this field is torn down std::vector m_unlinkEntries; + //Required to keep the registration table alive untill after we're dead. + std::shared_ptr m_registrationTable; public: operator const std::shared_ptr&(void) const { @@ -201,12 +203,20 @@ class Autowired: } }; - template - signal_relay operator()(autowiring::signal T::*handler) { + template + signal_relay operator()(autowiring::signal U::*handler) { + static_assert(std::is_base_of::value, "Cannot reference member of unrelated type"); + + auto handlerActual = static_cast 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)}; + + if (!m_registrationTable) + m_registrationTable = ctxt->template Inject(); + + return {*this, ctxt->RelayForType(handlerActual)}; } /// diff --git a/autowiring/BasicThread.h b/autowiring/BasicThread.h index d9a11a686..b53e89cd4 100644 --- a/autowiring/BasicThread.h +++ b/autowiring/BasicThread.h @@ -232,6 +232,26 @@ class BasicThread: /// bool IsCompleted(void) const; + /// + /// Adds a function object which will be called when this BasicThread stops running or is destroyed + /// + /// + /// The listener is invoked before the destruction of this BasicThread and also immediately after the + /// BasicThread instance has transitioned to the Stopped state. + /// + /// Users who attach a teardown listener MUST NOT attempt to invoke any methods not defined by + /// BasicThread, and MUST NOT attempt to invoke any non-final virtual functions available here. The + /// object itself may be partially destroyed by the time the listener is invoked, and virtual methods + /// may not have the expected behavior. + /// + /// It is guaranteed to be safe to call any non-virtual method defined by BasicThread from a teardown + /// listener. + /// + template + void AddTeardownListener(Fx&& listener) { + return TeardownNotifier::AddTeardownListener(std::forward(listener)); + } + /// /// Begins thread execution. /// diff --git a/autowiring/ConfigRegistry.h b/autowiring/ConfigRegistry.h index 7420b0449..2729e9fa8 100644 --- a/autowiring/ConfigRegistry.h +++ b/autowiring/ConfigRegistry.h @@ -1,6 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once -#include "AnySharedPointer.h" +#include "AutoConfigBase.h" #include "autowiring_error.h" #include "has_validate.h" #include @@ -8,6 +8,7 @@ #include FUNCTIONAL_HEADER #include MEMORY_HEADER +#include "AnySharedPointer.h" // Check if 'T' has a valid stream conversion operator template struct has_stream { @@ -34,9 +35,11 @@ struct get_last{ typedef T last; }; -// Stores information about an AutoConfig entry +// Stores information about an AutoConfigVar type struct ConfigRegistryEntry { - ConfigRegistryEntry(const std::type_info& ti, bool has_validator); + typedef std::function(const std::shared_ptr&, const void*)> injector_t; + + ConfigRegistryEntry(const std::type_info& ti, bool has_validator, injector_t&& inject); // Next entry in the list: const ConfigRegistryEntry* const pFlink; @@ -45,17 +48,17 @@ struct ConfigRegistryEntry { const std::string m_key; // True if a validator was provided - const bool m_has_validator; - - // Is this key identify this entry? - bool is(const std::string& key) const; + const bool m_hasValidator; + // Construct an instance of the AutoConfigVar with this key. + const injector_t m_injector; + // Verify 'ti' is the same type as this entry's value virtual bool verifyType(const std::type_info& ti) const = 0; - // Parse a string into this entrie's value type. + // Parse a string into this entry's value type. // Type must have operator>> T defined - virtual AnySharedPointer parse(const std::string&) const = 0; + virtual void parse(AutoConfigVarBase&, const std::string&) const = 0; // Returns function which validates this input. The validator function is // defined as KEY::Validate(const T&) where KEY is the type identifing this entry @@ -77,8 +80,8 @@ struct ConfigRegistryEntryT: // The "key" proper, without the namespace typedef typename get_last::last t_key; - ConfigRegistryEntryT(void): - ConfigRegistryEntry(typeid(ConfigTypeExtractor), has_validate::value) + ConfigRegistryEntryT(injector_t&& constructor) : + ConfigRegistryEntry(typeid(ConfigTypeExtractor), has_validate::value, std::move(constructor)) {} bool verifyType(const std::type_info& ti) const override { @@ -87,14 +90,14 @@ struct ConfigRegistryEntryT: // Parse string into this ConfigEntry's type. Throw an exception // if no such stream operator exists - AnySharedPointer parse(const std::string& str) const override { - return parseInternal(str); + void parse(AutoConfigVarBase& var, const std::string& str) const override { + var.SetParsed(str); } public: // Only use if there is a stream operator template - typename std::enable_if::value, AnySharedPointer>::type + typename std::enable_if::value, T>::type parseInternal(const std::string& str) const { std::istringstream ss(str); T val; @@ -102,16 +105,22 @@ struct ConfigRegistryEntryT: if (ss.fail()) autowiring::ThrowFailedTypeParseException(str, typeid(T)); - return AnySharedPointer(std::make_shared(val)); + return val; } // Throw exception if there is no stream operator template - typename std::enable_if::value, AnySharedPointer>::type + typename std::enable_if::value, T>::type parseInternal(const std::string&) const { throw autowiring_error("This type doesn't support stream conversions. Define one if you want this to be parsable"); }; + std::function validatorInternal(void) const { + return[](const T& ptr) { + return CallValidate::Call(ptr); + }; + } + std::function validator(void) const override { return [] (const AnySharedPointer& ptr) { return CallValidate::Call(*ptr.as().get()); @@ -122,19 +131,3 @@ struct ConfigRegistryEntryT: extern const ConfigRegistryEntry* g_pFirstConfigEntry; extern size_t g_confgiEntryCount; -/// -/// Adds the specified type to the universal type registry -/// -/// -/// Any instance of this type registry parameterized on type T will be added to the -/// global static type registry, and this registry is computed at link time. -/// -template -class RegConfig -{ -public: - static const ConfigRegistryEntryT r; -}; - -template -const ConfigRegistryEntryT RegConfig::r; diff --git a/autowiring/ContextMember.h b/autowiring/ContextMember.h index a949b2100..56d280f26 100644 --- a/autowiring/ContextMember.h +++ b/autowiring/ContextMember.h @@ -38,7 +38,9 @@ class ContextMember: /// /// A context may be destroyed if and only if none of its members are running and none of /// them may enter a runnable state. This happens when the last pointer to ContextMember - /// is lost. Resource cleanup must be started at this point. + /// is lost. Resource cleanup must be started at this point. Context members are deemed + /// to be unable to enter a running state if they were not signalled to enter this state + /// before the last shared pointer to their outer CoreContext is released. /// /// For contexts containing strictly heirarchial objects, implementors of this method do /// not need to do anything. If, however, there are circular references anywhere in the diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index a828b81f1..78a05df71 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -750,6 +750,39 @@ class CoreContext: FindByType(ptr); return ptr != nullptr; } + + /// + /// Runtime variant of All + /// + /// + /// This instance does not cause a correct instantiation of the underlying junction box. Users are cautioned against + /// using this method directly unless they are able to ensure a proper entry is made into the type registry. + /// + /// It is an error to call this method on an unregistered type. + /// + JunctionBoxBase& All(const std::type_info& ti) const; + + /// + /// Returns all members of and snoopers on this context which implement the specified interface + /// + /// + /// This method makes use of the JunctionBox subsystem, and thus is extremely efficient. The underlying system is + /// memoized, and new entries automatically update any existing memos, which gives this routine O(n) efficiency, where + /// n is the number of types in this context that implement the specified interface. + /// + /// Note that the junction box will also contain members of child contexts that implement the specified interface, and + /// instances that are snooping this context. + /// + /// This method's result will be an empty iterable if the context is not currently initiated. + /// + template + JunctionBox& All(void) { + // Type registration, needed to ensure our junction box actually exists + (void) RegEvent::r; + + // Simple coercive transfer: + return static_cast&>(All(typeid(T))); + } template std::shared_ptr DEPRECATED(Construct(Args&&... args), "'Construct' is deprecated, use 'Inject' instead"); @@ -981,13 +1014,20 @@ class CoreContext: /// The recipient of the event void FilterFiringException(const JunctionBoxBase* pProxy, CoreObject* pRecipient); + /// Identical to RemoveSnooper + void DEPRECATED(Snoop(const CoreObjectDescriptor& traits), "Use AddSnooper instead") { return AddSnooper(traits); } + template + void DEPRECATED(Snoop(const std::shared_ptr& pSnooper), "Use AddSnooper instead"); + template + void DEPRECATED(Snoop(const Autowired& snooper), "Use AddSnooper instead"); + /// - /// Runtime version of Snoop + /// Runtime version of AddSnooper /// - void Snoop(const CoreObjectDescriptor& traits); + void AddSnooper(const CoreObjectDescriptor& traits); /// - /// Registers the specified event receiver to receive messages broadcast within this context. + /// Registers the specified event receiver to receive messages from this context /// /// /// This enables the passed event receiver to snoop events that are broadcast from a @@ -998,16 +1038,16 @@ class CoreContext: /// broadcast in THIS context will be forwarded to the snooper. /// template - void Snoop(const std::shared_ptr& pSnooper) { - Snoop(CoreObjectDescriptor(pSnooper, (T*)nullptr)); + void AddSnooper(const std::shared_ptr& pSnooper) { + AddSnooper(CoreObjectDescriptor(pSnooper, (T*)nullptr)); } /// /// Resolution overload /// template - void Snoop(const Autowired& snooper) { - Snoop( + void AddSnooper(const Autowired& snooper) { + AddSnooper( CoreObjectDescriptor( static_cast&>(snooper), (T*)nullptr @@ -1015,28 +1055,35 @@ class CoreContext: ); } + /// Identical to RemoveSnooper + void DEPRECATED(Unsnoop(const CoreObjectDescriptor& traits), "Use RemoveSnooper instead") { return RemoveSnooper(traits); } + template + void DEPRECATED(Unsnoop(const std::shared_ptr& pSnooper), "Use RemoveSnooper instead"); + template + void DEPRECATED(Unsnoop(const Autowired& snooper), "Use RemoveSnooper instead"); + /// - /// Runtime version of Unsnoop + /// Runtime version of RemoveSnooper /// - void Unsnoop(const CoreObjectDescriptor& traits); + void RemoveSnooper(const CoreObjectDescriptor& traits); /// - /// Unregisters an event receiver previously registered to receive snooped events + /// Unregisters a snooper previously registered to receive snooped events /// /// - /// It is an error to call this method without a prior call to Snoop + /// It is an error to call this method without a prior call to AddSnooper /// template - void Unsnoop(const std::shared_ptr& pSnooper) { - Unsnoop(CoreObjectDescriptor(pSnooper, (T*)nullptr)); + void RemoveSnooper(const std::shared_ptr& pSnooper) { + RemoveSnooper(CoreObjectDescriptor(pSnooper, (T*)nullptr)); } /// - /// Resolution overload + /// Resolution overload of RemoveSnooper /// template - void Unsnoop(const Autowired& snooper) { - Unsnoop( + void RemoveSnooper(const Autowired& snooper) { + RemoveSnooper( CoreObjectDescriptor( static_cast&>(snooper), (T*)nullptr @@ -1138,7 +1185,6 @@ class CoreContext: } }; - /// \internal /// /// Adds a post-attachment listener in this context for a particular autowired member. /// There is no guarantee for the context in which the listener will be called. @@ -1272,6 +1318,30 @@ void CoreContext::AutoRequireMicroBolt(void) { Inject>(); } +template +void CoreContext::Snoop(const std::shared_ptr& pSnooper) +{ + return AddSnooper(pSnooper); +} + +template +void CoreContext::Snoop(const Autowired& snooper) +{ + return AddSnooper(snooper); +} + +template +void CoreContext::Unsnoop(const std::shared_ptr& pSnooper) +{ + return RemoveSnooper(pSnooper); +} + +template +void CoreContext::Unsnoop(const Autowired& snooper) +{ + return RemoveSnooper(snooper); +} + template class CoreContext::AutoFactory { diff --git a/autowiring/CoreThread.h b/autowiring/CoreThread.h index 03ebfa2a6..a64323880 100644 --- a/autowiring/CoreThread.h +++ b/autowiring/CoreThread.h @@ -45,39 +45,6 @@ class CoreThread: virtual void DoRunLoopCleanup(std::shared_ptr&& ctxt, std::shared_ptr&& refTracker) override; public: - /// \internal - /// - /// Waits until a lambda function is ready to run in this thread's dispatch queue, - /// dispatches the function, and then returns. - /// - void WaitForEvent(void); - - /// \internal - /// - /// Waits until a lambda function in the dispatch queue is ready to run or the specified - /// time period elapses, whichever comes first. - /// - /// - /// False if the timeout period elapsed before an event could be dispatched, true otherwise - /// - bool WaitForEvent(std::chrono::milliseconds milliseconds); - - /// \internal - /// - /// Waits until a lambda function in the dispatch queue is ready to run or the specified - /// time is reached, whichever comes first. - /// - /// - /// False if the timeout period elapsed before an event could be dispatched, true otherwise - /// - bool WaitForEvent(std::chrono::steady_clock::time_point wakeTime); - - /// \internal - /// - /// An unsafe variant of WaitForEvent - /// - bool WaitForEventUnsafe(std::unique_lock& lk, std::chrono::steady_clock::time_point wakeTime); - /// \internal /// /// Called automatically to begin core thread execution. diff --git a/autowiring/Decompose.h b/autowiring/Decompose.h index 242a40dd7..1edec3c7e 100644 --- a/autowiring/Decompose.h +++ b/autowiring/Decompose.h @@ -1,12 +1,15 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "is_any.h" +#include #include template struct TemplatePack { static const int N = sizeof...(Ts); + typedef std::tuple t_args; + /// /// An array of type T, parameterized by the bound function's arguments /// diff --git a/autowiring/DecorationDisposition.h b/autowiring/DecorationDisposition.h index d3ad221fa..31e8edc5a 100644 --- a/autowiring/DecorationDisposition.h +++ b/autowiring/DecorationDisposition.h @@ -49,11 +49,7 @@ namespace std { return key.tshift + (key.is_shared ? 0x80000 : 0x70000) + -#if AUTOWIRING_USE_LIBCXX key.ti->hash_code(); -#else - std::type_index(*key.ti).hash_code(); -#endif } }; } diff --git a/autowiring/DispatchQueue.h b/autowiring/DispatchQueue.h index f9cef3d71..bd273d7f9 100644 --- a/autowiring/DispatchQueue.h +++ b/autowiring/DispatchQueue.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "dispatch_aborted_exception.h" #include "DispatchThunk.h" #include #include @@ -9,18 +10,6 @@ class DispatchQueue; -/// \internal -/// -/// Thrown when a dispatch operation was aborted -/// -class dispatch_aborted_exception: - public std::exception -{ -public: - dispatch_aborted_exception(void); - virtual ~dispatch_aborted_exception(void); -}; - /// /// This is an asynchronous queue of zero-argument functions /// @@ -147,12 +136,64 @@ class DispatchQueue { /// The total number of events dispatched int DispatchAllEvents(void); + /// \internal + /// + /// Waits until a lambda function is ready to run in this thread's dispatch queue, + /// dispatches the function, and then returns. + /// + void WaitForEvent(void); + + /// \internal + /// + /// Waits until a lambda function in the dispatch queue is ready to run or the specified + /// time period elapses, whichever comes first. + /// + /// + /// False if the timeout period elapsed before an event could be dispatched, true otherwise + /// + bool WaitForEvent(std::chrono::milliseconds milliseconds); + + /// \internal + /// + /// Waits until a lambda function in the dispatch queue is ready to run or the specified + /// time is reached, whichever comes first. + /// + /// + /// False if the timeout period elapsed before an event could be dispatched, true otherwise + /// + bool WaitForEvent(std::chrono::steady_clock::time_point wakeTime); + + /// \internal + /// + /// An unsafe variant of WaitForEvent + /// + bool WaitForEventUnsafe(std::unique_lock& lk, std::chrono::steady_clock::time_point wakeTime); + public: /// /// Explicit overload for already-constructed dispatch thunk types /// void AddExisting(DispatchThunkBase* pBase); + /// + /// Blocks until all dispatchers on the DispatchQueue at the time of the call have been dispatched + /// + /// The maximum amount of time to wait + /// + /// This method does not cause any dispatchers to run. If the underlying dispatch queue does not have an event loop + /// operating on it, this method will deadlock. It is an error for the party responsible for driving the dispatch queue + /// via WaitForEvent or DispatchAllEvents unless that party first delegates the responsibility elsewhere. + /// + /// If DispatchQueue::Abort() is called before the dispatcher has been completed, this method will throw an exception. + /// If a dispatcher on the underlying DispatchQueue throws an exception, this method will also throw an exception. + /// + bool Barrier(std::chrono::nanoseconds timeout); + + /// + /// Identical to the timed version of Barrier, but does not time out + /// + void Barrier(void); + /// /// Recommends a point in time to wake up to check for events /// diff --git a/autowiring/ExceptionFilter.h b/autowiring/ExceptionFilter.h index 312fba13a..1f6e4ca0e 100644 --- a/autowiring/ExceptionFilter.h +++ b/autowiring/ExceptionFilter.h @@ -15,6 +15,7 @@ class CoreObject; /// filter the passed exception. Generally, the filtration technique should be written /// as follows: /// +/// \code /// try { /// throw; /// } catch(custom_type_1& t1) { @@ -22,17 +23,21 @@ class CoreObject; /// } catch(custom_type_2& t2) { /// ...handling code... /// } +/// \endcode /// /// Alternatively, this strategy may be used: /// +/// \code /// try { /// throw; /// } catch(custom_type_1&) { /// } catch(custom_type_2&) { /// } /// ...handling code... +/// \endcode /// /// Filtration methods may safely allow any unhandled rethrows to percolate to the caller. +/// Autowiring will ignore any exceptions thrown by a filter method. /// /// Unhandled exceptions thrown in a context will cause that context to be torn down. By /// the time the exception filter is called, teardown of the context originating the @@ -59,6 +64,8 @@ class ExceptionFilter /// prevent certain exceptions from being unhandled. /// /// Implementors can use "throw" with no arguments to trigger a rethrow of the originating exception. + /// + /// Exceptions thrown by this method are silently ignored by Autowiring. /// virtual void Filter(void) {}; @@ -68,6 +75,8 @@ class ExceptionFilter /// The target of the call /// /// Implementors can use "throw" with no arguments to trigger a rethrow of the originating exception. + /// + /// Exceptions thrown by this method are silently ignored by Autowiring. /// virtual void Filter(const JunctionBoxBase* pJunctionBox, CoreObject* pRecipient) {} }; diff --git a/autowiring/JunctionBox.h b/autowiring/JunctionBox.h index 8bb03f78c..45d25594c 100644 --- a/autowiring/JunctionBox.h +++ b/autowiring/JunctionBox.h @@ -43,9 +43,83 @@ class JunctionBox: volatile int m_numberOfDeletions; public: - /// - /// Convenience method allowing consumers to quickly determine whether any listeners exist - /// + class iterator { + public: + iterator(void) {} + + iterator(JunctionBox* pParent) : + pParent(pParent), + q(pParent->m_st.end()), + deleteCount(pParent->m_numberOfDeletions) + {} + + iterator(JunctionBox* pParent, typename t_listenerSet::iterator q) : + pParent(pParent), + q(q), + currentEvent(*q), + deleteCount(pParent->m_numberOfDeletions) + {} + + typedef std::forward_iterator_tag iterator_category; + + private: + JunctionBox* pParent = nullptr; + typename t_listenerSet::iterator q; + JunctionBoxEntry currentEvent; + int deleteCount; + + public: + // Required operator overloads: + T& operator*(void) const { return *currentEvent.m_ptr; } + T* operator->(void) const { return currentEvent.m_ptr.get(); } + bool operator==(const iterator& rhs) const { return q == rhs.q; } + bool operator!=(const iterator& rhs) const { return q != rhs.q; } + bool operator<(const iterator& rhs) const { return q < rhs.q; } + + iterator operator++(void) { + // Need to hold this here to prevent deletion from occuring while the lock is held + std::shared_ptr old = currentEvent.m_ptr; + std::lock_guard lk(pParent->m_lock); + + // Increment iterator correctly even if it's been invalidated + if (deleteCount == pParent->m_numberOfDeletions) + ++q; + else { + q = pParent->m_st.upper_bound(currentEvent); + deleteCount = pParent->m_numberOfDeletions; + } + + // Only update if we aren't at the end: + currentEvent = + q == pParent->m_st.end() ? + JunctionBoxEntry() : + *q; + return *this; + } + + iterator operator++(int) { + iterator prior = *this; + *this++; + return prior; + } + }; + + iterator begin(void) { + std::lock_guard lk(m_lock); + return iterator(this, m_st.begin()); + } + + iterator end(void) { + std::lock_guard lk(m_lock); + return iterator(this); + } + + size_t size(void) const { + std::lock_guard lk(m_lock); + return m_st.size(); + } + + // JunctionBoxBase overrides: bool HasListeners(void) const override { return (std::lock_guard)m_lock, !m_st.empty(); } diff --git a/autowiring/JunctionBoxBase.h b/autowiring/JunctionBoxBase.h index d942b6de4..87aca21f9 100644 --- a/autowiring/JunctionBoxBase.h +++ b/autowiring/JunctionBoxBase.h @@ -52,6 +52,12 @@ class JunctionBoxBase { bool IsInitiated(void) const {return m_isInitiated;} void Initiate(void) {m_isInitiated=true;} + /// + /// Convenience method allowing consumers to quickly determine whether any listeners exist + /// + /// + /// True if at least one listener has been registered + /// virtual bool HasListeners(void) const = 0; // Event attachment and detachment pure virtuals diff --git a/autowiring/JunctionBoxEntry.h b/autowiring/JunctionBoxEntry.h index 6dde5e5da..38bc66fe8 100644 --- a/autowiring/JunctionBoxEntry.h +++ b/autowiring/JunctionBoxEntry.h @@ -6,11 +6,15 @@ class CoreContext; struct JunctionBoxEntryBase { + JunctionBoxEntryBase(void) : + m_owner(nullptr) + {} + JunctionBoxEntryBase(CoreContext* owner) : m_owner(owner) {} - CoreContext* const m_owner; + CoreContext* m_owner; }; /// @@ -20,6 +24,8 @@ template struct JunctionBoxEntry: JunctionBoxEntryBase { + JunctionBoxEntry(void) {} + JunctionBoxEntry(CoreContext* owner, std::shared_ptr ptr) : JunctionBoxEntryBase(owner), m_ptr(ptr) @@ -27,11 +33,6 @@ struct JunctionBoxEntry: std::shared_ptr m_ptr; - JunctionBoxEntry& operator=(const JunctionBoxEntry& rhs) { - // This shouldn't be used. non-c++11 containers require this... - throw std::runtime_error("Can't copy a JunctionBoxEntry"); - } - bool operator==(const JunctionBoxEntry& rhs) const { return m_ptr == rhs.m_ptr; } diff --git a/autowiring/altitude.h b/autowiring/altitude.h new file mode 100644 index 000000000..9a53ac7d9 --- /dev/null +++ b/autowiring/altitude.h @@ -0,0 +1,87 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once + +namespace autowiring { + +/// +/// Defines the altitude enumeration concept for AutoFilter instances +/// +/// +/// A filter altitude is an indicator to the AutoFilter scheduler about when a particular filter +/// should be scheduled to receive control. Altitude is a hard requirement, but is subject to +/// a number of stipulations as to when it applies: +/// +/// 1) If two autofilters are both candidates to be run at the same time, the filter with the +/// higher altitude will be run first. +/// 2) If both filters have the same altitude, an arbitrary filter will be selected. +/// 3) When the current filter returns control (IE, its AutoFilter routine returns), the next +/// filter will be run. +/// 4) Deferred AutoFilters are a special case. A deferred AutoFilter is considered to have +/// returned control as soon as its execution has been scheduled; generally this happens very +/// fast. +/// 5) Altitudes only provide an order-of-execution guarantee if NO deferred AutoFilters have been +/// declared in the network. +/// +enum class altitude { + // Highest altitude level. Reserved for temporary debug logic and other nonpermanent code that + // must run before all other filter levels + Highest = 0x9000, + + // Instrumentation level, for use with instrumentation code. Instrumentation code often needs to + // observe the inputs to its AutoFilter before any other code has an opportunity to observe it, + // because this code needs information about + Instrumentation = 0x8000, + + // Default altitude for Deferred autofilters. Deferred autofilters are guaranteed to return very + // quickly, even though they may do a lot of work, because they do not tie up the main thread. + Dispatch = 0x7000, + + // Asynchronous filters are designed to be run with a higher priority than standard filters, + // but are still expected to complete very quickly. Because their speedy behavior is implemented + // by the filter, and not guaranteed by Autowiring, asynchronous filters are considered to + // have a lower priority than Deferred filters. + // + // It is expected that AutoFilters which are marked as Asynchronous will do the majority of their + // work in an std::async or other similar call. + Asynchronous = 0x6000, + + // The realtime altitude is a higher-than-normal altitude which may have some tight timing requirements + // but does not run in a separate thread. Realtime filters run after deferred filters have been + // scheduled to run. + Realtime = 0x5000, + + // Default altitude range. Unless otherwise specified, or the filter is marked Deferred, filters + // will normally execute at this priority level. + Standard = 0x4000, + + // Altitude indicator for filters with no hard timing requirements. This is a convenient place to put + // filters that may have extensive CPU usage requirements, or which are not strongly impacted by timing. + // Analytics and diagnostics are typically suitable for execution at the passive level. + Passive = 0x3000, + + // Lowest altitude level. Reserved for temporary debug logic and other nonpermanent code that + // must run after all other filter levels. + Lowest = 0x2000 +}; + +inline altitude operator+(altitude alt, int v) { + return (altitude) ((int) alt + v); +} + +/// +/// Extracts the altitude of type T, if declared, or infers it if not +/// +/// The outer type of the AutoFilter +/// The default to be used if one is not provied by T +template +struct altitude_of { + template + static std::integral_constant select(U*); + + template + static std::integral_constant select(...); + + static const altitude value = decltype(select(nullptr))::value; +}; + +} \ No newline at end of file diff --git a/autowiring/auto_signal.h b/autowiring/auto_signal.h index 5f7823e32..2aa329bcc 100644 --- a/autowiring/auto_signal.h +++ b/autowiring/auto_signal.h @@ -1,5 +1,7 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "Decompose.h" + #include #include #include @@ -32,11 +34,43 @@ namespace autowiring { // Forward and backward linkages: signal_node_base* pFlink; signal_node_base* pBlink; + + /// + /// Inserts a node after the current one + /// + void insert_after(signal_node_base* node) { + node->pBlink = this; + node->pFlink = pFlink; + if (pFlink) + pFlink->pBlink = node; + pFlink = node; + } + + /// + /// Removes this node from the list it's in. + /// + /// + /// If you call this function, you are assuming responsibility for the memory and + /// are expected to call delete on the node. + /// + /// + /// A pointer to itself for easier chaining of operations. + /// + signal_node_base* remove() { + // Clear linkage + if (this->pBlink) + this->pBlink->pFlink = this->pFlink; + if (this->pFlink) + this->pFlink->pBlink = this->pBlink; + + this->pBlink = this->pFlink = nullptr; + return this; + } }; // Holds a reference to one of the signal holders template - struct signal_node: + struct signal_node : signal_node_base { signal_node(const signal_node& rhs) = delete; @@ -44,10 +78,33 @@ namespace autowiring { signal_node(std::function&& fn) : fn(std::move(fn)) {} - + + //Functions where the first argument is a signal_node<...> or base type are also ok. + signal_node(std::function*, Args...)>&& newFn) : + fn([this, newFn](Args... args){ newFn(this, args...); }) + {} + const std::function fn; - }; + /// + /// Appends the specified handler to this list of nodes. + /// + template + typename std::enable_if::N == sizeof...(Args), signal_node*>::type + operator+=(t_Fn fn) { + auto retVal = new signal_node(std::function(std::forward(fn))); + insert_after(retVal); + return retVal; + } + + template + typename std::enable_if::N == sizeof...(Args) + 1, signal_node*>::type + operator+=(t_Fn fn) { + auto retVal = new signal_node(std::function*,Args...)>(std::forward(fn))); + insert_after(retVal); + return retVal; + } + }; struct signal_registration_base { signal_registration_base(void); @@ -117,40 +174,37 @@ namespace autowiring { /// /// Stores a signal /// - struct signal_relay + /// + /// This is functionally a sentinal head of the linked list. + /// + struct signal_relay : + internal::signal_node_base { - public: - signal_relay(void) : - pHead(nullptr) - {} + signal_relay(void) {} - ~signal_relay(void) { + ~signal_relay(void) override { // Standard linked list cleaup internal::signal_node_base* next = nullptr; - for (auto cur = pHead; cur; cur = next) { + for (auto cur = pFlink; cur; cur = next) { next = cur->pFlink; - delete cur; + delete cur; //don't bother unlinking.. } } - protected: - // First entry on the list: - internal::signal_node_base* pHead; + /// + /// Searches the list for this node and deletes it, or throws if it is not found. + /// + void operator-=(signal_node_base* node) { + signal_node_base* cur; + for (cur = pFlink; cur != nullptr; cur = cur->pFlink) { + if (cur == node){ + node->remove(); + delete node; + return; + } + } - 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; + throw std::runtime_error("Attempted to remove node which is not part of this list."); } }; @@ -162,21 +216,17 @@ namespace autowiring { signal_relay { internal::signal_node* GetHead(void) const { - return static_cast*>(pHead); + return static_cast*>(pFlink); } /// /// 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; + template + internal::signal_node* operator+=(t_Fn fn) { + return *reinterpret_cast*>(this) += std::forward(fn); } + }; /// @@ -198,19 +248,26 @@ namespace autowiring { 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; } + typedef internal::signal_node registration_t; + typedef std::function function_t; + + template + registration_t* operator+=(t_Fn fn) { return *m_relay += std::forward(fn); } + + void operator-=(registration_t* 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) - ) + auto cur = m_relay->GetHead(); + while(cur) { + //Grab the next pointer before we evaluate incase the current node is deleted by it's + //function. + auto next = static_cast(cur->pFlink); cur->fn(args...); + cur = next; + } } }; } diff --git a/autowiring/dispatch_aborted_exception.h b/autowiring/dispatch_aborted_exception.h new file mode 100644 index 000000000..d0ee7080a --- /dev/null +++ b/autowiring/dispatch_aborted_exception.h @@ -0,0 +1,16 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include "autowiring_error.h" + +/// \internal +/// +/// Thrown when a dispatch operation was aborted +/// +class dispatch_aborted_exception: + public autowiring_error +{ +public: + dispatch_aborted_exception(void); + dispatch_aborted_exception(const std::string& what); + virtual ~dispatch_aborted_exception(void); +}; diff --git a/autowiring/is_any.h b/autowiring/is_any.h index 8bdcb5a09..fda7bcc44 100644 --- a/autowiring/is_any.h +++ b/autowiring/is_any.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "C++11/cpp11.h" #include TYPE_TRAITS_HEADER /// diff --git a/publicDoxyfile.conf b/publicDoxyfile.conf index 1388a2fd6..36894ba8b 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.5.1 +PROJECT_NUMBER = 0.5.2 # 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/src/autonet/AutoNetServerImpl.hpp b/src/autonet/AutoNetServerImpl.hpp index c8b98dd3b..5f4ab9d97 100644 --- a/src/autonet/AutoNetServerImpl.hpp +++ b/src/autonet/AutoNetServerImpl.hpp @@ -9,11 +9,6 @@ #include SYSTEM_ERROR_HEADER #include ARRAY_HEADER -#if !AUTOWIRING_USE_LIBCXX - // No initializer lists on libstdc - #define BOOST_NO_CXX11_HDR_INITIALIZER_LIST -#endif - struct CoreObjectDescriptor; struct TypeIdentifierBase; diff --git a/src/autowiring/AutoConfig.cpp b/src/autowiring/AutoConfig.cpp deleted file mode 100644 index e958ebe5f..000000000 --- a/src/autowiring/AutoConfig.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. -#include "stdafx.h" -#include "AutoConfig.h" -#include "AutoConfigParser.hpp" - -AutoConfigBase::AutoConfigBase(const std::type_info& ti): - m_key(autowiring::ExtractKey(ti)) -{} diff --git a/src/autowiring/AutoConfigBase.cpp b/src/autowiring/AutoConfigBase.cpp new file mode 100644 index 000000000..bd855f32f --- /dev/null +++ b/src/autowiring/AutoConfigBase.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "AutoConfigBase.h" +#include "AutoConfigParser.hpp" +#include "AutoConfigListing.h" +#include "CoreContext.h" + +AutoConfigVarBase::AutoConfigVarBase(const std::type_info& ti, bool configured) : + ContextMember(), + m_key(autowiring::ExtractKey(ti)), + onChangedSignal(), + m_isConfigured(configured), + m_parentRegistration(nullptr) +{ + m_name = m_key.c_str(); +} + +void AutoConfigVarBase::AutoInit() { + auto ctxt = m_context.lock(); + auto listing = ctxt->Inject(); + auto self = GetSelf(); + + listing->NotifyConfigAdded(self); + if (!IsInherited()) + listing->NotifySetLocally(self); +} + +void AutoConfigVarBase::OnSetLocally() { + auto ctxt = m_context.lock(); + auto listing = ctxt->Inject(); //get or set + listing->NotifySetLocally(GetSelf()); +} \ No newline at end of file diff --git a/src/autowiring/AutoConfigListing.cpp b/src/autowiring/AutoConfigListing.cpp new file mode 100644 index 000000000..068bb28d3 --- /dev/null +++ b/src/autowiring/AutoConfigListing.cpp @@ -0,0 +1,145 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "AutoConfigBase.h" +#include "AutoConfigListing.h" +#include "AutoConfigParser.hpp" +#include "demangle.h" +#include + +using namespace autowiring; + +// Create map of all config values in the registry +static std::unordered_map FillRegistry(void) { + std::unordered_map registry; + + for (auto config = g_pFirstConfigEntry; config; config = config->pFlink) { + registry[config->m_key] = config; + } + + return registry; +} + +// Create map of all validators specified +static std::unordered_map FillValidators(void) { + std::unordered_map validator; + + for (auto config = g_pFirstConfigEntry; config; config = config->pFlink) { + if (config->m_hasValidator) { + validator[config->m_key] = config->validator(); + } + } + + return validator; +} + +const std::unordered_map AutoConfigListing::s_registry = FillRegistry(); +const std::unordered_map AutoConfigListing::s_validators = FillValidators(); + +AutoConfigListing::AutoConfigListing(void){ + auto ctxt = m_context.lock(); +} + +AutoConfigListing::~AutoConfigListing(void){} + +void AutoConfigListing::ThrowKeyNotFoundException(const std::string& key) const { + std::stringstream ss; + ss << "No configuration found for key '" << key << "'"; + throw autowiring_error(ss.str()); +} + +void AutoConfigListing::ThrowTypeMismatchException(const std::string& key, const std::type_info& ti) const { + std::stringstream ss; + ss << "Attempting to set config '" << key << "' with incorrect type '" + << autowiring::demangle(ti) << "'"; + throw autowiring_error(ss.str()); + +} + +bool AutoConfigListing::IsConfigured(const std::string& key) { + std::lock_guard lk(m_lock); + auto found = m_values.find(key); + + return found != m_values.end() && found->second.lock()->IsConfigured(); +} + +bool AutoConfigListing::IsInherited(const std::string& key) { + std::lock_guard lk(m_lock); + auto found = m_values.find(key); + + return found != m_values.end() && found->second.lock()->IsConfigured(); +} + +std::shared_ptr AutoConfigListing::Get(const std::string& key) { + std::lock_guard lk(m_lock); + + auto found = m_values.find(key); + if (found != m_values.end()) + return found->second.lock(); + + // Key not found, throw exception + std::stringstream ss; + ss << "Attepted to get key '" << key << "' which hasn't been set"; + throw autowiring_error(ss.str()); +} + +bool AutoConfigListing::SetParsed(const std::string& key, const std::string& value) { + auto config = GetOrConstruct(key, nullptr); + config->SetParsed(value); + return true; +} + +void AutoConfigListing::AddOnChanged(const std::string& key, std::function&& fx) { + auto config = GetOrConstruct(key, nullptr); + (*config).onChangedSignal += std::move(fx); +} + +AutoConfigListing::onAddSignal_t::registration_t* AutoConfigListing::AddCallback(onAddSignal_t::function_t&& fx) { + std::lock_guard lk(m_lock); + + for (auto& key : m_orderedKeys) { + auto config = m_values[key].lock(); + if ( config ) + fx(*config); + } + + return m_onAddedSignal += std::move(fx); +} + +void AutoConfigListing::SetInternal(const std::string& key, const void* value) { + auto config = GetOrConstruct(key, value); + config->Set(value); +} + +std::shared_ptr AutoConfigListing::GetOrConstruct(const std::string& key, const void* value) { + std::unique_lock lk(m_lock); + + auto found = m_values.find(key); + if (found != m_values.end()) + return found->second.lock(); + + auto entry = s_registry.find(key); + if (entry == s_registry.end()) { + ThrowKeyNotFoundException(key); + return nullptr; + } + + auto ctxt = m_context.lock(); + lk.unlock(); + auto newValue = entry->second->m_injector(ctxt, value); + lk.lock(); + + return newValue; +} + +void AutoConfigListing::NotifyConfigAdded(const std::shared_ptr& config){ + std::lock_guard lk(m_lock); + m_values.emplace(config->m_key, config); +} + +void AutoConfigListing::NotifySetLocally(const std::shared_ptr& config) { + { + std::lock_guard lk(m_lock); + m_orderedKeys.push_back(config->m_key); + } + m_onAddedSignal(*config); +} \ No newline at end of file diff --git a/src/autowiring/AutoConfigManager.cpp b/src/autowiring/AutoConfigManager.cpp deleted file mode 100644 index f7c4b3948..000000000 --- a/src/autowiring/AutoConfigManager.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. -#include "stdafx.h" -#include "AutoConfig.h" -#include "AnySharedPointer.h" -#include - -using namespace autowiring; - -// Create map of all config values in the registry -static std::unordered_map FillRegistry(void) { - std::unordered_map registry; - - for (auto config = g_pFirstConfigEntry; config; config = config->pFlink) { - registry[config->m_key] = config; - } - - return registry; -} - -// Create map of all validators specified -static std::unordered_map> FillValidators(void) { - std::unordered_map> validator; - - for (auto config = g_pFirstConfigEntry; config; config = config->pFlink) { - if (config->m_has_validator) { - validator[config->m_key].push_back(config->validator()); - } - } - - return validator; -} - -const std::unordered_map AutoConfigManager::s_registry = FillRegistry(); -const std::unordered_map> AutoConfigManager::s_validators = FillValidators(); - -AutoConfigManager::AutoConfigManager(void){ - // Copy parent's config settings - auto parent = GetContext()->GetParentContext(); - if (parent) { - AutowiredFast mgmt(parent); - - // Is there AutoConfigManager in an ancestor? - if (mgmt) { - std::lock_guard lk(mgmt->m_lock); - m_values = mgmt->m_values; - } - } -} - -AutoConfigManager::~AutoConfigManager(void){} - -void AutoConfigManager::ThrowKeyNotFoundException(const std::string& key) const { - std::stringstream ss; - ss << "No configuration found for key '" << key << "'"; - throw autowiring_error(ss.str()); -} - -void AutoConfigManager::ThrowTypeMismatchException(const std::string& key, const std::type_info& ti) const { - std::stringstream ss; - ss << "Attempting to set config '" << key << "' with incorrect type '" - << autowiring::demangle(ti) << "'"; - throw autowiring_error(ss.str()); - -} - -bool AutoConfigManager::IsConfigured(const std::string& key) { - std::lock_guard lk(m_lock); - return !!m_values.count(key); -} - -bool AutoConfigManager::IsInherited(const std::string& key) { - std::lock_guard lk(m_lock); - return m_values.count(key) && !m_setHere.count(key); -} - -AnySharedPointer& AutoConfigManager::Get(const std::string& key) { - std::lock_guard lk(m_lock); - - if (m_values.count(key)) { - return m_values[key]; - } - - // Key not found, throw exception - std::stringstream ss; - ss << "Attepted to get key '" << key << "' which hasn't been set"; - throw autowiring_error(ss.str()); -} - -void AutoConfigManager::Set(const std::string& key, const char* value) { - Set(key, std::string(value)); -} - -bool AutoConfigManager::SetParsed(const std::string& key, const std::string& value) { - // Key not found - if (!s_registry.count(key)) { - return false; - } - - SetRecursive(key, s_registry.find(key)->second->parse(value)); - return true; -} - -void AutoConfigManager::AddCallback(const std::string& key, t_callback&& fx) { - std::lock_guard lk(m_lock); - m_callbacks[key].push_back(fx); -} - -void AutoConfigManager::AddCallback(t_add_callback&& fx) { - // Grab lock until done setting value - std::lock_guard lk(m_lock); - - for (auto& key : m_orderedKeys) - fx(key, m_values[key]); - - m_addCallbacks.emplace_back(std::move(fx)); -} - -void AutoConfigManager::SetRecursive(const std::string& key, AnySharedPointer value) { - // Call all validators for this key - if (s_validators.count(key)) { - for (auto const& fx : s_validators.find(key)->second) { - if (!fx(value)) { - std::stringstream ss; - ss << "Attempted to set key '" << key << "'which didin't pass validator"; - throw autowiring_error(ss.str()); - } - } - } - - // Grab lock until done setting value - std::unique_lock lk(m_lock); - - // Actually set the value in this manager - SetInternal(key, value); - - // Mark key set from this manager - auto inserted = m_setHere.insert(key); - if (inserted.second) { - m_orderedKeys.push_back(key); - for (auto& callback : m_addCallbacks) { - lk.unlock(); - callback(key, value); - lk.lock(); - } - } - - // Enumerate descendant contexts - auto enumerator = ContextEnumerator(GetContext()); - auto ctxt = enumerator.begin(); - - // Skip current context - ++ctxt; - - // Recursivly set values in desendent contexts - while (ctxt != enumerator.end()) { - - // Make sure we get the AutoConfigManager from "ctxt" - std::shared_ptr mgmt; - (*ctxt)->FindByType(mgmt); - if (mgmt) { - std::lock_guard descendant_lk(mgmt->m_lock); - - // Check if value was set from this context - // If so, stop recursing down this branch, continue to next sibling - if (mgmt->m_setHere.count(key)){ - ctxt.NextSibling(); - continue; - } - - //Actaully set the value - mgmt->SetInternal(key, value); - } - // Continue to next context - ++ctxt; - } -} - -void AutoConfigManager::SetInternal(const std::string& key, const AnySharedPointer& value) { - m_values[key] = value; - - // Call callbacks for this key - for (const auto& cb : m_callbacks[key]) - cb(value); -} diff --git a/src/autowiring/AutoPacket.cpp b/src/autowiring/AutoPacket.cpp index 18ce30c67..04a02733f 100644 --- a/src/autowiring/AutoPacket.cpp +++ b/src/autowiring/AutoPacket.cpp @@ -424,6 +424,7 @@ bool AutoPacket::Wait(std::condition_variable& cv, const AutoFilterDescriptorInp stub, AutoFilterDescriptorStub( &typeid(AutoPacketFactory), + autowiring::altitude::Dispatch, inputs, false, [] (const AnySharedPointer& obj, AutoPacket&) { diff --git a/src/autowiring/AutoPacketFactory.cpp b/src/autowiring/AutoPacketFactory.cpp index 54994596e..a4bb4f064 100644 --- a/src/autowiring/AutoPacketFactory.cpp +++ b/src/autowiring/AutoPacketFactory.cpp @@ -144,9 +144,10 @@ void AutoPacketFactory::Clear(void) { Stop(false); } -void AutoPacketFactory::AddSubscriber(const AutoFilterDescriptor& rhs) { +const AutoFilterDescriptor& AutoPacketFactory::AddSubscriber(const AutoFilterDescriptor& rhs) { std::lock_guard lk(m_lock); m_autoFilters.insert(rhs); + return rhs; } void AutoPacketFactory::RemoveSubscriber(const AutoFilterDescriptor& autoFilter) { @@ -155,6 +156,10 @@ void AutoPacketFactory::RemoveSubscriber(const AutoFilterDescriptor& autoFilter) m_autoFilters.erase(autoFilter); } +void AutoPacketFactory::operator-=(const AutoFilterDescriptor& desc) { + RemoveSubscriber(desc); +} + AutoFilterDescriptor AutoPacketFactory::GetTypeDescriptorUnsafe(const std::type_info* nodeType) { //ASSUME: type_info uniquely specifies descriptor for (auto& af : m_autoFilters) diff --git a/src/autowiring/AutoPacketGraph.cpp b/src/autowiring/AutoPacketGraph.cpp index 087b7cd95..bfac398bd 100644 --- a/src/autowiring/AutoPacketGraph.cpp +++ b/src/autowiring/AutoPacketGraph.cpp @@ -10,6 +10,7 @@ #include #include #include FUNCTIONAL_HEADER +#include STL_UNORDERED_SET AutoPacketGraph::AutoPacketGraph() { } diff --git a/src/autowiring/BasicThread.cpp b/src/autowiring/BasicThread.cpp index 687c0c936..723dbd6a6 100644 --- a/src/autowiring/BasicThread.cpp +++ b/src/autowiring/BasicThread.cpp @@ -2,6 +2,7 @@ #include "stdafx.h" #include "BasicThread.h" #include "Autowired.h" +#include "autowiring_error.h" #include "BasicThreadStateBlock.h" #include "ContextEnumerator.h" #include "fast_pointer_cast.h" @@ -17,7 +18,9 @@ BasicThread::BasicThread(const char* pName): m_priority(ThreadPriority::Default) {} -BasicThread::~BasicThread(void){} +BasicThread::~BasicThread(void) { + NotifyTeardownListeners(); +} std::mutex& BasicThread::GetLock(void) { return m_state->m_lock; @@ -64,13 +67,13 @@ void BasicThread::DoRunLoopCleanup(std::shared_ptr&& ctxt, std::sha // need to hold a reference to. auto state = m_state; - // Perform a manual notification of teardown listeners - NotifyTeardownListeners(); - // Transition to stopped state. Synchronization not required, transitions are all one-way m_stop = true; m_running = false; + // Perform a manual notification of teardown listeners + NotifyTeardownListeners(); + // Tell our CoreRunnable parent that we're done to ensure that our reference count will be cleared. Stop(false); @@ -107,7 +110,7 @@ void BasicThread::WaitForStateUpdate(const std::function& fn) { } ); if(ShouldStop()) - throw dispatch_aborted_exception(); + throw dispatch_aborted_exception("Thread was stopped before the function returned true"); } void BasicThread::PerformStatusUpdate(const std::function& fn) { diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index c9134e3bc..238472e92 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -14,14 +14,16 @@ set(Autowiring_SRCS AnySharedPointer.h atomic_object.h at_exit.h + altitude.h auto_id.h auto_future.h auto_signal.h auto_signal.cpp - AutoConfig.cpp AutoConfig.h - AutoConfigManager.cpp - AutoConfigManager.h + AutoConfigBase.cpp + AutoConfigBase.h + AutoConfigListing.cpp + AutoConfigListing.h AutoConfigParser.cpp AutoConfigParser.hpp AutoFilterDescriptor.h @@ -99,6 +101,8 @@ set(Autowiring_SRCS demangle.cpp demangle.h Deserialize.h + dispatch_aborted_exception.h + dispatch_aborted_exception.cpp DispatchQueue.cpp DispatchQueue.h DispatchThunk.h diff --git a/src/autowiring/ConfigRegistry.cpp b/src/autowiring/ConfigRegistry.cpp index 4e23468fe..0fd1c417b 100644 --- a/src/autowiring/ConfigRegistry.cpp +++ b/src/autowiring/ConfigRegistry.cpp @@ -8,19 +8,16 @@ const ConfigRegistryEntry* g_pFirstConfigEntry = nullptr; size_t g_configEntryCount = 0; -ConfigRegistryEntry::ConfigRegistryEntry(const std::type_info& tinfo, bool has_validator) : +ConfigRegistryEntry::ConfigRegistryEntry(const std::type_info& tinfo, bool hasValidator, injector_t&& inject) : pFlink(g_pFirstConfigEntry), m_key(autowiring::ExtractKey(tinfo)), - m_has_validator(has_validator) + m_hasValidator(hasValidator), + m_injector(std::move(inject)) { g_configEntryCount++; g_pFirstConfigEntry = this; } -bool ConfigRegistryEntry::is(const std::string& key) const { - return m_key == key; -} - void autowiring::ThrowFailedTypeParseException(const std::string& str, const std::type_info& ti) { std::stringstream msg; msg << "Failed to parse '" << str << "' as type '" << autowiring::demangle(ti) << "'"; diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 6db150a79..57a436745 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -630,6 +630,13 @@ void CoreContext::AddBolt(const std::shared_ptr& pBase) { m_nameListeners[typeid(void)].push_back(pBase.get()); } +JunctionBoxBase& CoreContext::All(const std::type_info& ti) const { + auto jb = m_junctionBoxManager->Get(ti); + if (!jb) + throw autowiring_error("Attempted to obtain a junction box which has not been declared"); + return *jb; +} + void CoreContext::BuildCurrentState(void) { AutoGlobalContext glbl; glbl->Invoke(&AutowiringEvents::NewContext)(*this); @@ -1019,7 +1026,7 @@ void CoreContext::FilterFiringException(const JunctionBoxBase* pProxy, CoreObjec } } -void CoreContext::Snoop(const CoreObjectDescriptor& traits) { +void CoreContext::AddSnooper(const CoreObjectDescriptor& traits) { // Add to collections of snoopers InsertSnooper(traits.value); @@ -1032,7 +1039,7 @@ void CoreContext::Snoop(const CoreObjectDescriptor& traits) { AddPacketSubscriber(traits.subscriber); } -void CoreContext::Unsnoop(const CoreObjectDescriptor& traits) { +void CoreContext::RemoveSnooper(const CoreObjectDescriptor& traits) { RemoveSnooper(traits.value); // Cleanup if its an EventReceiver diff --git a/src/autowiring/CoreThread.cpp b/src/autowiring/CoreThread.cpp index 2c857e1b2..36a4f1f92 100644 --- a/src/autowiring/CoreThread.cpp +++ b/src/autowiring/CoreThread.cpp @@ -26,77 +26,6 @@ void CoreThread::DoRunLoopCleanup(std::shared_ptr&& ctxt, std::shar BasicThread::DoRunLoopCleanup(std::move(ctxt), std::move(refTracker)); } -void CoreThread::WaitForEvent(void) { - std::unique_lock lk(m_dispatchLock); - if(m_aborted) - throw dispatch_aborted_exception(); - - // Unconditional delay: - m_queueUpdated.wait(lk, [this] () -> bool { - if(m_aborted) - throw dispatch_aborted_exception(); - - return - // We will need to transition out if the delay queue receives any items: - !this->m_delayedQueue.empty() || - - // We also transition out if the dispatch queue has any events: - !this->m_dispatchQueue.empty(); - }); - - if(m_dispatchQueue.empty()) { - // The delay queue has items but the dispatch queue does not, we need to switch - // to the suggested sleep timeout variant: - WaitForEventUnsafe(lk, m_delayedQueue.top().GetReadyTime()); - } else { - // We have an event, we can just hop over to this variant: - DispatchEventUnsafe(lk); - } -} - -bool CoreThread::WaitForEvent(std::chrono::milliseconds milliseconds) { - return WaitForEvent(std::chrono::steady_clock::now() + milliseconds); -} - -bool CoreThread::WaitForEvent(std::chrono::steady_clock::time_point wakeTime) { - if(wakeTime == std::chrono::steady_clock::time_point::max()) { - // Maximal wait--we can optimize by using the zero-arguments version - return WaitForEvent(), true; - } - - std::unique_lock lk(m_dispatchLock); - return WaitForEventUnsafe(lk, wakeTime); -} - -bool CoreThread::WaitForEventUnsafe(std::unique_lock& lk, std::chrono::steady_clock::time_point wakeTime) { - if(m_aborted) - throw dispatch_aborted_exception(); - - while(m_dispatchQueue.empty()) { - // Derive a wakeup time using the high precision timer: - wakeTime = SuggestSoonestWakeupTimeUnsafe(wakeTime); - - // Now we wait, either for the timeout to elapse or for the dispatch queue itself to - // transition to the "aborted" state. - std::cv_status status = m_queueUpdated.wait_until(lk, wakeTime); - - // Short-circuit if the queue was aborted - if(m_aborted) - throw dispatch_aborted_exception(); - - if (PromoteReadyDispatchersUnsafe()) - // Dispatcher is ready to run! Exit our loop and dispatch an event - break; - - if(status == std::cv_status::timeout) - // Can't proceed, queue is empty and nobody is ready to be run - return false; - } - - DispatchEventUnsafe(lk); - return true; -} - void CoreThread::Run() { while(!ShouldStop()) WaitForEvent(); diff --git a/src/autowiring/DispatchQueue.cpp b/src/autowiring/DispatchQueue.cpp index a9c8e6f9d..ec143ce1b 100644 --- a/src/autowiring/DispatchQueue.cpp +++ b/src/autowiring/DispatchQueue.cpp @@ -4,9 +4,6 @@ #include "at_exit.h" #include -dispatch_aborted_exception::dispatch_aborted_exception(void){} -dispatch_aborted_exception::~dispatch_aborted_exception(void){} - DispatchQueue::DispatchQueue(void): m_dispatchCap(1024), m_aborted(false) @@ -76,6 +73,78 @@ void DispatchQueue::Abort(void) { m_queueUpdated.notify_all(); } +void DispatchQueue::WaitForEvent(void) { + std::unique_lock lk(m_dispatchLock); + if (m_aborted) + throw dispatch_aborted_exception("Dispatch queue was aborted prior to waiting for an event"); + + // Unconditional delay: + m_queueUpdated.wait(lk, [this]() -> bool { + if (m_aborted) + throw dispatch_aborted_exception("Dispatch queue was aborted while waiting for an event"); + + return + // We will need to transition out if the delay queue receives any items: + !this->m_delayedQueue.empty() || + + // We also transition out if the dispatch queue has any events: + !this->m_dispatchQueue.empty(); + }); + + if (m_dispatchQueue.empty()) { + // The delay queue has items but the dispatch queue does not, we need to switch + // to the suggested sleep timeout variant: + WaitForEventUnsafe(lk, m_delayedQueue.top().GetReadyTime()); + } + else { + // We have an event, we can just hop over to this variant: + DispatchEventUnsafe(lk); + } +} + +bool DispatchQueue::WaitForEvent(std::chrono::milliseconds milliseconds) { + return WaitForEvent(std::chrono::steady_clock::now() + milliseconds); +} + +bool DispatchQueue::WaitForEvent(std::chrono::steady_clock::time_point wakeTime) { + if (wakeTime == std::chrono::steady_clock::time_point::max()) { + // Maximal wait--we can optimize by using the zero-arguments version + return WaitForEvent(), true; + } + + std::unique_lock lk(m_dispatchLock); + return WaitForEventUnsafe(lk, wakeTime); +} + +bool DispatchQueue::WaitForEventUnsafe(std::unique_lock& lk, std::chrono::steady_clock::time_point wakeTime) { + if (m_aborted) + throw dispatch_aborted_exception("Dispatch queue was aborted prior to waiting for an event"); + + while (m_dispatchQueue.empty()) { + // Derive a wakeup time using the high precision timer: + wakeTime = SuggestSoonestWakeupTimeUnsafe(wakeTime); + + // Now we wait, either for the timeout to elapse or for the dispatch queue itself to + // transition to the "aborted" state. + std::cv_status status = m_queueUpdated.wait_until(lk, wakeTime); + + // Short-circuit if the queue was aborted + if (m_aborted) + throw dispatch_aborted_exception("Dispatch queue was aborted while waiting for an event"); + + if (PromoteReadyDispatchersUnsafe()) + // Dispatcher is ready to run! Exit our loop and dispatch an event + break; + + if (status == std::cv_status::timeout) + // Can't proceed, queue is empty and nobody is ready to be run + return false; + } + + DispatchEventUnsafe(lk); + return true; +} + bool DispatchQueue::DispatchEvent(void) { std::unique_lock lk(m_dispatchLock); @@ -107,6 +176,34 @@ void DispatchQueue::AddExisting(DispatchThunkBase* pBase) { OnPended(std::move(lk)); } +bool DispatchQueue::Barrier(std::chrono::nanoseconds timeout) { + // Set up the lambda: + auto complete = std::make_shared(false); + *this += [complete] { *complete = true; }; + + // Obtain the lock, wait until our variable is satisfied, which might be right away: + std::unique_lock lk(m_dispatchLock); + bool rv = m_queueUpdated.wait_for(lk, timeout, [&] { return m_aborted || *complete; }); + if (m_aborted) + throw dispatch_aborted_exception("Dispatch queue was aborted while a barrier was invoked"); + return rv; +} + +void DispatchQueue::Barrier(void) { + // Set up the lambda: + bool complete = false; + *this += [&] { complete = true; }; + + // Obtain the lock, wait until our variable is satisfied, which might be right away: + std::unique_lock lk(m_dispatchLock); + m_queueUpdated.wait(lk, [&] { return m_aborted || complete; }); + if (m_aborted) + // At this point, the dispatch queue MUST be completely run down. We have no outstanding references + // to our stack-allocated "complete" variable. Furthermore, after m_aborted is true, no further + // dispatchers are permitted to be run. + throw dispatch_aborted_exception("Dispatch queue was aborted while a barrier was invoked"); +} + std::chrono::steady_clock::time_point DispatchQueue::SuggestSoonestWakeupTimeUnsafe(std::chrono::steady_clock::time_point latestTime) const { return diff --git a/src/autowiring/dispatch_aborted_exception.cpp b/src/autowiring/dispatch_aborted_exception.cpp new file mode 100644 index 000000000..9730993e7 --- /dev/null +++ b/src/autowiring/dispatch_aborted_exception.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "dispatch_aborted_exception.h" + +dispatch_aborted_exception::dispatch_aborted_exception(void) : + autowiring_error("Dispatch queue aborted") +{} + +dispatch_aborted_exception::dispatch_aborted_exception(const std::string& what) : + autowiring_error(what) +{} + +dispatch_aborted_exception::~dispatch_aborted_exception(void){} diff --git a/src/autowiring/test/AnySharedPointerTest.cpp b/src/autowiring/test/AnySharedPointerTest.cpp index ac74e4e1b..a0da08c52 100644 --- a/src/autowiring/test/AnySharedPointerTest.cpp +++ b/src/autowiring/test/AnySharedPointerTest.cpp @@ -200,7 +200,7 @@ TEST_F(AnySharedPointerTest, CanHoldCoreObject) { } TEST_F(AnySharedPointerTest, CanFastCastToSelf) { - autowiring::fast_pointer_cast_initializer::sc_init; + (void)autowiring::fast_pointer_cast_initializer::sc_init; auto co = std::make_shared(); ASSERT_EQ( diff --git a/src/autowiring/test/AutoConfigListingTest.cpp b/src/autowiring/test/AutoConfigListingTest.cpp new file mode 100644 index 000000000..b6de2242c --- /dev/null +++ b/src/autowiring/test/AutoConfigListingTest.cpp @@ -0,0 +1,313 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include +#include +#include + +class AutoConfigListingTest : + public testing::Test +{}; + +struct Namespace1; +struct Namespace2; +struct XYZ; + +TEST_F(AutoConfigListingTest, VerifySimpleAssignment) { + // Set an attribute in the manager before injecting anything: + AutoRequired acm; + acm->Set("Namespace1.XYZ", 323); + + // Now inject the type which expects this value to be assigned: + AutoConfig cfg; + ASSERT_EQ(323, *cfg) << "Configurable type did not receive a value as expected"; +} + +struct NamespaceRoot; +struct NamespaceChild; + +TEST_F(AutoConfigListingTest, VerifyNestedNamespace) { + AutoRequired acm; + acm->Set("NamespaceRoot.NamespaceChild.Namespace1.Namespace2.XYZ", 142); + + AutoConfig cfg; + ASSERT_EQ(142, *cfg); +} + +struct MyBoolClass { + AutoConfig m_bool; +}; + +TEST_F(AutoConfigListingTest, VerifyPostHocAssignment) { + // Inject the configurable type first + AutoConfig cfg; + + // Configuration manager must exist at this point as a consequence of the earlier construction + Autowired acm; + ASSERT_TRUE(acm.IsAutowired()) << "AutoConfig field did not inject a configuration manager into this context as expected"; + + acm->Set("Namespace1.XYZ", 323); + + // Now inject the type which expects this value to be assigned: + ASSERT_EQ(323, *cfg) << "Configurable type did not receive a value as expected"; +} + +TEST_F(AutoConfigListingTest, VerifyRecursiveSearch) { + AutoRequired acm; + acm->Set("Namespace1.XYZ", 1001); + + { + AutoCreateContext ctxt; + CurrentContextPusher pshr(ctxt); + + // Now inject an element here, and verify that it was wired up as expected: + AutoConfig cfg; + ASSERT_TRUE(cfg->IsConfigured()) << "A configurable value was not configured as expected"; + ASSERT_EQ(*cfg, 1001) << "Configurable value obtained from a parent scope did not have the correct value"; + + // This must work as expected--a local context override will rewrite configuration values in the local scope + AutoRequired sub_mcc; + sub_mcc->Set("Namespace1.XYZ", 1002); + ASSERT_EQ(1002, *cfg) << "Override of a configurable value in a derived class did not take place as expected"; + } +} + +struct DefaultName { + AutoConfig m_def; +}; + +TEST_F(AutoConfigListingTest, DefaultNamespace) { + AutoRequired acm; + acm->Set("defaultname1", 123); + + AutoRequired def; + + ASSERT_EQ(*def->m_def, 123); +} + +TEST_F(AutoConfigListingTest, VerifyParsedAssignment) { + // We must also be able to support implicit string-to-type conversion via the shift operator for this type + AutoRequired acm; + + // Direct assignment to a string should not work, the type isn't a string it's an int + ASSERT_ANY_THROW(acm->Set("Namespace1.XYZ", "327")) << "An attempt to assign a value to an unrelated type did not generate an exception as expected"; + + ASSERT_ANY_THROW(acm->Set("Namespace1.XYZ", 3.0)) << "An attempt to assign a value to an unrelated type did not generate an exception as expected"; + + // Assignment to a string type should result in an appropriate coercion to the right value + ASSERT_TRUE(acm->SetParsed("Namespace1.XYZ", "324")); +} + +TEST_F(AutoConfigListingTest, VerifyDuplicateConfigAssignment) { + AutoRequired acm; + ASSERT_TRUE(acm->SetParsed("Namespace1.XYZ", "324")); + ASSERT_TRUE(acm->SetParsed("Namespace2.XYZ", "1111")); + + AutoConfig clz1; + AutoConfig clz2; + + ASSERT_EQ(*clz1, 324); + ASSERT_EQ(*clz2, 1111); +} + +class TypeWithoutAShiftOperator { +public: + int foo; +}; + +struct NoShift { + AutoConfig m_noshift; +}; + +static_assert(has_stream::value, "Stream operation not detected on a primitive type"); +static_assert(!has_stream::value, "Stream operation detected on a type that should not have supported it"); + +TEST_F(AutoConfigListingTest, TypeWithoutAShiftOperatorTest) { + AutoRequired noshift; + + AutoRequired mgr; + + // Indirect assignment should cause an exception + ASSERT_ANY_THROW(mgr->Set("MyNoShift", "")) << "Expected a throw in a case where a configurable value was used which cannot be assigned"; + + // Direct assignment should be supported still + TypeWithoutAShiftOperator tasoVal; + tasoVal.foo = 592; + mgr->Set("MyNoShift", tasoVal); + + ASSERT_EQ((*noshift->m_noshift)->foo, 592) << "Value assignment did not result in an update to a non-serializable configuration field"; +} + +TEST_F(AutoConfigListingTest, Callbacks) { + AutoRequired acm; + AutoConfig cfg; + + acm->Set("Namespace1.XYZ", 4); + + *cfg += [](int val) { + ASSERT_EQ(val, 42); + }; + + *cfg += [](int val) { + ASSERT_EQ(val, 42); + }; + + acm->Set("Namespace1.XYZ", 42); +} + +struct MyConfigurableClass { + AutoConfig cfg; +}; +struct MyConfigurableClass2 { + AutoConfig cfg; +}; + +TEST_F(AutoConfigListingTest, NestedContexts) { + // Set up contexts and members + AutoCurrentContext ctxt_outer; + std::shared_ptr ctxt_middle = ctxt_outer->Create(); + std::shared_ptr ctxt_sibling = ctxt_outer->Create(); + std::shared_ptr ctxt_inner = ctxt_middle->Create(); + std::shared_ptr ctxt_no_manager = ctxt_inner->Create(); + std::shared_ptr ctxt_leaf = ctxt_no_manager->Create(); + + AutoRequired mcc_outer(ctxt_outer); + AutoRequired mcc_middle(ctxt_middle); + AutoRequired mcc_sibling(ctxt_sibling); + AutoRequired mcc_inner(ctxt_inner); + AutoRequired mcc_leaf(ctxt_leaf); + + AutoRequired acm_outer(ctxt_outer); + AutoRequired acm_middle(ctxt_middle); + AutoRequired acm_leaf(ctxt_leaf); + + // Set initial value + acm_outer->Set("Namespace1.XYZ", 42); + ASSERT_EQ(42, *mcc_outer->cfg) << "Config value not set"; + ASSERT_EQ(42, *mcc_middle->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_sibling->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_inner->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_leaf->cfg) << "Config value not set in desendant context"; + EXPECT_TRUE(acm_middle->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; + EXPECT_TRUE(acm_leaf->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; + + // Set middle, inner shouldn't be able to be set from outer after this + bool callback_hit1 = false; + *mcc_inner->cfg += [&callback_hit1](int) { + callback_hit1 = true; + }; + acm_middle->Set("Namespace1.XYZ", 1337); + ASSERT_EQ(42, *mcc_outer->cfg) << "Config value changed in outer context"; + ASSERT_EQ(42, *mcc_sibling->cfg) << "Config value set from sibling context"; + ASSERT_EQ(1337, *mcc_middle->cfg) << "Config value not set"; + ASSERT_EQ(1337, *mcc_inner->cfg) << "Config value not set in child context"; + ASSERT_EQ(1337, *mcc_leaf->cfg) << "Config value not set in leaf context"; + ASSERT_TRUE(callback_hit1) << "Callback not hit in inner context"; + + // Set from outter, inner should be shielded by middle context + *mcc_inner->cfg += [](int) { + FAIL() << "This callback should never be hit"; + }; + + // Make sure sibling context is not shielded + bool callback_hit2 = false; + *mcc_sibling->cfg += [&callback_hit2](int) { + callback_hit2 = true; + }; + + // Set from outer, shouldn't effect middle or inner contexts + acm_outer->Set("Namespace1.XYZ", 999); + ASSERT_EQ(999, *mcc_outer->cfg) << "Config value not set"; + ASSERT_EQ(1337, *mcc_middle->cfg) << "Config value overwritten when value was set in this context"; + ASSERT_EQ(1337, *mcc_inner->cfg) << "Config value overwritten when value was set in parent context"; + + // Make sure sibling hit + ASSERT_EQ(999, *mcc_sibling->cfg) << "Value not set on sibling of context where value was previously set"; + ASSERT_TRUE(callback_hit2) << "Callback not called on sibling of context where value was previously set"; +} + +struct ValidatedKey{ + static bool Validate(const int& value) { + return value > 5; + } +}; + +struct MyValidatedClass{ + AutoConfig m_config; +}; + +TEST_F(AutoConfigListingTest, Validators) { + AutoRequired acm; + + ASSERT_ANY_THROW(acm->Set("ValidatedKey", 2)) << "AutoConfigListing didn't regect invalid value"; + + AutoRequired valid; + + acm->Set("ValidatedKey", 42); + ASSERT_EQ(42, *valid->m_config) << "Value not set for key"; + + ASSERT_ANY_THROW(acm->Set("ValidatedKey", 1)) << "AutoConfigListing didn't regect invalid value"; + ASSERT_EQ(42, *valid->m_config) << "Value not set for key"; + + acm->Set("ValidatedKey", 1337); + ASSERT_EQ(1337, *valid->m_config) << "Value not set for key"; +} + +struct OuterCtxt{}; +struct MiddleCtxt{}; +struct InnerCtxt{}; +TEST_F(AutoConfigListingTest, ListingConfigs) { + AutoCreateContextT ctxt_outer; + auto ctxt_middle = ctxt_outer->Create(); + auto ctxt_inner = ctxt_middle->Create(); + + AutoRequired acm_outer(ctxt_outer); + AutoRequired acm_inner(ctxt_inner); + + int callback_outer = 0; + acm_outer->AddCallback([&callback_outer](const AutoConfigVarBase& ptr) { + ++callback_outer; + }); + + int callback_inner = 0; + acm_inner->AddCallback([&callback_inner](const AutoConfigVarBase& ptr) { + ++callback_inner; + }); + + AutoRequired var1_inner(ctxt_inner); + *var1_inner->cfg = 1; + + ASSERT_EQ(0, callback_outer) << "OnAdded fired incorrectly"; + ASSERT_EQ(1, callback_inner) << "Callback fired incorrectly"; + ASSERT_EQ(0, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; + + ASSERT_EQ(1, callback_inner) << "Callback not called on existing keys"; + + AutoRequired var1_outer(ctxt_outer); + *var1_outer->cfg = 2; + + ASSERT_EQ(1, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; + + AutoRequired var2_outer(ctxt_outer); + AutoRequired var2_inner(ctxt_inner); + + ASSERT_EQ(1, acm_outer->GetLocalKeys().size()) << "Constructing an uninitialized config incremented the local count"; + + *var2_outer->cfg = 3; + + ASSERT_EQ(2, acm_outer->GetLocalKeys().size()) << "Incorrect number of local keys found in outer context"; + ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of local keys found in inner context"; + + ASSERT_EQ(2, callback_outer) << "Outer callback called an incorrect number of times"; + ASSERT_EQ(1, callback_inner) << "Inner callback called an incorrect number of times"; + + ASSERT_EQ(*var2_inner->cfg, *var2_outer->cfg) << "Value did not get set in child context"; + + auto keys_outer = acm_outer->GetLocalKeys(); + ASSERT_EQ(var1_outer->cfg->m_key, keys_outer[0]) << "Keys listed out of construction order"; + ASSERT_EQ(var2_outer->cfg->m_key, keys_outer[1]) << "Keys listed out of construction order"; + + auto keys_inner = acm_inner->GetLocalKeys(); + ASSERT_EQ(var1_inner->cfg->m_key, keys_inner[0]) << "Keys listed out of construction order"; +} diff --git a/src/autowiring/test/AutoConfigParserTest.cpp b/src/autowiring/test/AutoConfigParserTest.cpp new file mode 100644 index 000000000..d20bb2444 --- /dev/null +++ b/src/autowiring/test/AutoConfigParserTest.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include +#include + +class AutoConfigParserTest: + public testing::Test +{}; + +TEST_F(AutoConfigParserTest, ExtractKeyTestWin) { + std::string type1("struct ConfigTypeExtractor"); + const auto key1 = autowiring::ExtractKey(type1); + ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; + + std::string type2("struct ConfigTypeExtractor"); + const auto key2 = autowiring::ExtractKey(type2); + ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; + + std::string type3("struct ConfigTypeExtractor"); + const auto key3 = autowiring::ExtractKey(type3); + ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; + + std::string type4("struct ConfigTypeExtractor"); + const auto key4 = autowiring::ExtractKey(type4); + ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; + + std::string type5("struct ConfigTypeExtractor"); + const auto key5 = autowiring::ExtractKey(type5); + ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; +} + +TEST_F(AutoConfigParserTest, ExtractKeyTestPOSIX) { + std::string type1("ConfigTypeExtractor"); + const auto key1 = autowiring::ExtractKey(type1); + ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; + + std::string type2("ConfigTypeExtractor"); + const auto key2 = autowiring::ExtractKey(type2); + ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; + + std::string type3("ConfigTypeExtractor"); + const auto key3 = autowiring::ExtractKey(type3); + ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; + + std::string type4("ConfigTypeExtractor"); + const auto key4 = autowiring::ExtractKey(type4); + ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; + + std::string type5("ConfigTypeExtractor"); + const auto key5 = autowiring::ExtractKey(type5); + ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; +} + +struct Namespace1 {}; +struct Namespace2 {}; +struct XYZ {}; + +struct MyConfigurableClass { + AutoConfig m_myName; +}; + +TEST_F(AutoConfigParserTest, VerifyCorrectDeconstruction) { + AutoRequired mcc; + + EXPECT_STREQ("Namespace1.XYZ", mcc->m_myName->m_key.c_str()) + << "Configuration variable name was not correctly extracted"; +} diff --git a/src/autowiring/test/AutoConfigTest.cpp b/src/autowiring/test/AutoConfigTest.cpp index d50476c15..dd933e04e 100644 --- a/src/autowiring/test/AutoConfigTest.cpp +++ b/src/autowiring/test/AutoConfigTest.cpp @@ -1,225 +1,153 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" #include -#include -#include class AutoConfigTest: public testing::Test {}; -TEST_F(AutoConfigTest, ExtractKeyTestWin) { - std::string type1("struct ConfigTypeExtractor"); - const auto key1 = autowiring::ExtractKey(type1); - ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; +struct Namespace1 {}; +struct Namespace2 {}; +struct XYZ {}; - std::string type2("struct ConfigTypeExtractor"); - const auto key2 = autowiring::ExtractKey(type2); - ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; +TEST_F(AutoConfigTest, VerifyBasicAssignment) { + AutoConfig cfg1; + *cfg1 = 1; + ASSERT_EQ(*cfg1, 1) << "Operator = failed"; - std::string type3("struct ConfigTypeExtractor"); - const auto key3 = autowiring::ExtractKey(type3); - ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; + AutoConfig cfg2(2); + ASSERT_EQ(*cfg2, 2) << "Default construction failed"; - std::string type4("struct ConfigTypeExtractor"); - const auto key4 = autowiring::ExtractKey(type4); - ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; + AutoConfig cfg1a; + ASSERT_EQ(*cfg1a, 1) << "Failed to autowire AutoConfig value"; - std::string type5("struct ConfigTypeExtractor"); - const auto key5 = autowiring::ExtractKey(type5); - ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; -} - -TEST_F(AutoConfigTest, ExtractKeyTestPOSIX) { - std::string type1("ConfigTypeExtractor"); - const auto key1 = autowiring::ExtractKey(type1); - ASSERT_EQ("Namespace1.XYZ", key1) << "Failed to properly extract Key with a single namespace"; - - std::string type2("ConfigTypeExtractor"); - const auto key2 = autowiring::ExtractKey(type2); - ASSERT_EQ("Namespace1.Namespace2.XYZ", key2) << "Failed to properly extract Key with multiple namespaces"; + *cfg1a = 3; + ASSERT_EQ(*cfg1, 3) << "Failed to autowire AutoConfig value"; - std::string type3("ConfigTypeExtractor"); - const auto key3 = autowiring::ExtractKey(type3); - ASSERT_EQ("XYZ", key3) << "Failed to properly extract Key with no namespace"; - - std::string type4("ConfigTypeExtractor"); - const auto key4 = autowiring::ExtractKey(type4); - ASSERT_EQ("ClassNamespace1.AutoConfigTest.XYZ", key4) << "Failed to properly extract Key with class namespaces"; - - std::string type5("ConfigTypeExtractor"); - const auto key5 = autowiring::ExtractKey(type5); - ASSERT_EQ("ClassNamespace1.Base::Nested.XYZ", key5) << "Failed to properly extract Key from nested"; + AutoConfig cfg2a(5); + ASSERT_EQ(*cfg2a, 2) << "Constructor overwrote value when it wasn't supposed to!"; } -struct MyConfigurableClass { - AutoConfig m_myName; -}; - -struct MyConfigurableClass2 { - AutoConfig m_myName; -}; - -TEST_F(AutoConfigTest, VerifyCorrectDeconstruction) { - AutoRequired mcc; - - EXPECT_STREQ("Namespace1.XYZ", mcc->m_myName.m_key.c_str()) - << "Configuration variable name was not correctly extracted"; +//Ideally, this shouldn't even compile. As it stands, it should be at the very least an exception. +TEST_F(AutoConfigTest, DISABLED_VerifySingleUnderlyingType){ + AutoConfig cfg1; + bool threw = false; + try { + AutoConfig cfg2; + } + catch(...){ + threw = true; + } + ASSERT_TRUE(threw) << "Constructing an AutoConfig with the same name and a different type failed to cause an exception"; } -TEST_F(AutoConfigTest, VerifySimpleAssignment) { - // Set an attribute in the manager before injecting anything: - AutoRequired acm; - acm->Set("Namespace1.XYZ", 323); +TEST_F(AutoConfigTest, VerifyBasicSignals) { + AutoCurrentContext ctxt; - // Now inject the type which expects this value to be assigned: - AutoRequired mcc; - ASSERT_EQ(323, *mcc->m_myName) << "Configurable type did not receive a value as expected"; -} + int handler_called = 0; + int handler_value_read = 0; + Autowired> autoCfg; + autoCfg(&AutoConfigVarBase::onChangedSignal) += [&](const AutoConfigVarBase& var) { + ++handler_called; + var.Get(&handler_value_read); + }; -struct NamespaceRoot; -struct NamespaceChild; + AutoConfig cfg1(1); + ASSERT_EQ(handler_called, 1) << "OnChanged not triggered by construction"; + ASSERT_EQ(handler_value_read, 1) << "Bad value read in OnChanged"; -TEST_F(AutoConfigTest, VerifyNestedNamespace) { - AutoRequired acm; - acm->Set("NamespaceRoot.NamespaceChild.Namespace1.Namespace2.XYZ", 142); + *cfg1 = 20; + ASSERT_EQ(handler_called, 2) << "OnChanged not triggered by assignement"; + ASSERT_EQ(handler_value_read, 20) << "Bad value read in OnChanged"; - AutoConfig cfg; - ASSERT_EQ(142, *cfg); -} + AutoConfig cfg2; + *cfg2 = 2; -struct MyBoolClass { - AutoConfig m_bool; -}; + ASSERT_EQ(handler_called, 3) << "OnChanged not triggred by assignement from alternate autowired instance"; + ASSERT_EQ(handler_value_read, 2) << "Bad value read in OnChanged"; -TEST_F(AutoConfigTest, VerifyBool) { - AutoRequired acm; - AutoRequired clz1; - - acm->Set("bool_space.my_bool", true); - ASSERT_TRUE(*clz1->m_bool); - - acm->SetParsed("bool_space.my_bool", "false"); - ASSERT_FALSE(*clz1->m_bool); -} - -TEST_F(AutoConfigTest, VerifyPostHocAssignment) { - // Inject the configurable type first - AutoRequired mcc; - - // Configuration manager must exist at this point as a consequence of the earlier construction - Autowired acm; - ASSERT_TRUE(acm.IsAutowired()) << "AutoConfig field did not inject a configuration manager into this context as expected"; + int handler_direct_called = 0; + int handler_direct_value = 0; + auto* registration = *cfg2 += [&](const int& var) { + ++handler_direct_called; + handler_direct_value = var; + }; - acm->Set("Namespace1.XYZ", 323); + *cfg1 = 30; + ASSERT_EQ(handler_direct_called, 1) << "OnChanged not triggred by indirect +="; + ASSERT_EQ(handler_direct_value, 30) << "Bad value read in OnChanged"; - // Now inject the type which expects this value to be assigned: - ASSERT_EQ(323, *mcc->m_myName) << "Configurable type did not receive a value as expected"; + *cfg1 -= registration; + + *cfg1 = 123; + ASSERT_EQ(handler_direct_called, 1) << "OnChanged not unsubscribed by indirect -="; + ASSERT_EQ(handler_direct_value, 30) << "Bad value read in OnChanged"; } -TEST_F(AutoConfigTest, VerifyRecursiveSearch) { - AutoRequired acm; - acm->Set("Namespace1.XYZ", 1001); +TEST_F(AutoConfigTest, VerifyBasicInheritance) { + AutoConfig cfg_outer; + *cfg_outer = 1001; { - AutoCreateContext ctxt; - CurrentContextPusher pshr(ctxt); - - // Now inject an element here, and verify that it was wired up as expected: - AutoRequired mcc; - ASSERT_TRUE(mcc->m_myName.IsConfigured()) << "A configurable value was not configured as expected"; - ASSERT_EQ(1001, *mcc->m_myName) << "Configurable value obtained from a parent scope did not have the correct value"; - - // This must work as expected--a local context override will rewrite configuration values in the local scope - AutoRequired sub_mcc; - sub_mcc->Set("Namespace1.XYZ", 1002); - ASSERT_EQ(1002, *mcc->m_myName) << "Override of a configurable value in a derived class did not take place as expected"; + AutoCreateContext ctxt_middle; + CurrentContextPusher pshr(ctxt_middle); + AutoConfig cfg_middle; + ASSERT_EQ(*cfg_middle, 1001) << "Configurable value obtained from parent context did not have the correct value"; + + { + AutoCreateContext ctxt_inner; + CurrentContextPusher pshr(ctxt_inner); + + // Now inject an element here, and verify that it was wired up as expected: + AutoConfig cfg_inner; + ASSERT_EQ(1001, *cfg_inner) << "Configurable value obtained from a parent context did not have the correct value"; + + // Now change the parent value.... + *cfg_outer = 1002; + ASSERT_EQ(*cfg_inner, 1002) << "Configuration value did not update when value in a parent was updated"; + + // Now change the value in the intervening context... + *cfg_middle = 1003; + ASSERT_EQ(*cfg_inner, 1003) << "Configuration value did not update when value in a parent was updated"; + + // This must work as expected--a local context override will rewrite configuration values in the local scope + *cfg_inner = 1004; + ASSERT_EQ(*cfg_inner, 1004) << "Override of a configurable value in a derived class did not take place as expected"; + } + ASSERT_EQ(*cfg_middle, 1003) << "A configuration value was not updated when accessed directly."; } + + //Modification of the value in the enclosed context should not effect us. + ASSERT_EQ(1002, *cfg_outer) << "Override of a configurable value in a child scope affected parent"; } -struct DefaultName { - AutoConfig m_def; +struct Unparseable { + int v; }; -TEST_F(AutoConfigTest, DefaultNamespace) { - AutoRequired acm; - acm->Set("defaultname1", 123); - - AutoRequired def; - - ASSERT_EQ(123, *def->m_def); -} - TEST_F(AutoConfigTest, VerifyParsedAssignment) { // We must also be able to support implicit string-to-type conversion via the shift operator for this type - AutoRequired acm; + AutoConfig cfg; // Direct assignment to a string should not work, the type isn't a string it's an int - ASSERT_ANY_THROW(acm->Set("Namespace1.XYZ", "327")) << "An attempt to assign a value to an unrelated type did not generate an exception as expected"; - - ASSERT_ANY_THROW(acm->Set("Namespace1.XYZ", 3.0)) << "An attempt to assign a value to an unrelated type did not generate an exception as expected"; + ASSERT_ANY_THROW(cfg->SetParsed("badvalue")) << "An attempt to assign a value to an unparsable string did not throw an exception"; + ASSERT_FALSE(cfg->IsConfigured()) << "A failed parse incorrectly marked the value as configured"; // Assignment to a string type should result in an appropriate coercion to the right value - ASSERT_TRUE(acm->SetParsed("Namespace1.XYZ", "324")); + cfg->SetParsed("324"); + ASSERT_EQ(*cfg, 324); + ASSERT_TRUE(cfg->IsConfigured()) << "Assignement from a parsed value falied to mark the value as configured"; + + AutoConfig noparse; + ASSERT_ANY_THROW(cfg->SetParsed("anyvalue")) << "An attempt to parse a value into an unparsable structure did not throw an exception"; + *noparse = Unparseable{ 20 }; + ASSERT_EQ((*noparse)->v, 20); } -TEST_F(AutoConfigTest, VerifyDuplicateConfigAssignment) { - AutoRequired acm; - ASSERT_TRUE(acm->SetParsed("Namespace1.XYZ", "324")); - ASSERT_TRUE(acm->SetParsed("Namespace2.XYZ", "1111")); - - AutoRequired clz1; - AutoRequired clz2; - - ASSERT_EQ(324, *clz1->m_myName); - ASSERT_EQ(1111, *clz2->m_myName); -} - -class TypeWithoutAShiftOperator { -public: - int foo; -}; - -struct NoShift { - AutoConfig m_noshift; +struct MyConfigurableClass { + AutoConfig cfg; }; -static_assert(has_stream::value, "Stream operation not detected on a primitive type"); -static_assert(!has_stream::value, "Stream operation detected on a type that should not have supported it"); - -TEST_F(AutoConfigTest, TypeWithoutAShiftOperatorTest) { - AutoRequired noshift; - - AutoRequired mgr; - - // Indirect assignment should cause an exception - ASSERT_ANY_THROW(mgr->Set("MyNoShift", "")) << "Expected a throw in a case where a configurable value was used which cannot be assigned"; - - // Direct assignment should be supported still - TypeWithoutAShiftOperator tasoVal; - tasoVal.foo = 592; - mgr->Set("MyNoShift", tasoVal); - - ASSERT_EQ(592, noshift->m_noshift->foo) << "Value assignment did not result in an update to a non-serializable configuration field"; -} - -TEST_F(AutoConfigTest, Callbacks) { - AutoRequired acm; - AutoRequired mcc; - - acm->Set("Namespace1.XYZ", 4); - - mcc->m_myName += [](int val) { - ASSERT_EQ(val, 42); - }; - - mcc->m_myName += [](int val) { - ASSERT_EQ(val, 42); - }; - - acm->Set("Namespace1.XYZ", 42); -} - TEST_F(AutoConfigTest, NestedContexts) { // Set up contexts and members AutoCurrentContext ctxt_outer; @@ -235,52 +163,48 @@ TEST_F(AutoConfigTest, NestedContexts) { AutoRequired mcc_inner(ctxt_inner); AutoRequired mcc_leaf(ctxt_leaf); - AutoRequired acm_outer(ctxt_outer); - AutoRequired acm_middle(ctxt_middle); - AutoRequired acm_leaf(ctxt_leaf); - // Set initial value - acm_outer->Set("Namespace1.XYZ", 42); - ASSERT_EQ(42, *mcc_outer->m_myName) << "Config value not set"; - ASSERT_EQ(42, *mcc_middle->m_myName) << "Config value not set in descendant context"; - ASSERT_EQ(42, *mcc_sibling->m_myName) << "Config value not set in descendant context"; - ASSERT_EQ(42, *mcc_inner->m_myName) << "Config value not set in descendant context"; - ASSERT_EQ(42, *mcc_leaf->m_myName) << "Config value not set in desendant context"; - EXPECT_TRUE(acm_middle->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; - EXPECT_TRUE(acm_leaf->IsInherited("Namespace1.XYZ")) << "Inherited key not marked as such"; + *mcc_outer->cfg = 42; + ASSERT_EQ(42, *mcc_outer->cfg) << "Config value not set"; + ASSERT_EQ(42, *mcc_middle->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_sibling->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_inner->cfg) << "Config value not set in descendant context"; + ASSERT_EQ(42, *mcc_leaf->cfg) << "Config value not set in desendant context"; + EXPECT_TRUE(mcc_middle->cfg->IsInherited()) << "Inherited key not marked as such"; + EXPECT_TRUE(mcc_leaf->cfg->IsInherited()) << "Inherited key not marked as such"; // Set middle, inner shouldn't be able to be set from outer after this bool callback_hit1 = false; - mcc_inner->m_myName += [&callback_hit1](int) { + *mcc_inner->cfg += [&callback_hit1](int) { callback_hit1 = true; }; - acm_middle->Set("Namespace1.XYZ", 1337); - ASSERT_EQ(42, *mcc_outer->m_myName) << "Config value changed in outer context"; - ASSERT_EQ(42, *mcc_sibling->m_myName) << "Config value set from sibling context"; - ASSERT_EQ(1337, *mcc_middle->m_myName) << "Config value not set"; - ASSERT_EQ(1337, *mcc_inner->m_myName) << "Config value not set in child context"; - ASSERT_EQ(1337, *mcc_leaf->m_myName) << "Config value not set in leaf context"; + *mcc_middle->cfg = 1337; + ASSERT_EQ(42, *mcc_outer->cfg) << "Config value changed in outer context"; + ASSERT_EQ(42, *mcc_sibling->cfg) << "Config value set from sibling context"; + ASSERT_EQ(1337, *mcc_middle->cfg) << "Config value not set"; + ASSERT_EQ(1337, *mcc_inner->cfg) << "Config value not set in child context"; + ASSERT_EQ(1337, *mcc_leaf->cfg) << "Config value not set in leaf context"; ASSERT_TRUE(callback_hit1) << "Callback not hit in inner context"; // Set from outter, inner should be shielded by middle context - mcc_inner->m_myName += [](int) { + *mcc_inner->cfg += [](int) { FAIL() << "This callback should never be hit"; }; // Make sure sibling context is not shielded bool callback_hit2 = false; - mcc_sibling->m_myName += [&callback_hit2](int) { + *mcc_sibling->cfg += [&callback_hit2](int) { callback_hit2 = true; }; // Set from outer, shouldn't effect middle or inner contexts - acm_outer->Set("Namespace1.XYZ", 999); - ASSERT_EQ(999, *mcc_outer->m_myName) << "Config value not set"; - ASSERT_EQ(1337, *mcc_middle->m_myName) << "Config value overwritten when value was set in this context"; - ASSERT_EQ(1337, *mcc_inner->m_myName) << "Config value overwritten when value was set in parent context"; + *mcc_outer->cfg = 999; + ASSERT_EQ(999, *mcc_outer->cfg) << "Config value not set"; + ASSERT_EQ(1337, *mcc_middle->cfg) << "Config value overwritten when value was set in this context"; + ASSERT_EQ(1337, *mcc_inner->cfg) << "Config value overwritten when value was set in parent context"; // Make sure sibling hit - ASSERT_EQ(999, *mcc_sibling->m_myName) << "Value not set on sibling of context where value was previously set"; + ASSERT_EQ(999, *mcc_sibling->cfg) << "Value not set on sibling of context where value was previously set"; ASSERT_TRUE(callback_hit2) << "Callback not called on sibling of context where value was previously set"; } @@ -295,34 +219,18 @@ struct MyValidatedClass{ }; TEST_F(AutoConfigTest, Validators) { - AutoRequired acm; - - ASSERT_ANY_THROW(acm->Set("ValidatedKey", 2)) << "AutoConfigManager didn't regect invalid value"; - AutoRequired valid; - acm->Set("ValidatedKey", 42); + *valid->m_config = 42; ASSERT_EQ(42, *valid->m_config) << "Value not set for key"; - ASSERT_ANY_THROW(acm->Set("ValidatedKey", 1)) << "AutoConfigManager didn't regect invalid value"; + ASSERT_ANY_THROW(*valid->m_config = 1) << "AutoConfig validator didn't reject invalid value"; ASSERT_EQ(42, *valid->m_config) << "Value not set for key"; - acm->Set("ValidatedKey", 1337); + *valid->m_config = 1337; ASSERT_EQ(1337, *valid->m_config) << "Value not set for key"; } -TEST_F(AutoConfigTest, DirectAssignemnt) { - AutoConfig var; - var = 10; - ASSERT_EQ(10, *var); - - AutoRequired containsVar; - - ASSERT_EQ(10, *var); - ASSERT_EQ(10, *containsVar->m_myName); -} - - struct ComplexValue { int a; int b; @@ -330,6 +238,7 @@ struct ComplexValue { ComplexValue(int nA, int nB, int nC) : a(nA), b(nB), c(nC) {} ComplexValue(int repeated) : a(repeated), b(repeated), c(repeated) {} + ComplexValue() {} }; struct MyComplexValueClass { @@ -340,75 +249,23 @@ struct MyComplexValueClass { }; TEST_F(AutoConfigTest, ComplexConstruction){ - AutoRequired mgr; - ASSERT_FALSE(mgr->IsConfigured("Namespace1.MyCxValue")); - + //Default constuction is not allowed if the underlying type does not support it AutoConfig defaultConstructed; - - ASSERT_FALSE(mgr->IsConfigured("Namespace1.MyCxValue")) << "Improperly set config value when default constructing AutoConfig"; + ASSERT_FALSE(defaultConstructed->IsConfigured()) << "non-initalizing constructor should not configure a value!"; AutoConfig fancyConstructed(1, 2, 3); - ASSERT_TRUE(mgr->IsConfigured("Namespace1.MyCxValue")) << "Initializing constructor did not set config value"; - ASSERT_EQ(fancyConstructed->a, 1) << "Initializing constructor did not set config value"; - ASSERT_EQ(fancyConstructed->b, 2) << "Initializing constructor did not set config value"; - ASSERT_EQ(fancyConstructed->c, 3) << "Initializing constructor did not set config value"; - - - AutoConfig fancy2(7); - ASSERT_EQ(fancy2->a, 1) << "Second Initalizing constructor overrode the first!"; - ASSERT_EQ(fancy2->b, 2) << "Second Initalizing constructor overrode the first!"; - ASSERT_EQ(fancy2->c, 3) << "Second Initalizing constructor overrode the first!"; -} - -struct OuterCtxt{}; -struct MiddleCtxt{}; -struct InnerCtxt{}; -TEST_F(AutoConfigTest, ListingConfigs) { - AutoCreateContextT ctxt_outer; - auto ctxt_middle = ctxt_outer->Create(); - auto ctxt_inner = ctxt_middle->Create(); - - AutoRequired acm_outer(ctxt_outer); - AutoRequired acm_inner(ctxt_inner); - - AutoRequired var1_inner(ctxt_inner); - var1_inner->m_myName = 1; - - ASSERT_EQ(0, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; - ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; - - int callback_outer = 0; - acm_outer->AddCallback([&callback_outer](const std::string& key, const AnySharedPointer& ptr) { - ++callback_outer; - }); - - int callback_inner = 0; - acm_inner->AddCallback([&callback_inner](const std::string& key, const AnySharedPointer& ptr) { - ++callback_inner; - }); - - ASSERT_EQ(1, callback_inner) << "Callback not called on existing keys"; - - AutoRequired var1_outer(ctxt_outer); - var1_outer->m_myName = 2; - - ASSERT_EQ(1, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; - ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; - - AutoRequired var2_outer(ctxt_outer); - var2_outer->m_myName = 3; - - ASSERT_EQ(2, acm_outer->GetLocalKeys().size()) << "Incorrect number of keys found in outer context"; - ASSERT_EQ(1, acm_inner->GetLocalKeys().size()) << "Incorrect number of keys found in inner context"; - - ASSERT_EQ(2, callback_outer) << "Outer callback called an incorrect number of times"; - ASSERT_EQ(1, callback_inner) << "Inner callback called an incorrect number of times"; - - auto keys_outer = acm_outer->GetLocalKeys(); - ASSERT_EQ(var1_outer->m_myName.m_key, keys_outer[0]) << "Keys listed out of construction order"; - ASSERT_EQ(var2_outer->m_myName.m_key, keys_outer[1]) << "Keys listed out of construction order"; - - auto keys_inner = acm_inner->GetLocalKeys(); - ASSERT_EQ(var1_inner->m_myName.m_key, keys_inner[0]) << "Keys listed out of construction order"; + ASSERT_TRUE(fancyConstructed->IsConfigured()) << "Initializing constructor did not set config value"; + ASSERT_EQ((*fancyConstructed)->a, 1) << "Initializing constructor did not set config value"; + ASSERT_EQ((*fancyConstructed)->b, 2) << "Initializing constructor did not set config value"; + ASSERT_EQ((*fancyConstructed)->c, 3) << "Initializing constructor did not set config value"; + + AutoRequired complex; + ASSERT_EQ((*complex->m_cfg)->a, 1) << "Second Initalizing constructor overrode the first!"; + ASSERT_EQ((*complex->m_cfg)->b, 2) << "Second Initalizing constructor overrode the first!"; + ASSERT_EQ((*complex->m_cfg)->c, 3) << "Second Initalizing constructor overrode the first!"; + + ASSERT_EQ((*complex->m_cfg2)->a, 10) << "Initializing constructor was not called"; + ASSERT_EQ((*complex->m_cfg2)->b, 15) << "Initializing constructor was not called"; + ASSERT_EQ((*complex->m_cfg2)->c, 30) << "Initializing constructor was not called"; } diff --git a/src/autowiring/test/AutoFilterAltitudeTest.cpp b/src/autowiring/test/AutoFilterAltitudeTest.cpp new file mode 100644 index 000000000..255ac0a9f --- /dev/null +++ b/src/autowiring/test/AutoFilterAltitudeTest.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include + +class AutoFilterAltitudeTest: + public testing::Test +{}; + +struct AltitudeValue {}; + +struct AltitudeMonotonicCounter { + std::atomic order{0}; +}; + +template +struct HasProfilingAltitude { + static const autowiring::altitude altitude = A; + + AutoRequired ctr; + int order = -1; + + void AutoFilter(const AltitudeValue& val) { + order = ++ctr->order; + } +}; + +TEST_F(AutoFilterAltitudeTest, AltitudeDetection) { + AutoFilterDescriptor desc(std::make_shared>()); + ASSERT_EQ(autowiring::altitude::Highest, desc.GetAltitude()) << "Filter altitude was not correctly inferred"; +} + +TEST_F(AutoFilterAltitudeTest, StandardAltitudeArrangement) { + AutoCurrentContext()->Initiate(); + + AutoRequired> alt3; + AutoRequired> alt1; + AutoRequired> alt2; + AutoRequired> alt4; + AutoRequired> alt5; + AutoRequired> alt0; + + AutoRequired factory; + auto packet = factory->NewPacket(); + packet->Decorate(AltitudeValue{}); + + // Now we verify things got invoked in the right order: + ASSERT_EQ(1, alt0->order); + ASSERT_EQ(2, alt1->order); + ASSERT_EQ(3, alt2->order); + ASSERT_EQ(4, alt3->order); + ASSERT_EQ(5, alt4->order); + ASSERT_EQ(6, alt5->order); +} + +TEST_F(AutoFilterAltitudeTest, LambdaAltitudesOnFactory) { + AutoCurrentContext()->Initiate(); + AutoRequired factory; + + int seq = 0; + int ctr[9]; + + for (size_t i = 0; i < 9; i++) { + ctr[i] = -1; + *factory += (autowiring::altitude)i, [&, i] { + ctr[i] = ++seq; + }; + } + + // Generate a packet and trip the assignments: + auto packet = factory->NewPacket(); + ASSERT_EQ(9, ctr[0]); + ASSERT_EQ(8, ctr[1]); + ASSERT_EQ(7, ctr[2]); + ASSERT_EQ(6, ctr[3]); + ASSERT_EQ(5, ctr[4]); + ASSERT_EQ(4, ctr[5]); + ASSERT_EQ(3, ctr[6]); + ASSERT_EQ(2, ctr[7]); + ASSERT_EQ(1, ctr[8]); +} diff --git a/src/autowiring/test/AutoFilterFunctionTest.cpp b/src/autowiring/test/AutoFilterFunctionTest.cpp index dfd033fc5..fdf84cbbd 100644 --- a/src/autowiring/test/AutoFilterFunctionTest.cpp +++ b/src/autowiring/test/AutoFilterFunctionTest.cpp @@ -125,7 +125,6 @@ TEST_F(AutoFilterFunctionalTest, ObservingFunctionTest) { ASSERT_TRUE(*called) << "Receive-only filter attached to a const packet image was not correctly called"; } - TEST_F(AutoFilterFunctionalTest, TripleFunctionTest) { AutoRequired factory; @@ -151,3 +150,22 @@ TEST_F(AutoFilterFunctionalTest, TripleFunctionTest) { ASSERT_TRUE(*called1) << "Final observer method was not invoked as expected"; ASSERT_TRUE(*called2) << "Two-argument observer method not invoked as expected"; } + +TEST_F(AutoFilterFunctionalTest, MultiPostHocIntroductionTest) { + AutoCurrentContext()->Initiate(); + AutoRequired factory; + + int called = 0; + + *factory += [&called](int& out) { out = called++; }; + *factory += [&called](int& out) { out = called++; }; + + // Add a gather step on the packet: + auto packet = factory->NewPacket(); + *packet += [&called](const int* vals []){ + ASSERT_NE(nullptr, vals); + called++; + }; + + ASSERT_EQ(3, called) << "Not all lambda functions were called as expected"; +} diff --git a/src/autowiring/test/AutoFilterMultiDecorateTest.cpp b/src/autowiring/test/AutoFilterMultiDecorateTest.cpp new file mode 100644 index 000000000..b26ff1d74 --- /dev/null +++ b/src/autowiring/test/AutoFilterMultiDecorateTest.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include +#include CHRONO_HEADER +#include THREAD_HEADER + +class AutoFilterMultiDecorateTest: + public testing::Test +{ +public: + AutoFilterMultiDecorateTest(void) { + AutoCurrentContext()->Initiate(); + } + + AutoRequired factory; +}; + +TEST_F(AutoFilterMultiDecorateTest, EnumerateDecorationsTest) { + auto sample = [](const int* vals []) {}; + AutoFilterDescriptor desc(sample); + + size_t i = 0; + for (auto* pCur = desc.GetAutoFilterInput(); *pCur; pCur++) + i++; + + ASSERT_EQ(1, i) << "AutoFilterDescriptor parsed an incorrect number of arguments from a lambda"; +} + +TEST_F(AutoFilterMultiDecorateTest, MultiDecorateTest) { + int called = 0; + + *factory += [&called](int& out) { out = called++; }; + *factory += [&called](int& out) { out = called++; }; + *factory += [&called](const int* vals []) { + ASSERT_NE(nullptr, vals); + called++; + + // Guarantee that values were added in the expected order + int i; + for (i = 0; vals[i]; i++) + ASSERT_EQ(i, *(vals[i])) << "Incorrect values were added to the packet"; + + // Verify we got the number of values out that we wanted to get out + ASSERT_EQ(2, i) << "The wrong number of values were added to the packet"; + }; + ASSERT_EQ(0, called) << "Lambda functions were called before expected"; + + auto packet = factory->NewPacket(); + ASSERT_EQ(3, called) << "Not all lambda functions were called as expected"; +} + +TEST_F(AutoFilterMultiDecorateTest, MultiPostHocIntroductionTest) { + int called = 0; + + *factory += [&called](int& out) { out = called++; }; + *factory += [&called](int& out) { out = called++; }; + + // Add a gather step on the packet: + auto packet = factory->NewPacket(); + *packet += [&called](const int* vals []){ + ASSERT_NE(nullptr, vals); + called++; + }; + + ASSERT_EQ(3, called) << "Not all lambda functions were called as expected"; +} diff --git a/src/autowiring/test/AutoPacketFactoryTest.cpp b/src/autowiring/test/AutoPacketFactoryTest.cpp index aa63468cb..a6940c8ee 100644 --- a/src/autowiring/test/AutoPacketFactoryTest.cpp +++ b/src/autowiring/test/AutoPacketFactoryTest.cpp @@ -199,60 +199,15 @@ TEST_F(AutoPacketFactoryTest, AddSubscriberTest) { ASSERT_TRUE(second_called) << "Subscriber added with operator+= never called"; } -TEST_F(AutoPacketFactoryTest, EnumerateDecorationsTest) { - auto sample = [](const int* vals []) {}; - AutoFilterDescriptor desc(sample); - - size_t i = 0; - for (auto* pCur = desc.GetAutoFilterInput(); *pCur; pCur++) - i++; - - ASSERT_EQ(1, i) << "AutoFilterDescriptor parsed an incorrect number of arguments from a lambda"; -} - -TEST_F(AutoPacketFactoryTest, MultiDecorateTest) { - AutoCurrentContext ctxt; - AutoRequired factory(ctxt); - ctxt->Initiate(); - - int called = 0; - - *factory += [&called] (int& out) { out = called++; }; - *factory += [&called] (int& out) { out = called++; }; - *factory += [&called] (const int* vals[]) { - ASSERT_NE(nullptr, vals); - called++; - - // Guarantee that values were added in the expected order - int i; - for (i = 0; vals[i]; i++) - ASSERT_EQ(i, *(vals[i])) << "Incorrect values were added to the packet"; +TEST_F(AutoPacketFactoryTest, CanRemoveAddedLambda) { + AutoCurrentContext()->Initiate(); + AutoRequired factory; - // Verify we got the number of values out that we wanted to get out - ASSERT_EQ(2, i) << "The wrong number of values were added to the packet"; - }; - ASSERT_EQ(0, called) << "Lambda functions were called before expected"; + auto desc = *factory += [](int&){}; + auto packet1 = factory->NewPacket(); + *factory -= desc; + auto packet2 = factory->NewPacket(); - auto packet = factory->NewPacket(); - ASSERT_EQ(3, called) << "Not all lambda functions were called as expected"; + ASSERT_TRUE(packet1->Has()) << "First packet did not posess expected decoration"; + ASSERT_FALSE(packet2->Has()) << "Decoration present even after all filters were removed from a factory"; } - -TEST_F(AutoPacketFactoryTest, MultiPostHocIntroductionTest) { - AutoCurrentContext ctxt; - ctxt->Initiate(); - AutoRequired factory(ctxt); - - int called = 0; - - *factory += [&called](int& out) { out = called++; }; - *factory += [&called](int& out) { out = called++; }; - - // Add a gather step on the packet: - auto packet = factory->NewPacket(); - *packet += [&called](const int* vals []){ - ASSERT_NE(nullptr, vals); - called++; - }; - - ASSERT_EQ(3, called) << "Not all lambda functions were called as expected"; -} \ No newline at end of file diff --git a/src/autowiring/test/AutoParameterTest.cpp b/src/autowiring/test/AutoParameterTest.cpp index a06122ebc..432400ee7 100644 --- a/src/autowiring/test/AutoParameterTest.cpp +++ b/src/autowiring/test/AutoParameterTest.cpp @@ -7,7 +7,7 @@ class AutoParameterTest: public testing::Test {}; - +/* struct MyParamClass1 { struct MyIntParam1 { static int Default() { return 15; } @@ -114,7 +114,7 @@ struct MyParamClass4 { }; TEST_F(AutoParameterTest, VerifyInvalidPreconfiguredValue) { - AutoRequired acm; + AutoRequired acm; ASSERT_ANY_THROW(acm->Set("AutoParam.MyParamClass4::MyIntParam4", 0)); AutoRequired my4; @@ -156,3 +156,4 @@ TEST_F(AutoParameterTest, VerifyDefaultMinMaxKey) { ASSERT_TRUE(param.Set(10) && *param == 10) << "Should be able to set values that are valid according to the validation function"; } +*/ \ No newline at end of file diff --git a/src/autowiring/test/AutoSignalTest.cpp b/src/autowiring/test/AutoSignalTest.cpp index 7cba85b91..bc3f9b516 100644 --- a/src/autowiring/test/AutoSignalTest.cpp +++ b/src/autowiring/test/AutoSignalTest.cpp @@ -68,6 +68,65 @@ TEST_F(AutoSignalTest, SignalWithAutowiring) { ASSERT_FALSE(handler_called) << "A handler was unexpectedly called after it should have been destroyed"; } +struct RaisesASignalDerived : public RaisesASignal { + +}; + +struct ContainsRaises { + ContainsRaises() : count(0) { + ras(&RaisesASignal::signal) += [this](int v) { + count++; + }; + } + + ~ContainsRaises() { + static int i = 0; + i++; + } + Autowired ras; + int count; +}; + +TEST_F(AutoSignalTest, ConstructorAutowiredRegistration) { + bool handler_called = false; + int val = 202; + + { + AutoCurrentContext rootContext; + auto ctxt = rootContext->Create(); + CurrentContextPusher pshr(ctxt); + (void)pshr; + { + AutoRequired cRas; + + //Autowired ras; + Autowired rasDerived; + + // Register a signal handler: + rasDerived(&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: + rasDerived->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; @@ -122,3 +181,124 @@ TEST_F(AutoSignalTest, RaiseASignalWithinASlotTest) { 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"; } + +TEST_F(AutoSignalTest, NodeRemoval) { + autowiring::signal signal1; + autowiring::signal signal2; + + bool handler_called1 = false; + bool handler_called2 = false; + + auto* registration1 = signal1 += [&] { handler_called1 = true; }; + auto* registration2 = signal2 += [&] { handler_called2 = true; }; + + ASSERT_ANY_THROW(signal1 -= registration2) << "Removing a registration from a different signal than it was registered to failed to throw an exception"; + + registration1->remove(); + delete registration1; + signal1(); + signal2(); + + ASSERT_FALSE(handler_called1) << "Handler1 was called after being unregistered"; + ASSERT_TRUE(handler_called2) << "Handler2 was removed after an invalid -= operation"; +} + +TEST_F(AutoSignalTest, CallOrdering) { + autowiring::signal signal1; + + bool handler_called1 = false; + bool handler_called2 = false; + + //handlers are inserted at the begining, so this will be called last. + signal1 += [&] { + ASSERT_TRUE(handler_called2); + handler_called1 = true; + }; + signal1 += [&] { handler_called2 = true; }; + + signal1(); + + ASSERT_TRUE(handler_called1) << "Handler1 was called after being unregistered"; + ASSERT_TRUE(handler_called2) << "Handler2 was removed after an invalid -= operation"; +} + +TEST_F(AutoSignalTest, CallInsertion) { + autowiring::signal signal1; + + bool handler_called1 = false; + bool handler_called2 = false; + + auto* registration1 = signal1 += [&](void) { handler_called1 = true; }; + + //when += to a registration object, the new one is appended. + *registration1 += [&](void) { + ASSERT_TRUE(handler_called1); + handler_called2 = true; }; + + signal1(); + + ASSERT_TRUE(handler_called1) << "Handler1 was called after being unregistered"; + ASSERT_TRUE(handler_called2) << "Handler2 was removed after an invalid -= operation"; +} + +TEST_F(AutoSignalTest, SelfReferencingCall) { + typedef autowiring::signal signal_t; + signal_t signal1; + + bool handler_called1 = false; + int magic_number = 123; + + //The main test is just if this thing will compile + signal_t::registration_t* registration1 = + signal1 += [&](autowiring::internal::signal_node_base* reg, int magic) { + ASSERT_EQ(magic, magic_number); + ASSERT_EQ(registration1, reg); + handler_called1 = true; + }; + + signal1(magic_number); + + ASSERT_TRUE(handler_called1) << "Handler was not called!"; +} + +TEST_F(AutoSignalTest, SelfModifyingCall) { + typedef autowiring::signal signal_t; + signal_t signal1; + + int handler_called1 = 0; + int handler_called2 = 0; + int handler_called3 = 0; + + int magic_number = 123; + + signal_t::registration_t* registration1 = + signal1 += [&](autowiring::internal::signal_node_base* reg, int magic) { + ASSERT_EQ(magic, magic_number); + ASSERT_EQ(registration1, reg); + ++handler_called1; + delete reg->remove(); + }; + + auto lambda3 = [&](int magic) { + ++handler_called3; + }; + + signal_t::registration_t* registration2 = signal1 += [&](signal_t::registration_t* reg, int magic) { + ASSERT_EQ(magic, magic_number); + ASSERT_EQ(registration2, reg); + ++handler_called2; + + //+= is an append operation, but because when we're traveling the list and we grab the next pointer + //*before* the function get's called, this append won't be picked up until the 2nd pass. + *reg += std::move(lambda3); + + delete reg->remove(); + }; + + signal1(magic_number); + signal1(magic_number); + + ASSERT_EQ(handler_called1, 1) << "Handler was unable to remove itself!"; + ASSERT_EQ(handler_called2, 1) << "Specific handler was unable to remove itself"; + ASSERT_EQ(handler_called3, 1) << "Handler was unable to append to itself or was called prematurely."; +} \ No newline at end of file diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index 56ef33af5..a3a970941 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -2,10 +2,14 @@ set(AutowiringTest_SRCS AnySharedPointerTest.cpp ArgumentTypeTest.cpp AutoConfigTest.cpp + AutoConfigListingTest.cpp + AutoConfigParserTest.cpp AutoConstructTest.cpp + AutoFilterAltitudeTest.cpp AutoFilterCollapseRulesTest.cpp AutoFilterDiagnosticsTest.cpp AutoFilterFunctionTest.cpp + AutoFilterMultiDecorateTest.cpp AutoFilterSequencing.cpp AutoFilterTest.cpp AutoInjectableTest.cpp diff --git a/src/autowiring/test/ContextEnumeratorTest.cpp b/src/autowiring/test/ContextEnumeratorTest.cpp index 9cc7b5feb..12441e72e 100644 --- a/src/autowiring/test/ContextEnumeratorTest.cpp +++ b/src/autowiring/test/ContextEnumeratorTest.cpp @@ -3,6 +3,7 @@ #include #include #include MEMORY_HEADER +#include STL_UNORDERED_SET class ContextEnumeratorTest: public testing::Test diff --git a/src/autowiring/test/ContextMapTest.cpp b/src/autowiring/test/ContextMapTest.cpp index 268587e93..d8beb7f73 100644 --- a/src/autowiring/test/ContextMapTest.cpp +++ b/src/autowiring/test/ContextMapTest.cpp @@ -251,6 +251,7 @@ TEST_F(ContextMapTest, VerifyRangeBasedEnumeration) { // Internal map in ContextMap ensures entries are enumerated in-order for (auto& cur : mp) { + (void)cur; switch (ct) { case 0: // Release the entry we are presently enumerating diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index fada6dfec..287ae18e7 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -434,3 +434,34 @@ TEST_F(CoreContextTest, AppropriateShutdownInterleave) { ASSERT_TRUE(ctxtOuter->Wait(std::chrono::seconds(5))) << "Outer context did not tear down in a timely fashion"; ASSERT_TRUE(ctxtInner->Wait(std::chrono::seconds(5))) << "Inner context did not tear down in a timely fashion"; } + +class MyClassForAllBase {}; + +template +class MyClassForAll: + public MyClassForAllBase +{ +}; + +TEST_F(CoreContextTest, All) { + AutoCurrentContext ctxt; + ctxt->Initiate(); + + AutoRequired> c1; + AutoRequired> c2; + + // Sanity check first: + auto& allMembers = ctxt->All(); + ASSERT_LE(2UL, allMembers.size()) << "All members not correctly identified by call to all"; + + // Check all instances found + bool found1 = false; + bool found2 = false; + for (auto& cur : allMembers) { + found1 |= &cur == c1.get(); + found2 |= &cur == c2.get(); + } + + ASSERT_TRUE(found1) << "Failed to find MyClassForAll<1> via its ContextMember interface"; + ASSERT_TRUE(found2) << "Failed to find MyClassForAll<2> via its ContextMember interface"; +} diff --git a/src/autowiring/test/DispatchQueueTest.cpp b/src/autowiring/test/DispatchQueueTest.cpp index d2fe730be..18157b0a5 100644 --- a/src/autowiring/test/DispatchQueueTest.cpp +++ b/src/autowiring/test/DispatchQueueTest.cpp @@ -2,6 +2,8 @@ #include "stdafx.h" #include #include +#include +#include FUTURE_HEADER using namespace std; @@ -67,4 +69,65 @@ TEST_F(DispatchQueueTest, PathologicalStartAndStop){ ASSERT_TRUE(t4->WaitFor(std::chrono::seconds(10))); } +TEST_F(DispatchQueueTest, Barrier) { + AutoCurrentContext()->Initiate(); + AutoRequired ct; + // Barrier on an empty dispatch queue should return right away: + ASSERT_TRUE(ct->Barrier(std::chrono::seconds(5))) << "Barrier on an empty dispatch queue did not return immediately as expected"; + + // Attach a lambda that will wait until the promise is complete: + std::promise barrier; + *ct += [&] { + auto f = barrier.get_future(); + f.wait(); + }; + ASSERT_FALSE(ct->Barrier(std::chrono::milliseconds(1))) << "Barrier returned even though a dispatcher was not complete"; + barrier.set_value(true); + + // Now we should be able to complete: + ASSERT_TRUE(ct->Barrier(std::chrono::seconds(5))) << "Barrier did not return even though a dispatcher should have completed"; +} + +struct BarrierMonitor { + // Standard continuation behavior: + std::mutex lock; + std::condition_variable cv; + bool done; +}; + +TEST_F(DispatchQueueTest, BarrierWithAbort) { + AutoCurrentContext()->Initiate(); + AutoRequired ct; + + // Hold our initial lockdown: + auto b = std::make_shared(); + std::unique_lock lk(b->lock); + + // This dispatch entry will delay until we're ready for it to continue: + *ct += [b] { + std::lock_guard lk(b->lock); + }; + + // Launch something that will barrier: + auto exception = std::make_shared(false); + auto f = std::async( + std::launch::async, + [=] { + try { + ct->Barrier(std::chrono::seconds(5)); + } + catch (autowiring_error&) { + *exception = true; + } + } + ); + + // Delay for long enough for the barrier to be reached: + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + // Now abandon the queue, this should cause the async thread to quit: + ct->Abort(); + ASSERT_EQ(std::future_status::ready, f.wait_for(std::chrono::seconds(5))) << "Barrier did not abort fast enough"; + ASSERT_TRUE(*exception) << "Exception should have been thrown inside the Barrier call"; +} diff --git a/src/autowiring/test/ExceptionFilterTest.cpp b/src/autowiring/test/ExceptionFilterTest.cpp index 782e56541..1e36db4de 100644 --- a/src/autowiring/test/ExceptionFilterTest.cpp +++ b/src/autowiring/test/ExceptionFilterTest.cpp @@ -237,7 +237,3 @@ TEST_F(ExceptionFilterTest, VerifySimpleConfinement) { // Verify that the child scope was terminated as expected: EXPECT_TRUE(child->IsShutdown()) << "An event recipient in a child scope threw an exception and the child context was not correctly terminated"; } - -TEST_F(ExceptionFilterTest, NoRecursiveShutdowns) { - -} diff --git a/src/autowiring/test/SnoopTest.cpp b/src/autowiring/test/SnoopTest.cpp index 473019e97..1c995d685 100644 --- a/src/autowiring/test/SnoopTest.cpp +++ b/src/autowiring/test/SnoopTest.cpp @@ -59,7 +59,7 @@ class RemovesSelf: virtual void ZeroArgs(void){ counter++; AutoCurrentContext ctxt; - ctxt->Unsnoop(GetSelf()); + ctxt->RemoveSnooper(GetSelf()); } public: RemovesSelf(): @@ -83,7 +83,7 @@ TEST_F(SnoopTest, VerifySimpleSnoop) { AutoRequired childMember; // Snoop - child->Snoop(parentMember); + child->AddSnooper(parentMember); // Now fire an event from the child: AutoFired firer; @@ -111,8 +111,8 @@ TEST_F(SnoopTest, VerifyUnsnoop) { AutoRequired childMember; // Snoop, unsnoop: - snoopy->Snoop(parentMember); - snoopy->Unsnoop(parentMember); + snoopy->AddSnooper(parentMember); + snoopy->RemoveSnooper(parentMember); snoopy->Initiate(); // Fire one event: @@ -136,7 +136,7 @@ TEST_F(SnoopTest, AmbiguousReciept) { { AutoCreateContext subCtxt; - subCtxt->Snoop(parent); + subCtxt->AddSnooper(parent); subCtxt->Initiate(); // Verify that simple firing _here_ causes transmission as expected: @@ -178,7 +178,7 @@ TEST_F(SnoopTest, AvoidDoubleReciept) { childMember = child->Inject(); // Snoop - child->Snoop(parentMember); + child->AddSnooper(parentMember); } // Now fire an event from the parent: @@ -193,7 +193,7 @@ TEST_F(SnoopTest, AvoidDoubleReciept) { // Test sibling context AutoRequired sibMember(sibCtxt); AutoRequired alsoInParent; - child->Snoop(sibMember); + child->AddSnooper(sibMember); firer(&UpBroadcastListener::SimpleCall)(); @@ -203,7 +203,7 @@ TEST_F(SnoopTest, AvoidDoubleReciept) { ASSERT_EQ(1, sibMember->m_callCount) << "Sibling context member didn't receive message"; // Make sure unsnoop cleans up everything - child->Unsnoop(sibMember); + child->RemoveSnooper(sibMember); firer(&UpBroadcastListener::SimpleCall)(); ASSERT_EQ(3, childMember->m_callCount) << "Message not received by another member of the same context"; @@ -236,8 +236,8 @@ TEST_F(SnoopTest, MultiSnoop) { member->m_callCount = 0; // Snoop both. Invocation in an uninitialized context should not cause any handlers to be raised. - ctxt1->Snoop(member); - ctxt2->Snoop(member); + ctxt1->AddSnooper(member); + ctxt2->AddSnooper(member); base->Invoke(&UpBroadcastListener::SimpleCall)(); ctxt1->Invoke(&UpBroadcastListener::SimpleCall)(); ctxt2->Invoke(&UpBroadcastListener::SimpleCall)(); @@ -248,7 +248,7 @@ TEST_F(SnoopTest, MultiSnoop) { member2->m_callCount = 0; // Unsnoop one - ctxt2->Unsnoop(member); + ctxt2->RemoveSnooper(member); base->Invoke(&UpBroadcastListener::SimpleCall)(); ctxt1->Invoke(&UpBroadcastListener::SimpleCall)(); ctxt2->Invoke(&UpBroadcastListener::SimpleCall)(); @@ -265,7 +265,7 @@ TEST_F(SnoopTest, AntiCyclicRemoval) { CurrentContextPusher pshr(snoopy); snoopy->Initiate(); - snoopy->Snoop(removeself); + snoopy->AddSnooper(removeself); ASSERT_EQ(0, removeself->counter); @@ -289,7 +289,7 @@ TEST_F(SnoopTest, SimplePackets) { // Add filter to tracking AutoRequired filter(Tracking); AutoRequired detachedFilter(Tracking); - Pipeline->Snoop(filter); + Pipeline->AddSnooper(filter); ASSERT_FALSE(!!filter->m_called) << "Filter called prematurely"; ASSERT_FALSE(detachedFilter->m_called) << "Filter called prematurely"; @@ -308,7 +308,7 @@ TEST_F(SnoopTest, SimplePackets) { //reset filter->m_called = false; - Pipeline->Unsnoop(filter); + Pipeline->RemoveSnooper(filter); auto packet2 = factory->NewPacket(); packet2->Decorate(Decoration<0>()); packet2->Decorate(Decoration<1>()); @@ -321,7 +321,7 @@ TEST_F(SnoopTest, CanSnoopAutowired) { // Now autowire what we injected and verify we can snoop this directly Autowired so; - ctxt->Snoop(so); + ctxt->AddSnooper(so); } TEST_F(SnoopTest, RuntimeSnoopCall) { @@ -332,7 +332,7 @@ TEST_F(SnoopTest, RuntimeSnoopCall) { CoreObjectDescriptor traits(x); // Try to snoop and verify that the snooped member gets an event: - ctxt->Snoop(x); + ctxt->AddSnooper(x); AutoFired ubl; ubl(&UpBroadcastListener::SimpleCall)(); @@ -341,7 +341,7 @@ TEST_F(SnoopTest, RuntimeSnoopCall) { ASSERT_EQ(1UL, x->m_callCount) << "Call count to a child member was incorrect"; // And the alternative variant next: - ctxt->Unsnoop(x); + ctxt->RemoveSnooper(x); ubl(&UpBroadcastListener::SimpleCall)(); ASSERT_EQ(1UL, x->m_callCount) << "Snoop method was invoked after the related type was removed"; } \ No newline at end of file diff --git a/version.cmake b/version.cmake index fbbe55b7b..2084c19e0 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.5.1) +set(autowiring_VERSION 0.5.2)