From 4cd5ee94b970e57e14c1997271d6d3a5b904ee6a Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 14 Jul 2015 17:44:22 -0700 Subject: [PATCH 01/60] Bump autowiring version number --- Doxyfile | 2 +- publicDoxyfile.conf | 2 +- version.cmake | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doxyfile b/Doxyfile index 22a1d3798..7100161b3 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.7.1" +PROJECT_NUMBER = "0.7.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/publicDoxyfile.conf b/publicDoxyfile.conf index 7b5ac877a..418fbbde0 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.7.1 +PROJECT_NUMBER = 0.7.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/version.cmake b/version.cmake index 8c8595b69..b5367e0b9 100644 --- a/version.cmake +++ b/version.cmake @@ -1 +1 @@ -set(autowiring_VERSION 0.7.1) +set(autowiring_VERSION 0.7.2) From 9f94af5203a098ab4bdb56cad37d2a93f5c86ef0 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 15 Jul 2015 20:39:08 -0700 Subject: [PATCH 02/60] Minor optimization for zero-duration delayed dispatchers If a delayed lambda is pended to a `DispatchQueue` with a delay duration of zero, then just pend this to the ready queue directly, completely bypassing the waiting queue. Also, change the time offset calculation for relatively delayed lambdas to be performed after the lambda is constructed and about to be pended to the queue. --- autowiring/DispatchQueue.h | 43 +++++++++++++++++++---- src/autowiring/DispatchQueue.cpp | 4 +-- src/autowiring/test/DispatchQueueTest.cpp | 16 +++++++++ 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/autowiring/DispatchQueue.h b/autowiring/DispatchQueue.h index e3a4d6abd..aa5338300 100644 --- a/autowiring/DispatchQueue.h +++ b/autowiring/DispatchQueue.h @@ -236,16 +236,41 @@ class DispatchQueue { /// std::chrono::steady_clock::time_point SuggestSoonestWakeupTimeUnsafe(std::chrono::steady_clock::time_point latestTime) const; - class DispatchThunkDelayedExpression { + class DispatchThunkDelayedExpressionRel { public: - DispatchThunkDelayedExpression(DispatchQueue* pParent, std::chrono::steady_clock::time_point wakeup) : + DispatchThunkDelayedExpressionRel(DispatchQueue* pParent, std::chrono::microseconds delay) : + m_pParent(pParent), + m_delay(delay) + {} + + private: + DispatchQueue* const m_pParent; + const std::chrono::microseconds m_delay; + + public: + template + void operator,(_Fx&& fx) { + // Let the parent handle this one directly after composing a delayed dispatch thunk r-value + if (m_delay.count()) + *m_pParent += DispatchThunkDelayed( + std::chrono::steady_clock::now() + m_delay, + new DispatchThunk<_Fx>(std::forward<_Fx&&>(fx)) + ); + else + *m_pParent += std::forward<_Fx&&>(fx); + } + }; + + class DispatchThunkDelayedExpressionAbs { + public: + DispatchThunkDelayedExpressionAbs(DispatchQueue* pParent, std::chrono::steady_clock::time_point wakeup) : m_pParent(pParent), m_wakeup(wakeup) {} private: - DispatchQueue* m_pParent; - std::chrono::steady_clock::time_point m_wakeup; + DispatchQueue* const m_pParent; + const std::chrono::steady_clock::time_point m_wakeup; public: template @@ -266,8 +291,12 @@ class DispatchQueue { /// /// Overload for the introduction of a delayed dispatch thunk /// + /// + /// If the passed duration is equal to zero, the returned expression template will pend a lambda + /// to the dispatch queue as though that lambda were added with operator+= without any delay. + /// template - DispatchThunkDelayedExpression operator+=(std::chrono::duration rhs) { + DispatchThunkDelayedExpressionRel operator+=(std::chrono::duration rhs) { // Verify that the duration is at least microseconds. If you're getting an assertion here, try // using std::duration_cast(duration) static_assert( @@ -276,13 +305,13 @@ class DispatchQueue { ); std::chrono::steady_clock::time_point timepoint = std::chrono::steady_clock::now() + rhs; - return *this += timepoint; + return{this, rhs}; } /// /// Overload for absolute-time based delayed dispatch thunk /// - DispatchThunkDelayedExpression operator+=(std::chrono::steady_clock::time_point rhs); + DispatchThunkDelayedExpressionAbs operator+=(std::chrono::steady_clock::time_point rhs); /// /// Directly pends a delayed dispatch thunk diff --git a/src/autowiring/DispatchQueue.cpp b/src/autowiring/DispatchQueue.cpp index 462e0c388..86dad2f93 100644 --- a/src/autowiring/DispatchQueue.cpp +++ b/src/autowiring/DispatchQueue.cpp @@ -312,8 +312,8 @@ void DispatchQueue::operator+=(DispatchQueue&& rhs) { OnPended(std::move(lk)); } -DispatchQueue::DispatchThunkDelayedExpression DispatchQueue::operator+=(std::chrono::steady_clock::time_point rhs) { - return DispatchThunkDelayedExpression(this, rhs); +DispatchQueue::DispatchThunkDelayedExpressionAbs DispatchQueue::operator+=(std::chrono::steady_clock::time_point rhs) { + return{this, rhs}; } void DispatchQueue::operator+=(DispatchThunkDelayed&& rhs) { diff --git a/src/autowiring/test/DispatchQueueTest.cpp b/src/autowiring/test/DispatchQueueTest.cpp index 04fc9ab1b..cca3ce6a0 100644 --- a/src/autowiring/test/DispatchQueueTest.cpp +++ b/src/autowiring/test/DispatchQueueTest.cpp @@ -207,3 +207,19 @@ TEST_F(DispatchQueueTest, MoveConstructor) { } ASSERT_EQ(counter, 3) << "Lambdas not trasfered by move constructor"; } + +TEST_F(DispatchQueueTest, ZeroIdenticalToPend) { + DispatchQueue dq; + + int reference = 101; + int observation = 1; + dq += std::chrono::seconds(0), [&] { observation = reference; }; + dq += [&] { reference = 102; }; + ASSERT_EQ(2UL, dq.GetDispatchQueueLength()) << "Two dispatchers were added but two were not detected on the queue"; + + // Verify that the lambdas were executed in order: + dq.DispatchAllEvents(); + ASSERT_EQ(0UL, dq.GetDispatchQueueLength()) << "Not all dispatchers were executed even though all dispatchers should have been ready"; + ASSERT_NE(1, observation) << "Zero-delay lambda was not executed"; + ASSERT_EQ(101, observation) << "Zero-delay lambda did not run in the order it was pended"; +} From 92f47bb70e4846c12737e09febd6d473b737be77 Mon Sep 17 00:00:00 2001 From: Walter Gray Date: Wed, 15 Jul 2015 21:33:50 -0700 Subject: [PATCH 03/60] added missing C++11 header --- autowiring/TeardownNotifier.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autowiring/TeardownNotifier.h b/autowiring/TeardownNotifier.h index 6abba7644..352c89823 100644 --- a/autowiring/TeardownNotifier.h +++ b/autowiring/TeardownNotifier.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "C++11/cpp11.h" #include RVALUE_HEADER /// @@ -28,7 +29,7 @@ class TeardownNotifier Entry(Fx&& fx): fx(std::forward(fx)) {} - + Fx fx; void operator()(void) override { fx(); } From a0d07b089eb8b4c2dbdf12a300d961c8fd198295 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 21 Jul 2015 12:37:26 -0700 Subject: [PATCH 04/60] Restore Windows XP build compatibility `GetThreadId` can be replaced with `GetCurrentThreadId` in the only place where it's called. --- src/autowiring/CoreThreadWin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autowiring/CoreThreadWin.cpp b/src/autowiring/CoreThreadWin.cpp index 4563a469b..616aff6ff 100644 --- a/src/autowiring/CoreThreadWin.cpp +++ b/src/autowiring/CoreThreadWin.cpp @@ -42,7 +42,7 @@ void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) } void BasicThread::SetCurrentThreadName(void) const { - DWORD threadId = ::GetThreadId(m_state->m_thisThread.native_handle()); + DWORD threadId = ::GetCurrentThreadId(); ::SetThreadName(threadId, m_name); } From 5026a3792d1ddca42d6f98bca9cf64ee904bfd60 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 22 Jul 2015 02:32:35 -0700 Subject: [PATCH 05/60] Add a unit test expressly for AutoFilter descriptors Some of the planned shared pointer slot refactoring is breaking `AutoFilterDescriptor` behaviors. Because this type is fairly low-level, it makes sense to separate out tests dealing with this type into their own test case in order to speed debugging. --- .../test/AutoFilterDescriptorTest.cpp | 37 +++++++++++++++++++ src/autowiring/test/AutoPacketFactoryTest.cpp | 9 ----- src/autowiring/test/CMakeLists.txt | 1 + 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 src/autowiring/test/AutoFilterDescriptorTest.cpp diff --git a/src/autowiring/test/AutoFilterDescriptorTest.cpp b/src/autowiring/test/AutoFilterDescriptorTest.cpp new file mode 100644 index 000000000..4991b95ce --- /dev/null +++ b/src/autowiring/test/AutoFilterDescriptorTest.cpp @@ -0,0 +1,37 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "TestFixtures/Decoration.hpp" +#include + +class AutoFilterDescriptorTest: + public testing::Test +{}; + +TEST_F(AutoFilterDescriptorTest, DescriptorNonEquivalence) { + AutoFilterDescriptor descs[2]; + for (size_t i = 0; i < 2; i++) + descs[i] = AutoFilterDescriptor([i](int) {}); + ASSERT_NE(descs[0], descs[1]) << "Descriptors evaluated to equivalence even though they were constructed distinctly"; + ASSERT_NE(descs[0].GetAutoFilter(), descs[1].GetAutoFilter()) << "Shared pointers to underlying autofilters were equal when they should not have been"; + ASSERT_NE(descs[0] < descs[1], descs[1] < descs[0]) << "Two inequal descriptors violated disjunctive syllogism"; +} + +TEST_F(AutoFilterDescriptorTest, ArityCheck) { + AutoFilterDescriptor desc([](int, Decoration<0>&, const Decoration<1>&) {}); + ASSERT_EQ(3UL, desc.GetArity()) << "Descriptor did not extract the correct argument count"; + + size_t nIn = 0; + size_t nOut = 0; + size_t nTotal = 0; + for (auto* pCur = desc.GetAutoFilterArguments(); *pCur; pCur++) { + nTotal++; + if (pCur->is_input) + nIn++; + if (pCur->is_output) + nOut++; + } + + ASSERT_EQ(3UL, nTotal) << "AutoFilter argument array didn't appear to contain the correct number of arguments"; + ASSERT_EQ(2UL, nIn) << "Input argument count mismatch"; + ASSERT_EQ(1UL, nOut) << "Output argument count mismatch"; +} diff --git a/src/autowiring/test/AutoPacketFactoryTest.cpp b/src/autowiring/test/AutoPacketFactoryTest.cpp index 2746c126e..96ecb3c53 100644 --- a/src/autowiring/test/AutoPacketFactoryTest.cpp +++ b/src/autowiring/test/AutoPacketFactoryTest.cpp @@ -169,15 +169,6 @@ TEST_F(AutoPacketFactoryTest, AutoPacketStatistics) { ASSERT_LE(packetDelay, factory->GetMeanPacketLifetime()) << "The mean packet lifetime was less than the delay on each packet"; } -TEST_F(AutoPacketFactoryTest, DescriptorNonEquivalence) { - AutoFilterDescriptor descs[2]; - for (size_t i = 0; i < 2; i++) - descs[i] = AutoFilterDescriptor([i] (int) { }); - ASSERT_NE(descs[0], descs[1]) << "Descriptors evaluated to equivalence even though they were constructed distinctly"; - ASSERT_NE(descs[0].GetAutoFilter(), descs[1].GetAutoFilter()) << "Shared pointers to underlying autofilters were equal when they should not have been"; - ASSERT_NE(descs[0] < descs[1], descs[1] < descs[0]) << "Two inequal descriptors violated disjunctive syllogism"; -} - TEST_F(AutoPacketFactoryTest, MultipleInstanceAddition) { AutoCurrentContext ctxt; AutoRequired factory; diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index a17a58ca8..b803868ca 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -8,6 +8,7 @@ set(AutowiringTest_SRCS AutoFilterAltitudeTest.cpp AutoFilterCollapseRulesTest.cpp AutoFilterConstructRulesTest.cpp + AutoFilterDescriptorTest.cpp AutoFilterDiagnosticsTest.cpp AutoFilterFunctionTest.cpp AutoFilterMultiDecorateTest.cpp From 43df88d96951eeae5c97d8d3c5535a3bcae6063a Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 22 Jul 2015 10:24:18 -0700 Subject: [PATCH 06/60] Fix deprecated method calls in unit tests and internal calls `AutoPacket::Unsatisfiable` has been marked as deprecated since 0.7.1, failing to refactor Autowiring to use the new method name was an oversight. --- autowiring/auto_out.h | 2 +- src/autowiring/test/AutoFilterMultiDecorateTest.cpp | 2 +- src/autowiring/test/AutoFilterTest.cpp | 4 ++-- src/autowiring/test/SatisfiabilityTest.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/autowiring/auto_out.h b/autowiring/auto_out.h index 64bce627d..02801cf5e 100644 --- a/autowiring/auto_out.h +++ b/autowiring/auto_out.h @@ -66,7 +66,7 @@ class auto_out if (m_decoration) m_packet->Decorate(std::move(m_decoration)); else - m_packet->Unsatisfiable(); + m_packet->MarkUnsatisfiable(); } private: diff --git a/src/autowiring/test/AutoFilterMultiDecorateTest.cpp b/src/autowiring/test/AutoFilterMultiDecorateTest.cpp index 459519d42..c4e85ea2b 100644 --- a/src/autowiring/test/AutoFilterMultiDecorateTest.cpp +++ b/src/autowiring/test/AutoFilterMultiDecorateTest.cpp @@ -90,7 +90,7 @@ TEST_F(AutoFilterMultiDecorateTest, UnsatDecTest) { auto packet = f->NewPacket(); packet->Decorate(Decoration<0>{}); - packet->Unsatisfiable>(); + packet->MarkUnsatisfiable>(); packet->Decorate(Decoration<2>{}); auto strs = packet->GetAll(); diff --git a/src/autowiring/test/AutoFilterTest.cpp b/src/autowiring/test/AutoFilterTest.cpp index 74731dd23..6ff7997d5 100644 --- a/src/autowiring/test/AutoFilterTest.cpp +++ b/src/autowiring/test/AutoFilterTest.cpp @@ -227,7 +227,7 @@ TEST_F(AutoFilterTest, VerifyAntiDecorate) { { // Obtain a new packet and mark an unsatisfiable decoration: auto packet = factory->NewPacket(); - packet->Unsatisfiable>(); + packet->MarkUnsatisfiable>(); ASSERT_ANY_THROW(packet->Decorate(Decoration<0>())) << "Incorrectly allowed a decoration to be added to a packet when that decoration was unsatisfiable"; } @@ -235,7 +235,7 @@ TEST_F(AutoFilterTest, VerifyAntiDecorate) { // Obtain a new packet and try to make a satisfied decoration unsatisfiable. auto packet = factory->NewPacket(); packet->Decorate(Decoration<0>()); - ASSERT_NO_THROW(packet->Unsatisfiable>()) << "Failed to expunge a decoration from a packet"; + ASSERT_NO_THROW(packet->MarkUnsatisfiable>()) << "Failed to expunge a decoration from a packet"; } } diff --git a/src/autowiring/test/SatisfiabilityTest.cpp b/src/autowiring/test/SatisfiabilityTest.cpp index 931799734..d0104253a 100644 --- a/src/autowiring/test/SatisfiabilityTest.cpp +++ b/src/autowiring/test/SatisfiabilityTest.cpp @@ -25,7 +25,7 @@ TEST_F(SatisfiabilityTest, MarkUnsatisfiableCalls) { bool bSharedPtrCalled = false; *packet += [&bSharedPtrCalled](std::shared_ptr> dec) { bSharedPtrCalled = true; }; - packet->Unsatisfiable>(); + packet->MarkUnsatisfiable>(); ASSERT_FALSE(bRefCalled) << "Reference version should not have been called"; ASSERT_TRUE(bSharedPtrCalled) << "Shared pointer version should have been called as a result of Unsatisfiable"; From 3932e908fc326f3d0ea51608b7b31ab411af5729 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 09:12:34 -0700 Subject: [PATCH 07/60] Make SystemThreadPoolWin.h private This header shouldn't be used externally to Autowiring anyway, it's internal. --- src/autowiring/CMakeLists.txt | 2 +- src/autowiring/SystemThreadPoolWin.cpp | 2 +- .../autowiring/SystemThreadPoolWin.hpp | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename autowiring/SystemThreadPoolWin.h => src/autowiring/SystemThreadPoolWin.hpp (100%) diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index bba985fa1..1ee782344 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -194,7 +194,7 @@ add_windows_sources(Autowiring_SRCS auto_future_win.h CoreThreadWin.cpp SystemThreadPoolWin.cpp - SystemThreadPoolWin.h + SystemThreadPoolWin.hpp InterlockedExchangeWin.cpp thread_specific_ptr_win.h ) diff --git a/src/autowiring/SystemThreadPoolWin.cpp b/src/autowiring/SystemThreadPoolWin.cpp index e9ef6dac3..a58aede68 100644 --- a/src/autowiring/SystemThreadPoolWin.cpp +++ b/src/autowiring/SystemThreadPoolWin.cpp @@ -1,6 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" -#include "SystemThreadPoolWin.h" +#include "SystemThreadPoolWin.hpp" #include "DispatchThunk.h" using namespace autowiring; diff --git a/autowiring/SystemThreadPoolWin.h b/src/autowiring/SystemThreadPoolWin.hpp similarity index 100% rename from autowiring/SystemThreadPoolWin.h rename to src/autowiring/SystemThreadPoolWin.hpp From 5c15278a47b17644e1151c34cc94057fcf26e143 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 09:14:08 -0700 Subject: [PATCH 08/60] Implement Windows XP fallback mechanism for thread pools This is necessary to ensure that compatibility libraries that use Autowiring are still dynamically linkable on Windows XP without delay loading. Hopefully this is the last LH+ API that needs to be refactored. --- autowiring/ThreadPool.h | 7 +++ src/autowiring/CMakeLists.txt | 4 ++ src/autowiring/SystemThreadPoolWin.cpp | 72 ++++----------------- src/autowiring/SystemThreadPoolWin.hpp | 19 +++--- src/autowiring/SystemThreadPoolWinLH.cpp | 73 +++++++++++++++++++++ src/autowiring/SystemThreadPoolWinLH.hpp | 35 +++++++++++ src/autowiring/SystemThreadPoolWinXP.cpp | 50 +++++++++++++++ src/autowiring/SystemThreadPoolWinXP.hpp | 26 ++++++++ src/autowiring/test/ThreadPoolTest.cpp | 80 ++++++++++++++---------- 9 files changed, 263 insertions(+), 103 deletions(-) create mode 100644 src/autowiring/SystemThreadPoolWinLH.cpp create mode 100644 src/autowiring/SystemThreadPoolWinLH.hpp create mode 100644 src/autowiring/SystemThreadPoolWinXP.cpp create mode 100644 src/autowiring/SystemThreadPoolWinXP.hpp diff --git a/autowiring/ThreadPool.h b/autowiring/ThreadPool.h index f118983f3..4774c44b7 100644 --- a/autowiring/ThreadPool.h +++ b/autowiring/ThreadPool.h @@ -35,6 +35,9 @@ class ThreadPool: /// /// Called when the thread pool is being cleaned up /// + /// + /// Where possible, this method should return immediately. + /// virtual void OnStop(void) {} public: @@ -48,6 +51,10 @@ class ThreadPool: /// /// This method is idempotent. Unlike CoreThread instances, a thread pool may be restarted. /// The returned shared pointer must be held for as long as the thread pool should be kept running. + /// If the returned token is destroyed, the thread pool will be stopped automatically. Termination + /// of work items in the thread pool may occur at a later time on certain platforms; in some cases, + /// a call to Start may result in the creation of a new thread pool before the previous thread + /// pool is completely torn down. /// virtual std::shared_ptr Start(void); diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index 1ee782344..c8eadefe3 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -195,6 +195,10 @@ add_windows_sources(Autowiring_SRCS CoreThreadWin.cpp SystemThreadPoolWin.cpp SystemThreadPoolWin.hpp + SystemThreadPoolWinXP.cpp + SystemThreadPoolWinXP.hpp + SystemThreadPoolWinLH.cpp + SystemThreadPoolWinLH.hpp InterlockedExchangeWin.cpp thread_specific_ptr_win.h ) diff --git a/src/autowiring/SystemThreadPoolWin.cpp b/src/autowiring/SystemThreadPoolWin.cpp index a58aede68..390435082 100644 --- a/src/autowiring/SystemThreadPoolWin.cpp +++ b/src/autowiring/SystemThreadPoolWin.cpp @@ -1,79 +1,33 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" #include "SystemThreadPoolWin.hpp" +#include "SystemThreadPoolWinLH.hpp" +#include "SystemThreadPoolWinXP.hpp" #include "DispatchThunk.h" using namespace autowiring; +using namespace autowiring::detail; + +static const HMODULE hKernel32 = LoadLibrary("kernel32.dll"); +const decltype(&CreateThreadpoolWork) autowiring::detail::g_CreateThreadpoolWork = (decltype(&CreateThreadpoolWork)) GetProcAddress(hKernel32, "CreateThreadpoolWork"); +const decltype(&CloseThreadpoolWork) autowiring::detail::g_CloseThreadpoolWork = (decltype(&CloseThreadpoolWork)) GetProcAddress(hKernel32, "CloseThreadpoolWork"); SystemThreadPoolWin::SystemThreadPoolWin(void) : m_toBeDone(~0) {} -SystemThreadPoolWin::~SystemThreadPoolWin(void) -{ - if (m_pwkSingle) - CloseThreadpoolWork(m_pwkSingle); - if (m_pwkDispatchRundown) - CloseThreadpoolWork(m_pwkDispatchRundown); -} +SystemThreadPoolWin::~SystemThreadPoolWin(void) {} std::shared_ptr SystemThreadPool::New(void) { - return std::make_shared(); -} - -void SystemThreadPoolWin::OnStartUnsafe(void) { - m_pwkDispatchRundown = CreateThreadpoolWork( - [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { - auto* pThis = static_cast(Context); - - // Grab an arbitrary dispatch queue from the set of pending queues: - std::shared_ptr dqEntry; - if (!pThis->m_rundownTargets.try_pop(dqEntry)) - // Short circuit, someone must have got to it already - return; - - // Now just dispatch everything in order: - dqEntry->DispatchAllEvents(); - }, - this, - nullptr - ); + if (!g_CreateThreadpoolWork) + // Fallback to the XP APIs: + return std::make_shared(); - m_pwkSingle = CreateThreadpoolWork( - [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { - // Spin down our dispatch queue until it is empty: - static_cast(Context)->m_toBeDone.DispatchAllEvents(); - }, - this, - nullptr - ); + // Use the latest greatest API: + return std::make_shared(); } void SystemThreadPoolWin::OnStop(void) { m_toBeDone.Abort(); - - std::lock_guard lk(m_lock); - CloseThreadpoolWork(m_pwkSingle); - m_pwkSingle = nullptr; - CloseThreadpoolWork(m_pwkDispatchRundown); - m_pwkDispatchRundown = nullptr; -} - -void SystemThreadPoolWin::Consume(const std::shared_ptr& dq) -{ - // Append the entry and then signal the rundown queue that there is work to be done - std::lock_guard lk(m_lock); - m_rundownTargets.push(dq); - if (m_pwkDispatchRundown) - SubmitThreadpoolWork(m_pwkDispatchRundown); -} - -bool SystemThreadPoolWin::Submit(std::unique_ptr&& thunk) -{ - std::lock_guard lk(m_lock); - m_toBeDone.AddExisting(std::move(thunk)); - if (m_pwkSingle) - SubmitThreadpoolWork(m_pwkSingle); - return true; } diff --git a/src/autowiring/SystemThreadPoolWin.hpp b/src/autowiring/SystemThreadPoolWin.hpp index fd7f8618a..fa9975f7b 100644 --- a/src/autowiring/SystemThreadPoolWin.hpp +++ b/src/autowiring/SystemThreadPoolWin.hpp @@ -7,8 +7,13 @@ namespace autowiring { + namespace detail { + extern const decltype(&CreateThreadpoolWork) g_CreateThreadpoolWork; + extern const decltype(&CloseThreadpoolWork) g_CloseThreadpoolWork; + } + /// -/// A thread pool that makes use of the underlying system's APIs +/// Abstract base class used by Windows XP and LH compatibility layers /// class SystemThreadPoolWin: public SystemThreadPool @@ -17,11 +22,7 @@ class SystemThreadPoolWin: SystemThreadPoolWin(void); ~SystemThreadPoolWin(void); -private: - // Work item for single dispatchers - PTP_WORK m_pwkDispatchRundown; - PTP_WORK m_pwkSingle; - +protected: // Vector of dispathc queues that need to be run down concurrency::concurrent_queue> m_rundownTargets; @@ -29,13 +30,7 @@ class SystemThreadPoolWin: DispatchQueue m_toBeDone; // ThreadPool overrides: - void OnStartUnsafe(void) override; void OnStop(void) override; - -public: - // ThreadPool overrides: - void Consume(const std::shared_ptr& dq) override; - bool Submit(std::unique_ptr&& thunk) override; }; } diff --git a/src/autowiring/SystemThreadPoolWinLH.cpp b/src/autowiring/SystemThreadPoolWinLH.cpp new file mode 100644 index 000000000..0b5547b9a --- /dev/null +++ b/src/autowiring/SystemThreadPoolWinLH.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "SystemThreadPoolWinLH.hpp" +#include "DispatchThunk.h" + +using namespace autowiring; +using namespace autowiring::detail; + +SystemThreadPoolWinLH::SystemThreadPoolWinLH(void) {} + +SystemThreadPoolWinLH::~SystemThreadPoolWinLH(void) +{ + if (m_pwkSingle) + g_CloseThreadpoolWork(m_pwkSingle); + if (m_pwkDispatchRundown) + g_CloseThreadpoolWork(m_pwkDispatchRundown); +} + +void SystemThreadPoolWinLH::OnStartUnsafe(void) { + m_pwkDispatchRundown = CreateThreadpoolWork( + [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { + auto* pThis = static_cast(Context); + + // Grab an arbitrary dispatch queue from the set of pending queues: + std::shared_ptr dqEntry; + if (!pThis->m_rundownTargets.try_pop(dqEntry)) + // Short circuit, someone must have got to it already + return; + + // Now just dispatch everything in order: + dqEntry->DispatchAllEvents(); + }, + this, + nullptr + ); + + m_pwkSingle = CreateThreadpoolWork( + [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { + // Spin down our dispatch queue until it is empty: + static_cast(Context)->m_toBeDone.DispatchAllEvents(); + }, + this, + nullptr + ); +} + +void SystemThreadPoolWinLH::OnStop(void) { + SystemThreadPoolWin::OnStop(); + + std::lock_guard lk(m_lock); + CloseThreadpoolWork(m_pwkSingle); + m_pwkSingle = nullptr; + CloseThreadpoolWork(m_pwkDispatchRundown); + m_pwkDispatchRundown = nullptr; +} + +void SystemThreadPoolWinLH::Consume(const std::shared_ptr& dq) +{ + // Append the entry and then signal the rundown queue that there is work to be done + std::lock_guard lk(m_lock); + m_rundownTargets.push(dq); + if (m_pwkDispatchRundown) + SubmitThreadpoolWork(m_pwkDispatchRundown); +} + +bool SystemThreadPoolWinLH::Submit(std::unique_ptr&& thunk) +{ + std::lock_guard lk(m_lock); + m_toBeDone.AddExisting(std::move(thunk)); + if (m_pwkSingle) + SubmitThreadpoolWork(m_pwkSingle); + return true; +} diff --git a/src/autowiring/SystemThreadPoolWinLH.hpp b/src/autowiring/SystemThreadPoolWinLH.hpp new file mode 100644 index 000000000..38c37d4f6 --- /dev/null +++ b/src/autowiring/SystemThreadPoolWinLH.hpp @@ -0,0 +1,35 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include "DispatchQueue.h" +#include "SystemThreadPoolWin.hpp" +#include +#include + +namespace autowiring { + +/// +/// A thread pool that makes use of the underlying system's APIs +/// +class SystemThreadPoolWinLH: + public SystemThreadPoolWin +{ +public: + SystemThreadPoolWinLH(void); + ~SystemThreadPoolWinLH(void); + +private: + // Work item for single dispatchers + PTP_WORK m_pwkDispatchRundown; + PTP_WORK m_pwkSingle; + + // ThreadPool overrides: + void OnStartUnsafe(void) override; + void OnStop(void) override; + +public: + // ThreadPool overrides: + void Consume(const std::shared_ptr& dq) override; + bool Submit(std::unique_ptr&& thunk) override; +}; + +} diff --git a/src/autowiring/SystemThreadPoolWinXP.cpp b/src/autowiring/SystemThreadPoolWinXP.cpp new file mode 100644 index 000000000..0c4bfdbed --- /dev/null +++ b/src/autowiring/SystemThreadPoolWinXP.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "SystemThreadPoolWinXP.hpp" +#include "DispatchThunk.h" + +using namespace autowiring; + +SystemThreadPoolWinXP::SystemThreadPoolWinXP(void) {} + +SystemThreadPoolWinXP::~SystemThreadPoolWinXP(void) {} + +void SystemThreadPoolWinXP::Consume(const std::shared_ptr& dq) +{ + // Append the entry and then signal the rundown queue that there is work to be done + std::lock_guard lk(m_lock); + m_rundownTargets.push(dq); + QueueUserWorkItem( + [](void* Context) { + auto* pThis = static_cast(Context); + + // Grab an arbitrary dispatch queue from the set of pending queues: + std::shared_ptr dqEntry; + if (!pThis->m_rundownTargets.try_pop(dqEntry)) + // Short circuit, someone must have got to it already + return 0UL; + + // Now just dispatch everything in order: + dqEntry->DispatchAllEvents(); + return 0UL; + }, + this, + WT_EXECUTEDEFAULT + ); +} + +bool SystemThreadPoolWinXP::Submit(std::unique_ptr&& thunk) +{ + std::lock_guard lk(m_lock); + m_toBeDone.AddExisting(std::move(thunk)); + QueueUserWorkItem( + [](void* Context) { + // Spin down our dispatch queue until it is empty: + static_cast(Context)->m_toBeDone.DispatchAllEvents(); + return 0UL; + }, + this, + WT_EXECUTEDEFAULT + ); + return true; +} diff --git a/src/autowiring/SystemThreadPoolWinXP.hpp b/src/autowiring/SystemThreadPoolWinXP.hpp new file mode 100644 index 000000000..4d53b14be --- /dev/null +++ b/src/autowiring/SystemThreadPoolWinXP.hpp @@ -0,0 +1,26 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once +#include "DispatchQueue.h" +#include "SystemThreadPoolWin.hpp" +#include +#include + +namespace autowiring { + +/// +/// A thread pool that makes use of the underlying system's APIs +/// +class SystemThreadPoolWinXP: + public SystemThreadPoolWin +{ +public: + SystemThreadPoolWinXP(void); + ~SystemThreadPoolWinXP(void); + +public: + // ThreadPool overrides: + void Consume(const std::shared_ptr& dq) override; + bool Submit(std::unique_ptr&& thunk) override; +}; + +} diff --git a/src/autowiring/test/ThreadPoolTest.cpp b/src/autowiring/test/ThreadPoolTest.cpp index 6d5b0f430..920a43ac2 100644 --- a/src/autowiring/test/ThreadPoolTest.cpp +++ b/src/autowiring/test/ThreadPoolTest.cpp @@ -6,6 +6,11 @@ #include #include FUTURE_HEADER +#ifdef _MSC_VER +#include "SystemThreadPoolWinXP.hpp" +#include "SystemThreadPoolWinLH.hpp" +#endif + class ThreadPoolTest: public testing::Test {}; @@ -32,38 +37,6 @@ TEST_F(ThreadPoolTest, SimpleSubmission) { ASSERT_EQ(std::future_status::ready, rs.wait_for(std::chrono::seconds(5))) << "Thread pool lambda was not dispatched in a timely fashion"; } -static void PoolOverload(void) { - AutoCurrentContext ctxt; - ctxt->Initiate(); - - size_t cap = 1000; - auto ctr = std::make_shared>(cap); - auto p = std::make_shared>(); - - for (size_t i = cap; i--;) - *ctxt += [=] { - if (!--*ctr) - p->set_value(); - }; - - auto rs = p->get_future(); - ASSERT_EQ(std::future_status::ready, rs.wait_for(std::chrono::seconds(5))) << "Pool saturation did not complete in a timely fashion"; -} - -TEST_F(ThreadPoolTest, PoolOverload) { - ::PoolOverload(); -} - -// On systems that don't have any OS-specific thread pool customizations, this method is redundant -// On systems that do, this method ensures parity of behavior -TEST_F(ThreadPoolTest, StlPoolTest) { - AutoCurrentContext ctxt; - auto pool = std::make_shared(); - ctxt->SetThreadPool(pool); - pool->SuggestThreadPoolSize(2); - ::PoolOverload(); -} - TEST_F(ThreadPoolTest, PendBeforeContextStart) { AutoCurrentContext ctxt; @@ -126,3 +99,46 @@ TEST_F(ThreadPoolTest, ManualThreadPoolBehavior) { token->Leave(); ASSERT_EQ(std::future_status::ready, launch.wait_for(std::chrono::seconds(5))) << "Token cancellation did not correctly release a single waiting thread"; } + +template +class SystemThreadPoolTest: + public testing::Test +{}; + +TYPED_TEST_CASE_P(SystemThreadPoolTest); + +TYPED_TEST_P(SystemThreadPoolTest, PoolOverload) { + AutoCurrentContext ctxt; + auto pool = std::make_shared(); + ctxt->SetThreadPool(pool); + pool->SuggestThreadPoolSize(2); + ctxt->Initiate(); + + size_t cap = 1000; + auto ctr = std::make_shared>(cap); + auto p = std::make_shared>(); + + for (size_t i = cap; i--;) + *ctxt += [=] { + if (!--*ctr) + p->set_value(); + }; + + auto rs = p->get_future(); + ASSERT_EQ(std::future_status::ready, rs.wait_for(std::chrono::seconds(5))) << "Pool saturation did not complete in a timely fashion"; +} + +REGISTER_TYPED_TEST_CASE_P(SystemThreadPoolTest, PoolOverload); + +typedef ::testing::Types< +#ifdef _MSC_VER + // These pool types are Windows-only + autowiring::SystemThreadPoolWinXP, + autowiring::SystemThreadPoolWinLH, +#endif + + // All platforms test the STL thread pool + autowiring::SystemThreadPoolStl +> t_testTypes; + +INSTANTIATE_TYPED_TEST_CASE_P(My, SystemThreadPoolTest, t_testTypes); From 11b85ad09f1db74742b727d1741d76cbf6eb2c97 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 09:46:56 -0700 Subject: [PATCH 09/60] Eliminate copy ctor for AutoPacketFactory This type cannot be safely copied as it is concurrent anyway, might as well make this explicit. --- autowiring/AutoPacketFactory.h | 1 + 1 file changed, 1 insertion(+) diff --git a/autowiring/AutoPacketFactory.h b/autowiring/AutoPacketFactory.h index 49e8d1cbf..cf6adce3d 100644 --- a/autowiring/AutoPacketFactory.h +++ b/autowiring/AutoPacketFactory.h @@ -25,6 +25,7 @@ class AutoPacketFactory: { public: AutoPacketFactory(void); + AutoPacketFactory(const AutoPacketFactory& rhs) = delete; ~AutoPacketFactory(void); private: From 8165d0f6450be96e5a31b4c48c6b65af2ce0d2ce Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 23 Jul 2015 10:44:41 -0700 Subject: [PATCH 10/60] Use GetProcAddress versions of {Create,Close}ThreadpoolWork() --- src/autowiring/SystemThreadPoolWinLH.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autowiring/SystemThreadPoolWinLH.cpp b/src/autowiring/SystemThreadPoolWinLH.cpp index 0b5547b9a..46f399606 100644 --- a/src/autowiring/SystemThreadPoolWinLH.cpp +++ b/src/autowiring/SystemThreadPoolWinLH.cpp @@ -17,7 +17,7 @@ SystemThreadPoolWinLH::~SystemThreadPoolWinLH(void) } void SystemThreadPoolWinLH::OnStartUnsafe(void) { - m_pwkDispatchRundown = CreateThreadpoolWork( + m_pwkDispatchRundown = g_CreateThreadpoolWork( [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { auto* pThis = static_cast(Context); @@ -34,7 +34,7 @@ void SystemThreadPoolWinLH::OnStartUnsafe(void) { nullptr ); - m_pwkSingle = CreateThreadpoolWork( + m_pwkSingle = g_CreateThreadpoolWork( [](PTP_CALLBACK_INSTANCE Instance, void* Context, PTP_WORK Work) { // Spin down our dispatch queue until it is empty: static_cast(Context)->m_toBeDone.DispatchAllEvents(); @@ -48,9 +48,9 @@ void SystemThreadPoolWinLH::OnStop(void) { SystemThreadPoolWin::OnStop(); std::lock_guard lk(m_lock); - CloseThreadpoolWork(m_pwkSingle); + g_CloseThreadpoolWork(m_pwkSingle); m_pwkSingle = nullptr; - CloseThreadpoolWork(m_pwkDispatchRundown); + g_CloseThreadpoolWork(m_pwkDispatchRundown); m_pwkDispatchRundown = nullptr; } From 484b8223e637570b1eaed48ed66683d15b36d562 Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 23 Jul 2015 11:10:20 -0700 Subject: [PATCH 11/60] Use GetProcAddress for SubmitThreadpoolWork() on XP --- src/autowiring/SystemThreadPoolWin.cpp | 1 + src/autowiring/SystemThreadPoolWin.hpp | 1 + src/autowiring/SystemThreadPoolWinLH.cpp | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/autowiring/SystemThreadPoolWin.cpp b/src/autowiring/SystemThreadPoolWin.cpp index 390435082..b1d776a3c 100644 --- a/src/autowiring/SystemThreadPoolWin.cpp +++ b/src/autowiring/SystemThreadPoolWin.cpp @@ -11,6 +11,7 @@ using namespace autowiring::detail; static const HMODULE hKernel32 = LoadLibrary("kernel32.dll"); const decltype(&CreateThreadpoolWork) autowiring::detail::g_CreateThreadpoolWork = (decltype(&CreateThreadpoolWork)) GetProcAddress(hKernel32, "CreateThreadpoolWork"); const decltype(&CloseThreadpoolWork) autowiring::detail::g_CloseThreadpoolWork = (decltype(&CloseThreadpoolWork)) GetProcAddress(hKernel32, "CloseThreadpoolWork"); +const decltype(&SubmitThreadpoolWork) autowiring::detail::g_SubmitThreadpoolWork = (decltype(&SubmitThreadpoolWork)) GetProcAddress(hKernel32, "SubmitThreadpoolWork"); SystemThreadPoolWin::SystemThreadPoolWin(void) : m_toBeDone(~0) diff --git a/src/autowiring/SystemThreadPoolWin.hpp b/src/autowiring/SystemThreadPoolWin.hpp index fa9975f7b..56dac6af7 100644 --- a/src/autowiring/SystemThreadPoolWin.hpp +++ b/src/autowiring/SystemThreadPoolWin.hpp @@ -10,6 +10,7 @@ namespace autowiring { namespace detail { extern const decltype(&CreateThreadpoolWork) g_CreateThreadpoolWork; extern const decltype(&CloseThreadpoolWork) g_CloseThreadpoolWork; + extern const decltype(&SubmitThreadpoolWork) g_SubmitThreadpoolWork; } /// diff --git a/src/autowiring/SystemThreadPoolWinLH.cpp b/src/autowiring/SystemThreadPoolWinLH.cpp index 46f399606..3127f7691 100644 --- a/src/autowiring/SystemThreadPoolWinLH.cpp +++ b/src/autowiring/SystemThreadPoolWinLH.cpp @@ -60,7 +60,7 @@ void SystemThreadPoolWinLH::Consume(const std::shared_ptr& dq) std::lock_guard lk(m_lock); m_rundownTargets.push(dq); if (m_pwkDispatchRundown) - SubmitThreadpoolWork(m_pwkDispatchRundown); + g_SubmitThreadpoolWork(m_pwkDispatchRundown); } bool SystemThreadPoolWinLH::Submit(std::unique_ptr&& thunk) @@ -68,6 +68,6 @@ bool SystemThreadPoolWinLH::Submit(std::unique_ptr&& thunk) std::lock_guard lk(m_lock); m_toBeDone.AddExisting(std::move(thunk)); if (m_pwkSingle) - SubmitThreadpoolWork(m_pwkSingle); + g_SubmitThreadpoolWork(m_pwkSingle); return true; } From 3e611deb83affece0b8f8a72ad672b482c9aecb9 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 16:50:23 -0700 Subject: [PATCH 12/60] Support shared_ptr input Seems like a logical thing to want, yet currently attempting to use this will cause cryptic template error messages. --- autowiring/AutoPacket.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/autowiring/AutoPacket.h b/autowiring/AutoPacket.h index 8b15c7d4f..3573550aa 100644 --- a/autowiring/AutoPacket.h +++ b/autowiring/AutoPacket.h @@ -723,6 +723,27 @@ class auto_arg } }; +/// +/// AutoPacket specialization for shared pointer +/// +template<> +class auto_arg> +{ +public: + typedef AutoPacket& type; + typedef AutoPacket& arg_type; + typedef AutoPacket id_type; + static const bool is_input = false; + static const bool is_output = false; + static const bool is_shared = false; + static const bool is_multi = false; + static const int tshift = 0; + + static std::shared_ptr arg(AutoPacket& packet) { + return packet.shared_from_this(); + } +}; + template void AutoPacket::Unsatisfiable(void) { MarkUnsatisfiable(DecorationKey(auto_id::key(), 0)); From 399f4514a4a7c40cf7dce564adb060e325963343 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Sun, 5 Jul 2015 04:57:12 -0700 Subject: [PATCH 13/60] Eliminate SharedPointerSlot, implement an identifier index concept This identifier index is intended to eliminate inefficient uses to `std::type_info`'s hashing behavior. --- autowiring/AnySharedPointer.h | 143 ++++--- autowiring/AutoFilterArgument.h | 14 +- autowiring/AutoFilterDescriptor.h | 42 +- autowiring/AutoPacket.h | 58 +-- autowiring/AutoPacketFactory.h | 3 +- autowiring/AutoPacketGraph.h | 6 +- autowiring/AutowirableSlot.h | 21 +- autowiring/Autowired.h | 5 +- autowiring/CallExtractor.h | 16 +- autowiring/CoreContext.h | 8 +- autowiring/CoreObjectDescriptor.h | 11 +- autowiring/DecorationDisposition.h | 12 +- autowiring/Parallel.h | 2 +- autowiring/SharedPointerSlot.h | 377 ------------------ autowiring/SlotInformation.h | 5 +- autowiring/auto_arg.h | 30 +- autowiring/auto_id.h | 194 ++++++++- autowiring/auto_in.h | 4 - autowiring/auto_out.h | 2 +- autowiring/auto_prev.h | 2 +- autowiring/demangle.h | 2 + autowiring/noop.h | 7 + src/autonet/AutoNetServerImpl.cpp | 6 +- src/autowiring/AnySharedPointer.cpp | 27 +- src/autowiring/AutoFilterDescriptor.cpp | 12 +- src/autowiring/AutoPacket.cpp | 33 +- src/autowiring/AutoPacketFactory.cpp | 3 +- src/autowiring/AutoPacketGraph.cpp | 38 +- src/autowiring/AutowiringDebug.cpp | 27 +- src/autowiring/CMakeLists.txt | 3 +- src/autowiring/CoreContext.cpp | 18 +- src/autowiring/JunctionBoxBase.cpp | 2 +- src/autowiring/SatCounter.cpp | 2 +- src/autowiring/auto_id.cpp | 11 + src/autowiring/demangle.cpp | 9 +- src/autowiring/test/AnySharedPointerTest.cpp | 116 +++--- .../test/AutoFilterConstructRulesTest.cpp | 5 +- .../test/AutoFilterDescriptorTest.cpp | 22 +- .../test/AutoFilterDiagnosticsTest.cpp | 6 +- src/autowiring/test/AutoFilterTest.cpp | 56 +-- src/autowiring/test/AutoIDTest.cpp | 18 + src/autowiring/test/AutowiringTest.cpp | 4 +- src/autowiring/test/CMakeLists.txt | 1 + src/autowiring/test/ContextMemberTest.cpp | 14 +- src/autowiring/test/DecoratorTest.cpp | 10 +- 45 files changed, 658 insertions(+), 749 deletions(-) delete mode 100644 autowiring/SharedPointerSlot.h create mode 100644 autowiring/noop.h create mode 100644 src/autowiring/auto_id.cpp create mode 100644 src/autowiring/test/AutoIDTest.cpp diff --git a/autowiring/AnySharedPointer.h b/autowiring/AnySharedPointer.h index 1c236b02b..ef95880af 100644 --- a/autowiring/AnySharedPointer.h +++ b/autowiring/AnySharedPointer.h @@ -1,78 +1,106 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once -#include "SharedPointerSlot.h" - -/// \file SharedPointerSlot.h -/// \internal +#include "auto_id.h" +#include "fast_pointer_cast.h" /// AnySharedPointer struct AnySharedPointer { public: - AnySharedPointer(void); + AnySharedPointer(void) = default; AnySharedPointer(AnySharedPointer&& rhs); - AnySharedPointer(const AnySharedPointer& rhs); - AnySharedPointer(const SharedPointerSlot&& rhs); - AnySharedPointer(const SharedPointerSlot& rhs); + AnySharedPointer(const AnySharedPointer& rhs) = default; template - AnySharedPointer(const std::shared_ptr& rhs) { - // Delegate the remainder to the assign operation: - new (m_space) SharedPointerSlotT(rhs); - } + AnySharedPointer(const std::shared_ptr& rhs) : + m_ti(auto_id_t{}), + m_ptr(rhs) + {} ~AnySharedPointer(void); protected: - unsigned char m_space[sizeof(SharedPointerSlot)]; + auto_id m_ti; + std::shared_ptr m_ptr; public: - // Convenience method to cast the space to a slot - SharedPointerSlot* slot(void) { return (SharedPointerSlot*) m_space; } - const SharedPointerSlot* slot(void) const { return (const SharedPointerSlot*) m_space; } + explicit operator bool(void) const { return (bool)m_ptr; } + void* ptr(void) const { return m_ptr.get(); } + bool empty(void) const { return !m_ptr; } + auto_id type(void) const { return m_ti; } - explicit operator bool(void) const { return slot()->operator bool(); } + std::shared_ptr& operator*(void) { return m_ptr; } + const std::shared_ptr& operator*(void) const { return m_ptr; } - SharedPointerSlot& operator*(void) { return *slot(); } - const SharedPointerSlot& operator*(void) const { return *slot(); } + void reset(void) { + m_ptr.reset(); + } - SharedPointerSlot* operator->(void) { return slot(); } - const SharedPointerSlot* operator->(void) const { return slot(); } + std::shared_ptr as_obj(void) const { + return + m_ti.block->pToObj ? + m_ti.block->pToObj(m_ptr) : + nullptr; + } + + /// + /// Attempts to dynamically assign this slot to the specified object without changing the current type + /// + /// True if the assignment succeeds + bool try_assign(const std::shared_ptr& rhs) { + if (!m_ti.block->pFromObj) + return nullptr; + auto ptr = m_ti.block->pFromObj(rhs); + if (!ptr) + return false; + m_ptr = std::move(ptr); + return true; + } + + /// + /// Attempts to dynamically assign this slot to the specified object without changing the current type + /// + /// True if the assignment succeeds + bool try_assign(const AnySharedPointer& rhs) { + auto obj = rhs.as_obj(); + return obj && try_assign(obj); + } template - SharedPointerSlotT& as(void) const { return (SharedPointerSlotT&)*slot(); } + const std::shared_ptr& as(void) const { + // The safety of this routine is verified by the AnySharedPointer unit tests + return *reinterpret_cast*>(&m_ptr); + } bool operator==(const AnySharedPointer& rhs) const { - return *slot() == *rhs.slot(); + // Need to compare the control blocks, not the pointer values, because we could be pointing to + // different spots in the same object. + return + !m_ptr.owner_before(rhs.m_ptr) && + !rhs.m_ptr.owner_before(m_ptr); } - template + template bool operator==(const std::shared_ptr& rhs) const { - return *slot() == rhs; + return m_ptr == rhs; } - // Additional operator overloads: - bool operator<(const AnySharedPointer& rhs) const { return *slot() < *rhs.slot();} - bool operator!=(const AnySharedPointer& rhs) const { return !(*this == rhs); } + bool operator!=(std::nullptr_t) const { + return !!m_ptr; + } - /// - /// Copy assignment operator - /// - /// - /// Consumer beware: This is a transformative assignment. The true polymorphic - /// type will be carried from the right-hand side into this element, which is a - /// different behavior from how things are normally done during assignment. Other - /// than that, however, the behavior is very similar to boost::any's assignment - /// implementation. - /// - SharedPointerSlot& operator=(const AnySharedPointer& rhs) { - return **this = *rhs; + template + void init(void) { + m_ti = auto_id_t{}; + m_ptr.reset(); } - /// - /// Convenience overload for slot assignment - /// - SharedPointerSlot& operator=(const SharedPointerSlot& rhs) { - return *slot() = rhs; + // Additional operator overloads: + bool operator<(const AnySharedPointer& rhs) const { return m_ptr < rhs.m_ptr;} + bool operator!=(const AnySharedPointer& rhs) const { return !(*this == rhs); } + + void operator=(const AnySharedPointer& rhs) { + m_ti = rhs.m_ti; + m_ptr = rhs.m_ptr; } /// @@ -80,31 +108,34 @@ struct AnySharedPointer { /// template void operator=(const std::shared_ptr& rhs) { - **this = rhs; + m_ptr = rhs; } }; /// /// Convenience implementation of AnySharedPointer which is initially of type T /// +/// +/// Using this type will automatically ensure that the underlying auto_id is fully instantiated +/// template class AnySharedPointerT: public AnySharedPointer { public: - AnySharedPointerT(void) { - new (m_space) SharedPointerSlotT(); - } + AnySharedPointerT(void): + AnySharedPointer(std::shared_ptr{}) + {} - // Convenience method to cast the space to a slot, but with the expected type - SharedPointerSlotT* slot(void) { return (SharedPointerSlotT*) m_space; } - const SharedPointerSlotT* slot(void) const { return (const SharedPointerSlotT*) m_space; } + T& operator*(void) { return *as(); } + const T& operator*(void) const { return *as(); } - T& operator*(void) { return *slot()->get(); } - const T& operator*(void) const { return **slot(); } + T* operator->(void) { return as()->get(); } + const T* operator->(void) const { return as()->get(); } - T* operator->(void) { return slot()->get().get(); } - const T* operator->(void) const { return slot()->get().get(); } + const std::shared_ptr& get(void) const { + return AnySharedPointer::as(); + } }; template diff --git a/autowiring/AutoFilterArgument.h b/autowiring/AutoFilterArgument.h index 2b24312d6..b1e156cb6 100644 --- a/autowiring/AutoFilterArgument.h +++ b/autowiring/AutoFilterArgument.h @@ -9,31 +9,33 @@ struct AutoFilterArgument { AutoFilterArgument(void) = default; +protected: AutoFilterArgument( bool is_input, bool is_output, bool is_shared, bool is_multi, - const std::type_info* ti, + auto_id id, int tshift ) : is_input(is_input), is_output(is_output), is_shared(is_shared), is_multi(is_multi), - ti(ti), + id(id), tshift(tshift) {} +public: const bool is_input = false; const bool is_output = false; const bool is_shared = false; const bool is_multi = false; - const std::type_info* const ti = nullptr; + const auto_id id = auto_id_t{}; const int tshift = 0; - operator bool(void) const { - return !!ti; + explicit operator bool(void) const { + return static_cast(id); } }; @@ -47,7 +49,7 @@ struct AutoFilterArgumentT: auto_arg::is_output, auto_arg::is_shared, auto_arg::is_multi, - &typeid(typename auto_arg::id_type), + typename auto_arg::id_type(), auto_arg::tshift ) {} diff --git a/autowiring/AutoFilterDescriptor.h b/autowiring/AutoFilterDescriptor.h index 0b126d5fc..aeaead78c 100644 --- a/autowiring/AutoFilterDescriptor.h +++ b/autowiring/AutoFilterDescriptor.h @@ -24,7 +24,7 @@ struct AutoFilterDescriptorStub { /// /// Constructs a new packet subscriber entry based on the specified call extractor and call pointer /// - /// The type of the underlying filter + /// The type of the underlying filter /// The inputs accepted by the filter /// True if the filter is deferred /// A pointer to the AutoFilter call routine itself @@ -33,11 +33,11 @@ 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, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall); + AutoFilterDescriptorStub(auto_id type, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall); protected: // Type of the subscriber itself - const std::type_info* m_pType = nullptr; + auto_id m_type; // Altitude--controls when the filter gets called autowiring::altitude m_altitude = autowiring::altitude::Standard; @@ -69,12 +69,12 @@ struct AutoFilterDescriptorStub { public: // Accessor methods: autowiring::altitude GetAltitude(void) const { return m_altitude; } - const std::type_info* GetType() const { return m_pType; } + auto_id GetType() const { return m_type; } size_t GetArity(void) const { return m_arity; } size_t GetRequiredCount(void) const { return m_requiredCount; } const AutoFilterArgument* GetAutoFilterArguments(void) const { return m_pArgs; } bool IsDeferred(void) const { return m_deferred; } - const std::type_info* GetAutoFilterTypeInfo(void) const { return m_pType; } + auto_id GetAutoFilterTypeInfo(void) const { return m_type; } /// /// True if the specified type is present as an output argument on this filter @@ -92,7 +92,7 @@ struct AutoFilterDescriptorStub { /// /// Returns nullptr when no argument is of the requested type. /// - const AutoFilterArgument* GetArgumentType(const std::type_info* argType) const; + const AutoFilterArgument* GetArgumentType(auto_id argType) const; /// A call lambda wrapping the associated subscriber /// @@ -146,7 +146,7 @@ struct AutoFilterDescriptor: // then take the type of the decomposed result. std::static_pointer_cast::type>(subscriber) ), - &typeid(T), + auto_id_t{}, autowiring::altitude_of< T, CallExtractor::deferred ? autowiring::altitude::Dispatch : autowiring::altitude::Standard @@ -155,7 +155,10 @@ struct AutoFilterDescriptor: CallExtractor::deferred, &CallExtractor::template Call<&T::AutoFilter> ) - {} + { + // T must be completely defined at this point because we are trying to pull out an AutoFilter from it + (void) auto_id_t_init::init; + } /// /// Adds a function to be called as an AutoFilter for this packet only. @@ -167,13 +170,16 @@ struct AutoFilterDescriptor: AutoFilterDescriptor(Fn fn, autowiring::altitude altitude = autowiring::altitude::Standard): AutoFilterDescriptor( AnySharedPointer(std::make_shared(std::forward(fn))), - &typeid(Fn), + auto_id_t{}, altitude, CallExtractor::template Enumerate::types, false, &CallExtractor::template Call<&Fn::operator()> ) - {} + { + // Fn must be completely defined at this point because we are trying to pull out an AutoFilter from it + (void) auto_id_t_init::init; + } /// /// Alternative constructor which can bind a stub @@ -193,8 +199,8 @@ 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, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall) : - AutoFilterDescriptorStub(pType, altitude, pArgs, deferred, pCall), + AutoFilterDescriptor(const AnySharedPointer& autoFilter, auto_id type, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall) : + AutoFilterDescriptorStub(type, altitude, pArgs, deferred, pCall), m_autoFilter(autoFilter) {} @@ -212,13 +218,15 @@ struct AutoFilterDescriptor: ), // The remainder is fairly straightforward - &typeid(pfn), + auto_id_t{}, altitude, CallExtractor::template Enumerate::types, false, CallExtractor::Call ) - {} + { + (void) auto_id_t_init::init; + } protected: // A hold on the enclosed autoFilter @@ -226,7 +234,7 @@ struct AutoFilterDescriptor: public: // Accessor methods: - bool empty(void) const { return !m_pCall || m_autoFilter->empty(); } + bool empty(void) const { return !m_pCall || m_autoFilter.empty(); } AnySharedPointer& GetAutoFilter(void) { return m_autoFilter; } const AnySharedPointer& GetAutoFilter(void) const { return m_autoFilter; } @@ -235,7 +243,7 @@ struct AutoFilterDescriptor: /// void ReleaseAutoFilter(void) { m_arity = 0; - m_autoFilter->reset(); + m_autoFilter.reset(); } /// True when both the AutoFilter method and subscriber instance are equal. @@ -303,7 +311,7 @@ namespace std { struct hash { size_t operator()(const AutoFilterDescriptor& subscriber) const { - return (size_t) subscriber.GetAutoFilter()->ptr(); + return (size_t) subscriber.GetAutoFilter().ptr(); } }; } diff --git a/autowiring/AutoPacket.h b/autowiring/AutoPacket.h index 8b15c7d4f..716b12f09 100644 --- a/autowiring/AutoPacket.h +++ b/autowiring/AutoPacket.h @@ -9,6 +9,7 @@ #include "demangle.h" #include "is_any.h" #include "is_shared_ptr.h" +#include "noop.h" #include "TeardownNotifier.h" #include #include CHRONO_HEADER @@ -182,7 +183,7 @@ class AutoPacket: /// /// If the type is not a subscriber GetSatisfaction().GetType() == nullptr will be true /// - const SatCounter& GetSatisfaction(const std::type_info& subscriber) const; + const SatCounter& GetSatisfaction(auto_id subscriber) const; /// /// Throws a formatted runtime error corresponding to the case where an absent decoration was demanded @@ -220,7 +221,7 @@ class AutoPacket: template bool Has(int tshift=0) const { std::lock_guard lk(m_lock); - return HasUnsafe(DecorationKey(auto_id::key(), tshift)); + return HasUnsafe(DecorationKey(auto_id_t{}, tshift)); } /// @@ -232,7 +233,7 @@ class AutoPacket: const T* retVal; if (!Get(retVal, tshift)) - ThrowNotDecoratedException(DecorationKey(auto_id::key(), tshift)); + ThrowNotDecoratedException(DecorationKey(auto_id_t{}, tshift)); return *retVal; } @@ -245,7 +246,7 @@ class AutoPacket: /// template bool Get(const T*& out, int tshift=0) const { - DecorationKey key(auto_id::key(), tshift); + DecorationKey key(auto_id_t{}, tshift); const DecorationDisposition* pDisposition = GetDisposition(key); if (pDisposition) { switch (pDisposition->m_decorations.size()) { @@ -254,7 +255,7 @@ class AutoPacket: break; case 1: // Single decoration, we can do what the user is asking - out = static_cast(pDisposition->m_decorations[0]->ptr()); + out = static_cast(pDisposition->m_decorations[0].ptr()); return true; default: ThrowMultiplyDecoratedException(key); @@ -289,7 +290,7 @@ class AutoPacket: typedef typename std::remove_const::type TActual; // Decoration must be present and the shared pointer itself must also be present - DecorationKey key(auto_id::key(), tshift); + DecorationKey key(auto_id_t{}, tshift); const DecorationDisposition* pDisposition = GetDisposition(key); if (!pDisposition) { out = nullptr; @@ -302,7 +303,7 @@ class AutoPacket: return false; case 1: // Single decoration available, we can return here - out = &pDisposition->m_decorations[0]->as_unsafe(); + out = &pDisposition->m_decorations[0].as(); return true; default: ThrowMultiplyDecoratedException(key); @@ -320,11 +321,11 @@ class AutoPacket: template bool Get(std::shared_ptr& out, int tshift = 0) const { std::lock_guard lk(m_lock); - auto deco = m_decoration_map.find(DecorationKey(auto_id::key(), tshift)); + auto deco = m_decoration_map.find(DecorationKey(auto_id_t{}, tshift)); if(deco != m_decoration_map.end() && deco->second.m_state == DispositionState::Complete) { auto& disposition = deco->second; if(disposition.m_decorations.size() == 1) { - out = disposition.m_decorations[0]->as_unsafe(); + out = disposition.m_decorations[0].as(); return true; } } @@ -351,7 +352,7 @@ class AutoPacket: std::lock_guard lk(m_lock); // If decoration doesn't exist, return empty null-terminated buffer - auto q = m_decoration_map.find(DecorationKey(auto_id::key(), tshift)); + auto q = m_decoration_map.find(DecorationKey(auto_id_t{}, tshift)); if (q == m_decoration_map.end()) return std::unique_ptr{ new const T*[1] {nullptr} @@ -361,7 +362,7 @@ class AutoPacket: const auto& decorations = q->second.m_decorations; std::unique_ptr retVal{new const T*[decorations.size() + 1]}; for (size_t i = 0; i < decorations.size(); i++) - retVal[i] = static_cast(decorations[i]->ptr()); + retVal[i] = static_cast(decorations[i].ptr()); retVal[decorations.size()] = nullptr; return retVal; } @@ -376,7 +377,7 @@ class AutoPacket: typedef typename std::remove_const::type TActual; // If decoration doesn't exist, return empty null-terminated buffer - auto q = m_decoration_map.find(DecorationKey(auto_id::key(), tshift)); + auto q = m_decoration_map.find(DecorationKey(auto_id_t{}, tshift)); if (q == m_decoration_map.end()) return std::unique_ptr[]>{ new std::shared_ptr[1] {nullptr} @@ -388,7 +389,7 @@ class AutoPacket: new std::shared_ptr[decorations.size() + 1] }; for (size_t i = 0; i < decorations.size(); i++) - retVal[i] = decorations[i].as().get(); + retVal[i] = decorations[i].as(); return retVal; } @@ -422,10 +423,11 @@ class AutoPacket: /// template void MarkUnsatisfiable(void) { - MarkUnsatisfiable(DecorationKey(auto_id::key(), 0)); + MarkUnsatisfiable(DecorationKey(auto_id_t{}, 0)); } /// + /// Decoration method specialized for shared pointer types /// /// @@ -436,14 +438,14 @@ class AutoPacket: /// template void Decorate(const std::shared_ptr& ptr) { - DecorationKey key(auto_id::key(), 0); + DecorationKey key(auto_id_t{}, 0); // We don't want to see this overload used on a const T static_assert(!std::is_const::value, "Cannot decorate a shared pointer to const T with this overload"); // Injunction to prevent existential loops: static_assert(!std::is_same::value, "Cannot decorate a packet with another packet"); - + // Either decorate, or prevent anyone from decorating if (ptr) Decorate(AnySharedPointer(ptr), key); @@ -483,7 +485,7 @@ class AutoPacket: auto ptr = std::make_shared(std::forward(t)); Decorate( AnySharedPointer(ptr), - DecorationKey(auto_id::key(), 0) + DecorationKey(auto_id_t{}, 0) ); return *ptr; } @@ -503,7 +505,7 @@ class AutoPacket: auto ptr = std::make_shared(std::forward(args)...); Decorate( AnySharedPointer(ptr), - DecorationKey(auto_id::key(), 0) + DecorationKey(auto_id_t(), 0) ); return *ptr; } @@ -531,8 +533,8 @@ class AutoPacket: // Perform standard decoration with a short initialization: std::unique_lock lk(m_lock); DecorationDisposition* pTypeSubs[1 + sizeof...(Ts)] = { - &DecorateImmediateUnsafe(DecorationKey(auto_id::key(), 0), &immed), - &DecorateImmediateUnsafe(DecorationKey(auto_id::key(), 0), &immeds)... + &DecorateImmediateUnsafe(DecorationKey(auto_id_t(), 0), &immed), + &DecorateImmediateUnsafe(DecorationKey(auto_id_t(), 0), &immeds)... }; // Pulse satisfaction: @@ -545,8 +547,10 @@ class AutoPacket: } // Now trigger a rescan to hit any deferred, unsatisfiable entries: - for (const std::type_info* ti : {&auto_id::key(), &auto_id::key()...}) - MarkUnsatisfiable(DecorationKey(*ti, 0)); + autowiring::noop( + (MarkUnsatisfiable(DecorationKey(auto_id_t(), 0)), false), + (MarkUnsatisfiable(DecorationKey(auto_id_t(), 0)), false)... + ); }), PulseSatisfactionUnsafe(std::move(lk), pTypeSubs, 1 + sizeof...(Ts)); } @@ -591,7 +595,7 @@ class AutoPacket: /// If the type is not a subscriber GetSatisfaction().GetType() == nullptr will be true /// template - inline const SatCounter& GetSatisfaction(void) const { return GetSatisfaction(auto_id::key()); } + inline const SatCounter& GetSatisfaction(void) const { return GetSatisfaction(auto_id_t{}); } /// /// Returns the next packet that will be issued by the packet factory in this context relative to this context @@ -601,13 +605,13 @@ class AutoPacket: /// True if the indicated type has been requested for use by some consumer template bool HasSubscribers(void) const { - return HasSubscribers(DecorationKey{auto_id::key(), 0}); + return HasSubscribers(DecorationKey{auto_id_t{}, 0}); } /// Zero if there are no publishers, otherwise the number of publishers template size_t HasPublishers(void) const { - return HasPublishers(DecorationKey{auto_id::key(), 0}); + return HasPublishers(DecorationKey{auto_id_t{}, 0}); } struct SignalStub { @@ -711,7 +715,7 @@ class auto_arg public: typedef AutoPacket& type; typedef AutoPacket& arg_type; - typedef AutoPacket id_type; + typedef auto_id_t id_type; static const bool is_input = false; static const bool is_output = false; static const bool is_shared = false; @@ -725,5 +729,5 @@ class auto_arg template void AutoPacket::Unsatisfiable(void) { - MarkUnsatisfiable(DecorationKey(auto_id::key(), 0)); + MarkUnsatisfiable(DecorationKey(auto_id_t{}, 0)); } diff --git a/autowiring/AutoPacketFactory.h b/autowiring/AutoPacketFactory.h index cf6adce3d..266cc904d 100644 --- a/autowiring/AutoPacketFactory.h +++ b/autowiring/AutoPacketFactory.h @@ -170,7 +170,7 @@ class AutoPacketFactory: /// /// If a matching description was not found GetTypeDescriptor(type).GetAutoFilterTypeInfo() == nullptr /// - AutoFilterDescriptor GetTypeDescriptorUnsafe(const std::type_info* nodeType); + AutoFilterDescriptor GetTypeDescriptorUnsafe(auto_id nodeType); static bool IsAutoPacketType(const std::type_info& dataType); @@ -235,7 +235,6 @@ class AutoPacketFactory: // Extern explicit template instantiation declarations added to prevent // exterior instantation of internally used template instances extern template struct SlotInformationStump; -extern template const std::shared_ptr& SharedPointerSlot::as(void) const; extern template std::shared_ptr autowiring::fast_pointer_cast(const std::shared_ptr& Other); extern template class RegType; extern template struct autowiring::fast_pointer_cast_blind; diff --git a/autowiring/AutoPacketGraph.h b/autowiring/AutoPacketGraph.h index 345b330a4..5efc4a495 100644 --- a/autowiring/AutoPacketGraph.h +++ b/autowiring/AutoPacketGraph.h @@ -16,7 +16,7 @@ struct DeliveryEdge { // The type info - const std::type_info* type_info; + auto_id type_info; // The AutoFilterDescriptor AutoFilterDescriptor descriptor; @@ -41,7 +41,7 @@ namespace std { struct hash { size_t operator()(const DeliveryEdge& edge) const { - return (size_t) edge.descriptor.GetAutoFilter()->ptr(); + return (size_t) edge.descriptor.GetAutoFilter().ptr(); } }; } @@ -93,7 +93,7 @@ class AutoPacketGraph: /// /// Record the delivery of a packet and increment the number of times the packet has been delivered /// - void RecordDelivery(const std::type_info* ti, const AutoFilterDescriptor& descriptor, bool input); + void RecordDelivery(auto_id id, const AutoFilterDescriptor& descriptor, bool input); /// AutowiringEvents overrides virtual void NewContext(CoreContext&) override {} diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 01fc6f7bd..7af754c3f 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -1,9 +1,9 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "AnySharedPointer.h" +#include "autowiring_error.h" #include "fast_pointer_cast.h" -#include "SharedPointerSlot.h" #include "SlotInformation.h" -#include "AnySharedPointer.h" #include MEMORY_HEADER class CoreContext; @@ -92,13 +92,13 @@ class DeferrableAutowiring: /// /// The type on which this deferred slot is bound /// - const std::type_info& GetType(void) const { - return AnySharedPointer::slot()->type(); + auto_id GetType(void) const { + return AnySharedPointer::type(); } // Reset this pointer. Similar to shared_ptr::reset(). void reset() { - slot()->reset(); + m_ptr.reset(); } /// @@ -151,6 +151,7 @@ class AutowirableSlot: // type is autowired, they are required at a minimum to know what that type's inheritance relations // are to other types in the system. (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; return !!get(); } @@ -160,6 +161,7 @@ class AutowirableSlot: T* get(void) const { // For now, we require that the full type be available to use this method (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; return get_unsafe(); } @@ -172,12 +174,7 @@ class AutowirableSlot: /// this may prevent the type from ever being detected as autowirable as a result. /// T* get_unsafe(void) const { - return - static_cast*>( - static_cast( - this - ) - )->slot()->get().get(); + return static_cast(ptr()); } explicit operator bool(void) const { @@ -189,6 +186,7 @@ class AutowirableSlot: // where we can guarantee that the type will be completely defined, because the user is about // to make use of this type. (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; auto retVal = get(); if (!retVal) @@ -204,6 +202,7 @@ class AutowirableSlot: // We have to initialize here, in the operator context, because we don't actually know if the // user will be making use of this type. (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; return *retVal; } diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index 9f49b9456..186323d55 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "auto_id.h" #include "auto_signal.h" #include "AutowiringDebug.h" #include "AutowirableSlot.h" @@ -154,7 +155,7 @@ class Autowired: static_cast( this ) - )->slot()->get(); + )->get(); } operator std::weak_ptr(void) const { @@ -349,6 +350,7 @@ class AutowiredFast: // !!!!! Read comment in AutoRequired if you get a compiler error here !!!!! AutowiredFast(const std::shared_ptr& ctxt = CoreContext::CurrentContext()) { (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; if (ctxt) ctxt->FindByTypeRecursive(*this); @@ -356,6 +358,7 @@ class AutowiredFast: AutowiredFast(const CoreContext* pCtxt) { (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; pCtxt->FindByTypeRecursive(*this); } diff --git a/autowiring/CallExtractor.h b/autowiring/CallExtractor.h index 8015c4cee..39c02b22a 100644 --- a/autowiring/CallExtractor.h +++ b/autowiring/CallExtractor.h @@ -7,6 +7,7 @@ #include "CurrentContextPusher.h" #include "Decompose.h" #include "index_tuple.h" +#include "noop.h" #include class Deferred; @@ -17,11 +18,6 @@ typedef void(*t_extractedCall)(const AnySharedPointer& obj, AutoPacket&); template::N>::type> struct CallExtractor; -namespace autowiring { - template - void noop(Args...) {} -} - template struct CallExtractorSetup { @@ -67,7 +63,7 @@ struct CallExtractor>: /// Binder struct, lets us refer to an instance of Call by type /// static void Call(const AnySharedPointer& obj, AutoPacket& packet) { - const void* pfn = obj->ptr(); + const void* pfn = obj.ptr(); // Setup, handoff, commit CallExtractorSetup extractor(packet, (auto_arg::arg(packet))...); @@ -94,12 +90,12 @@ struct CallExtractor> : /// template static void Call(const AnySharedPointer& obj, AutoPacket& packet) { - const void* pObj = obj->ptr(); + const void* pObj = obj.ptr(); // This exception type indicates that an attempt was made to construct an AutoFilterDescriptor with an // AnySharedPointer which was not the type of its own member function. Be sure to cast the AnySharedPointer // to the correct foundation type before attempting to construct an AutoFilterDescriptor. - assert(typeid(auto_id) == obj->type()); + assert(auto_id_t{} == obj.type()); // Extract, call, commit CallExtractorSetup extractor(packet, (auto_arg::arg(packet))...); @@ -123,7 +119,7 @@ struct CallExtractor> : template static void Call(const AnySharedPointer& obj, AutoPacket& packet) { - const void* pObj = obj->ptr(); + const void* pObj = obj.ptr(); // Extract, call, commit CallExtractorSetup extractor(packet, (auto_arg::arg(packet))...); @@ -147,7 +143,7 @@ struct CallExtractor> : template static void Call(const AnySharedPointer& obj, AutoPacket& autoPacket) { - const void* pObj = obj->ptr(); + const void* pObj = obj.ptr(); // Obtain a shared pointer of the AutoPacket in order to ensure the packet // is not destroyed when we pend this lambda to the destination object's diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 10753ff72..3d102a2d8 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -224,7 +224,7 @@ class CoreContext: std::list m_concreteTypes; // This is a memoization map used to memoize any already-detected interfaces. - mutable std::unordered_map m_typeMemos; + mutable std::unordered_map m_typeMemos; // All known context members, exception filters: std::vector m_contextMembers; @@ -548,7 +548,7 @@ class CoreContext: /// /// The type identifier of the referenced instance. /// - const std::type_info& GetAutoTypeId(const AnySharedPointer& ptr) const; + auto_id GetAutoTypeId(const AnySharedPointer& ptr) const; /// \internal /// @@ -1077,7 +1077,7 @@ class CoreContext: void FindByType(std::shared_ptr& slot, bool localOnly = false) const { AnySharedPointerT reference; FindByType(reference, localOnly); - slot = reference.slot()->template as(); + slot = reference.template as(); } /// @@ -1087,7 +1087,7 @@ class CoreContext: void FindByTypeRecursive(std::shared_ptr& ptr) const { AnySharedPointerT slot; FindByTypeRecursive(slot, AutoSearchLambdaDefault()); - ptr = slot.slot()->get(); + ptr = slot.get(); } /// diff --git a/autowiring/CoreObjectDescriptor.h b/autowiring/CoreObjectDescriptor.h index 9a1222b19..19e187e84 100644 --- a/autowiring/CoreObjectDescriptor.h +++ b/autowiring/CoreObjectDescriptor.h @@ -1,6 +1,7 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "AnySharedPointer.h" +#include "auto_id.h" #include "AutoFilterDescriptor.h" #include "AutowiringEvents.h" #include "BoltBase.h" @@ -25,8 +26,8 @@ struct CoreObjectDescriptor { template CoreObjectDescriptor(const std::shared_ptr& value, T*) : - type(&typeid(T)), - actual_type(&typeid(auto_id)), + type(auto_id_t{}), + actual_type(auto_id_t{}), stump(&SlotInformationStump::s_stump), value(value), subscriber(MakeAutoFilterDescriptor(value)), @@ -55,6 +56,8 @@ struct CoreObjectDescriptor { (void) autowiring::fast_pointer_cast_initializer::sc_init; (void) autowiring::fast_pointer_cast_initializer::sc_init; (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) auto_id_t_init::init; + (void) auto_id_t_init::init; } /// @@ -66,12 +69,12 @@ struct CoreObjectDescriptor { {} // The type of the passed pointer - const std::type_info* type; + const auto_id type; // The "actual type" used by Autowiring. This type may differ from CoreObjectDescriptor::type in cases // where a type unifier is used, or if the concrete type is defined in an external module--for // instance, by a class factory. - const std::type_info* actual_type; + const auto_id actual_type; /// /// Used to obtain a list of slots defined on this type, for reflection purposes diff --git a/autowiring/DecorationDisposition.h b/autowiring/DecorationDisposition.h index 0b05cf9d0..3a15d226a 100644 --- a/autowiring/DecorationDisposition.h +++ b/autowiring/DecorationDisposition.h @@ -12,24 +12,24 @@ struct DecorationKey { DecorationKey(void) = default; DecorationKey(const DecorationKey& rhs) : - ti(rhs.ti), + id(rhs.id), tshift(rhs.tshift) {} - explicit DecorationKey(const std::type_info& ti, int tshift) : - ti(&ti), + explicit DecorationKey(auto_id id, int tshift) : + id(id), tshift(tshift) {} // The type index - const std::type_info* ti = nullptr; + auto_id id; // Zero refers to a decoration created on this packet, a positive number [tshift] indicates // a decoration attached [tshift] packets ago. int tshift = -1; bool operator==(const DecorationKey& rhs) const { - return ti == rhs.ti && tshift == rhs.tshift; + return id == rhs.id && tshift == rhs.tshift; } }; @@ -37,7 +37,7 @@ namespace std { template<> struct hash { size_t operator()(const DecorationKey& key) const { - return key.tshift + key.ti->hash_code(); + return key.tshift + (size_t)key.id.block; } }; } diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index 4b2d6be5c..ff93e42d5 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -60,7 +60,7 @@ class parallel { m_queueUpdated.wait(lk, [this]{ return !m_queue[typeid(T)].empty(); }); - return *static_cast(m_queue[typeid(T)].front()->ptr()); + return *static_cast(m_queue[typeid(T)].front().ptr()); } // Iterator that acts as a proxy to diff --git a/autowiring/SharedPointerSlot.h b/autowiring/SharedPointerSlot.h deleted file mode 100644 index 73c42b38b..000000000 --- a/autowiring/SharedPointerSlot.h +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. -#pragma once -#include "auto_id.h" -#include "autowiring_error.h" -#include "fast_pointer_cast.h" -#include "CoreObject.h" -#include "SlotInformation.h" -#include MEMORY_HEADER -#include -#include - -template -struct SharedPointerSlotT; - -/// -/// Specialized space-invariant implementation of boost::any for shared pointers -/// -/// -/// This implementation is superior to boost::any because it does not require the -/// use of the heap for allocations, and is slightly faster under teardown for -/// the same reason. -/// -struct SharedPointerSlot { -public: - SharedPointerSlot(void) {} - - explicit SharedPointerSlot(const SharedPointerSlot& rhs) { - *this = rhs; - } - - template - explicit SharedPointerSlot(const std::shared_ptr& rhs) { - // Delegate the remainder to the assign operation: - *this = rhs; - } - - // Base destructor, doesn't do anything because there's nothing to release from - // the base class - virtual ~SharedPointerSlot(void) {} - -protected: - // Space, used to store a shared pointer--by default, though, it's just empty. - unsigned char m_space[sizeof(std::shared_ptr)]; - - /// - /// Assignment routine - /// - /// - /// If this method is called, there will be a strong guarantee that the type of rhs - /// will be precisely equal to the type of this. - /// - /// Implementors MUST treat this as a type of constructor replacement. The contents - /// of m_space are indeterminate and certainly invalid, and should be treated as - /// uninitialized memory. - /// - virtual void assign(const SharedPointerSlot& rhs) {} - -public: - explicit operator bool(void) const { return !empty(); } - virtual operator std::shared_ptr(void) const { return std::shared_ptr(); } - virtual void* ptr(void) { return nullptr; } - virtual const void* ptr(void) const { return nullptr; } - - /// - /// Performs a placement new on the specified space with a type matching the current instance - /// - /// - /// This method will also initialize the returned space with a copy of the shared pointer held by this - /// slot. - /// - virtual void New(void* pSpace, size_t nBytes) const { - if(nBytes < sizeof(*this)) - throw std::runtime_error("Attempted to construct a SharedPointerSlot in a space that was too small"); - new (pSpace) SharedPointerSlot; - } - - /// - /// A void pointer to the underlying shared pointer implementation - /// - /// - /// Use this method with great caution. The return value may be safely cast to type std::shared_ptr - /// _if_ the correct type for T is known at compile time, but if this pointer is shared at runtime between - /// modules, compilation differences can change the layout of the shared pointer in subtle ways. - /// Generally speaking, this function should only be called by modules which are guaranteed to have - /// been statically linked in the same executable. - /// - const void* shared_ptr(void) const { return m_space; } - - /// - /// Attempts to dynamically assign this slot to the specified object without changing the current type - /// - /// True if the assignment succeeds - virtual bool try_assign(const std::shared_ptr& rhs) { - return false; - } - - /// - /// Alters the type of this slot to match the specified type - /// - /// The type to initialize this slot into - /// - /// This operation releases any previously held value, and causes the slot to hold nullptr with - /// the specified type - /// - template - SharedPointerSlotT& init(void) { - // Release what we're holding - reset(); - - // Reinitialize, now that the slot is empty - return *new (this) SharedPointerSlotT(); - } - - /// - /// True if this slot holds nothing - /// - virtual bool empty(void) const { return true; } - - /// - /// True if this pointer slot holds an instance of the specified type - /// - template - bool is(void) const { return type() == typeid(auto_id); } - - /// - /// Returns the template type of the shared pointer held in this slot, or typeid(void) if empty - /// - virtual const std::type_info& type(void) const { return typeid(void); } - - /// - /// Clears this type, if a shared pointer is currently held - /// - /// - /// This method will preserve the polymorphic type of this slot--IE, it does not change the return - /// value of this->type() - /// - virtual void reset(void) {} - - template - static const std::shared_ptr& null(void) { - static const std::shared_ptr s_empty; - return s_empty; - } - - /// - /// Attempts to coerce this type to the specified type - /// - template - const std::shared_ptr& as(void) const { - if (type() == typeid(void)) - // This is allowed, we always permit null to be cast to the requested type. - return null(); - - if (type() != typeid(auto_id)) - throw std::runtime_error("Attempted to obtain a shared pointer for an unrelated type"); - - // Instantiate the static cast with "false" because this function should not be attempting to - // instantiate any casts. - return static_cast*>(this)->get(); - } - - /// - /// Identical to as(), but performs no type safety checks - /// - template - const std::shared_ptr& as_unsafe(void) const { - static const std::shared_ptr s_empty; - - if (type() == typeid(void)) - // This is allowed, we always permit null to be cast to the requested type. - return s_empty; - - // Instantiate the static cast with "false" because this function should not be attempting to - // instantiate any casts. - return static_cast*>(this)->get(); - } - - bool operator<(const SharedPointerSlot& rhs) const { - return - &type() < &rhs.type() ? - true : - &type() == &rhs.type() ? - ptr() < rhs.ptr() : - false; - } - - /// - /// Comparison by reference. Comparison of unequal types always fails, - /// even when different type casts of the same instance are referenced. - /// - bool operator==(const SharedPointerSlot& rhs) const { - // Unequal types are always unequal - if(type() != rhs.type()) - return false; - return ptr() == rhs.ptr(); - } - - /// - /// Comparison by reference. Comparison of unequal types always fails, - /// even when different type casts of the same instance are referenced. - /// - template - bool operator==(const std::shared_ptr& rhs) const { - // Unequal types are always unequal - if(type() != typeid(auto_id)) - return false; - - // Everything lines up, coerce ourselves to the derived type and handoff the - // comparison behavior. - return (SharedPointerSlotT&)*this == rhs; - } - - /// - /// Specialization for the CoreObject base type - /// - bool operator==(const std::shared_ptr& rhs) const { - return this->operator std::shared_ptr() == rhs; - } - - /// - /// Copy assignment operator - /// - /// - /// Consumer beware: This is a transformative assignment. The true polymorphic - /// type will be carried from the right-hand side into this element, which is a - /// different behavior from how things are normally done during assignment. Other - /// than that, however, the behavior is very similar to boost::any's assignment - /// implementation. - /// - SharedPointerSlot& operator=(const SharedPointerSlot& rhs) { - // Our own stuff is going away, need to reset ourselves - this->~SharedPointerSlot(); - - // Placement construct the right-hand side into ourselves: - rhs.New(this, sizeof(*this)); - return *this; - } - - /// - /// In-place polymorphic transformer - /// - template - SharedPointerSlotT& operator=(const std::shared_ptr& rhs) { - if (type() == typeid(auto_id)) { - // We can just use the equivalence operator, no need to make two calls - *((SharedPointerSlotT*)this) = rhs; - return *((SharedPointerSlotT*)this); - } - - // Clear out what we're holding: - reset(); - - // Now we can safely reinitialize: - static_assert(sizeof(SharedPointerSlotT) == sizeof(*this), "Cannot instantiate a templated shared pointer slot on this type, it's too large to fit here"); - return *new (this) SharedPointerSlotT(rhs); - } -}; - -template -struct SharedPointerSlotT: - SharedPointerSlot -{ - SharedPointerSlotT(const std::shared_ptr& rhs = std::shared_ptr()) { - static_assert( - sizeof(std::shared_ptr) == sizeof(m_space), - "Slot instance is too large to fit in the base type" - ); - - // Make use of our space to make a shared pointer: - new (m_space) std::shared_ptr(rhs); - } - - SharedPointerSlotT(const SharedPointerSlotT& rhs) { - new (m_space) std::shared_ptr(rhs.get()); - } - - ~SharedPointerSlotT(void) override { - // Recast and in-place destroy our shared pointer: - get().~shared_ptr(); - } - -protected: - void assign(const SharedPointerSlot& rhs) override { - // Static cast rhs to our own type, we know a priori that the passed - // value will match so we elide the typical safety checks. - auto& rhsCasted = static_cast(rhs); - - // And now it's just a matter of copying things over. - new (m_space) std::shared_ptr(rhsCasted.get()); - } - -public: - /// - /// The shared pointer held by this slot - /// - std::shared_ptr& get(void) { return *(std::shared_ptr*)m_space; } - const std::shared_ptr& get(void) const { return *(std::shared_ptr*)m_space; } - - virtual void* ptr(void) override { - // At this level, because this type is runtime, we can only provide runtime detection of misuse - if(std::is_const::value) - throw autowiring_error("Attempted to obtain a non-const void pointer value from a const-type shared pointer"); - return (void*)get().get(); - } - virtual const void* ptr(void) const override { - return (const void*)get().get(); - } - - virtual void New(void* pSpace, size_t nBytes) const override { - if(nBytes < sizeof(*this)) - throw std::runtime_error("Attempted to construct a SharedPointerSlotT in a space that was too small"); - new (pSpace) SharedPointerSlotT(*this); - } - - bool empty(void) const override { return get() == nullptr; } - const std::type_info& type(void) const override { return typeid(auto_id); } - - void reset(void) override { - get().reset(); - } - - template - bool operator==(const std::shared_ptr& rhs) const { - return get() == rhs; - } - - // We have a better opeartor overload for type T: - void operator=(const std::shared_ptr& rhs) { - get() = rhs; - } - - T* operator->(void) const { - return get().get(); - } -}; - -template -struct SharedPointerSlotT: - SharedPointerSlotT -{ - SharedPointerSlotT(void) {} - - SharedPointerSlotT(const std::shared_ptr& rhs) : - SharedPointerSlotT(rhs) - {} - - SharedPointerSlotT(const SharedPointerSlotT& rhs) : - SharedPointerSlotT(rhs) - {} - - SharedPointerSlotT(SharedPointerSlotT&& rhs) : - SharedPointerSlotT(std::move(rhs)) - {} - - virtual void New(void* pSpace, size_t nBytes) const override { - if (nBytes < sizeof(*this)) - throw std::runtime_error("Attempted to construct a SharedPointerSlotT in a space that was too small"); - new (pSpace) SharedPointerSlotT(*this); - } - - bool try_assign(const std::shared_ptr& rhs) override { - // Just perform a dynamic cast: - auto casted = autowiring::fast_pointer_cast_blind::cast(rhs); - if (!casted) - return false; - - SharedPointerSlotT::get() = casted; - return true; - } - - virtual operator std::shared_ptr(void) const override { - return autowiring::fast_pointer_cast_blind::cast(SharedPointerSlotT::get()); - } - - using SharedPointerSlotT::operator=; - using SharedPointerSlotT::operator==; -}; diff --git a/autowiring/SlotInformation.h b/autowiring/SlotInformation.h index 903828145..43fd072b8 100644 --- a/autowiring/SlotInformation.h +++ b/autowiring/SlotInformation.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include "auto_id.h" #include "TypeUnifier.h" #include #include MEMORY_HEADER @@ -10,7 +11,7 @@ class DeferrableAutowiring; /// Represents information about a single slot detected as having been declared in a context member /// struct SlotInformation { - SlotInformation(const SlotInformation* pFlink, const std::type_info& type, size_t slotOffset, bool autoRequired) : + SlotInformation(const SlotInformation* pFlink, auto_id type, size_t slotOffset, bool autoRequired) : pFlink(pFlink), type(type), slotOffset(slotOffset), @@ -21,7 +22,7 @@ struct SlotInformation { const SlotInformation* const pFlink; // The type of this slot: - const std::type_info& type; + const auto_id type; // The offset of this slot relative to the base of the enclosing object size_t slotOffset; diff --git a/autowiring/auto_arg.h b/autowiring/auto_arg.h index 66e91ea04..77dd310b5 100644 --- a/autowiring/auto_arg.h +++ b/autowiring/auto_arg.h @@ -1,7 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "auto_id.h" -#include "SharedPointerSlot.h" class AutoPacket; template class auto_in; @@ -30,7 +29,7 @@ class auto_arg public: typedef const T& type; typedef type arg_type; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; static const bool is_shared = false; @@ -39,6 +38,7 @@ class auto_arg template static const T& arg(C& packet) { + (void) auto_id_t_init::init; return packet.template Get(); } }; @@ -68,7 +68,7 @@ class auto_arg> public: typedef const std::shared_ptr& type; typedef type arg_type; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; static const bool is_shared = true; @@ -77,9 +77,12 @@ class auto_arg> template static const std::shared_ptr& arg(C& packet) { + (void) auto_id_t_init::init; + + static const std::shared_ptr null; auto retVal = packet.template GetShared(); if (!retVal) - return SharedPointerSlot::null(); + return null; return *retVal; } }; @@ -94,7 +97,7 @@ class auto_arg static_assert(std::is_const::value, "Pointer-typed input parameters must point to a const-qualified type (T must be const-qualified)"); typedef T* type; typedef T* arg_type; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; static const bool is_shared = false; @@ -103,6 +106,7 @@ class auto_arg template static const T* arg(C& packet) { + (void) auto_id_t_init::init; return packet.template Get(); } }; @@ -171,7 +175,7 @@ class auto_arg operator T&() const { return arg; } }; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = false; static const bool is_output = true; static const bool is_shared = false; @@ -179,6 +183,9 @@ class auto_arg static const int tshift = 0; static std::shared_ptr arg(AutoPacket& packet) { + // Need to ensure the identifier is initialized properly, we can do a full + // initialization because this is a byref output type + (void) auto_id_t_init::init; return detail::auto_arg_ctor_helper::arg(packet); } @@ -210,6 +217,7 @@ class auto_arg&>: template static std::shared_ptr arg(C&) { + (void) auto_id_t_init::init; return std::shared_ptr(); } }; @@ -234,7 +242,7 @@ class auto_arg public: typedef CoreContext& type; typedef CoreContext& arg_type; - typedef CoreContext id_type; + typedef auto_id_t id_type; static const bool is_input = false; static const bool is_output = false; static const bool is_shared = false; @@ -253,7 +261,7 @@ class auto_arg> public: typedef std::shared_ptr type; typedef std::shared_ptr arg_type; - typedef CoreContext id_type; + typedef auto_id_t id_type; static const bool is_input = false; static const bool is_output = false; static const bool is_shared = false; @@ -288,7 +296,7 @@ class auto_arg operator const T**(void) const { return ptr.get(); } }; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; static const bool is_shared = false; @@ -297,6 +305,7 @@ class auto_arg template static type arg(C& packet) { + (void) auto_id_t_init::init; return type{packet.template GetAll()}; } }; @@ -329,7 +338,7 @@ class auto_arg*> operator std::shared_ptr*(void) const { return ptr.get(); } }; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; static const bool is_shared = false; @@ -338,6 +347,7 @@ class auto_arg*> template static type arg(C& packet) { + (void) auto_id_t_init::init; return type{packet.template GetAllShared()}; } }; diff --git a/autowiring/auto_id.h b/autowiring/auto_id.h index 28078838a..466616183 100644 --- a/autowiring/auto_id.h +++ b/autowiring/auto_id.h @@ -1,20 +1,198 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once -#include TYPE_TRAITS_HEADER +#include "fast_pointer_cast.h" +#include #include TYPE_INDEX_HEADER +namespace autowiring { + /// + /// Returns an index for use with the auto_id system + /// + int CreateIndex(void); + + struct auto_id_block { + auto_id_block(void) = default; + + auto_id_block(int index): + index(index), + pToObj(NullToObj), + pFromObj(NullFromObj) + {} + + static std::shared_ptr NullToObj(const std::shared_ptr&) { return nullptr; } + static std::shared_ptr NullFromObj(const std::shared_ptr&) { return nullptr; } + + template + auto_id_block( + int index, + std::shared_ptr(*pToObj)(const std::shared_ptr&), + std::shared_ptr(*pFromObj)(const std::shared_ptr&) + ): + index(index), + ti(&typeid(T)), + ncb(sizeof(T)), + pToObj( + reinterpret_cast(*)(const std::shared_ptr&)>(pToObj) + ), + pFromObj( + reinterpret_cast(*)(const std::shared_ptr&)>(pFromObj) + ) + {} + + // Index and underlying type + int index; + const std::type_info* ti; + + // General properties of the underlying type + size_t ncb; + + // Generic fast casters to CoreObject + std::shared_ptr(*pToObj)(const std::shared_ptr&); + std::shared_ptr(*pFromObj)(const std::shared_ptr&); + + bool operator==(const auto_id_block& rhs) const { return index == rhs.index && ti == rhs.ti; } + bool operator!=(const auto_id_block& rhs) const { return !(*this == rhs); } + }; +} + +/// +/// Base type identifier structure +/// +struct auto_id { + auto_id(void) = default; + auto_id(const autowiring::auto_id_block* block) : + block(block) + {} + + // A pointer to the identifier block containing the index + const autowiring::auto_id_block* block = nullptr; + + bool operator==(auto_id rhs) const { + return block == rhs.block; + } + + explicit operator bool(void) const { + return block && block->index; + } +}; + +namespace std { + template<> + struct hash { + size_t operator()(const auto_id& id) const { + return id.block->index; + } + }; +} + /// /// Identifier sigil structure /// /// -/// This structure is used to avoid attempts to obtain the typeid of an incomplete type. -/// It's only used in cases where typeid(T) would have been used alone. +/// This structure is used to avoid uses of the very inefficient std::type_info comparison +/// and hashing operators. /// template -struct auto_id { - - // Return this type_info for this type with 'const' and 'volatile' removed - static const std::type_info& key(void) { - return typeid(auto_id::type>); +struct auto_id_t: + auto_id +{ + // The identifier block proper + static autowiring::auto_id_block s_block; + + auto_id_t(void) : + auto_id(&s_block) + { + static_assert(sizeof(auto_id_t) == sizeof(auto_id), "auto_id_t must not define additional members"); } }; + +// Recursive auto_id_t definitions are not allowed, don't try to use them +template +struct auto_id_t>; + +template<> +struct auto_id_t: + auto_id +{ + // Void is defined to have an index of zero + static const autowiring::auto_id_block s_block; + + auto_id_t(void) : + auto_id(&s_block) + {} +}; + +/// +/// Invokes the ctor for the auto_id_t field. +/// +/// +/// Performs the full initialization. We have to do template shenanegans to prevent an accidental +/// mention of sizeof(T) or typeid(T), because one or both of these might not be available in this +/// context depending on what types are available at this parse point in the compilation unit and +/// what platform we are currently compiling for. +/// +template +class auto_id_t_init; + +// Performs full initialization for type T +template +class auto_id_t_init +{ + auto_id_t_init(void) { + auto* ptr = &auto_id_t::s_block; + + // We are instantiating the fast pointer casters here, also initialize the blind block: + (void) autowiring::fast_pointer_cast_initializer::sc_init; + (void) autowiring::fast_pointer_cast_initializer::sc_init; + + // Only create an index if one is not already assigned. We rely on the fact that the "index" + // field will be allocated in the segment, where we can be assured it will default to 0 due + // to the nature of the segment. + new (ptr) autowiring::auto_id_block( + ptr->index ? ptr->index : autowiring::CreateIndex(), + &autowiring::fast_pointer_cast, + &autowiring::fast_pointer_cast + ); + } + +public: + static const auto_id_t_init init; +}; + + +// Specialization just initialize the traits known to be available on T +template +class auto_id_t_init +{ + auto_id_t_init(void) { + auto* ptr = &auto_id_t::s_block; + new (ptr) autowiring::auto_id_block(ptr->index ? ptr->index : autowiring::CreateIndex()); + } + +public: + static const auto_id_t_init init; +}; + +template +class auto_id_t_init : public auto_id_t_init{}; +template +class auto_id_t_init : public auto_id_t_init{}; +template +class auto_id_t_init : public auto_id_t_init{}; +template +class auto_id_t_init : public auto_id_t_init{}; + +template +const auto_id_t_init auto_id_t_init::init; + +template +const auto_id_t_init auto_id_t_init::init; + +template +struct auto_id_t : auto_id_t {}; + +template +struct auto_id_t : auto_id_t {}; + +template +autowiring::auto_id_block auto_id_t::s_block; diff --git a/autowiring/auto_in.h b/autowiring/auto_in.h index a55fe1a2d..c9138b81a 100644 --- a/autowiring/auto_in.h +++ b/autowiring/auto_in.h @@ -16,10 +16,6 @@ template class auto_in { public: - typedef std::shared_ptr id_type; - static const bool is_input = true; - static const bool is_output = false; - auto_in(const T& value) : m_value(value) {} diff --git a/autowiring/auto_out.h b/autowiring/auto_out.h index 02801cf5e..5622f2988 100644 --- a/autowiring/auto_out.h +++ b/autowiring/auto_out.h @@ -120,7 +120,7 @@ class auto_arg> typedef auto_out type; typedef auto_out arg_type; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = false; static const bool is_output = true; static const bool is_shared = false; diff --git a/autowiring/auto_prev.h b/autowiring/auto_prev.h index c88b4bba6..d852e4d96 100644 --- a/autowiring/auto_prev.h +++ b/autowiring/auto_prev.h @@ -40,7 +40,7 @@ class auto_arg> public: typedef auto_prev type; typedef auto_prev arg_type; - typedef auto_id id_type; + typedef auto_id_t id_type; static const bool is_input = true; static const bool is_output = false; diff --git a/autowiring/demangle.h b/autowiring/demangle.h index 4c05214bc..3f4997dcf 100644 --- a/autowiring/demangle.h +++ b/autowiring/demangle.h @@ -4,6 +4,7 @@ #include struct AnySharedPointer; +struct auto_id; // // Demangle type names on mac and linux. @@ -17,6 +18,7 @@ namespace autowiring { std::string demangle(const std::type_index* ti); std::string demangle(const AnySharedPointer& ptr); + std::string demangle(auto_id id); /// Returns a human-readable std::string describing the type. template diff --git a/autowiring/noop.h b/autowiring/noop.h new file mode 100644 index 000000000..00f442428 --- /dev/null +++ b/autowiring/noop.h @@ -0,0 +1,7 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once + +namespace autowiring { + template + void noop(Args...) {} +} \ No newline at end of file diff --git a/src/autonet/AutoNetServerImpl.cpp b/src/autonet/AutoNetServerImpl.cpp index cf92fb9eb..4542f40ca 100644 --- a/src/autonet/AutoNetServerImpl.cpp +++ b/src/autonet/AutoNetServerImpl.cpp @@ -186,7 +186,7 @@ void AutoNetServerImpl::NewObject(CoreContext& ctxt, const CoreObjectDescriptor& // Add object data objData["name"] = autowiring::demangle(object.type); - objData["id"] = autowiring::demangle(object.value.slot()->type()); + objData["id"] = autowiring::demangle(object.value.type()); // Add slots for this object { @@ -227,8 +227,8 @@ void AutoNetServerImpl::NewObject(CoreContext& ctxt, const CoreObjectDescriptor& if (!object.subscriber.empty()) { Json::object args; for (auto pArg = object.subscriber.GetAutoFilterArguments(); *pArg; ++pArg) { - args[autowiring::demangle(pArg->ti)] = Json::object{ - {"id", autowiring::demangle(pArg->ti)}, + args[autowiring::demangle(pArg->id)] = Json::object{ + {"id", autowiring::demangle(pArg->id)}, {"isInput", pArg->is_input}, {"isOutput", pArg->is_output} }; diff --git a/src/autowiring/AnySharedPointer.cpp b/src/autowiring/AnySharedPointer.cpp index 31d48ffca..45d4995c5 100644 --- a/src/autowiring/AnySharedPointer.cpp +++ b/src/autowiring/AnySharedPointer.cpp @@ -2,31 +2,12 @@ #include "stdafx.h" #include "AnySharedPointer.h" -AnySharedPointer::AnySharedPointer(void) { - new (m_space) SharedPointerSlot; -} - -AnySharedPointer::AnySharedPointer(AnySharedPointer&& rhs) -{ - new (m_space) SharedPointerSlot(std::move(*rhs.slot())); -} - -AnySharedPointer::AnySharedPointer(const AnySharedPointer& rhs) { - new (m_space) SharedPointerSlot(*rhs.slot()); -} - -AnySharedPointer::AnySharedPointer(const SharedPointerSlot&& rhs) { - new (m_space) SharedPointerSlot(std::move(rhs)); -} - -AnySharedPointer::AnySharedPointer(const SharedPointerSlot& rhs){ - new (m_space) SharedPointerSlot(rhs); -} - +AnySharedPointer::AnySharedPointer(AnySharedPointer&& rhs) : + m_ti(rhs.m_ti), + m_ptr(std::move(rhs.m_ptr)) +{} AnySharedPointer::~AnySharedPointer(void) { - // Pass control to the *real* destructor: - slot()->~SharedPointerSlot(); } static_assert(sizeof(AnySharedPointerT) == sizeof(AnySharedPointer), "AnySharedPointer realization cannot have members"); diff --git a/src/autowiring/AutoFilterDescriptor.cpp b/src/autowiring/AutoFilterDescriptor.cpp index c0cdffabb..e7c8d3bab 100644 --- a/src/autowiring/AutoFilterDescriptor.cpp +++ b/src/autowiring/AutoFilterDescriptor.cpp @@ -2,8 +2,8 @@ #include "stdafx.h" #include "AutoFilterDescriptor.h" -AutoFilterDescriptorStub::AutoFilterDescriptorStub(const std::type_info* pType, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall) : - m_pType(pType), +AutoFilterDescriptorStub::AutoFilterDescriptorStub(auto_id type, autowiring::altitude altitude, const AutoFilterArgument* pArgs, bool deferred, t_extractedCall pCall) : + m_type(type), m_altitude(altitude), m_pArgs(pArgs), m_deferred(deferred), @@ -20,21 +20,21 @@ AutoFilterDescriptorStub::AutoFilterDescriptorStub(const std::type_info* pType, bool AutoFilterDescriptorStub::Provides(const std::type_info& ti) const { for (size_t i = GetArity(); i--;) - if (*m_pArgs[i].ti == ti) + if (*m_pArgs[i].id.block->ti == ti) return m_pArgs[i].is_output; return false; } bool AutoFilterDescriptorStub::Consumes(const std::type_info& ti) const { for (size_t i = GetArity(); i--;) - if (*m_pArgs[i].ti == ti) + if (*m_pArgs[i].id.block->ti == ti) return m_pArgs[i].is_output; return false; } -const AutoFilterArgument* AutoFilterDescriptorStub::GetArgumentType(const std::type_info* argType) const { +const AutoFilterArgument* AutoFilterDescriptorStub::GetArgumentType(auto_id argType) const { for (auto pArg = m_pArgs; *pArg; pArg++) - if (pArg->ti == argType) + if (pArg->id == argType) return pArg; return nullptr; } diff --git a/src/autowiring/AutoPacket.cpp b/src/autowiring/AutoPacket.cpp index b9f638b61..fe7053ee1 100644 --- a/src/autowiring/AutoPacket.cpp +++ b/src/autowiring/AutoPacket.cpp @@ -16,7 +16,10 @@ using namespace autowiring; AutoPacket::AutoPacket(AutoPacketFactory& factory, std::shared_ptr&& outstanding): m_parentFactory(std::static_pointer_cast(factory.shared_from_this())), m_outstanding(std::move(outstanding)) -{} +{ + // Need to ensure our identity type is instantiated + (void) auto_id_t_init::init; +} AutoPacket::~AutoPacket(void) { m_parentFactory->RecordPacketDuration( @@ -29,7 +32,7 @@ AutoPacket::~AutoPacket(void) { // originating from this packet as unsatisfiable for (auto& pair : m_decoration_map) if (!pair.first.tshift && pair.second.m_state != DispositionState::Complete) - MarkSuccessorsUnsatisfiable(DecorationKey(*pair.first.ti, 0)); + MarkSuccessorsUnsatisfiable(DecorationKey(pair.first.id, 0)); // Needed for the AutoPacketGraph NotifyTeardownListeners(); @@ -63,7 +66,7 @@ DecorationDisposition& AutoPacket::DecorateImmediateUnsafe(const DecorationKey& if (dec.m_state != DispositionState::Unsatisfied) { std::stringstream ss; - ss << "Cannot perform immediate decoration with type " << autowiring::demangle(key.ti) + ss << "Cannot perform immediate decoration with type " << autowiring::demangle(key.id) << ", the requested decoration already exists"; throw std::runtime_error(ss.str()); } @@ -76,14 +79,14 @@ DecorationDisposition& AutoPacket::DecorateImmediateUnsafe(const DecorationKey& void AutoPacket::AddSatCounterUnsafe(SatCounter& satCounter) { for(auto pCur = satCounter.GetAutoFilterArguments(); *pCur; pCur++) { - DecorationKey key(*pCur->ti, pCur->tshift); + DecorationKey key(pCur->id, pCur->tshift); DecorationDisposition& entry = m_decoration_map[key]; // Decide what to do with this entry: if (pCur->is_input) { if (entry.m_publishers.size() > 1 && !pCur->is_multi) { std::stringstream ss; - ss << "Cannot add listener for multi-broadcast type " << autowiring::demangle(pCur->ti); + ss << "Cannot add listener for multi-broadcast type " << autowiring::demangle(pCur->id); throw std::runtime_error(ss.str()); } @@ -111,9 +114,9 @@ void AutoPacket::AddSatCounterUnsafe(SatCounter& satCounter) { if(!entry.m_publishers.empty()) for (const auto& subscriber : entry.m_subscribers) for (auto pOther = subscriber.satCounter->GetAutoFilterArguments(); *pOther; pOther++) { - if (*pOther->ti == *pCur->ti && !pOther->is_multi) { + if (pOther->id == pCur->id && !pOther->is_multi) { std::stringstream ss; - ss << "Added identical data broadcasts of type " << autowiring::demangle(pCur->ti) << " with existing subscriber."; + ss << "Added identical data broadcasts of type " << autowiring::demangle(pCur->id) << " with existing subscriber."; throw std::runtime_error(ss.str()); } } @@ -123,7 +126,7 @@ void AutoPacket::AddSatCounterUnsafe(SatCounter& satCounter) { // Make sure decorations exist for timeshifts less that key's timeshift for (int tshift = 0; tshift < key.tshift; ++tshift) - m_decoration_map[DecorationKey(*key.ti, tshift)]; + m_decoration_map[DecorationKey(key.id, tshift)]; } } @@ -229,7 +232,7 @@ void AutoPacket::UpdateSatisfactionUnsafe(std::unique_lock lk, const // Mark all unsatisfiable output types for (auto unsatOutputArg : unsatOutputArgs) { // One more producer run, even though we couldn't attach any new decorations - auto& entry = m_decoration_map[DecorationKey{*unsatOutputArg->ti, 0}]; + auto& entry = m_decoration_map[DecorationKey{unsatOutputArg->id, 0}]; entry.m_nProducersRun++; // Now recurse on this entry @@ -388,23 +391,23 @@ size_t AutoPacket::HasPublishers(const DecorationKey& key) const { q->second.m_publishers.size(); } -const SatCounter& AutoPacket::GetSatisfaction(const std::type_info& subscriber) const { +const SatCounter& AutoPacket::GetSatisfaction(auto_id subscriber) const { std::lock_guard lk(m_lock); for (auto* sat = m_firstCounter; sat; sat = sat->flink) - if (sat->GetType() == &subscriber) + if (sat->GetType() == subscriber) return *sat; throw autowiring_error("Attempted to get the satisfaction counter for an unavailable subscriber"); } void AutoPacket::ThrowNotDecoratedException(const DecorationKey& key) { std::stringstream ss; - ss << "Attempted to obtain a type " << autowiring::demangle(key.ti) << " which was not decorated on this packet"; + ss << "Attempted to obtain a type " << autowiring::demangle(key.id) << " which was not decorated on this packet"; throw std::runtime_error(ss.str()); } void AutoPacket::ThrowMultiplyDecoratedException(const DecorationKey& key) { std::stringstream ss; - ss << "Attempted to obtain a type " << autowiring::demangle(key.ti) << " which was decorated more than once on this packet"; + ss << "Attempted to obtain a type " << autowiring::demangle(key.id) << " which was decorated more than once on this packet"; throw std::runtime_error(ss.str()); } @@ -498,12 +501,12 @@ bool AutoPacket::Wait(std::condition_variable& cv, const AutoFilterArgument* inp AutoFilterDescriptor( stub, AutoFilterDescriptorStub( - &typeid(AutoPacketFactory), + auto_id_t{}, autowiring::altitude::Dispatch, inputs, false, [] (const AnySharedPointer& obj, AutoPacket&) { - auto stub = obj->as(); + auto stub = obj.as(); // Completed, mark the output as satisfied and update the condition variable std::lock_guard(stub->packet.m_lock); diff --git a/src/autowiring/AutoPacketFactory.cpp b/src/autowiring/AutoPacketFactory.cpp index ba87033d1..840e0c00c 100644 --- a/src/autowiring/AutoPacketFactory.cpp +++ b/src/autowiring/AutoPacketFactory.cpp @@ -170,7 +170,7 @@ void AutoPacketFactory::operator-=(const AutoFilterDescriptor& desc) { RemoveSubscriber(desc); } -AutoFilterDescriptor AutoPacketFactory::GetTypeDescriptorUnsafe(const std::type_info* nodeType) { +AutoFilterDescriptor AutoPacketFactory::GetTypeDescriptorUnsafe(auto_id nodeType) { //ASSUME: type_info uniquely specifies descriptor for (auto& af : m_autoFilters) if (af.GetAutoFilterTypeInfo() == nodeType) @@ -208,7 +208,6 @@ void AutoPacketFactory::ResetPacketStatistics(void) { } template struct SlotInformationStump; -template const std::shared_ptr& SharedPointerSlot::as(void) const; template std::shared_ptr autowiring::fast_pointer_cast(const std::shared_ptr& Other); template class RegType; template struct autowiring::fast_pointer_cast_blind; diff --git a/src/autowiring/AutoPacketGraph.cpp b/src/autowiring/AutoPacketGraph.cpp index 99593892d..c9966bca6 100644 --- a/src/autowiring/AutoPacketGraph.cpp +++ b/src/autowiring/AutoPacketGraph.cpp @@ -15,18 +15,6 @@ AutoPacketGraph::AutoPacketGraph() { } -std::string AutoPacketGraph::DemangleTypeName(const std::type_info* type_info) const { - std::string demangled = autowiring::demangle(type_info); - - size_t demangledLength = demangled.length(); - size_t newLength = - demangled[demangledLength - 2] == ' ' ? - demangledLength - 10 : - demangledLength - 9; - - return demangled.substr(demangled.find("<") + 1, newLength); -} - void AutoPacketGraph::LoadEdges() { std::lock_guard lk(m_lock); @@ -35,23 +23,23 @@ void AutoPacketGraph::LoadEdges() { for (auto& descriptor : descriptors) { for(auto pCur = descriptor.GetAutoFilterArguments(); *pCur; pCur++) { - const std::type_info& type_info = *pCur->ti; + auto_id id = pCur->id; // Skip the AutoPacketGraph - const std::type_info& descType = m_factory->GetContext()->GetAutoTypeId(descriptor.GetAutoFilter()); - if (descType == typeid(AutoPacketGraph)) { + const auto_id descType = m_factory->GetContext()->GetAutoTypeId(descriptor.GetAutoFilter()); + if (descType == auto_id_t{}) { continue; } if (pCur->is_input) { - DeliveryEdge edge { &type_info, descriptor, true }; + DeliveryEdge edge { id, descriptor, true }; if (m_deliveryGraph.find(edge) == m_deliveryGraph.end()) { m_deliveryGraph[edge] = 0; } } if (pCur->is_output) { - DeliveryEdge edge { &type_info, descriptor, false }; + DeliveryEdge edge { id, descriptor, false }; if (m_deliveryGraph.find(edge) == m_deliveryGraph.end()) { m_deliveryGraph[edge] = 0; } @@ -60,8 +48,8 @@ void AutoPacketGraph::LoadEdges() { } } -void AutoPacketGraph::RecordDelivery(const std::type_info* ti, const AutoFilterDescriptor& descriptor, bool input) { - DeliveryEdge edge { ti, descriptor, input }; +void AutoPacketGraph::RecordDelivery(auto_id id, const AutoFilterDescriptor& descriptor, bool input) { + DeliveryEdge edge { id, descriptor, input }; auto itr = m_deliveryGraph.find(edge); assert(itr != m_deliveryGraph.end()); @@ -82,7 +70,7 @@ void AutoPacketGraph::AutoFilter(AutoPacket& packet) { packet.AddTeardownListener([this, &packet] () { for (auto& cur : packet.GetDecorations()) { auto& decoration = cur.second; - auto type = cur.first.ti; + auto type = cur.first.id; for (auto& publisher : decoration.m_publishers) if (!publisher->remaining) @@ -90,8 +78,8 @@ void AutoPacketGraph::AutoFilter(AutoPacket& packet) { for (auto& subscriber : decoration.m_subscribers) { // Skip the AutoPacketGraph - const std::type_info& descType = m_factory->GetContext()->GetAutoTypeId(subscriber.satCounter->GetAutoFilter()); - if (descType == typeid(AutoPacketGraph)) + auto_id descType = m_factory->GetContext()->GetAutoTypeId(subscriber.satCounter->GetAutoFilter()); + if (descType == auto_id_t{}) continue; if (subscriber.satCounter->remaining) @@ -122,12 +110,12 @@ bool AutoPacketGraph::WriteGV(const std::string& filename, bool numPackets) cons auto count = itr.second; // Skip the AutoPacketGraph - const std::type_info& descType = m_factory->GetContext()->GetAutoTypeId(descriptor.GetAutoFilter()); - if (descType == typeid(AutoPacketGraph)) { + auto_id descType = m_factory->GetContext()->GetAutoTypeId(descriptor.GetAutoFilter()); + if (descType == auto_id_t{}) { continue; } - std::string typeName = DemangleTypeName(type); + std::string typeName = autowiring::demangle(type); std::string descriptorName = autowiring::demangle(descType); // Get a unique set of types/descriptors diff --git a/src/autowiring/AutowiringDebug.cpp b/src/autowiring/AutowiringDebug.cpp index b56307077..dfaf63e88 100644 --- a/src/autowiring/AutowiringDebug.cpp +++ b/src/autowiring/AutowiringDebug.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace autowiring; @@ -36,11 +37,11 @@ bool autowiring::dbg::IsLambda(const std::type_info& ti) { } #endif -static std::string DemangleWithAutoID(const std::type_info& ti) { - auto retVal = demangle(ti); +static std::string DemangleWithAutoID(auto_id id) { + auto retVal = demangle(id.block->ti); // prefix is at the beginning of the string, skip over it - static const char prefix [] = "auto_id<"; + static const char prefix [] = "auto_id_t<"; if (retVal.compare(0, sizeof(prefix) - 1, prefix) == 0) { size_t off = sizeof(prefix) - 1; @@ -150,10 +151,10 @@ std::string autowiring::dbg::AutoFilterInfo(const char* name) { // Who provides this input? for (const auto& providerDesc : descs) { - auto providerArg = providerDesc.GetArgumentType(args[i].ti); + auto providerArg = providerDesc.GetArgumentType(args[i].id); if (providerArg && providerArg->is_output) { // Need to print information about this provider: - os << demangle(*args[i].ti) << ' ' << std::string(nLevels, ' ') << demangle(*providerDesc.GetType()) << std::endl; + os << demangle(args[i].id) << ' ' << std::string(nLevels, ' ') << demangle(providerDesc.GetType()) << std::endl; // The current descriptor provides an input to the parent, recurse fnCall(providerDesc, nLevels + 1); @@ -179,24 +180,24 @@ std::vector autowiring::dbg::ListRootDecorations(void) { const std::vector descs = factory->GetAutoFilters(); // Get all baseline descriptor types, figure out what isn't satisfied: - std::unordered_map outputs; - std::unordered_map inputs; + std::unordered_set outputs; + std::unordered_set inputs; for (const auto& desc : descs) { auto args = desc.GetAutoFilterArguments(); for (size_t i = 0; i < desc.GetArity(); i++) - (args[i].is_output ? outputs : inputs)[*args[i].ti] = args[i].ti; + (args[i].is_output ? outputs : inputs).insert(args[i].id); } // Remove all inputs that exist in the outputs set: for (auto& output : outputs) - inputs.erase(*output.second); + inputs.erase(output); // Any remaining inputs are unsatisfied std::vector retVal; - for (auto& input : inputs) - if (input.second) - retVal.push_back(DemangleWithAutoID(*input.second)); + for (auto_id input : inputs) + if (input) + retVal.push_back(DemangleWithAutoID(input)); return retVal; } @@ -249,7 +250,7 @@ void autowiring::dbg::WriteAutoFilterGraph(std::ostream& os, CoreContext& ctxt) for (size_t i = 0; i < desc.GetArity(); i++) { const AutoFilterArgument& arg = args[i]; - std::string decoration = DemangleWithAutoID(*arg.ti); + std::string decoration = DemangleWithAutoID(arg.id); // Assign each decoration and ID if (decorations.find(decoration) == decorations.end()) { diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index c8eadefe3..37086fd52 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -18,6 +18,7 @@ set(Autowiring_SRCS auto_arg.h auto_arg.cpp auto_id.h + auto_id.cpp auto_in.h auto_future.h auto_out.h @@ -138,6 +139,7 @@ set(Autowiring_SRCS MemoEntry.cpp MicroAutoFilter.h MicroBolt.h + noop.h NullPool.h NullPool.cpp ObjectPool.h @@ -148,7 +150,6 @@ set(Autowiring_SRCS CoreObjectDescriptor.h SatCounter.h SatCounter.cpp - SharedPointerSlot.h SlotInformation.cpp SlotInformation.h SystemThreadPool.cpp diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 82f4ebd26..88edb5e00 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -189,16 +189,16 @@ std::shared_ptr CoreContext::NextSibling(void) const { return std::shared_ptr(); } -const std::type_info& CoreContext::GetAutoTypeId(const AnySharedPointer& ptr) const { +auto_id CoreContext::GetAutoTypeId(const AnySharedPointer& ptr) const { std::lock_guard lk(m_stateBlock->m_lock); - const std::type_info& ti = ptr->type(); + auto ti = ptr.type(); auto q = m_typeMemos.find(ti); if (q == m_typeMemos.end() || !q->second.pObjTraits) throw autowiring_error("Attempted to obtain the true type of a shared pointer that was not a member of this context"); const CoreObjectDescriptor* pObjTraits = q->second.pObjTraits; - return *pObjTraits->type; + return pObjTraits->type; } void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { @@ -210,7 +210,7 @@ void CoreContext::AddInternal(const CoreObjectDescriptor& traits) { // concrete type defined in another context or potentially a unifier type. Creating a slot here // is also undesirable because the complete type is not available and we can't create a dynaimc // caster to identify when this slot gets satisfied. If a slot was non-local, overwrite it. - auto q = m_typeMemos.find(*traits.actual_type); + auto q = m_typeMemos.find(traits.actual_type); if(q != m_typeMemos.end()) { auto& v = q->second; @@ -272,7 +272,7 @@ void CoreContext::AddInternal(const AnySharedPointer& ptr) { std::unique_lock lk(m_stateBlock->m_lock); // Verify that this type isn't already satisfied - MemoEntry& entry = m_typeMemos[ptr->type()]; + MemoEntry& entry = m_typeMemos[ptr.type()]; if (entry.m_value) throw autowiring_error("This interface is already present in the context"); @@ -287,7 +287,7 @@ void CoreContext::FindByType(AnySharedPointer& reference, bool localOnly) const } MemoEntry& CoreContext::FindByTypeUnsafe(AnySharedPointer& reference, bool localOnly) const { - const std::type_info& type = reference->type(); + auto_id type = reference.type(); // If we've attempted to search for this type before, we will return the value of the memo immediately: auto q = m_typeMemos.find(type); @@ -302,7 +302,7 @@ MemoEntry& CoreContext::FindByTypeUnsafe(AnySharedPointer& reference, bool local // Resolve based on iterated dynamic casts for each concrete type: const CoreObjectDescriptor* pObjTraits = nullptr; for(const auto& type : m_concreteTypes) { - if(!reference->try_assign(*type.value)) + if(!reference.try_assign(type.value)) // No match, try the next entry continue; @@ -756,7 +756,7 @@ void CoreContext::Dump(std::ostream& os) const { for(const auto& entry : m_typeMemos) { os << demangle(entry.first); - const void* pObj = entry.second.m_value->ptr(); + const void* pObj = entry.second.m_value.ptr(); if(pObj) os << " 0x" << std::hex << pObj; os << std::endl; @@ -904,7 +904,7 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // Determine whether the current candidate element satisfies the autowiring we are considering. // This is done internally via a dynamic cast on the interface type for which this polymorphic // base type was constructed. - if (!value.m_value->try_assign(entry.pCoreObject)) + if (!value.m_value.try_assign(entry.pCoreObject)) continue; // Success, assign the traits diff --git a/src/autowiring/JunctionBoxBase.cpp b/src/autowiring/JunctionBoxBase.cpp index 84bd743a1..e5cc2e843 100644 --- a/src/autowiring/JunctionBoxBase.cpp +++ b/src/autowiring/JunctionBoxBase.cpp @@ -15,7 +15,7 @@ void JunctionBoxBase::TerminateAll(const std::list>& } void JunctionBoxBase::FilterFiringException(const AnySharedPointer& pRecipient) const { - std::shared_ptr obj = *pRecipient; + std::shared_ptr obj = pRecipient.as_obj(); // Obtain the current context and pass control: CoreContext::CurrentContext()->FilterFiringException(this, obj.get()); diff --git a/src/autowiring/SatCounter.cpp b/src/autowiring/SatCounter.cpp index 7d2bbba97..b4b8206d8 100644 --- a/src/autowiring/SatCounter.cpp +++ b/src/autowiring/SatCounter.cpp @@ -5,6 +5,6 @@ void SatCounter::ThrowRepeatedCallException(void) const { std::stringstream ss; - ss << "Repeated call to " << autowiring::demangle(m_pType); + ss << "Repeated call to " << autowiring::demangle(m_type); throw std::runtime_error(ss.str()); } diff --git a/src/autowiring/auto_id.cpp b/src/autowiring/auto_id.cpp new file mode 100644 index 000000000..0751cc8e1 --- /dev/null +++ b/src/autowiring/auto_id.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "auto_id.h" + +static int s_index = 1; + +const autowiring::auto_id_block auto_id_t::s_block{}; + +int autowiring::CreateIndex(void) { + return s_index++; +} diff --git a/src/autowiring/demangle.cpp b/src/autowiring/demangle.cpp index 4e9aac9b4..c86a8536b 100644 --- a/src/autowiring/demangle.cpp +++ b/src/autowiring/demangle.cpp @@ -70,7 +70,14 @@ std::string autowiring::demangle(const std::type_index* ti) { return demangle_name(ti->name()); } +std::string autowiring::demangle(auto_id id) { + return + id.block->ti ? + autowiring::demangle(id.block->ti) : + ""; +} + std::string autowiring::demangle(const AnySharedPointer& ptr) { - return autowiring::demangle(ptr->type()); + return autowiring::demangle(ptr.type()); } diff --git a/src/autowiring/test/AnySharedPointerTest.cpp b/src/autowiring/test/AnySharedPointerTest.cpp index 8117448b3..dfd855860 100644 --- a/src/autowiring/test/AnySharedPointerTest.cpp +++ b/src/autowiring/test/AnySharedPointerTest.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" +#include "TestFixtures/Decoration.hpp" #include "TestFixtures/SimpleObject.hpp" #include #include @@ -25,24 +26,6 @@ TEST_F(AnySharedPointerTest, CanReinterpretCastSharedPtr) { class MyUnusedClass {}; -template<> -struct SharedPointerSlotT: - SharedPointerSlot -{ - SharedPointerSlotT(const std::shared_ptr& rhs) - { - dtorStrike() = false; - } - - ~SharedPointerSlotT(void) { - dtorStrike() = true; - } - - bool& dtorStrike(void) { - return (bool&) *m_space; - } -}; - TEST_F(AnySharedPointerTest, OperatorEq) { AutoRequired sobj; @@ -58,28 +41,6 @@ TEST_F(AnySharedPointerTest, OperatorEq) { ASSERT_EQ(sobj, sobjAny2) << "An AnySharedPointer instance initialized by assignment violated an identity test"; } -TEST_F(AnySharedPointerTest, SimpleDestructorStrike) -{ - // We will need a buffer big enough for entire slot: - unsigned char buf[sizeof(SharedPointerSlotT)]; - auto& mucSlot = *(SharedPointerSlotT*)buf; - - // Placement new a basic shared pointer slot in the buffer: - SharedPointerSlot& slot = *new(buf) SharedPointerSlot; - - // In-place polymorphism on the slot: - slot = std::make_shared(); - - // Destructor shouldn't be hit until we call it: - ASSERT_FALSE(mucSlot.dtorStrike()) << "Destructor was struck prematurely"; - - // Direct destructor call: - slot.~SharedPointerSlot(); - - // Verify we hit our dtor in the specialization we declared above: - ASSERT_TRUE(mucSlot.dtorStrike()) << "Virtual destructor on in-place polymorphic class was not hit as expected"; -} - TEST_F(AnySharedPointerTest, AnySharedPointerRelease) { auto t = std::make_shared(5); @@ -140,10 +101,10 @@ TEST_F(AnySharedPointerTest, SlotDuplication) { { // Create a base slot to hold the shared pointer: AnySharedPointer slot1(sharedPtr); - ASSERT_FALSE(slot1->empty()) << "A slot initialized from a shared pointer was incorrectly marked as empty"; + ASSERT_FALSE(slot1.empty()) << "A slot initialized from a shared pointer was incorrectly marked as empty"; // Verify the type came across: - ASSERT_EQ(typeid(auto_id), slot1->type()) << "Dynamic initialization did not correctly adjust the dynamic type"; + ASSERT_EQ(auto_id_t{}, slot1.type()) << "Dynamic initialization did not correctly adjust the dynamic type"; // Now copy it over: slot2 = slot1; @@ -153,7 +114,7 @@ TEST_F(AnySharedPointerTest, SlotDuplication) { } // Verify that the slot still holds a reference and that the reference count is correct: - ASSERT_FALSE(slot2->empty()) << "A slot should have continued to hold a shared pointer, but was prematurely cleared"; + ASSERT_FALSE(slot2.empty()) << "A slot should have continued to hold a shared pointer, but was prematurely cleared"; ASSERT_EQ(2, sharedPtr.use_count()) << "A slot going out of scope did not correctly decrement a shared pointer reference"; } @@ -170,7 +131,7 @@ TEST_F(AnySharedPointerTest, TrivialRelease) { ASSERT_FALSE(b.unique()) << "Expected slot to hold a reference to the second specified instance"; // Now release, and verify that a release actually took place - slot->reset(); + slot.reset(); ASSERT_TRUE(b.unique()) << "Releasing a slot did not actually release the held value as expected"; } @@ -183,7 +144,7 @@ TEST_F(AnySharedPointerTest, NoMultipleDelete) { { AnySharedPointer slot; slot = a; - slot->reset(); + slot.reset(); } // Now verify that we didn't accidentally overdecrement the count: @@ -192,9 +153,9 @@ TEST_F(AnySharedPointerTest, NoMultipleDelete) { TEST_F(AnySharedPointerTest, InitDerivesCorrectType) { AnySharedPointer slot; - slot->init(); + slot.init(); - ASSERT_EQ(typeid(auto_id), slot->type()) << "A manually initialized slot did not have the expected type"; + ASSERT_EQ(auto_id_t{}, slot.type()) << "A manually initialized slot did not have the expected type"; } TEST_F(AnySharedPointerTest, VoidReturnExpected) { @@ -204,7 +165,7 @@ TEST_F(AnySharedPointerTest, VoidReturnExpected) { slot = v; // Validate equivalence of the void operator: - ASSERT_EQ(v.get(), slot->ptr()) << "Shared pointer slot did not return a void* with an expected value"; + ASSERT_EQ(v.get(), slot.ptr()) << "Shared pointer slot did not return a void* with an expected value"; } TEST_F(AnySharedPointerTest, CanHoldCoreObject) { @@ -222,4 +183,61 @@ TEST_F(AnySharedPointerTest, CanFastCastToSelf) { co, autowiring::fast_pointer_cast(co) ) << "Could not cast a CoreObject instance to itself"; -} \ No newline at end of file +} + +class AlternateBase { +public: + virtual ~AlternateBase(void) {} +}; + +class AnySharedPtrObjA: + public CoreObject +{ +public: + int aVal = 101; + int aVal2 = 101; +}; + +class AnySharedPtrObjB: + public AlternateBase, + public Decoration<0>, + public AnySharedPtrObjA +{ +public: + int bVal = 102; +}; + +TEST_F(AnySharedPointerTest, CanCrossCast) { + (void) auto_id_t_init::init; + (void) auto_id_t_init::init; + + // Ensure that dynamic casters are non-null in the block: + auto nullCaster = &autowiring::null_cast; + ASSERT_NE( + nullCaster, + (autowiring::fast_pointer_cast_blind::cast) + ) << "Fast pointer caster for AnySharedPtrObjA was not correctly initialized"; + ASSERT_NE( + reinterpret_cast(*)(const std::shared_ptr&)>(nullCaster), + auto_id_t::s_block.pFromObj + ) << "AnySharedPtrObjA dynamic caster was incorrectly assigned"; + + auto rootB = std::make_shared(); + auto rootA = std::static_pointer_cast(rootB); + auto rootBPtr = rootB.get(); + auto rootAPtr = static_cast(rootB.get()); + ASSERT_NE((void*) rootBPtr, (void*) rootAPtr) << "Objects were not correctly detected as having separate offsets"; + + AnySharedPointer aASP = rootA; + std::shared_ptr obj = aASP.as_obj(); + ASSERT_NE(nullptr, obj) << "An object cast attempt did not succeed as expected"; + ASSERT_EQ(101, aASP.as()->aVal); + + AnySharedPointer bASP; + bASP.init(); + bASP.try_assign(obj); + + ASSERT_NE(bASP, nullptr) << "An attempted cast incorrectly resulted in a null return"; + ASSERT_EQ(102, bASP.as()->bVal); + ASSERT_EQ(aASP, bASP) << "An aliased shared pointer was not detected as being equal"; +} diff --git a/src/autowiring/test/AutoFilterConstructRulesTest.cpp b/src/autowiring/test/AutoFilterConstructRulesTest.cpp index 886ca19a5..cac0239f1 100644 --- a/src/autowiring/test/AutoFilterConstructRulesTest.cpp +++ b/src/autowiring/test/AutoFilterConstructRulesTest.cpp @@ -116,9 +116,8 @@ TEST_F(AutoFilterConstructRulesTest, CorrectlyCallsCustomAllocator) { ASSERT_FALSE(HasCustomNewFunction::s_invoked) << "Custom allocator was invoked prematurely"; auto packet = factory->NewPacket(); - auto* phcnf = packet->GetShared(); - auto& hcnf = *phcnf; - ASSERT_TRUE(phcnf && hcnf) << "Decoration with custom allocator not present on a packet as expected"; + std::shared_ptr hcnf = *packet->GetShared(); + ASSERT_NE(nullptr, hcnf) << "Decoration with custom allocator not present on a packet as expected"; ASSERT_TRUE(HasCustomNewFunction::s_invoked) << "Custom new allocator was not invoked as expected"; for (size_t i = 0; i < 128; i++) diff --git a/src/autowiring/test/AutoFilterDescriptorTest.cpp b/src/autowiring/test/AutoFilterDescriptorTest.cpp index 4991b95ce..99f6532b3 100644 --- a/src/autowiring/test/AutoFilterDescriptorTest.cpp +++ b/src/autowiring/test/AutoFilterDescriptorTest.cpp @@ -16,14 +16,16 @@ TEST_F(AutoFilterDescriptorTest, DescriptorNonEquivalence) { ASSERT_NE(descs[0] < descs[1], descs[1] < descs[0]) << "Two inequal descriptors violated disjunctive syllogism"; } -TEST_F(AutoFilterDescriptorTest, ArityCheck) { - AutoFilterDescriptor desc([](int, Decoration<0>&, const Decoration<1>&) {}); - ASSERT_EQ(3UL, desc.GetArity()) << "Descriptor did not extract the correct argument count"; +TEST_F(AutoFilterDescriptorTest, ArgumentTypeInferenceCheck) { + AutoFilterDescriptor desc([](AutoPacket&, Decoration<0>&, int, const Decoration<1>&, std::shared_ptr) {}); + ASSERT_EQ(5UL, desc.GetArity()) << "Descriptor did not extract the correct argument count"; + // Verify the general shape of things: size_t nIn = 0; size_t nOut = 0; size_t nTotal = 0; - for (auto* pCur = desc.GetAutoFilterArguments(); *pCur; pCur++) { + const auto* pFirstArg = desc.GetAutoFilterArguments(); + for (auto* pCur = pFirstArg; *pCur; pCur++) { nTotal++; if (pCur->is_input) nIn++; @@ -31,7 +33,15 @@ TEST_F(AutoFilterDescriptorTest, ArityCheck) { nOut++; } - ASSERT_EQ(3UL, nTotal) << "AutoFilter argument array didn't appear to contain the correct number of arguments"; - ASSERT_EQ(2UL, nIn) << "Input argument count mismatch"; + // Note: AutoPacket is not considered an input argument, this is why inputs + outputs != arity + ASSERT_EQ(5UL, nTotal) << "AutoFilter argument array didn't appear to contain the correct number of arguments"; + ASSERT_EQ(3UL, nIn) << "Input argument count mismatch"; ASSERT_EQ(1UL, nOut) << "Output argument count mismatch"; + + // Now verify specific args: + ASSERT_EQ(pFirstArg[0].id, auto_id_t{}); + ASSERT_EQ(pFirstArg[1].id, auto_id_t>{}); + ASSERT_EQ(pFirstArg[2].id, auto_id_t{}); + ASSERT_EQ(pFirstArg[3].id, auto_id_t>{}); + ASSERT_EQ(pFirstArg[4].id, auto_id_t{}); } diff --git a/src/autowiring/test/AutoFilterDiagnosticsTest.cpp b/src/autowiring/test/AutoFilterDiagnosticsTest.cpp index 1bfbd1dc5..d406d43f7 100644 --- a/src/autowiring/test/AutoFilterDiagnosticsTest.cpp +++ b/src/autowiring/test/AutoFilterDiagnosticsTest.cpp @@ -33,7 +33,7 @@ TEST_F(AutoFilterDiagnosticsTest, CanGetExpectedTrueType) { // Get more information about this object from the enclosing context: AutoCurrentContext ctxt; - const std::type_info* ti = nullptr; - ASSERT_NO_THROW(ti = &ctxt->GetAutoTypeId(asp)) << "Exception thrown while attempting to get true type information"; - ASSERT_EQ(typeid(FilterMemberNotInheritingObject), *ti) << "True type not correctly reported, got " << autowiring::demangle(*ti) << ", expected FilterMemberNotInheritingObject"; + auto_id id; + ASSERT_NO_THROW(id = ctxt->GetAutoTypeId(asp)) << "Exception thrown while attempting to get true type information"; + ASSERT_EQ(auto_id_t{}, id) << "True type not correctly reported, got " << autowiring::demangle(id) << ", expected FilterMemberNotInheritingObject"; } diff --git a/src/autowiring/test/AutoFilterTest.cpp b/src/autowiring/test/AutoFilterTest.cpp index 6ff7997d5..bde561ab6 100644 --- a/src/autowiring/test/AutoFilterTest.cpp +++ b/src/autowiring/test/AutoFilterTest.cpp @@ -45,7 +45,7 @@ TEST_F(AutoFilterTest, VerifySimpleFilter) { break; } - ASSERT_TRUE(bFound) << "Failed to find an added subscriber "; + ASSERT_TRUE(bFound) << "Failed to find an added subscriber"; // Obtain a packet from the factory: auto packet = factory->NewPacket(); @@ -783,7 +783,7 @@ std::string decoration_status_string(DispositionState s) { void print_decorations(const std::string &label, const AutoPacket::t_decorationMap &d) { std::cout << label << " {\n"; for (auto it : d) { - std::cout << " " << autowiring::demangle(it.first.ti) << " : " << decoration_status_string(it.second.m_state) << '\n'; + std::cout << " " << autowiring::demangle(it.first.id.block->ti) << " : " << decoration_status_string(it.second.m_state) << '\n'; } std::cout << "}\n"; } @@ -803,8 +803,8 @@ TEST_F(AutoFilterTest, AutoOut2) { packet->Decorate(123.45); d = packet->GetDecorations(); ASSERT_EQ(1, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; bool filter_0_called = false; auto filter_0 = [&filter_0_called](const double &x, auto_out y) { @@ -824,11 +824,11 @@ TEST_F(AutoFilterTest, AutoOut2) { ASSERT_FALSE(filter_1_called) << "We expected filter_0 to not have been called by now."; d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; // Being Complete and having no decorations indicates that it has been MarkUnsatisfiable()'d. - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(0, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(0, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; // Because the auto_out was never assigned to, it went unsatisfiable, so this filter should never be called. *packet += filter_1; @@ -858,10 +858,10 @@ TEST_F(AutoFilterTest, AutoOut3) { packet->Decorate(123.45); d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(0, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(0, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; bool filter_0_called = false; auto filter_0 = [&filter_0_called](const double &x, auto_out y) { @@ -877,11 +877,11 @@ TEST_F(AutoFilterTest, AutoOut3) { ASSERT_FALSE(filter_1_called) << "We expected filter_0 to not have been called by now."; d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; // Being Complete and having no decorations indicates that it has been MarkUnsatisfiable()'d. - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(0, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(0, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; // Because the auto_out was never assigned to, it went unsatisfiable, so this filter should never be called. ASSERT_TRUE(filter_0_called) << "We expected filter_0 to have been called by now."; @@ -910,10 +910,10 @@ TEST_F(AutoFilterTest, AutoOut4) { packet->Decorate(123.45); d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(0, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(0, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; bool filter_0_called = false; auto_out ao; @@ -931,12 +931,12 @@ TEST_F(AutoFilterTest, AutoOut4) { ASSERT_FALSE(filter_1_called) << "We expected filter_0 to not have been called by now."; d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; // Being Complete and having no decorations indicates that it has been MarkUnsatisfiable()'d. - ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(0, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Unsatisfied, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(0, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; // Assign to ao, thereby decorating the packet with int. *ao = 42; @@ -946,9 +946,9 @@ TEST_F(AutoFilterTest, AutoOut4) { ASSERT_TRUE(filter_1_called) << "We expected filter_0 to have been called by now."; d = packet->GetDecorations(); ASSERT_EQ(2, d.size()) << "Unexpected number of AutoFilter parameters."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; - ASSERT_EQ(DispositionState::Complete, d[DecorationKey(typeid(auto_id),0)].m_state) << "Incorrect `int` decoration disposition."; - ASSERT_EQ(1, d[DecorationKey(typeid(auto_id),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `double` decoration disposition."; + ASSERT_EQ(DispositionState::Complete, d[DecorationKey(auto_id_t(),0)].m_state) << "Incorrect `int` decoration disposition."; + ASSERT_EQ(1, d[DecorationKey(auto_id_t(),0)].m_decorations.size()) << "Incorrect `int` decoration disposition."; } } diff --git a/src/autowiring/test/AutoIDTest.cpp b/src/autowiring/test/AutoIDTest.cpp new file mode 100644 index 000000000..5c4d4fc79 --- /dev/null +++ b/src/autowiring/test/AutoIDTest.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "TestFixtures/Decoration.hpp" +#include + +class AutoIDTest: + public testing::Test +{}; + +TEST_F(AutoIDTest, VerifyIndexUniqueness) { + const autowiring::auto_id_block* v0 = &auto_id_t>::s_block; + const autowiring::auto_id_block* v1 = &auto_id_t>::s_block; + + ASSERT_NE(nullptr, v0); + ASSERT_NE(nullptr, v1); + ASSERT_NE(v0, v1) << "Indexes were equal when they should have been distinct"; + ASSERT_NE(*v0, *v1) << "Blocks at distinct addresses incorrectly evaluated as being equal"; +} diff --git a/src/autowiring/test/AutowiringTest.cpp b/src/autowiring/test/AutowiringTest.cpp index 35d1188fc..2b326ce48 100644 --- a/src/autowiring/test/AutowiringTest.cpp +++ b/src/autowiring/test/AutowiringTest.cpp @@ -28,10 +28,12 @@ TEST_F(AutowiringTest, VerifyAutowiredFastNontrivial) { // This will cause a cache entry to be inserted into the CoreContext's memoization system. // If there is any improper or incorrect invalidation in that system, then the null entry // will create problems when we attempt to perform an AutowiredFast later on. - AutowiredFast(); + AutowiredFast ciEmpty; + ASSERT_FALSE(ciEmpty.IsAutowired()) << "An entry was autowired prematurely"; // Now we add the object AutoRequired(); + ASSERT_FALSE(ciEmpty.IsAutowired()) << "An AutowiredFast field was incorrectly satisfied post-hoc"; // Verify that AutowiredFast can find this object from its interface AutowiredFast ci; diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index b803868ca..b9bedef77 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -14,6 +14,7 @@ set(AutowiringTest_SRCS AutoFilterMultiDecorateTest.cpp AutoFilterSequencing.cpp AutoFilterTest.cpp + AutoIDTest.cpp AutoInjectableTest.cpp AutoPacketTest.cpp AutoPacketFactoryTest.cpp diff --git a/src/autowiring/test/ContextMemberTest.cpp b/src/autowiring/test/ContextMemberTest.cpp index d2283b6fe..c23bea2bb 100644 --- a/src/autowiring/test/ContextMemberTest.cpp +++ b/src/autowiring/test/ContextMemberTest.cpp @@ -40,13 +40,21 @@ TEST_F(ContextMemberTest, VerifyDetectedMembers) { AutoRequired hasAFew; + (void) auto_id_t_init::init; + (void) auto_id_t_init>::init; + (void) auto_id_t_init>::init; + // Slots defined in reverse order here, because that's how they will be present in the collection SlotInformation expected [] = { - SlotInformation(nullptr, typeid(auto_id), offsetof_nowarn(HasAFewSlots, m_sobj), false), - SlotInformation(nullptr, typeid(auto_id>), offsetof_nowarn(HasAFewSlots, m_sthread2), false), - SlotInformation(nullptr, typeid(auto_id>), offsetof_nowarn(HasAFewSlots, m_sthread1), false) + {nullptr, auto_id_t{}, offsetof_nowarn(HasAFewSlots, m_sobj), false}, + {nullptr, auto_id_t>{}, offsetof_nowarn(HasAFewSlots, m_sthread2), false}, + {nullptr, auto_id_t>{}, offsetof_nowarn(HasAFewSlots, m_sthread1), false} }; + ASSERT_EQ(typeid(SimpleObject), *expected[0].type.block->ti); + ASSERT_EQ(typeid(SimpleThreadedT), *expected[1].type.block->ti); + ASSERT_EQ(typeid(SimpleThreadedT), *expected[2].type.block->ti); + // Validate all pointers are what we expect to find, and in the right order size_t ct = 0; diff --git a/src/autowiring/test/DecoratorTest.cpp b/src/autowiring/test/DecoratorTest.cpp index a8dca7c17..91db2c4bc 100644 --- a/src/autowiring/test/DecoratorTest.cpp +++ b/src/autowiring/test/DecoratorTest.cpp @@ -18,18 +18,18 @@ class DecoratorTest: }; TEST_F(DecoratorTest, VerifyCorrectExtraction) { - vector v; + vector v; // Run our prop extractor based on a known decorator: AutoRequired filterA; AutoFilterDescriptor desc(static_cast&>(filterA)); for(const AutoFilterArgument* cur = desc.GetAutoFilterArguments(); *cur; cur++) - v.push_back(cur->ti); + v.push_back(cur->id); ASSERT_EQ(2UL, v.size()) << "Extracted an insufficient number of types from a known filter function"; // Arguments MUST be in order: - ASSERT_EQ(typeid(auto_id>), *v[0]); - ASSERT_EQ(typeid(auto_id>), *v[1]); + ASSERT_EQ(auto_id_t>{}, v[0]); + ASSERT_EQ(auto_id_t>{}, v[1]); } TEST_F(DecoratorTest, VerifyEmptyExtraction) { @@ -88,7 +88,7 @@ TEST_F(DecoratorTest, VerifyDecoratorAwareness) { ASSERT_FALSE(packet1->HasSubscribers>()) << "Subscription was incorrectly, retroactively added to a packet"; // Verify the second one does: - ASSERT_THROW(packet2->GetSatisfaction(), autowiring_error) << "Packet lacked an expected subscription"; + ASSERT_NO_THROW(packet2->GetSatisfaction()) << "Packet lacked an expected subscription"; ASSERT_EQ(2UL, packet2->GetDecorationTypeCount()) << "Incorrect count of expected decorations"; ASSERT_TRUE(packet2->HasSubscribers>()) << "Packet lacked an expected subscription"; } From 7370b0f53c519bb9273a3f02b56921e2d3481ee2 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 24 Jul 2015 07:02:16 -0700 Subject: [PATCH 14/60] Teach AutoBench how to accept multiple benchmarks It's handy to be able to run multiple benchmarks at once without having to specify all of them and doesn't require that substantial of a refactor. --- src/autowiring/benchmark/AutoBench.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/autowiring/benchmark/AutoBench.cpp b/src/autowiring/benchmark/AutoBench.cpp index b0d907af1..113f7c762 100644 --- a/src/autowiring/benchmark/AutoBench.cpp +++ b/src/autowiring/benchmark/AutoBench.cpp @@ -73,27 +73,34 @@ Benchmark PrintUsage(void) { } int main(int argc, const char* argv[]) { - switch (argc) { - default: + if (argc <= 1) { PrintUsage(); return -1; - case 2: - auto& cur = sc_commands[argv[1]]; - if (!cur.name) { + } + + // First pass, ensure we recognize all commands: + for (int i = 1; i < argc; i++) + if (!sc_commands[argv[i]].name) { std::cerr << "Unrecognized command" << std::endl; PrintUsage(); return -1; } + // Now execute everything: + bool errors = false; + for (int i = 1; i < argc; i++) { + auto& cur = sc_commands[argv[i]]; try { Benchmark benchmark = cur.pfn(); if (!cur.utility) - std::cout << benchmark; + std::cout + << cur.name << std::endl + << benchmark << std::endl; } catch (std::exception& ex) { std::cerr << ex.what() << std::endl; - return -1; + errors = true; } } - return 0; + return errors ? -1 : 0; } From bedc2d67a05f226d25b63408b4ef563114474b11 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 24 Jul 2015 07:51:42 -0700 Subject: [PATCH 15/60] Minor cleanup in CoreContext We don't support libstdc anymore, now it's safe to just return `nullptr` instead. --- src/autowiring/CoreContext.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 82f4ebd26..aad7c8d89 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -164,17 +164,16 @@ std::shared_ptr CoreContext::FirstChild(void) const { } // Seems like we have no children, return here - return std::shared_ptr(); + return nullptr; } std::shared_ptr CoreContext::NextSibling(void) const { // Root contexts do not have siblings if(!m_pParent) - return std::shared_ptr(); + return nullptr; // Our iterator will always be valid in our parent collection. Take a copy, lock the parent collection down // to prevent it from being modified, and then see what happens when we increment - std::lock_guard lk(m_pParent->m_stateBlock->m_lock); for( auto cur = m_backReference; @@ -186,7 +185,7 @@ std::shared_ptr CoreContext::NextSibling(void) const { } // Failed to lock any successor child in the parent context, return unsuccessful - return std::shared_ptr(); + return nullptr; } const std::type_info& CoreContext::GetAutoTypeId(const AnySharedPointer& ptr) const { From 48fc559c5c334b987fed9c39b63a6cda28644644 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 24 Jul 2015 07:51:02 -0700 Subject: [PATCH 16/60] ContextMap actually has const_iterator semantics The `ContextMap` type's begin and end function are actually returning an iterator with `const_iterator` semantics. Strengthen this case by making things const where necessary and making the `begin` and `end` const members. --- autowiring/ContextMap.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/autowiring/ContextMap.h b/autowiring/ContextMap.h index 7f33e3c14..58d12d774 100644 --- a/autowiring/ContextMap.h +++ b/autowiring/ContextMap.h @@ -52,7 +52,7 @@ class ContextMap class iterator { public: - iterator(ContextMap& parent) : + iterator(const ContextMap& parent) : parent(parent) { std::lock_guard lk(*parent.m_tracker); @@ -67,15 +67,15 @@ class ContextMap iter++; } - iterator(ContextMap& parent, typename t_mpType::iterator iter, std::shared_ptr ctxt) : + iterator(const ContextMap& parent, typename t_mpType::const_iterator iter, std::shared_ptr ctxt) : parent(parent), iter(iter), ctxt(ctxt) {} private: - ContextMap& parent; - typename t_mpType::iterator iter; + const ContextMap& parent; + typename t_mpType::const_iterator iter; std::shared_ptr ctxt; public: @@ -103,8 +103,10 @@ class ContextMap } }; - iterator begin(void) { return iterator(*this); } - iterator end(void) { return iterator(*this, m_contexts.end(), nullptr); } + typedef iterator const_iterator; + + iterator begin(void) const { return iterator(*this); } + iterator end(void) const { return iterator(*this, m_contexts.end(), nullptr); } template void Enumerate(Fn&& fn) { From 730137f647edf70cb0065a96a8ccf9f19e7fe5c4 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 24 Jul 2015 06:58:10 -0700 Subject: [PATCH 17/60] Add a benchmark for ContextEnumerator versus ContextMap There have recently been some concerns that `ContextEnumerator` may be very slow in parallel environments. Determine whether this is true, and then benchmark `ContextMap` in the same setting to ensure that it is suitable as a higher performance alternative. --- src/autowiring/benchmark/AutoBench.cpp | 5 +- src/autowiring/benchmark/Benchmark.h | 3 + src/autowiring/benchmark/CMakeLists.txt | 2 + .../benchmark/ContextTrackingBm.cpp | 127 ++++++++++++++++++ src/autowiring/benchmark/ContextTrackingBm.h | 10 ++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/autowiring/benchmark/ContextTrackingBm.cpp create mode 100644 src/autowiring/benchmark/ContextTrackingBm.h diff --git a/src/autowiring/benchmark/AutoBench.cpp b/src/autowiring/benchmark/AutoBench.cpp index b0d907af1..fcdaa936f 100644 --- a/src/autowiring/benchmark/AutoBench.cpp +++ b/src/autowiring/benchmark/AutoBench.cpp @@ -2,6 +2,7 @@ #include "stdafx.h" #include "Benchmark.h" #include "ContextSearchBm.h" +#include "ContextTrackingBm.h" #include "DispatchQueueBm.h" #include "PrintableDuration.h" #include "PriorityBoost.h" @@ -42,7 +43,9 @@ static std::map sc_commands = { MakeEntry("search", "Autowiring context search cost", &ContextSearchBm::Search), MakeEntry("cache", "Autowiring cache behavior", &ContextSearchBm::Cache), MakeEntry("fast", "Autowired versus AutowiredFast", &ContextSearchBm::Fast), - MakeEntry("dispatch", "Dispatch queue execution rate", &DispatchQueueBm::Dispatch) + MakeEntry("dispatch", "Dispatch queue execution rate", &DispatchQueueBm::Dispatch), + MakeEntry("contextenum", "CoreContextEnumerator profiling", &ContextTrackingBm::ContextEnum), + MakeEntry("contextmap", "ContextMap profiling", &ContextTrackingBm::ContextMap), }; static Benchmark All(void) { diff --git a/src/autowiring/benchmark/Benchmark.h b/src/autowiring/benchmark/Benchmark.h index 842f9668a..00a480a9c 100644 --- a/src/autowiring/benchmark/Benchmark.h +++ b/src/autowiring/benchmark/Benchmark.h @@ -9,6 +9,9 @@ struct Stopwatch { start = std::chrono::profiling_clock::now(); } + /// + /// Stops timing, with [n] operations having been conducted between start and stop + /// void Stop(size_t n) { auto stop = std::chrono::profiling_clock::now(); std::chrono::duration delta = stop - start; diff --git a/src/autowiring/benchmark/CMakeLists.txt b/src/autowiring/benchmark/CMakeLists.txt index 463dd9fa8..623a4f326 100644 --- a/src/autowiring/benchmark/CMakeLists.txt +++ b/src/autowiring/benchmark/CMakeLists.txt @@ -4,6 +4,8 @@ set(AutoBench_SRCS Benchmark.cpp ContextSearchBm.h ContextSearchBm.cpp + ContextTrackingBm.h + ContextTrackingBm.cpp DispatchQueueBm.h DispatchQueueBm.cpp Foo.h diff --git a/src/autowiring/benchmark/ContextTrackingBm.cpp b/src/autowiring/benchmark/ContextTrackingBm.cpp new file mode 100644 index 000000000..e5e0bf5de --- /dev/null +++ b/src/autowiring/benchmark/ContextTrackingBm.cpp @@ -0,0 +1,127 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "ContextTrackingBm.h" +#include "Benchmark.h" +#include +#include +#include + +static const size_t n = 100; + +static std::vector> create(void) { + // Create a bunch of subcontexts from here + std::vector> contexts(n); + for (size_t i = n; i--;) + contexts[i] = AutoCreateContext(); + return contexts; +} + +template +static void do_parallel_enum(Stopwatch& sw) { + AutoCurrentContext ctxt; + auto all = create(); + + // Create threads which will cause contention: + auto proceed = std::make_shared(true); + for (size_t nParallel = N; nParallel--;) { + std::thread([proceed, all, ctxt] { + while (*proceed) + for (auto cur : ContextEnumerator(ctxt)) + ; + }).detach(); + } + auto cleanup = MakeAtExit([&] { *proceed = false; }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Perform parallel enumeration + sw.Start(); + for (auto cur : ContextEnumerator(ctxt)) + ; + sw.Stop(n); +} + +Benchmark ContextTrackingBm::ContextEnum(void) { + return { + { + "single", + [](Stopwatch& sw) { + auto all = create(); + + // Now enumerate + sw.Start(); + AutoCurrentContext ctxt; + for (auto cur : ContextEnumerator(ctxt)) + ; + sw.Stop(n); + } + }, + { + "parallel", + do_parallel_enum<1> + }, + { + "parallel x10", + do_parallel_enum<1> + } + }; +} + +template +static void do_parallel_map(Stopwatch& sw) { + // Create a bunch of subcontexts from here and put them in a map + auto all = create(); + ::ContextMap mp; + for (size_t i = N; i < all.size(); i++) + mp.Add(i, all[i]); + + // Now the threads that will make progress hard: + auto proceed = std::make_shared(true); + for (size_t i = N; i--;) + std::thread([proceed, mp] { + while (*proceed) + for (const auto& cur : mp) + ; + }).detach(); + auto cleanup = MakeAtExit([&] { *proceed = false; }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Enumerate the map while iteration is underway + sw.Start(); + for (auto& cur : mp) + ; + sw.Stop(n); +} + +Benchmark ContextTrackingBm::ContextMap(void) { + static const size_t n = 100; + + return { + { + "single", + [](Stopwatch& sw) { + // Create a bunch of subcontexts from here and put them in a map + auto all = create(); + ::ContextMap mp; + for (size_t i = 0; i < all.size(); i++) + mp.Add(i, all[i]); + + // Just enumerate the map, not much to it + sw.Start(); + for (auto& cur : mp) + ; + sw.Stop(n); + } + }, + { + "parallel", + do_parallel_map<1> + }, + { + "parallel x10", + do_parallel_map<10> + } + }; + +} \ No newline at end of file diff --git a/src/autowiring/benchmark/ContextTrackingBm.h b/src/autowiring/benchmark/ContextTrackingBm.h new file mode 100644 index 000000000..4556b7927 --- /dev/null +++ b/src/autowiring/benchmark/ContextTrackingBm.h @@ -0,0 +1,10 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once + +struct Benchmark; + +class ContextTrackingBm { +public: + static Benchmark ContextEnum(void); + static Benchmark ContextMap(void); +}; \ No newline at end of file From 17cd75d8f3ac9d1426fb8d4dbdd17edf7feaa672 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 27 Jul 2015 07:33:54 -0700 Subject: [PATCH 18/60] Add a cross-platform alignment concept [`alignof`](http://en.cppreference.com/w/cpp/language/alignof) has been present in MSVC since VS 2003, and [`alignas`](http://en.cppreference.com/w/cpp/language/alignas) has been present as `__declspec(align(n))` since the same date. Because memory magic is going to be increasingly important for performance reasons in `AutoPacket`, provide alignment declaration in a cross-platform way with `AUTO_ALIGN` for use by downstream clients. --- autowiring/C++11/cpp11.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index fabce6523..a73344f3b 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -37,6 +37,24 @@ #define __func__ __FUNCTION__ #endif +/********************* + * alignas support added in all versions of GCC, but not until MSVC 2015 + *********************/ +#if defined(_MSC_VER) && _MSC_VER <= 1800 + #define AUTO_ALIGNAS(n) __declspec(align(n)) +#else + #define AUTO_ALIGNAS alignas +#endif + +/********************* + * alignof has similar support, but has a strange name in older versions + *********************/ +#if defined(_MSC_VER) && _MSC_VER <= 1800 + #define AUTO_ALIGNOF __alignof +#else + #define AUTO_ALIGNAS alignof +#endif + /********************* * Location of the unordered_set header *********************/ From 2acaa6206d735f793b7d441d0bdcc66849bedeec Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 27 Jul 2015 08:52:18 -0700 Subject: [PATCH 19/60] Add aligned allocator and unit tests to creation rules --- autowiring/CreationRules.h | 10 ++++++++ src/autowiring/CMakeLists.txt | 2 ++ src/autowiring/CreationRulesUnix.cpp | 15 ++++++++++++ src/autowiring/CreationRulesWin.cpp | 11 +++++++++ src/autowiring/test/CMakeLists.txt | 1 + src/autowiring/test/CreationRulesTest.cpp | 29 +++++++++++++++++++++++ 6 files changed, 68 insertions(+) create mode 100644 src/autowiring/CreationRulesUnix.cpp create mode 100644 src/autowiring/CreationRulesWin.cpp create mode 100644 src/autowiring/test/CreationRulesTest.cpp diff --git a/autowiring/CreationRules.h b/autowiring/CreationRules.h index a301ffab8..b29e27cb1 100644 --- a/autowiring/CreationRules.h +++ b/autowiring/CreationRules.h @@ -33,6 +33,16 @@ struct select_strategy { construction_strategy::standard; }; +/// +/// Performs a platform-independent aligned allocation +/// +void* aligned_malloc(size_t ncb, size_t align); + +/// +/// Corrolary of aligned_malloc +/// +void aligned_free(void* ptr); + template struct strategy_impl {}; diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index 37086fd52..0d745d226 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -194,6 +194,7 @@ add_conditional_sources( add_windows_sources(Autowiring_SRCS auto_future_win.h CoreThreadWin.cpp + CreationRulesWin.cpp SystemThreadPoolWin.cpp SystemThreadPoolWin.hpp SystemThreadPoolWinXP.cpp @@ -210,6 +211,7 @@ add_mac_sources(Autowiring_SRCS ) add_unix_sources(Autowiring_SRCS + CreationRulesUnix.cpp InterlockedExchangeUnix.cpp thread_specific_ptr_unix.h ) diff --git a/src/autowiring/CreationRulesUnix.cpp b/src/autowiring/CreationRulesUnix.cpp new file mode 100644 index 000000000..829d47d79 --- /dev/null +++ b/src/autowiring/CreationRulesUnix.cpp @@ -0,0 +1,15 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "CreationRules.h" +#include + +void* autowiring::aligned_malloc(size_t ncb, size_t align) { + void* pRetVal; + if(posix_memalign(&pRetVal, ncb, align)) + return nullptr; + return pRetVal; +} + +void autowiring::aligned_free(void* ptr) { + free(ptr); +} diff --git a/src/autowiring/CreationRulesWin.cpp b/src/autowiring/CreationRulesWin.cpp new file mode 100644 index 000000000..bad37a569 --- /dev/null +++ b/src/autowiring/CreationRulesWin.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "CreationRules.h" + +void* autowiring::aligned_malloc(size_t ncb, size_t align) { + return _aligned_malloc(ncb, align); +} + +void autowiring::aligned_free(void* ptr) { + return _aligned_free(ptr); +} diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index b9bedef77..179e546c9 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -34,6 +34,7 @@ set(AutowiringTest_SRCS ContextMapTest.cpp ContextMemberTest.cpp CoreThreadTest.cpp + CreationRulesTest.cpp CurrentContextPusherTest.cpp DecoratorTest.cpp DemangleTest.cpp diff --git a/src/autowiring/test/CreationRulesTest.cpp b/src/autowiring/test/CreationRulesTest.cpp new file mode 100644 index 000000000..4c1c4dd40 --- /dev/null +++ b/src/autowiring/test/CreationRulesTest.cpp @@ -0,0 +1,29 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "TestFixtures/Decoration.hpp" +#include + +class CreationRulesTest: + public testing::Test +{}; + +struct AlignedFreer { + void operator()(void* pMem) const { + autowiring::aligned_free(pMem); + } +}; + +TEST_F(CreationRulesTest, AlignedAllocation) { + // Use a vector because we don't want the underlying allocator to give us the same memory block + // over and over--we are, after all, freeing memory and then immediately asking for another block + // with identical size requirements. + std::vector> memory; + memory.resize(100); + + // Allocate one byte of 256-byte aligned memory 100 times. If the aligned allocator isn't working + // properly, then the odds are good that at least one of these allocations will be wrong. + for (auto& ptr : memory) { + ptr.reset(autowiring::aligned_malloc(1, 256)); + ASSERT_EQ(0UL, (size_t)ptr.get() % 256) << "Aligned allocator did not return a correctly aligned pointer"; + } +} From fb17643ae501904b0327163a24fa6ad31829136b Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Mon, 27 Jul 2015 15:46:32 -0700 Subject: [PATCH 20/60] Fix warning --- autowiring/C++11/cpp11.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index a73344f3b..00615e5a4 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -52,7 +52,7 @@ #if defined(_MSC_VER) && _MSC_VER <= 1800 #define AUTO_ALIGNOF __alignof #else - #define AUTO_ALIGNAS alignof + #define AUTO_ALIGNOF alignof #endif /********************* From b2784bffec2f925ab870ddc3b686d6e84fc5457d Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 20:44:24 -0700 Subject: [PATCH 21/60] Implement a heterogenous block concept Heterogenous blocks are intended to be used by AutoPacket. They contain a multitude of unrelated types that are packed together in the same arena based allocator. The intended use case is for situations where a particular decoration is known to never be needed as a `shared_ptr`. Such cases free us from having to prospectively allocate a shared pointer. The heterogenous block will also need to be designed to support post-hoc generation of a shared pointer. This is necessary because there will be a minority of cases where a shared pointer is unexpectedly needed. --- autowiring/HeteroBlock.h | 59 ++++++++++++++++++++++++++++++++++ src/autowiring/CMakeLists.txt | 2 ++ src/autowiring/HeteroBlock.cpp | 20 ++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 autowiring/HeteroBlock.h create mode 100644 src/autowiring/HeteroBlock.cpp diff --git a/autowiring/HeteroBlock.h b/autowiring/HeteroBlock.h new file mode 100644 index 000000000..a20b6b83b --- /dev/null +++ b/autowiring/HeteroBlock.h @@ -0,0 +1,59 @@ +#pragma once +#include "auto_id.h" +#include + +namespace autowiring { + +/// +/// A single heterogenous block entry +/// +struct HeteroBlockEntry { + // The size of the type that will be constructed + size_t ncb; +}; + +/// +/// Constructs a heterogenous lookaside block +/// +/// +/// A heterogenous block +/// +class HeteroBlock +{ +public: + HeteroBlock(std::initializer_list entries); + ~HeteroBlock(); + +private: + // A shared pointer to the block proper. This shared pointer points to a memory block + // containing all of the subelements allocated in the arena. + std::shared_ptr block; + + struct entry { + auto_id id; + size_t index; + }; + struct entry_hash { + size_t operator()(const entry& e) const { + std::hash h; + return h(e.id) + e.index; + } + }; + + // A map of identifier entries to their offsets in the block + std::unordered_map offsets; + +public: + /// + /// Retrieves the nth instance of type T on the block + /// + template + std::shared_ptr get(size_t index) { + auto q = offsets.find({auto_id_t{}, index}); + if (q == offsets.end()) + throw std::runtime_error("Attempted to obtain an index not provided by this type"); + ; + } +}; + +} \ No newline at end of file diff --git a/src/autowiring/CMakeLists.txt b/src/autowiring/CMakeLists.txt index 0d745d226..25de727a9 100644 --- a/src/autowiring/CMakeLists.txt +++ b/src/autowiring/CMakeLists.txt @@ -121,6 +121,8 @@ set(Autowiring_SRCS has_static_new.h has_validate.h hash_tuple.h + HeteroBlock.h + HeteroBlock.cpp index_tuple.h InterlockedExchange.h InvokeRelay.h diff --git a/src/autowiring/HeteroBlock.cpp b/src/autowiring/HeteroBlock.cpp new file mode 100644 index 000000000..d03a0f7fc --- /dev/null +++ b/src/autowiring/HeteroBlock.cpp @@ -0,0 +1,20 @@ +#include "stdafx.h" +#include "HeteroBlock.h" +#include + +using namespace autowiring; + +HeteroBlock::HeteroBlock(std::initializer_list entries) +{ + // Compute the total amount of space that will be needed + size_t nTotal = 0; + for (const auto& entry : entries) + nTotal += entry.ncb; + + // Allocate just this much space + block.reset(new uint8_t[nTotal]); +} + +HeteroBlock::~HeteroBlock(void) +{ +} From 39510cf8408d3b5705bfb7ca5d9dac7e13657b62 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 07:42:19 -0700 Subject: [PATCH 22/60] Fix an issue with the call to posix_memalign --- src/autowiring/CreationRulesUnix.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autowiring/CreationRulesUnix.cpp b/src/autowiring/CreationRulesUnix.cpp index 829d47d79..6eedeb744 100644 --- a/src/autowiring/CreationRulesUnix.cpp +++ b/src/autowiring/CreationRulesUnix.cpp @@ -5,7 +5,7 @@ void* autowiring::aligned_malloc(size_t ncb, size_t align) { void* pRetVal; - if(posix_memalign(&pRetVal, ncb, align)) + if(posix_memalign(&pRetVal, align, ncb)) return nullptr; return pRetVal; } From dce9449f9fdb58a1dc465eab5d062e2244878e88 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 23 Jul 2015 21:22:08 -0700 Subject: [PATCH 23/60] Add unit tests, initial feature implementation --- autowiring/HeteroBlock.h | 136 ++++++++++++++++++++---- autowiring/auto_id.h | 18 ++++ src/autowiring/HeteroBlock.cpp | 105 ++++++++++++++++-- src/autowiring/test/CMakeLists.txt | 1 + src/autowiring/test/HeteroBlockTest.cpp | 79 ++++++++++++++ 5 files changed, 309 insertions(+), 30 deletions(-) create mode 100644 src/autowiring/test/HeteroBlockTest.cpp diff --git a/autowiring/HeteroBlock.h b/autowiring/HeteroBlock.h index a20b6b83b..28186c8b2 100644 --- a/autowiring/HeteroBlock.h +++ b/autowiring/HeteroBlock.h @@ -1,15 +1,62 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once #include "auto_id.h" #include +#include +#include namespace autowiring { +struct HeteroBlockEntryBase { + HeteroBlockEntryBase(auto_id id, size_t index) : + id(id), + index(index) + {} + + // Block entry for the constructed type. This entry must be fully instantiated + auto_id id; + + // A unique index for this block entry + size_t index; + + struct hash { + size_t operator()(const HeteroBlockEntryBase& e) const { + std::hash h; + return h(e.id) + e.index; + } + }; + + bool operator==(const HeteroBlockEntryBase& rhs) const { + return id == rhs.id && index == rhs.index; + } +}; + /// /// A single heterogenous block entry /// -struct HeteroBlockEntry { - // The size of the type that will be constructed - size_t ncb; +struct HeteroBlockEntry: + HeteroBlockEntryBase +{ + template + HeteroBlockEntry(auto_id_t id, size_t index) : + HeteroBlockEntryBase{ id, index }, + pfnCtor(&PlacementNew), + pfnDtor(&Destructor) + { + (void) auto_id_t_init::init; + } + + template + static void PlacementNew(void* pMem) { new (pMem) T; } + + template + static void Destructor(void* pMem) { ((T*)pMem)->~T(); } + + // Constructor for this entry: + void(*pfnCtor)(void*); + + // Destructor for this entry: + void(*pfnDtor)(void*); }; /// @@ -21,39 +68,88 @@ struct HeteroBlockEntry { class HeteroBlock { public: - HeteroBlock(std::initializer_list entries); + HeteroBlock(const HeteroBlockEntry* pFirst, const HeteroBlockEntry* pLast); + HeteroBlock(std::initializer_list entries) : + HeteroBlock(entries.begin(), entries.end()) + {} ~HeteroBlock(); private: // A shared pointer to the block proper. This shared pointer points to a memory block - // containing all of the subelements allocated in the arena. + // containing all of the subelements allocated in the arena. Entries in the block are + // structured like this: + // + // <--- sizeof(T) ---> | sizeof(void*) | sizeof(void*) | <--- sizeof(U) ---> | sizeof(void*) | sizeof(void*) + // T ----------------- | &T::~T | sizeof(U) | U ----------------- | &U::~U | sizeof(V) + // + // The last entry looks like this: + // + // + // <--- sizeof(X) ---> | sizeof(void*) | sizeof(void*) + // X ----------------- | &X::~X | 0 std::shared_ptr block; - struct entry { - auto_id id; - size_t index; + // Structure describing the layout of block headers + struct BlockHeader { + // Deleter we'll be using to free the "data" field + void(*pfnDtor)(void*); + + // Size of the next block's data field + size_t nextSize; }; - struct entry_hash { - size_t operator()(const entry& e) const { - std::hash h; - return h(e.id) + e.index; - } + + struct entry { + // Offset, from the base of the block, where the object is stored + size_t offset; + + // May potentially be null, if the shared pointer isn't initialized + std::shared_ptr ptr; }; // A map of identifier entries to their offsets in the block - std::unordered_map offsets; + std::unordered_map entries; public: /// /// Retrieves the nth instance of type T on the block /// + /// The type to be obtained, must be complete at the time of the call + /// The index of the type to be obtained + template + T& get(size_t index) { + auto q = entries.find({auto_id_t{}, index}); + if (q == entries.end()) + throw std::runtime_error("Attempted to obtain a type or index not initialized on this block"); + return *(T*)((uint8_t*)block.get() + q->second.offset); + } + + /// + /// Identical to get, except retrieves a shared pointer + /// + /// The type to be obtained, must be complete at the time of the call + /// The index of the type to be obtained + /// + /// In a multithreaded environment, this method can have worse performance than the non-shared override. + /// This is due to the fact that initializing a shared_ptr requires a memory barrier, and barriers are + /// very slow. + /// template - std::shared_ptr get(size_t index) { - auto q = offsets.find({auto_id_t{}, index}); - if (q == offsets.end()) - throw std::runtime_error("Attempted to obtain an index not provided by this type"); - ; + std::shared_ptr& get_shared(size_t index) { + auto q = entries.find({ auto_id_t{}, index }); + if (q == entries.end()) + throw std::runtime_error("Attempted to obtain a type or index not initialized on this block"); + + if (!q->second.ptr) + // Need to initialize now + q->second.ptr = std::shared_ptr( + block, + (uint8_t*)block.get() + q->second.offset + ); + + // Safety is assured by AnySharedPointer unit tests. We use a native reinterpret_cast and not + // std::static_pointer_cast because we are using a form of type erasure on std::shared_ptr + return reinterpret_cast&>(q->second.ptr); } }; -} \ No newline at end of file +} diff --git a/autowiring/auto_id.h b/autowiring/auto_id.h index 466616183..3088b98af 100644 --- a/autowiring/auto_id.h +++ b/autowiring/auto_id.h @@ -10,6 +10,20 @@ namespace autowiring { /// int CreateIndex(void); + /// + /// Safe alignment extractor, useful because MSVC doesn't allow __alignof to be used on abstract types + /// + template::value> + struct safe_align_of: + std::integral_constant + {}; + + // Abstract type, the alignment of this type is irrelevant + template + struct safe_align_of: + std::integral_constant + {}; + struct auto_id_block { auto_id_block(void) = default; @@ -31,6 +45,7 @@ namespace autowiring { index(index), ti(&typeid(T)), ncb(sizeof(T)), + align(safe_align_of::value), pToObj( reinterpret_cast(*)(const std::shared_ptr&)>(pToObj) ), @@ -46,6 +61,9 @@ namespace autowiring { // General properties of the underlying type size_t ncb; + // Alignment requirement of this type: + size_t align; + // Generic fast casters to CoreObject std::shared_ptr(*pToObj)(const std::shared_ptr&); std::shared_ptr(*pFromObj)(const std::shared_ptr&); diff --git a/src/autowiring/HeteroBlock.cpp b/src/autowiring/HeteroBlock.cpp index d03a0f7fc..9faf7bfc2 100644 --- a/src/autowiring/HeteroBlock.cpp +++ b/src/autowiring/HeteroBlock.cpp @@ -1,20 +1,105 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" #include "HeteroBlock.h" +#include "CreationRules.h" #include +#include using namespace autowiring; -HeteroBlock::HeteroBlock(std::initializer_list entries) +HeteroBlock::HeteroBlock(const HeteroBlockEntry* pFirst, const HeteroBlockEntry* pLast) { - // Compute the total amount of space that will be needed - size_t nTotal = 0; - for (const auto& entry : entries) - nTotal += entry.ncb; + entries.reserve(pLast - pFirst); - // Allocate just this much space - block.reset(new uint8_t[nTotal]); -} + // All entries we will stamp on the block space once everything is figured out + std::vector headers; + headers.resize(pLast - pFirst); -HeteroBlock::~HeteroBlock(void) -{ + // Minimum alignment requirement for the entire space + size_t maxAlign = sizeof(void*); + + // Need to set up the first and last entries + headers.front().pfnDtor = pFirst->pfnDtor; + + // Allocate enough space for everything: + size_t ncb = 0; + + size_t temp; + size_t* sizePrior = &temp; + + for (size_t i = 0; i < headers.size(); i++) { + // Minimum alignment is the word size for this architecture. This prevents bytes + // from getting sliced up and shared among multiple processors, which can create + // performance problems. This also ensures that the data members of the block + // header are correctly aligned. + size_t align = pFirst[i].id.block->align; + if (align <= sizeof(void*)) + align = sizeof(void*); + else if (maxAlign < align) + maxAlign = align; + + // Shift amount is the amount by which ncb changes in the next block + *sizePrior = ncb; + + // Shift over the amount needed due to misalignment. This works because we always + // assume that the space at offset [0] is perfectly aligned for all data. + ncb += (align - ncb) % align; + + // Update the offset, advance by the size of the block header + entries[pFirst[i]].offset = ncb; + ncb += pFirst[i].id.block->ncb; + + // Store the block entry + *sizePrior = ncb - *sizePrior; + sizePrior = &headers[i].nextSize; + headers[i].pfnDtor = pFirst[i].pfnDtor; + + // Increment past the header: + ncb += sizeof(BlockHeader); + } + uint8_t* pBlock = (uint8_t*)autowiring::aligned_malloc(ncb + sizeof(BlockHeader), maxAlign); + + // Initialize the deleter sentinel with null: + *(BlockHeader*)(pBlock + ncb) = {}; + + size_t firstSize = pFirst->id.block->ncb; + block.reset( + pBlock, + [firstSize] (void* ptr) { + size_t curSize = firstSize; + for ( + BlockHeader* pCur = (BlockHeader*)((uint8_t*)ptr + firstSize); + pCur->pfnDtor; + curSize = pCur->nextSize, + pCur = (BlockHeader*)((uint8_t*)pCur + pCur->nextSize) + 1 + ) + // Run the deleter here + pCur->pfnDtor((uint8_t*)pCur - curSize); + + // Now we can free the whole space + autowiring::aligned_free(ptr); + } + ); + + // Placement construct all objects and accumulate the offset: + size_t curSize = firstSize; + BlockHeader* pCurHeader = (BlockHeader*)&pBlock[firstSize]; + for (size_t i = 0; i < headers.size(); i++) { + try { + // Allocate this space right away: + pFirst[i].pfnCtor((uint8_t*)pCurHeader - curSize); + } + catch (...) { + // Zeroize the deleter, something went wrong, and forward the exception + pCurHeader->pfnDtor = nullptr; + throw; + } + + // Initialize, track, and advance + *pCurHeader = headers[i]; + curSize = pCurHeader->nextSize; + pCurHeader = (BlockHeader*)((uint8_t*)pCurHeader + pCurHeader->nextSize) + 1; + } } + +HeteroBlock::~HeteroBlock(void) {} diff --git a/src/autowiring/test/CMakeLists.txt b/src/autowiring/test/CMakeLists.txt index 179e546c9..659878124 100644 --- a/src/autowiring/test/CMakeLists.txt +++ b/src/autowiring/test/CMakeLists.txt @@ -48,6 +48,7 @@ set(AutowiringTest_SRCS GlobalInitTest.hpp GlobalInitTest.cpp GuardObjectTest.cpp + HeteroBlockTest.cpp InterlockedRoutinesTest.cpp MultiInheritTest.cpp ObjectPoolTest.cpp diff --git a/src/autowiring/test/HeteroBlockTest.cpp b/src/autowiring/test/HeteroBlockTest.cpp new file mode 100644 index 000000000..323f0b331 --- /dev/null +++ b/src/autowiring/test/HeteroBlockTest.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "TestFixtures/Decoration.hpp" +#include + +using autowiring::HeteroBlock; +using autowiring::HeteroBlockEntry; + +class HeteroBlockTest: + public testing::Test +{}; + +TEST_F(HeteroBlockTest, ObjectOrganizationTest) { + HeteroBlock block{ + HeteroBlockEntry { auto_id_t>(), 0 }, + HeteroBlockEntry { auto_id_t>(), 1 }, + HeteroBlockEntry { auto_id_t>(), 2 } + }; + + // Obtain one of each of the known entries: + auto& obj0 = block.get>(0); + auto& obj1 = block.get>(1); + auto& obj2 = block.get>(2); + ASSERT_LT(&obj0, &obj1); + ASSERT_LT(&obj1, &obj2); +} + +TEST_F(HeteroBlockTest, SharedPtrAliasTest) { + HeteroBlock block{ + { auto_id_t>(), 0 }, + { auto_id_t>(), 1 }, + { auto_id_t>(), 2 } + }; + + // Obtain one of each of the known entries: + auto& obj0 = block.get>(0); + auto obj0Shared = block.get_shared>(0); + auto& obj1 = block.get>(1); + auto obj1Shared = block.get_shared>(1); + auto& obj2 = block.get>(2); + auto obj2Shared = block.get_shared>(2); + ASSERT_EQ(&obj0, obj0Shared.get()); + ASSERT_EQ(&obj1, obj1Shared.get()); + ASSERT_EQ(&obj2, obj2Shared.get()); +} + +struct HeteroBlockTest_DestructorValidationTest { + HeteroBlockTest_DestructorValidationTest(void) {} + + std::shared_ptr destroyed; +}; + +TEST_F(HeteroBlockTest, DestroyObjTest) { + std::shared_ptr ref; + { + HeteroBlock block{ + { auto_id_t(), 0 }, + { auto_id_t(), 1 }, + { auto_id_t(), 2 } + }; + auto& obj0 = block.get(0); + obj0.destroyed = std::make_shared(false); + ref = obj0.destroyed; + } + ASSERT_TRUE(ref.unique()) << "Object referred to by a shared pointer was not destroyed as expected"; +} + +TEST_F(HeteroBlockTest, DestroySharedPtrTest) { + std::shared_ptr ref; + { + HeteroBlock block { + { auto_id_t(), 0 } + }; + auto& ptr0 = block.get_shared(0); + ptr0->destroyed = std::make_shared(false); + ref = ptr0->destroyed; + } + ASSERT_TRUE(ref.unique()) << "Object referred to by a shared pointer was not destroyed as expected"; +} From e4edadc0fbf71483871cbe8efc34988f4ab53bf5 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 07:32:04 -0700 Subject: [PATCH 24/60] Add an exception when something goes wrong with aligned allocation --- src/autowiring/HeteroBlock.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/autowiring/HeteroBlock.cpp b/src/autowiring/HeteroBlock.cpp index 9faf7bfc2..7943fec21 100644 --- a/src/autowiring/HeteroBlock.cpp +++ b/src/autowiring/HeteroBlock.cpp @@ -3,6 +3,7 @@ #include "HeteroBlock.h" #include "CreationRules.h" #include +#include #include using namespace autowiring; @@ -58,6 +59,8 @@ HeteroBlock::HeteroBlock(const HeteroBlockEntry* pFirst, const HeteroBlockEntry* ncb += sizeof(BlockHeader); } uint8_t* pBlock = (uint8_t*)autowiring::aligned_malloc(ncb + sizeof(BlockHeader), maxAlign); + if (!pBlock) + throw std::bad_alloc(); // Initialize the deleter sentinel with null: *(BlockHeader*)(pBlock + ncb) = {}; From cdf8dbcb2a57d622df9b41c9fc62102b5cc2be33 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 07:35:15 -0700 Subject: [PATCH 25/60] Ensure we are requested an aligned number of bytes --- src/autowiring/HeteroBlock.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/autowiring/HeteroBlock.cpp b/src/autowiring/HeteroBlock.cpp index 7943fec21..d19c62838 100644 --- a/src/autowiring/HeteroBlock.cpp +++ b/src/autowiring/HeteroBlock.cpp @@ -58,7 +58,10 @@ HeteroBlock::HeteroBlock(const HeteroBlockEntry* pFirst, const HeteroBlockEntry* // Increment past the header: ncb += sizeof(BlockHeader); } - uint8_t* pBlock = (uint8_t*)autowiring::aligned_malloc(ncb + sizeof(BlockHeader), maxAlign); + + size_t alignedRequest = ncb + sizeof(BlockHeader); + alignedRequest += (maxAlign - alignedRequest) % maxAlign; + uint8_t* pBlock = (uint8_t*)autowiring::aligned_malloc(alignedRequest, maxAlign); if (!pBlock) throw std::bad_alloc(); From f9e35f95aa65ff41318b68baf9f7c424d06c4341 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 07:56:12 -0700 Subject: [PATCH 26/60] Need a const cast for this nullptr `DeferrableAutowiring` may be deferring a `const` field, but `const` type erasure is required to prevent underlying system complexity from overloading. Allow the `Autowired` front-end for this type to handle const correctness for us rather than pushing this to the subystem. --- autowiring/AutowirableSlot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 7af754c3f..fb882877a 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -129,7 +129,7 @@ class AutowirableSlot: typedef T value_type; AutowirableSlot(const std::shared_ptr& ctxt) : - DeferrableAutowiring(AnySharedPointerT(), ctxt) + DeferrableAutowiring(AnySharedPointerT::type>(), ctxt) { SlotInformationStackLocation::RegisterSlot(this); } From bc04d65c93c3383a556477d4df8d57aebfdf34ae Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 08:13:29 -0700 Subject: [PATCH 27/60] Add a benchmark test for the object pool `ObjectPool` will need to be a performant type. Add a benchmark so we can track its improvement. Current object pool times show `ObjectPool` as about 4x slower than `malloc` for flat datatypes, and 2x slower than `malloc` for types that allocate additional memory. This is clearly an unacceptable situation. --- src/autowiring/benchmark/AutoBench.cpp | 2 + src/autowiring/benchmark/CMakeLists.txt | 2 + src/autowiring/benchmark/ObjectPoolBm.cpp | 65 +++++++++++++++++++++++ src/autowiring/benchmark/ObjectPoolBm.h | 9 ++++ 4 files changed, 78 insertions(+) create mode 100644 src/autowiring/benchmark/ObjectPoolBm.cpp create mode 100644 src/autowiring/benchmark/ObjectPoolBm.h diff --git a/src/autowiring/benchmark/AutoBench.cpp b/src/autowiring/benchmark/AutoBench.cpp index c000c90aa..fcff9122b 100644 --- a/src/autowiring/benchmark/AutoBench.cpp +++ b/src/autowiring/benchmark/AutoBench.cpp @@ -4,6 +4,7 @@ #include "ContextSearchBm.h" #include "ContextTrackingBm.h" #include "DispatchQueueBm.h" +#include "ObjectPoolBm.h" #include "PrintableDuration.h" #include "PriorityBoost.h" #include @@ -46,6 +47,7 @@ static std::map sc_commands = { MakeEntry("dispatch", "Dispatch queue execution rate", &DispatchQueueBm::Dispatch), MakeEntry("contextenum", "CoreContextEnumerator profiling", &ContextTrackingBm::ContextEnum), MakeEntry("contextmap", "ContextMap profiling", &ContextTrackingBm::ContextMap), + MakeEntry("objpool", "Object pool behaviors", &ObjectPoolBm::Allocation), }; static Benchmark All(void) { diff --git a/src/autowiring/benchmark/CMakeLists.txt b/src/autowiring/benchmark/CMakeLists.txt index 623a4f326..628ff00d9 100644 --- a/src/autowiring/benchmark/CMakeLists.txt +++ b/src/autowiring/benchmark/CMakeLists.txt @@ -9,6 +9,8 @@ set(AutoBench_SRCS DispatchQueueBm.h DispatchQueueBm.cpp Foo.h + ObjectPoolBm.h + ObjectPoolBm.cpp PriorityBoost.h PriorityBoost.cpp PrintableDuration.h diff --git a/src/autowiring/benchmark/ObjectPoolBm.cpp b/src/autowiring/benchmark/ObjectPoolBm.cpp new file mode 100644 index 000000000..b354de693 --- /dev/null +++ b/src/autowiring/benchmark/ObjectPoolBm.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#include "stdafx.h" +#include "ObjectPoolBm.h" +#include "Benchmark.h" +#include +#include +#include + +static const size_t n = 100; + +// Dummy struct of ten integers--needed because unique_ptr doesn't compile +struct tenPack { int dummy[10]; }; + +// Type that will perform a secondary allocation on construction +struct filling_vector { + filling_vector(void) { + other.resize(100); + } + + std::vector other; +}; + +template +void profile_basic(Stopwatch& sw) { + std::vector> mem; + + // Initial set of allocations: + for (size_t i = n; i--;) + mem.emplace_back(new T); + mem.clear(); + + // Now simulate something where an object pool might have been handy + for (size_t k = n; k--;) { + sw.Start(); + for (size_t i = n; i--;) + mem.emplace_back(new T); + sw.Stop(n * n); + mem.clear(); + } +} + +template +void profile_pool(Stopwatch& sw) { + ObjectPool pool; + pool.Preallocate(n); + + // Now simulate something where an object pool might have been handy + std::vector> mem; + for (size_t k = n; k--;) { + sw.Start(); + for (size_t i = n; i--;) + mem.emplace_back(pool()); + sw.Stop(n * n); + mem.clear(); + } +} + +Benchmark ObjectPoolBm::Allocation(void) { + return { + { "basic", &profile_basic }, + { "complex", &profile_basic }, + { "pool_basic", &profile_pool }, + { "pool_complex", &profile_pool } + }; +} diff --git a/src/autowiring/benchmark/ObjectPoolBm.h b/src/autowiring/benchmark/ObjectPoolBm.h new file mode 100644 index 000000000..318a2991f --- /dev/null +++ b/src/autowiring/benchmark/ObjectPoolBm.h @@ -0,0 +1,9 @@ +// Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. +#pragma once + +struct Benchmark; + +class ObjectPoolBm { +public: + static Benchmark Allocation(void); +}; \ No newline at end of file From 5b63a939d95d4faa4b94bf3e5b81fbad04ba583d Mon Sep 17 00:00:00 2001 From: Jonathan Marsden Date: Tue, 28 Jul 2015 10:11:55 -0700 Subject: [PATCH 28/60] Fix copy/paste typo --- autowiring/C++11/cpp11.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index a73344f3b..00615e5a4 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -52,7 +52,7 @@ #if defined(_MSC_VER) && _MSC_VER <= 1800 #define AUTO_ALIGNOF __alignof #else - #define AUTO_ALIGNAS alignof + #define AUTO_ALIGNOF alignof #endif /********************* From 67a62627db3a6fd57d8db8097bd9bb608b09036f Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 08:30:18 -0700 Subject: [PATCH 29/60] Improve object pool performance Reduce the number of things that have to be copied to perform an allocation to just two--the pool version and the monitor reference. --- autowiring/ObjectPool.h | 138 +++++++++++++++------------ autowiring/ObjectPoolMonitor.h | 35 +++++-- src/autowiring/ObjectPoolMonitor.cpp | 4 +- 3 files changed, 103 insertions(+), 74 deletions(-) diff --git a/autowiring/ObjectPool.h b/autowiring/ObjectPool.h index a82ae6635..860c538e6 100644 --- a/autowiring/ObjectPool.h +++ b/autowiring/ObjectPool.h @@ -46,25 +46,18 @@ class ObjectPool const std::function& initial = &DefaultInitialize, const std::function& final = &DefaultFinalize ) : - m_monitor(std::make_shared(this)), + m_monitor(std::make_shared>(this, initial, final)), m_maxPooled(maxPooled), m_limit(limit), - m_initial(initial), - m_final(final), m_alloc(alloc) {} - /// The maximum number of objects this pool will allow to be outstanding at any time. - /// The maximum number of objects cached by the pool. - ObjectPool( + /// An allocator to be used when creating objects. + DEPRECATED(ObjectPool( const std::function& alloc, const std::function& initial = &DefaultInitialize, - const std::function& final = &DefaultFinalize, - size_t limit = ~0, - size_t maxPooled = ~0 - ) : - ObjectPool(limit, maxPooled, alloc, initial, final) - {} + const std::function& final = &DefaultFinalize + ), "Superceded by the placement construction version"); ObjectPool(ObjectPool&& rhs) { @@ -84,25 +77,60 @@ class ObjectPool } protected: - std::shared_ptr m_monitor; + std::shared_ptr> m_monitor; std::condition_variable m_setCondition; + struct PoolEntry { + PoolEntry(ObjectPool& pool, size_t poolVersion, T* ptr) : + monitor(pool.m_monitor), + poolVersion(poolVersion), + ptr(ptr) + {} + + ~PoolEntry(void) { + delete ptr; + } + + void Clean(void) { + // Finalize object before destruction or return to pool. + monitor->fnl(*ptr); + + bool inPool = false; + { + // Obtain lock before deciding whether to delete or return to pool. + std::lock_guard lk(*monitor); + if (!monitor->IsAbandoned()) + // Attempt to return object to pool + inPool = monitor->owner->ReturnUnsafe(this); + } + if (!inPool) + // Destroy returning object outside of lock. + delete ptr; + } + + // Pointer to the monitor used to get us back to our pool when we're returned + const std::shared_ptr> monitor; + + // Pool version at the time of construction + const size_t poolVersion; + + // TODO: This should be an embedded member. As soon as we can deprecate the allocating + // ctor overload of ObjectPool::ObjectPool, do so. + T* const ptr; + }; + // The set of pooled objects, and the pool version. The pool version is incremented every // time the ClearCachedEntities method is called, and causes entities which might be trying // to return to the pool to instead free themselves. // IMPORTANT: m_objs cannot be a vector of std::unique_ptr instances because the required move // implementation of std::vector is missing when not building with c++11. size_t m_poolVersion = 0; - std::vector m_objs; + std::vector m_objs; size_t m_maxPooled; size_t m_limit; size_t m_outstanding = 0; - // Resetters: - std::function m_initial; - std::function m_final; - // Allocator: std::function m_alloc; @@ -113,44 +141,18 @@ class ObjectPool /// The Initialize is applied immediate when Wrap is called. /// The Finalize function will be applied is in the shared_ptr destructor. /// - std::shared_ptr Wrap(T* pObj) { - // Fill the shared pointer with the object we created, and ensure that we override - // the destructor so that the object is returned to the pool when it falls out of - // scope. - size_t poolVersion = m_poolVersion; - auto monitor = m_monitor; - std::function final = m_final; - - auto retVal = std::shared_ptr( - pObj, - [poolVersion, monitor, final](T* ptr) { - // Finalize object before destruction or return to pool. - final(*ptr); - - bool inPool = false; - { - // Obtain lock before deciding whether to delete or return to pool. - std::lock_guard lk(*monitor); - if(!monitor->IsAbandoned()) { - // Attempt to return object to pool - inPool = static_cast*>(monitor->GetOwner())->ReturnUnsafe(poolVersion, ptr); - } - } - if (!inPool) { - // Destroy returning object outside of lock. - delete ptr; - } - } - ); + std::shared_ptr Wrap(PoolEntry* entry) { + // Create the shared pointer which will delegate cleanup + std::shared_ptr pe(entry, [](PoolEntry* entry) { entry->Clean(); }); // Initialize the issued object, now that a shared pointer has been created for it. - m_initial(*pObj); + m_monitor->initial(*entry->ptr); - // All done - return retVal; + // All done, use an aliased shared pointer here + return std::shared_ptr(std::move(pe), entry->ptr); } - bool ReturnUnsafe(size_t poolVersion, T* ptr) { + bool ReturnUnsafe(PoolEntry* ptr) { // ASSERT: Object has already been finalized. // Always decrement the count when an object is no longer outstanding. assert(m_outstanding); @@ -159,11 +161,11 @@ class ObjectPool bool inPool = false; if( // Pool versions have to match, or the object should be dumped. - poolVersion == m_poolVersion && + ptr->poolVersion == m_poolVersion && // Object pool needs to be capable of accepting another object as an input. m_objs.size() < m_maxPooled - ) { + ) { // Return the object to the pool: m_objs.push_back(ptr); inPool = true; @@ -189,17 +191,19 @@ class ObjectPool // Cached, or construct? if(m_objs.empty()) { + // Get the version before releasing the lock, we need to remain coherent with m_outstanding + size_t poolVersion = m_poolVersion; + // Lock release, so construction does not have to be synchronized: lk.unlock(); // We failed to recover an object, create a new one: - auto obj = Wrap(m_alloc()); - return obj; + return Wrap(new PoolEntry(*this, poolVersion, m_alloc())); } // Transition from pooled to issued: - std::shared_ptr iObj = Wrap(m_objs.back()); // Takes ownership - m_objs.pop_back(); // Remove unsafe reference + std::shared_ptr iObj = Wrap(m_objs.back()); + m_objs.pop_back(); return iObj; } @@ -231,7 +235,7 @@ class ObjectPool /// void ClearCachedEntities(void) { std::lock_guard lk(*m_monitor); - for (T* obj : m_objs) + for (PoolEntry* obj : m_objs) delete obj; m_objs.clear(); m_poolVersion++; @@ -406,7 +410,7 @@ class ObjectPool void operator=(ObjectPool&& rhs) { // Abandon current monitor, we are orphaning current objects if(m_monitor) { - m_monitor->SetOwner(&rhs); + m_monitor->owner = &rhs; m_monitor->Abandon(); } @@ -420,10 +424,20 @@ class ObjectPool m_outstanding = rhs.m_outstanding; std::swap(m_objs, rhs.m_objs); std::swap(m_alloc, rhs.m_alloc); - std::swap(m_initial, rhs.m_initial); - std::swap(m_final, rhs.m_final); // Now we can take ownership of this monitor object: - m_monitor->SetOwner(this); + m_monitor->owner = this; } }; + +template +ObjectPool::ObjectPool( + const std::function& alloc, + const std::function& initial, + const std::function& final +) : + m_monitor(std::make_shared>(this, initial, final)), + m_maxPooled(~0), + m_limit(~0), + m_alloc(alloc) +{} diff --git a/autowiring/ObjectPoolMonitor.h b/autowiring/ObjectPoolMonitor.h index 04b74500a..125f6c752 100644 --- a/autowiring/ObjectPoolMonitor.h +++ b/autowiring/ObjectPoolMonitor.h @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #pragma once +#include #include MUTEX_HEADER template @@ -20,20 +21,12 @@ class ObjectPoolMonitor: public std::mutex { public: - /// The owner of this object pool monitor - ObjectPoolMonitor(void* pOwner); + ObjectPoolMonitor(void); private: - void* m_pOwner; bool m_abandoned = false; public: - // Accessor methods: - void* GetOwner(void) const { return m_pOwner; } - - // Mutator methods: - void SetOwner(void* pOwner) { m_pOwner = pOwner; } - /// /// True if this pool has been abandoned /// @@ -44,3 +37,27 @@ class ObjectPoolMonitor: /// void Abandon(void); }; + +template +class ObjectPoolMonitorT: + public ObjectPoolMonitor +{ +public: + ObjectPoolMonitorT(ObjectPool* owner) : + owner(owner), + initial([](T&) {}), + fnl([](T&) {}) + {} + ObjectPoolMonitorT(ObjectPool* owner, std::function initial, std::function fnl) : + owner(owner), + initial(initial), + fnl(fnl) + {} + + // Owner back-reference + ObjectPool* owner; + + // Resetters: + const std::function initial; + const std::function fnl; +}; \ No newline at end of file diff --git a/src/autowiring/ObjectPoolMonitor.cpp b/src/autowiring/ObjectPoolMonitor.cpp index c37e84d40..826b6194e 100644 --- a/src/autowiring/ObjectPoolMonitor.cpp +++ b/src/autowiring/ObjectPoolMonitor.cpp @@ -2,9 +2,7 @@ #include "stdafx.h" #include "ObjectPoolMonitor.h" -ObjectPoolMonitor::ObjectPoolMonitor(void* pOwner) : - m_pOwner(pOwner) -{} +ObjectPoolMonitor::ObjectPoolMonitor(void) {} void ObjectPoolMonitor::Abandon(void) { (std::lock_guard)*this, From 35407e981dc6311710e944d629ef418cf884d853 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 10:38:54 -0700 Subject: [PATCH 30/60] Fix typo --- src/autowiring/benchmark/ObjectPoolBm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autowiring/benchmark/ObjectPoolBm.cpp b/src/autowiring/benchmark/ObjectPoolBm.cpp index b354de693..f6a7a9890 100644 --- a/src/autowiring/benchmark/ObjectPoolBm.cpp +++ b/src/autowiring/benchmark/ObjectPoolBm.cpp @@ -60,6 +60,6 @@ Benchmark ObjectPoolBm::Allocation(void) { { "basic", &profile_basic }, { "complex", &profile_basic }, { "pool_basic", &profile_pool }, - { "pool_complex", &profile_pool } + { "pool_complex", &profile_pool } }; } From 640f1a2c54294ef0de0da3a16ab13a86ff1678b7 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 10:43:53 -0700 Subject: [PATCH 31/60] Add zenhub badge At least for as long as zenhub is being used --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 72ef2db12..d8681a0a6 100644 --- a/README.md +++ b/README.md @@ -78,3 +78,5 @@ to find Autowiring once the package is installed by this means. Generally speaking, there is not really much reason to build an installer yourself unless you're testing out the bleeding edge. The [releases page](https://github.com/leapmotion/autowiring/releases) lists the officially supported Autowiring releases. + +[![ZenHub Badge](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) \ No newline at end of file From 70b88c4161fafa8b7f4bd37d3c02e30ec706a938 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 16:06:14 -0700 Subject: [PATCH 32/60] Fix compatibility, implement ObjPool placement constructor We would like to deprecate the allocating constructor variant of `ObjectPool`, but we cannot do this all at once. Encourage users to use a placement constructor rather than an allocating constructor. --- autowiring/ObjectPool.h | 75 +++++++++++++++++--------- src/autowiring/test/ObjectPoolTest.cpp | 16 ++++++ 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/autowiring/ObjectPool.h b/autowiring/ObjectPool.h index 860c538e6..90976fac7 100644 --- a/autowiring/ObjectPool.h +++ b/autowiring/ObjectPool.h @@ -19,6 +19,11 @@ void DefaultInitialize(T&){} template void DefaultFinalize(T&){} +namespace autowiring { + struct placement_t {}; + static const placement_t placement; +} + /// /// Allows the management of a pool of objects based on an embedded factory. /// @@ -37,20 +42,22 @@ template class ObjectPool { public: - /// The maximum number of objects this pool will allow to be outstanding at any time. - /// The maximum number of objects cached by the pool. - ObjectPool( - size_t limit = ~0, + ObjectPool(void) : + ObjectPool(~0, ~0) + {} + + ObjectPool(ObjectPool&& rhs) + { + *this = std::move(rhs); + } + + DEPRECATED(ObjectPool( + size_t limit, size_t maxPooled = ~0, const std::function& alloc = &DefaultCreate, const std::function& initial = &DefaultInitialize, const std::function& final = &DefaultFinalize - ) : - m_monitor(std::make_shared>(this, initial, final)), - m_maxPooled(maxPooled), - m_limit(limit), - m_alloc(alloc) - {} + ), "Superceded by the placement construction version"); /// An allocator to be used when creating objects. DEPRECATED(ObjectPool( @@ -58,11 +65,19 @@ class ObjectPool const std::function& initial = &DefaultInitialize, const std::function& final = &DefaultFinalize ), "Superceded by the placement construction version"); - - ObjectPool(ObjectPool&& rhs) - { - *this = std::move(rhs); - } + + /// The maximum number of objects this pool will allow to be outstanding at any time. + /// The maximum number of objects cached by the pool. + ObjectPool( + const autowiring::placement_t&, + const std::function& placement, + const std::function& initial = &DefaultInitialize, + const std::function& final = &DefaultFinalize + ) : + m_monitor(std::make_shared>(this, initial, final)), + m_alloc(&DefaultCreate), + m_placement(placement) + {} ~ObjectPool(void) { if(!m_monitor) @@ -127,12 +142,13 @@ class ObjectPool size_t m_poolVersion = 0; std::vector m_objs; - size_t m_maxPooled; - size_t m_limit; + size_t m_maxPooled = ~0; + size_t m_limit = ~0; size_t m_outstanding = 0; - // Allocator: + // Allocator, placement ctor: std::function m_alloc; + std::function m_placement{ [](T*) {} }; /// /// Creates a shared pointer to wrap the specified object while it is issued. @@ -198,7 +214,9 @@ class ObjectPool lk.unlock(); // We failed to recover an object, create a new one: - return Wrap(new PoolEntry(*this, poolVersion, m_alloc())); + T* pObj = m_alloc(); + m_placement(pObj); + return Wrap(new PoolEntry(*this, poolVersion, pObj)); } // Transition from pooled to issued: @@ -220,11 +238,6 @@ class ObjectPool return m_objs.empty() && !m_outstanding; } - // Mutator methods: - void SetAlloc(const std::function& alloc) { - m_alloc = alloc; - } - /// /// Discards all entities currently saved in the pool. /// @@ -430,6 +443,20 @@ class ObjectPool } }; +template +ObjectPool::ObjectPool( + size_t limit, + size_t maxPooled, + const std::function& alloc, + const std::function& initial, + const std::function& final +) : + m_monitor(std::make_shared>(this, initial, final)), + m_maxPooled(maxPooled), + m_limit(limit), + m_alloc(alloc) +{} + template ObjectPool::ObjectPool( const std::function& alloc, diff --git a/src/autowiring/test/ObjectPoolTest.cpp b/src/autowiring/test/ObjectPoolTest.cpp index ba4bc649c..2d3cc6c60 100644 --- a/src/autowiring/test/ObjectPoolTest.cpp +++ b/src/autowiring/test/ObjectPoolTest.cpp @@ -388,3 +388,19 @@ TEST_F(ObjectPoolTest, VerifyInitializerFinalizer) { ASSERT_FALSE(*initFlag) << "Returned item incorrectly caused a new initialization"; ASSERT_TRUE(*termFlag) << "Returned item was not correctly finalized"; } + +TEST_F(ObjectPoolTest, PlacementConstructor) { + ObjectPool pool( + autowiring::placement, + [](int* pVal) { + *pVal = 109; + }, + [](int& val) { + ASSERT_EQ(val, 109) << "Value was not placement constructed properly"; + val = 110; + } + ); + + auto obj = pool(); + ASSERT_EQ(110, *obj) << "Value was not correctly initialized"; +} \ No newline at end of file From 480fdff13f23739ef2df89b993b05943c008e9f1 Mon Sep 17 00:00:00 2001 From: Jonathan Marsden Date: Tue, 28 Jul 2015 16:15:35 -0700 Subject: [PATCH 33/60] Remove unused timepoint variable --- autowiring/DispatchQueue.h | 1 - 1 file changed, 1 deletion(-) diff --git a/autowiring/DispatchQueue.h b/autowiring/DispatchQueue.h index aa5338300..016d8cb85 100644 --- a/autowiring/DispatchQueue.h +++ b/autowiring/DispatchQueue.h @@ -304,7 +304,6 @@ class DispatchQueue { "Dispatch queues cannot be used to describe intervals less than one microseconds in duration" ); - std::chrono::steady_clock::time_point timepoint = std::chrono::steady_clock::now() + rhs; return{this, rhs}; } From d034e68941b7fcd4b8920e98679976211ac7978b Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 16:22:14 -0700 Subject: [PATCH 34/60] Add some comments on a complicated arithmetic expression --- src/autowiring/HeteroBlock.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/autowiring/HeteroBlock.cpp b/src/autowiring/HeteroBlock.cpp index d19c62838..6434dbc57 100644 --- a/src/autowiring/HeteroBlock.cpp +++ b/src/autowiring/HeteroBlock.cpp @@ -44,6 +44,18 @@ HeteroBlock::HeteroBlock(const HeteroBlockEntry* pFirst, const HeteroBlockEntry* // Shift over the amount needed due to misalignment. This works because we always // assume that the space at offset [0] is perfectly aligned for all data. + // This crazy arithmetic is a simplification of the following expression: + // + // size_t slop = ncb % align; + // size_t slack = (align - slop) % align; + // ncb += slack; + // + // Written in one line, this is jsut: + // + // ncb += (align - (ncb % align)) % align; + // + // However, `modulo x` is an idempotent operation under addition and subtraction, + // which means that the above expression simplifies down to just this: ncb += (align - ncb) % align; // Update the offset, advance by the size of the block header From 0198d475a59170ad3e7dba69e5c39398711c8758 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 16:26:01 -0700 Subject: [PATCH 35/60] Fix mac build, fix `operator=` --- autowiring/ObjectPool.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autowiring/ObjectPool.h b/autowiring/ObjectPool.h index 90976fac7..d4de319b2 100644 --- a/autowiring/ObjectPool.h +++ b/autowiring/ObjectPool.h @@ -21,7 +21,7 @@ void DefaultFinalize(T&){} namespace autowiring { struct placement_t {}; - static const placement_t placement; + static const placement_t placement{}; } /// @@ -437,6 +437,7 @@ class ObjectPool m_outstanding = rhs.m_outstanding; std::swap(m_objs, rhs.m_objs); std::swap(m_alloc, rhs.m_alloc); + std::swap(m_placement, rhs.m_placement); // Now we can take ownership of this monitor object: m_monitor->owner = this; From ffb8ef28bb12fc28dd81b0e589754d366227e224 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Tue, 28 Jul 2015 16:27:18 -0700 Subject: [PATCH 36/60] Fix stale comments --- autowiring/ObjectPool.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/autowiring/ObjectPool.h b/autowiring/ObjectPool.h index d4de319b2..2e1bb50aa 100644 --- a/autowiring/ObjectPool.h +++ b/autowiring/ObjectPool.h @@ -66,8 +66,15 @@ class ObjectPool const std::function& final = &DefaultFinalize ), "Superceded by the placement construction version"); - /// The maximum number of objects this pool will allow to be outstanding at any time. - /// The maximum number of objects cached by the pool. + /// + /// A placement constructor to be used on the memory allocated for objects + /// + /// + /// The initializer that will be run on all objects issued by the pool before returning them to callers + /// + /// + /// The finalizer that will be run on objects as they return to the pool + /// ObjectPool( const autowiring::placement_t&, const std::function& placement, From a646ed4e3012e7a64ba8a870971ea894091d9b07 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 08:27:32 -0700 Subject: [PATCH 37/60] Don't use stacks Stacks are slow, and `std::vector` is faster and has all of the same features. The semantic simplicity of a stack is attractive, but unnecessary where we're using it. --- src/autowiring/CoreContext.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index c2092f1b0..c09d9cbf5 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -15,7 +15,6 @@ #include "thread_specific_ptr.h" #include "ThreadPool.h" #include -#include #include using namespace autowiring; @@ -819,14 +818,14 @@ void CoreContext::SatisfyAutowiring(std::unique_lock& lk, MemoEntry& // Now we need to take on the responsibility of satisfying this deferral. We will do this by // nullifying the flink, and by ensuring that the memo is satisfied at the point where we // release the lock. - std::stack stk; - stk.push(entry.pFirst); + std::vector stk; + stk.push_back(entry.pFirst); entry.pFirst = nullptr; // Depth-first search while (!stk.empty()) { - auto top = stk.top(); - stk.pop(); + auto top = stk.back(); + stk.pop_back(); for (DeferrableAutowiring* pCur = top; pCur; pCur = pCur->GetFlink()) { pCur->SatisfyAutowiring(entry.m_value); @@ -834,7 +833,7 @@ void CoreContext::SatisfyAutowiring(std::unique_lock& lk, MemoEntry& // See if there's another chain we need to process: auto child = pCur->ReleaseDependentChain(); if (child) - stk.push(child); + stk.push_back(child); // Not everyone needs to be finalized. The entities that don't require finalization // are identified by an empty strategy, and we just skip them. @@ -892,7 +891,7 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // also removed at the same time. // // Each connected nonroot deferrable autowiring is referred to as a "dependant chain". - std::stack stk; + std::vector stk; for (auto& cur : m_typeMemos) { MemoEntry& value = cur.second; @@ -915,13 +914,13 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // Now we need to take on the responsibility of satisfying this deferral. We will do this by // nullifying the flink, and by ensuring that the memo is satisfied at the point where we // release the lock. - stk.push(value.pFirst); + stk.push_back(value.pFirst); value.pFirst = nullptr; // Finish satisfying the remainder of the chain while we hold the lock: while (!stk.empty()) { - auto top = stk.top(); - stk.pop(); + auto top = stk.back(); + stk.pop_back(); for (DeferrableAutowiring* pNext = top; pNext; pNext = pNext->GetFlink()) { pNext->SatisfyAutowiring(value.m_value); @@ -929,7 +928,7 @@ void CoreContext::UpdateDeferredElements(std::unique_lock&& lk, cons // See if there's another chain we need to process: auto child = pNext->ReleaseDependentChain(); if (child) - stk.push(child); + stk.push_back(child); // Not everyone needs to be finalized. The entities that don't require finalization // are identified by an empty strategy, and we just skip them. From e5f67c42cb836cc0bd8249fdbe8edf3cd5b8ab79 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 08:03:48 -0700 Subject: [PATCH 38/60] Break AutowirableSlot inheritance from AnySharedPointer This inheritance is causing operator overload resolution to be a lot more complex, simplify it by breaking an inappropriate inheritance relation. --- autowiring/AutowirableSlot.h | 28 +++++++++++++++++++++------- autowiring/Autowired.h | 16 +++------------- src/autowiring/AutowirableSlot.cpp | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index fb882877a..032b3d57e 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -48,14 +48,16 @@ class DeferrableUnsynchronizedStrategy { /// /// Utility class which represents any kind of autowiring entry that may be deferred to a later date /// -class DeferrableAutowiring: - protected AnySharedPointer +class DeferrableAutowiring { public: DeferrableAutowiring(AnySharedPointer&& witness, const std::shared_ptr& context); virtual ~DeferrableAutowiring(void); protected: + // The held shared pointer + AnySharedPointer m_ptr; + /// /// This is the context that was available at the time the autowiring was performed. /// @@ -77,7 +79,7 @@ class DeferrableAutowiring: public: // Accessor methods: DeferrableAutowiring* GetFlink(void) { return m_pFlink; } - const AnySharedPointer& GetSharedPointer(void) const { return *this; } + const AnySharedPointer& GetSharedPointer(void) const { return m_ptr; } // Mutator methods: void SetFlink(DeferrableAutowiring* pFlink) { @@ -93,7 +95,7 @@ class DeferrableAutowiring: /// The type on which this deferred slot is bound /// auto_id GetType(void) const { - return AnySharedPointer::type(); + return m_ptr.type(); } // Reset this pointer. Similar to shared_ptr::reset(). @@ -117,7 +119,7 @@ class DeferrableAutowiring: /// Satisfies autowiring with a so-called "witness slot" which is guaranteed to be satisfied on the same type /// virtual void SatisfyAutowiring(const AnySharedPointer& ptr) { - (AnySharedPointer&)*this = ptr; + m_ptr = ptr; } }; @@ -174,7 +176,7 @@ class AutowirableSlot: /// this may prevent the type from ever being detected as autowirable as a result. /// T* get_unsafe(void) const { - return static_cast(ptr()); + return static_cast(m_ptr.ptr()); } explicit operator bool(void) const { @@ -207,9 +209,21 @@ class AutowirableSlot: return *retVal; } - using AnySharedPointer::operator=; + bool operator==(const AnySharedPointer& rhs) const { + return m_ptr == rhs; + } + + template + bool operator==(const std::shared_ptr& rhs) const { + return m_ptr == rhs; + } }; +template +bool operator==(const std::shared_ptr& lhs, const AutowirableSlot& rhs) { + return rhs == lhs; +} + /// /// A function-based autowirable slot, which invokes a lambda rather than binding a shared pointer /// diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index 186323d55..dfc63d2f0 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -112,12 +112,7 @@ class Autowired: AutowirableSlot(ctxt) { if(ctxt) - ctxt->Autowire( - *static_cast*>( - static_cast(this) - ), - *this - ); + ctxt->Autowire(static_cast&>(this->m_ptr), *this); } ~Autowired(void) { @@ -150,12 +145,7 @@ class Autowired: public: operator const std::shared_ptr&(void) const { - return - static_cast*>( - static_cast( - this - ) - )->get(); + return static_cast&>(this->m_ptr).get(); } operator std::weak_ptr(void) const { @@ -231,7 +221,7 @@ class Autowired: if(pFirstChild == this) { // Trivially satisfy, and then return. This might look like a leak, but it's not, because we know // that Finalize is going to destroy the object. - newHead->SatisfyAutowiring(*this); + newHead->SatisfyAutowiring(this->m_ptr); newHead->Finalize(); return; } diff --git a/src/autowiring/AutowirableSlot.cpp b/src/autowiring/AutowirableSlot.cpp index 5d782aab6..280741015 100644 --- a/src/autowiring/AutowirableSlot.cpp +++ b/src/autowiring/AutowirableSlot.cpp @@ -9,7 +9,7 @@ using namespace std; DeferrableAutowiring::DeferrableAutowiring(AnySharedPointer&& witness, const std::shared_ptr& context) : - AnySharedPointer(std::move(witness)), + m_ptr(std::move(witness)), m_context(context), m_pFlink(nullptr) {} From 1e4a55fa37f3dc3eb11a80e36ea1dd26126590e8 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 10:15:47 -0700 Subject: [PATCH 39/60] Eliminate Autowired::GetContext Eliminate this low-level internal routine, as its use implies an antipattern. Users instead should track their contexts themselves, and only use `Autowired` to access the type that's actually been wired in. This change will allow us greater flexibility in deciding how to use `m_context` and when to reset it . --- autowiring/AutowirableSlot.h | 5 ----- src/autowiring/test/ScopeTest.cpp | 1 - 2 files changed, 6 deletions(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index fb882877a..ef4cb5af7 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -84,11 +84,6 @@ class DeferrableAutowiring: m_pFlink = pFlink; } - /// - /// The context corresponding to this slot, if it hasn't already expired - /// - std::shared_ptr GetContext(void) const { return m_context.lock(); } - /// /// The type on which this deferred slot is bound /// diff --git a/src/autowiring/test/ScopeTest.cpp b/src/autowiring/test/ScopeTest.cpp index 9cc82f3bb..e40b808cf 100644 --- a/src/autowiring/test/ScopeTest.cpp +++ b/src/autowiring/test/ScopeTest.cpp @@ -181,7 +181,6 @@ TEST_F(ScopeTest, RequireVsWire) { Autowired a_wired_inner; ASSERT_FALSE(a_wired_inner.IsAutowired()) << "Autowired member became autowired too quickly"; - ASSERT_EQ(a_wired_inner.GetContext(), ctxt_inner) << "Autowired member created in the wrong context"; AutoRequired a_required_outer(ctxt_outer); ASSERT_TRUE(a_required_outer.IsAutowired()) << "AutoRequired member unsatisfied after construction"; From c136bca9c1cc2e63f7cd94f5e52c3b9bef54e299 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 08:10:32 -0700 Subject: [PATCH 40/60] Implement an unlink-on-teardown feature Fixes #672 --- autowiring/AutowirableSlot.h | 26 ++++++----- autowiring/Autowired.h | 4 +- autowiring/CoreContext.h | 23 +++++++++ src/autowiring/AutowirableSlot.cpp | 3 +- src/autowiring/CoreContext.cpp | 35 ++++++++++++++ src/autowiring/test/CoreContextTest.cpp | 62 +++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 15 deletions(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index 032b3d57e..59bd4f849 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -69,7 +69,7 @@ class DeferrableAutowiring std::weak_ptr m_context; // The next entry to be autowired in this sequence - DeferrableAutowiring* m_pFlink; + DeferrableAutowiring* m_pFlink = nullptr; /// /// Causes this deferrable to unregister itself with the enclosing context @@ -86,6 +86,11 @@ class DeferrableAutowiring m_pFlink = pFlink; } + /// + /// True if the underlying field is autowired + /// + bool IsAutowired(void) const { return !!m_ptr.ptr(); } + /// /// The context corresponding to this slot, if it hasn't already expired /// @@ -121,6 +126,14 @@ class DeferrableAutowiring virtual void SatisfyAutowiring(const AnySharedPointer& ptr) { m_ptr = ptr; } + + bool operator!=(const AnySharedPointer& rhs) const { return m_ptr != rhs; } + bool operator==(const AnySharedPointer& rhs) const { return m_ptr == rhs; } + + template + bool operator==(const std::shared_ptr& rhs) const { + return m_ptr == rhs; + } }; template @@ -154,7 +167,7 @@ class AutowirableSlot: // are to other types in the system. (void) autowiring::fast_pointer_cast_initializer::sc_init; (void) auto_id_t_init::init; - return !!get(); + return DeferrableAutowiring::IsAutowired(); } /// @@ -208,15 +221,6 @@ class AutowirableSlot: return *retVal; } - - bool operator==(const AnySharedPointer& rhs) const { - return m_ptr == rhs; - } - - template - bool operator==(const std::shared_ptr& rhs) const { - return m_ptr == rhs; - } }; template diff --git a/autowiring/Autowired.h b/autowiring/Autowired.h index dfc63d2f0..9df7c80cc 100644 --- a/autowiring/Autowired.h +++ b/autowiring/Autowired.h @@ -111,7 +111,7 @@ class Autowired: Autowired(const std::shared_ptr& ctxt = CoreContext::CurrentContext()) : AutowirableSlot(ctxt) { - if(ctxt) + if (ctxt) ctxt->Autowire(static_cast&>(this->m_ptr), *this); } @@ -309,7 +309,7 @@ class AutoRequired: return std::shared_ptr::get(); } - bool IsAutowired(void) const {return std::shared_ptr::get() != nullptr;} + bool IsAutowired(void) const { return std::shared_ptr::get() != nullptr; } }; /// diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 3d102a2d8..3291ecb8b 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -253,6 +253,9 @@ class CoreContext: // The start token for the thread pool, if one exists std::shared_ptr m_startToken; + // Unlink flag + bool m_unlinkOnTeardown = false; + // Creation rules are allowed to refer to private methods in this type template friend struct autowiring::crh; @@ -640,6 +643,26 @@ class CoreContext: AddInternal(AnySharedPointer(ptr)); } + /// + /// Sets the context's teardown unlink behavior + /// + /// + /// If this feature is turned on, then during context destruction and after teardown listeners have + /// been run, all context members will be scanned for uses of Autowired. If a context member has + /// such a field, and that field points to another member of the current context, then the field + /// will be unlinked. + /// + /// If a field is AutoRequired, it is skipped. If the autowiring was satisfied outside of the + /// context (for instance, at global scope), it's also skipped. AutowiredFast uses aren't registered + /// anywhere, so they are also skipped. + /// + /// This method may be called at any time where a valid CoreContext reference exists. CoreContext + /// does not track additional state if this flag is set. + /// + void SetUnlinkOnTeardown(bool unlinkOnTeardown) { + m_unlinkOnTeardown = unlinkOnTeardown; + } + /// /// Injects the specified types into this context. /// diff --git a/src/autowiring/AutowirableSlot.cpp b/src/autowiring/AutowirableSlot.cpp index 280741015..9ab73e931 100644 --- a/src/autowiring/AutowirableSlot.cpp +++ b/src/autowiring/AutowirableSlot.cpp @@ -10,8 +10,7 @@ using namespace std; DeferrableAutowiring::DeferrableAutowiring(AnySharedPointer&& witness, const std::shared_ptr& context) : m_ptr(std::move(witness)), - m_context(context), - m_pFlink(nullptr) + m_context(context) {} DeferrableAutowiring::~DeferrableAutowiring(void) { diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index c2092f1b0..4e5eb7587 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -90,6 +90,41 @@ CoreContext::~CoreContext(void) { // Tell all context members that we're tearing down: for(ContextMember* q : m_contextMembers) q->NotifyContextTeardown(); + + // Perform unlinking, if requested: + if(m_unlinkOnTeardown) + for (const auto& ccType : m_concreteTypes) { + uint8_t* pBase = (uint8_t*)ccType.value.ptr(); + + // Enumerate all slots and unlink them one at a time + for (auto cur = ccType.stump->pHead; cur; cur = cur->pFlink) { + if (cur->autoRequired) + // Only unlink slots that were Autowired. AutoRequired slots will never participate + // in a cycle (because we would wind up with constructive chaos) so we don't really + // need to worry about them. Furthermore, there are cases where users may want to + // refer to a context member in their destructor; in that case, they should use + // AutoRequired to enforce the relationship. + continue; + + auto& da = *reinterpret_cast(pBase + cur->slotOffset); + if (!da.IsAutowired()) + // Nothing to do here, just short-circuit + continue; + + auto q = m_typeMemos.find(da.GetType()); + if (q == m_typeMemos.end()) + // Weird. Not in the context. Circle around. + continue; + + if (da != q->second.m_value) + // Not equal to the entry already here, came from an ancestor context or somewhere else. + // Circle around. + continue; + + // OK, interior pointer and context teardown is underway, clear it out + da.reset(); + } + } } std::shared_ptr CoreContext::CreateInternal(t_pfnCreate pfnCreate) { diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index d2b8abaaf..b273048a3 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -1,5 +1,6 @@ // Copyright (C) 2012-2015 Leap Motion, Inc. All rights reserved. #include "stdafx.h" +#include "TestFixtures/SimpleObject.hpp" #include #include #include @@ -458,3 +459,64 @@ TEST_F(CoreContextTest, All) { ASSERT_TRUE(found1) << "Failed to find MyClassForAll<1> via its ContextMember interface"; ASSERT_TRUE(found2) << "Failed to find MyClassForAll<2> via its ContextMember interface"; } + +class ClassThatPoints1; +class ClassThatPoints2; + +class ClassThatPoints1 { +public: + Autowired so; + Autowired b; +}; +class ClassThatPoints2 { +public: + ClassThatPoints2(const std::shared_ptr& ctxt) : + v(ctxt) + {} + + // Contrived case, but we need to be sure that only Autowired fields pointing to objects + // in this context are actually unlinked. Autowired fields pointing elsewhere should be + // left alone. + Autowired> v; + + Autowired so; + Autowired a; +}; + +TEST_F(CoreContextTest, UnlinkOnTeardown) { + std::weak_ptr weakA; + std::shared_ptr strongB; + AutoRequired so; + + // Set up a subcontext with some cycles and external links: + { + // Main context that gets reset second + AutoCreateContext ctxt; + + // Sibling context that we're also going to reset + AutoCreateContext otherContext; + otherContext->Inject>(); + + AutoRequired a(ctxt); + AutoRequired b(ctxt, otherContext); + weakA = a; + strongB = b; + + ctxt->AddTeardownListener( + [weakA, strongB] { + // Verify that nothing got screwed up at this point: + auto a = weakA.lock(); + ASSERT_FALSE(weakA.expired()) << "Weak pointer expired prematurely"; + ASSERT_EQ(strongB, a->b) << "Unlink occurred prematurely"; + ASSERT_EQ(a, strongB->a) << "Unlink occurred prematurely"; + } + ); + + // Set the flag at the last possible and to ensure things still get torn down + ctxt->SetUnlinkOnTeardown(true); + } + + ASSERT_TRUE(weakA.expired()) << "A reference was leaked even though unlinking was turned on"; + ASSERT_TRUE(strongB->v.IsAutowired()) << "An Autowired field pointing to a foreign context was incorrectly unlinked"; + ASSERT_EQ(so.get(), strongB->so.get()) << "An Autowired field was unlinked on teardown even though it pointed outside of a context"; +} From 861287916a1d2810297264ab5503d3e1cd06239e Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 10:03:35 -0700 Subject: [PATCH 41/60] Devirtualize SatisfyAutowiring This method used to be virtual back when `AnySharedPointer` was a much less useful type. `DeferrableAutowiring::SatisfyAutowiring` is no longer overridden by anyone, we can devirtualize it. --- autowiring/AutowirableSlot.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autowiring/AutowirableSlot.h b/autowiring/AutowirableSlot.h index fb882877a..59e3e1ba3 100644 --- a/autowiring/AutowirableSlot.h +++ b/autowiring/AutowirableSlot.h @@ -116,7 +116,7 @@ class DeferrableAutowiring: /// /// Satisfies autowiring with a so-called "witness slot" which is guaranteed to be satisfied on the same type /// - virtual void SatisfyAutowiring(const AnySharedPointer& ptr) { + void SatisfyAutowiring(const AnySharedPointer& ptr) { (AnySharedPointer&)*this = ptr; } }; From 8649c9c0082569e7e2b4064b9a59fb257ef91a3d Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 10:42:57 -0700 Subject: [PATCH 42/60] Support an "all" operation to autowiring::parallel This allows for a much more compact parallel enumeration form that doesn't require the use of explicit iterators. New syntax looks like this: ```C++ autowiring::parallel ll; ll += [] {return 1;} ll += [] {return 2;} for(int val : ll.all()) { std::cout << "Value: " << val << std::endl; } ``` Also clean up some syntactic issues in parallel, support a prefix `operator++`, and ensure that our incrementation operators are actually [InputIterator](http://en.cppreference.com/w/cpp/concept/InputIterator) compatible. --- autowiring/Parallel.h | 58 ++++++++++++++++++++++------ src/autowiring/test/ParallelTest.cpp | 20 +++++++++- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index ff93e42d5..859ce619f 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -15,7 +15,6 @@ namespace autowiring { // are run in the thread pool of the current context class parallel { public: - // Add job to be run in the thread pool template void operator+=(_Fx&& fx) { @@ -65,23 +64,36 @@ class parallel { // Iterator that acts as a proxy to template - struct iterator: + struct parallel_iterator: public std::iterator { - iterator(parallel& p, const size_t& remaining): + parallel_iterator(parallel& p, const size_t& remaining): m_parent(p), m_remaining(remaining) {} - bool operator!=(const iterator& rhs) { + bool operator!=(const parallel_iterator& rhs) { return m_remaining != rhs.m_remaining || &m_parent != &rhs.m_parent; } - iterator operator++(void) { + parallel_iterator operator++(void) { m_parent.Pop(); return *this; } + struct wrap { + wrap(T val) : val(val) {} + + T val; + T& operator*(void) { return val; } + }; + + wrap operator++(int) { + wrap retVal = **this; + m_parent.Pop(); + return retVal; + } + T operator*(void) { return m_parent.Top(); } @@ -91,20 +103,44 @@ class parallel { const size_t& m_remaining; }; + template + class collection { + public: + typedef parallel_iterator iterator; + + explicit collection(parallel& ll): + m_begin(ll.begin()), + m_end(ll.end()) + {} + + private: + iterator m_begin; + iterator m_end; + + public: + const iterator& begin(void) { return m_begin; } + const iterator& end(void) const { return m_end; } + }; + + // Get a collection containing all entries of the specified type + template + collection all(void) { + return collection { *this }; + } + // Get an iterator to the begining of out queue of job results template - iterator begin(void) { - return iterator(*this, m_outstandingCount); + parallel_iterator begin(void) { + return{ *this, m_outstandingCount }; } // Iterator representing no jobs results remaining template - iterator end(void) { - static const size_t empty = 0; - return iterator(*this, empty); + parallel_iterator end(void) { + static const size_t zero = 0; + return { *this, zero }; } - protected: std::mutex m_queueMutex; std::condition_variable m_queueUpdated; diff --git a/src/autowiring/test/ParallelTest.cpp b/src/autowiring/test/ParallelTest.cpp index 4133864f2..2035fd2e4 100644 --- a/src/autowiring/test/ParallelTest.cpp +++ b/src/autowiring/test/ParallelTest.cpp @@ -26,8 +26,11 @@ TEST_F(ParallelTest, Basic) { } std::vector result; - for (auto it = p.begin(); it != p.end(); ++it) { + auto it = p.begin(); + result.push_back(*it++); + while (it != p.end()) { result.push_back(*it); + ++it; } ASSERT_EQ(result.size(), 6) << "Didn't receive all value"; @@ -37,3 +40,18 @@ TEST_F(ParallelTest, Basic) { ASSERT_EQ(i, result[i]) << "Didn't receive correct values"; } } + +TEST_F(ParallelTest, All) { + AutoCurrentContext()->Initiate(); + autowiring::parallel p; + + for (size_t i = 0; i < 10; i++) + p += [i] { return i; }; + + std::vector entries; + for(size_t cur : p.all()) + entries.push_back(cur); + std::sort(entries.begin(), entries.end()); + for (size_t i = 1; i < entries.size(); i++) + ASSERT_EQ(entries[i - 1], entries[i] - 1) << "Entry did not complete as expected"; +} From ad38234600bd35c349778254fc7c81faedc53bee Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 30 Jul 2015 10:49:59 -0700 Subject: [PATCH 43/60] Fix initialization ordering issue This is causing warnings on Mac, reorder to address --- src/autowiring/CoreContextStateBlock.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autowiring/CoreContextStateBlock.cpp b/src/autowiring/CoreContextStateBlock.cpp index bec012e8f..967bbbc95 100644 --- a/src/autowiring/CoreContextStateBlock.cpp +++ b/src/autowiring/CoreContextStateBlock.cpp @@ -7,7 +7,6 @@ using namespace autowiring; RunCounter::RunCounter(const std::shared_ptr& stateBlock, const std::shared_ptr& owner) : stateBlock(stateBlock), - owner(owner), // Increment the parent's outstanding count as well. This will be held by the lambda, and will cause the enclosing // context's outstanding thread count to be incremented by one as long as we have any threads still running in our @@ -16,7 +15,8 @@ RunCounter::RunCounter(const std::shared_ptr& stateBlock, stateBlock->parent ? stateBlock->parent->IncrementOutstandingThreadCount(owner->GetParentContext()) : nullptr - ) + ), + owner(owner) {} RunCounter::~RunCounter(void) { From ad988235ff9a6c80537df118d6e01e68885f8c3d Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 10:45:12 -0700 Subject: [PATCH 44/60] Support no-return parallel entries This allows lambdas that have no return type to be added to the parallel collection, and have their completion status be tracked the same way. Fixes #687 --- autowiring/Parallel.h | 215 ++++++++++++++++++--------- src/autowiring/Parallel.cpp | 5 + src/autowiring/test/ParallelTest.cpp | 30 +++- 3 files changed, 180 insertions(+), 70 deletions(-) diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index 859ce619f..a98bfca07 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -10,6 +10,98 @@ namespace autowiring { +class parallel; + +/// +/// Iterator that acts as a proxy to the outputs of a parallel structure +/// +template +struct parallel_iterator : + public std::iterator +{ +public: + parallel_iterator(parallel& p, const size_t& remaining): + m_parent(p), + m_remaining(remaining) + {} + +protected: + parallel& m_parent; + const size_t& m_remaining; + +public: + bool operator!=(const parallel_iterator& rhs) { + return m_remaining != rhs.m_remaining || &m_parent != &rhs.m_parent; + } + + // Wrap, required to satisfy InputIterator requirements. + struct wrap { + wrap(T val) : val(val) {} + + T val; + T& operator*(void) { return val; } + }; + + // Iterator operaror overloads: + parallel_iterator operator++(void); + wrap operator++(int); + T operator*(void); +}; + +template +class parallel_collection { +public: + typedef parallel_iterator iterator; + + explicit parallel_collection(iterator begin, iterator end): + m_begin(begin), + m_end(end) + {} + +private: + iterator m_begin; + iterator m_end; + +public: + const iterator& begin(void) { return m_begin; } + const iterator& end(void) const { return m_end; } +}; + +/// +/// Full specialization for null responses +/// +/// +/// Technically, this isn't even an iterator, but it's provided to allow parallel::begin to work +/// properly with void as the template type. +/// +template<> +struct parallel_iterator +{ +public: + parallel_iterator(parallel& p, const size_t& remaining) : + m_parent(p), + m_remaining(remaining) + {} + +protected: + parallel& m_parent; + const size_t& m_remaining; + +public: + bool operator!=(const parallel_iterator& rhs) { + return m_remaining != rhs.m_remaining || &m_parent != &rhs.m_parent; + } + + parallel_iterator operator++(void) { + this->operator++(0); + return *this; + } + void operator++(int); + + struct unused {}; + unused operator*(void) const { return{}; }; +}; + // Provides fan-out and gather functionality. Lambda "jobs" can be started using operator+= // and gathered using the standard container iteration interface using begin and end. Jobs // are run in the thread pool of the current context @@ -17,7 +109,10 @@ class parallel { public: // Add job to be run in the thread pool template - void operator+=(_Fx&& fx) { + typename std::enable_if< + !std::is_same::type>::value + >::type + operator+=(_Fx&& fx) { using RetType = typename std::remove_cv::type; // Increment remain jobs. This is decremented by calls to "Pop" @@ -32,20 +127,42 @@ class parallel { }; } + // Specialization for jobs that don't return anything + template + typename std::enable_if< + std::is_same::type>::value + >::type + operator+=(_Fx&& fx) { + // Increment remain jobs. This is decremented by calls to "Pop" + (std::lock_guard)m_queueMutex, ++m_outstandingCount; + + *m_ctxt += [this, fx] { + std::lock_guard lk(m_queueMutex); + m_nVoidEntries++; + m_queueUpdated.notify_all(); + }; + } + // Discard the most recent result. Blocks until the next result arives. template void Pop(void) { std::unique_lock lk(m_queueMutex); - if (m_queue[typeid(T)].empty()) - if (!m_outstandingCount) + if (std::is_same::value) { + if(!m_nVoidEntries) throw std::out_of_range("No outstanding jobs"); - m_queueUpdated.wait(lk, [this]{ - return !m_queue[typeid(T)].empty(); - }); + m_queueUpdated.wait(lk, [this] { return m_nVoidEntries != 0; }); + m_nVoidEntries--; + } else { + auto& qu = m_queue[typeid(T)]; + if (qu.empty()) + throw std::out_of_range("No outstanding jobs"); + + m_queueUpdated.wait(lk, [&qu] { return !qu.empty(); }); + qu.pop_front(); + } - m_queue[typeid(T)].pop_front(); --m_outstandingCount; } @@ -62,70 +179,10 @@ class parallel { return *static_cast(m_queue[typeid(T)].front().ptr()); } - // Iterator that acts as a proxy to - template - struct parallel_iterator: - public std::iterator - { - parallel_iterator(parallel& p, const size_t& remaining): - m_parent(p), - m_remaining(remaining) - {} - - bool operator!=(const parallel_iterator& rhs) { - return m_remaining != rhs.m_remaining || &m_parent != &rhs.m_parent; - } - - parallel_iterator operator++(void) { - m_parent.Pop(); - return *this; - } - - struct wrap { - wrap(T val) : val(val) {} - - T val; - T& operator*(void) { return val; } - }; - - wrap operator++(int) { - wrap retVal = **this; - m_parent.Pop(); - return retVal; - } - - T operator*(void) { - return m_parent.Top(); - } - - protected: - parallel& m_parent; - const size_t& m_remaining; - }; - - template - class collection { - public: - typedef parallel_iterator iterator; - - explicit collection(parallel& ll): - m_begin(ll.begin()), - m_end(ll.end()) - {} - - private: - iterator m_begin; - iterator m_end; - - public: - const iterator& begin(void) { return m_begin; } - const iterator& end(void) const { return m_end; } - }; - // Get a collection containing all entries of the specified type template - collection all(void) { - return collection { *this }; + parallel_collection all(void) { + return parallel_collection { begin(), end() }; } // Get an iterator to the begining of out queue of job results @@ -146,10 +203,30 @@ class parallel { std::condition_variable m_queueUpdated; std::unordered_map> m_queue; + // For void entries we don't need a queue, we can just keep a general count of "done" + size_t m_nVoidEntries = 0; + size_t m_outstandingCount = 0; AutoCurrentContext m_ctxt; }; +template +parallel_iterator parallel_iterator::operator++(void) { + m_parent.Pop(); + return *this; +} + +template +typename parallel_iterator::wrap parallel_iterator::operator++(int) { + wrap retVal = **this; + m_parent.Pop(); + return retVal; +} + +template +T parallel_iterator::operator*(void) { + return m_parent.Top(); +} }//namespace autowiring diff --git a/src/autowiring/Parallel.cpp b/src/autowiring/Parallel.cpp index e5f78f0aa..160f05bb6 100644 --- a/src/autowiring/Parallel.cpp +++ b/src/autowiring/Parallel.cpp @@ -2,3 +2,8 @@ #include "stdafx.h" #include "Parallel.h" +using namespace autowiring; + +void parallel_iterator::operator++(int) { + m_parent.Pop(); +} \ No newline at end of file diff --git a/src/autowiring/test/ParallelTest.cpp b/src/autowiring/test/ParallelTest.cpp index 2035fd2e4..05d4d572f 100644 --- a/src/autowiring/test/ParallelTest.cpp +++ b/src/autowiring/test/ParallelTest.cpp @@ -19,7 +19,7 @@ TEST_F(ParallelTest, Basic) { for (int i : {0,4,2,5,1,3}) { int sleepTime = dist(mt); - p += [i, sleepTime]() { + p += [i, sleepTime] { std::this_thread::sleep_for(sleepTime*std::chrono::milliseconds(1)); return i; }; @@ -55,3 +55,31 @@ TEST_F(ParallelTest, All) { for (size_t i = 1; i < entries.size(); i++) ASSERT_EQ(entries[i - 1], entries[i] - 1) << "Entry did not complete as expected"; } + +TEST_F(ParallelTest, VoidReturn) { + AutoCurrentContext()->Initiate(); + autowiring::parallel p; + + auto val = std::make_shared>(0); + for (size_t i = 0; i < 100; i++) + p += [val] { (*val)++; }; + + size_t i = 0; + for (auto q = p.begin(); q != p.end(); ++q) + i++; + ASSERT_EQ(100UL, i) << "A sufficient number of empty lambdas were not encountered"; +} + +TEST_F(ParallelTest, VoidReturnAll) { + AutoCurrentContext()->Initiate(); + autowiring::parallel p; + + auto val = std::make_shared>(0); + for (size_t i = 0; i < 100; i++) + p += [val] { (*val)++; }; + + size_t i = 0; + for (auto entry : p.all()) + i++; + ASSERT_EQ(100UL, i) << "A sufficient number of empty lambdas were not encountered"; +} From 6650b100116a2b0ae9ba3024225a87feab02d105 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Wed, 29 Jul 2015 12:24:41 -0700 Subject: [PATCH 45/60] Throw exceptions if the context isn't running during a call to begin() It's easy to make this mistake--because the main thread is often the one making the call to begin, do not allow users to block on a call to `autowiring::parallel::begin` until after the context is started. Fixes #688 --- autowiring/Parallel.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index 859ce619f..d86d3df47 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -131,6 +131,11 @@ class parallel { // Get an iterator to the begining of out queue of job results template parallel_iterator begin(void) { + if (!m_ctxt->IsRunning()) + if (m_ctxt->IsShutdown()) + throw std::runtime_error("Attempted to enumerate members of a context after the context was shut down"); + else + throw std::runtime_error("Start the context before attempting to enumerate members of an autowiring::parallel collection"); return{ *this, m_outstandingCount }; } From 775263721ca15334421dbc26a4c8f740571b540e Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 30 Jul 2015 11:15:22 -0700 Subject: [PATCH 46/60] Fix broken unit test --- autowiring/Parallel.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index a98bfca07..dffe8219b 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -147,18 +147,14 @@ class parallel { template void Pop(void) { std::unique_lock lk(m_queueMutex); + if (!m_outstandingCount) + throw std::out_of_range("No outstanding jobs"); if (std::is_same::value) { - if(!m_nVoidEntries) - throw std::out_of_range("No outstanding jobs"); - m_queueUpdated.wait(lk, [this] { return m_nVoidEntries != 0; }); m_nVoidEntries--; } else { auto& qu = m_queue[typeid(T)]; - if (qu.empty()) - throw std::out_of_range("No outstanding jobs"); - m_queueUpdated.wait(lk, [&qu] { return !qu.empty(); }); qu.pop_front(); } From 4c345e85f8cc0ce5f253436429af95965c56b1c6 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 30 Jul 2015 20:14:08 -0700 Subject: [PATCH 47/60] Performance improvement to SetCurrent Very simple improvement that does not change the current context if no change is necessary --- src/autowiring/CoreContext.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index d2783e1e6..25e941502 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -1269,7 +1269,14 @@ std::ostream& operator<<(std::ostream& os, const CoreContext& rhs) { } std::shared_ptr CoreContext::SetCurrent(const std::shared_ptr& ctxt) { - std::shared_ptr retVal = CoreContext::CurrentContextOrNull(); + auto currentContext = autoCurrentContext.get(); + + // Short-circuit test, no need to proceed if we aren't changing the context: + if (*currentContext == ctxt) + return *currentContext; + + // Value is changing, update: + auto retVal = *currentContext; if (ctxt) autoCurrentContext.reset(new std::shared_ptr(ctxt)); else From 08384fe87894fd5d6609219c42a771f39ec7ae57 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Thu, 30 Jul 2015 20:17:23 -0700 Subject: [PATCH 48/60] Also improve performance of CurrentContextOrNull --- autowiring/CoreContext.h | 2 +- src/autowiring/CoreContext.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/autowiring/CoreContext.h b/autowiring/CoreContext.h index 3291ecb8b..136180dc7 100644 --- a/autowiring/CoreContext.h +++ b/autowiring/CoreContext.h @@ -1000,7 +1000,7 @@ class CoreContext: /// context is assigned before invoking a CoreRunnable instance's Run method, and it's also assigned /// when a context is first constructed by a thread. /// - static std::shared_ptr CurrentContextOrNull(void); + static const std::shared_ptr& CurrentContextOrNull(void); /// /// Identical to CurrentContextNoCheck, except returns the global context instead of a null pointer diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 25e941502..4ebd54c02 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -617,9 +617,10 @@ bool CoreContext::DelayUntilInitiated(void) { return !IsShutdown(); } -std::shared_ptr CoreContext::CurrentContextOrNull(void) { +const std::shared_ptr& CoreContext::CurrentContextOrNull(void) { + static const std::shared_ptr empty; auto retVal = autoCurrentContext.get(); - return retVal ? *retVal : nullptr; + return retVal ? *retVal : empty; } std::shared_ptr CoreContext::CurrentContext(void) { From dbc6731cea452824752f7749ee7d126af06cb601 Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:29:32 -0700 Subject: [PATCH 49/60] Fix hang running NoEnumerateBeforeBoltReturn in valgrind --- src/autowiring/test/CoreContextTest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index b273048a3..d11bbc484 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -201,9 +201,11 @@ TEST_F(CoreContextTest, NoEnumerateBeforeBoltReturn) { }); // Verify that the context does not appear until the bolt has finished running: - while(!*finished) + while(!*finished) { for(auto cur : ContextEnumeratorT(ctxt)) ASSERT_TRUE(longTime->m_bDoneRunning) << "A context was enumerated before a bolt finished running"; + AutoRequired()->WaitForEvent(std::chrono::milliseconds(100)); + } // Need to block until this thread is done t.join(); From 0eadaf1d59b55a1e9e6ebe70409e06783ba5a867 Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:39:25 -0700 Subject: [PATCH 50/60] Fix misleading indentation --- .../test/TestFixtures/FiresManyEventsWhenRun.hpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp index 8c4880f2a..85d88bb73 100644 --- a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp +++ b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp @@ -17,9 +17,10 @@ class FiresManyEventsWhenRun: AutoFired m_ci; void Run(void) override { - while(!ShouldStop() && totalXmit < 0x7FFFF000) - // Jam for awhile in an asynchronous way: - while(++totalXmit % 100) - m_ci(&CallableInterface::ZeroArgs)(); + while(!ShouldStop() && totalXmit < 0x7FFFF000) { + // Jam for awhile in an asynchronous way: + while(++totalXmit % 100) + m_ci(&CallableInterface::ZeroArgs)(); + } } }; From 7152ea50f27fe137ffb466c940dcc975d1fe3e41 Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:50:01 -0700 Subject: [PATCH 51/60] Fix valgrind hang for PathologicalTransmitterTest --- src/autowiring/test/EventReceiverTest.cpp | 6 ++++-- src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/autowiring/test/EventReceiverTest.cpp b/src/autowiring/test/EventReceiverTest.cpp index 13d4b081b..de8c9ff20 100644 --- a/src/autowiring/test/EventReceiverTest.cpp +++ b/src/autowiring/test/EventReceiverTest.cpp @@ -299,7 +299,9 @@ TEST_F(EventReceiverTest, PathologicalTransmitterTest) { } // Spin until the jammer has transmitted a thousand messages: - while(jammer->totalXmit < 1000); + while(jammer->totalXmit < 1000) { + jammer->WaitForEvent(std::chrono::milliseconds(1)); + } jammer->Stop(); jammer->Wait(); @@ -507,4 +509,4 @@ TEST_F(EventReceiverTest, OddReturnTypeTest) { af(&HasAWeirdReturnType::FiredMethod)(); ASSERT_TRUE(hawrt->bCalled) << "Method with a strange fired return type did not get invoked as expected"; -} \ No newline at end of file +} diff --git a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp index 85d88bb73..9d800734f 100644 --- a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp +++ b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp @@ -21,6 +21,7 @@ class FiresManyEventsWhenRun: // Jam for awhile in an asynchronous way: while(++totalXmit % 100) m_ci(&CallableInterface::ZeroArgs)(); + WaitForEvent(std::chrono::milliseconds(1)); } } }; From 9c2510078b3556e6c3ae0f4990e60f1ca73f61f4 Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:50:06 -0700 Subject: [PATCH 52/60] Minimize unnecessary delay --- src/autowiring/test/CoreContextTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autowiring/test/CoreContextTest.cpp b/src/autowiring/test/CoreContextTest.cpp index d11bbc484..212b318fe 100644 --- a/src/autowiring/test/CoreContextTest.cpp +++ b/src/autowiring/test/CoreContextTest.cpp @@ -204,7 +204,7 @@ TEST_F(CoreContextTest, NoEnumerateBeforeBoltReturn) { while(!*finished) { for(auto cur : ContextEnumeratorT(ctxt)) ASSERT_TRUE(longTime->m_bDoneRunning) << "A context was enumerated before a bolt finished running"; - AutoRequired()->WaitForEvent(std::chrono::milliseconds(100)); + AutoRequired()->WaitForEvent(std::chrono::milliseconds(1)); } // Need to block until this thread is done From 9627731431f187d930a7ddc097b75389395770bc Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:55:33 -0700 Subject: [PATCH 53/60] Similar fix for PathologicalChildContextTest --- src/autowiring/test/EventReceiverTest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/autowiring/test/EventReceiverTest.cpp b/src/autowiring/test/EventReceiverTest.cpp index de8c9ff20..343a87a99 100644 --- a/src/autowiring/test/EventReceiverTest.cpp +++ b/src/autowiring/test/EventReceiverTest.cpp @@ -279,7 +279,9 @@ TEST_F(EventReceiverTest, PathologicalChildContextTest) { } // Spin until the jammer has transmitted a thousand messages: - while(jammer->totalXmit < 1000); + while(jammer->totalXmit < 1000) { + jammer->WaitForEvent(std::chrono::milliseconds(1)); + } jammer->Stop(); jammer->Wait(); From b269ba9a1fa303d73ca44fc6e206bffe01be6c9c Mon Sep 17 00:00:00 2001 From: James Donald Date: Thu, 30 Jul 2015 20:56:57 -0700 Subject: [PATCH 54/60] Don't bother yielding until a couple thousand transfers --- src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp index 9d800734f..b96dd76e3 100644 --- a/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp +++ b/src/autowiring/test/TestFixtures/FiresManyEventsWhenRun.hpp @@ -21,7 +21,8 @@ class FiresManyEventsWhenRun: // Jam for awhile in an asynchronous way: while(++totalXmit % 100) m_ci(&CallableInterface::ZeroArgs)(); - WaitForEvent(std::chrono::milliseconds(1)); + if (totalXmit % 2000 == 0) + WaitForEvent(std::chrono::milliseconds(1)); } } }; From ba8c4463a133fd8f09817204dd11586c694c92eb Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 31 Jul 2015 09:44:13 -0700 Subject: [PATCH 55/60] Fix crash caused by an incorrect check --- src/autowiring/CoreContext.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/autowiring/CoreContext.cpp b/src/autowiring/CoreContext.cpp index 4ebd54c02..ce5baa297 100644 --- a/src/autowiring/CoreContext.cpp +++ b/src/autowiring/CoreContext.cpp @@ -1270,14 +1270,14 @@ std::ostream& operator<<(std::ostream& os, const CoreContext& rhs) { } std::shared_ptr CoreContext::SetCurrent(const std::shared_ptr& ctxt) { - auto currentContext = autoCurrentContext.get(); + const auto& currentContext = CurrentContextOrNull(); // Short-circuit test, no need to proceed if we aren't changing the context: - if (*currentContext == ctxt) - return *currentContext; + if (currentContext == ctxt) + return currentContext; // Value is changing, update: - auto retVal = *currentContext; + auto retVal = currentContext; if (ctxt) autoCurrentContext.reset(new std::shared_ptr(ctxt)); else From 41bbbbe3f2c06dfb74c768b039fa39edfe1cdbaf Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 31 Jul 2015 09:47:28 -0700 Subject: [PATCH 56/60] Fix dangling else warning General policy, fix warnings as we see them. --- autowiring/Parallel.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/autowiring/Parallel.h b/autowiring/Parallel.h index b25932d89..4bb4c7b5e 100644 --- a/autowiring/Parallel.h +++ b/autowiring/Parallel.h @@ -184,11 +184,12 @@ class parallel { // Get an iterator to the begining of out queue of job results template parallel_iterator begin(void) { - if (!m_ctxt->IsRunning()) + if (!m_ctxt->IsRunning()) { if (m_ctxt->IsShutdown()) throw std::runtime_error("Attempted to enumerate members of a context after the context was shut down"); else throw std::runtime_error("Start the context before attempting to enumerate members of an autowiring::parallel collection"); + } return{ *this, m_outstandingCount }; } From 5de7f3b2682e5cf817b22938b6a07f4601f513ee Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 31 Jul 2015 09:51:15 -0700 Subject: [PATCH 57/60] Disable deprecation warnings internally Do not warn of deprecated uses of types and methods when those uses are by the Autowiring project itself. Generally speaking, we are aware of such uses, and they will be removed when the corresponding deprecated methods and classes are themselves removed. --- autowiring/C++11/cpp11.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/autowiring/C++11/cpp11.h b/autowiring/C++11/cpp11.h index 00615e5a4..826cd0e26 100644 --- a/autowiring/C++11/cpp11.h +++ b/autowiring/C++11/cpp11.h @@ -26,6 +26,13 @@ // If Boost.Thread is used, we want it to provide the new name for its class #define BOOST_THREAD_PROVIDES_FUTURE +#ifdef AUTOWIRING_IS_BEING_BUILT + // We know that we're using deprecated stuff in our unit tests, but the tests still + // need to do what they do. Undefine all of the deprecated macros so we don't get + // spammed with warnings telling us what we already know. + #define AUTOWIRING_NO_DEPRECATE +#endif + #ifndef __has_feature #define __has_feature(x) (AUTOWIRE_##x) #endif @@ -339,7 +346,7 @@ /********************* * Deprecation convenience macro *********************/ -#ifndef _DEBUG +#if !defined(_DEBUG) && !defined(AUTOWIRING_NO_DEPRECATE) #ifdef _MSC_VER #define DEPRECATED(signature, msg) __declspec(deprecated(msg)) signature #define DEPRECATED_CLASS(classname, msg) __declspec(deprecated(msg)) classname From 99db8d9b9e28ff747bcf75b117dfc316ddbd4188 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 31 Jul 2015 10:03:38 -0700 Subject: [PATCH 58/60] Eliminate internal use of deprecated ctor --- autowiring/ObjectPool.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/autowiring/ObjectPool.h b/autowiring/ObjectPool.h index 2e1bb50aa..eae6f1531 100644 --- a/autowiring/ObjectPool.h +++ b/autowiring/ObjectPool.h @@ -43,7 +43,9 @@ class ObjectPool { public: ObjectPool(void) : - ObjectPool(~0, ~0) + m_monitor(std::make_shared>(this, &DefaultInitialize, &DefaultFinalize)), + m_maxPooled(~0), + m_limit(~0) {} ObjectPool(ObjectPool&& rhs) @@ -82,7 +84,6 @@ class ObjectPool const std::function& final = &DefaultFinalize ) : m_monitor(std::make_shared>(this, initial, final)), - m_alloc(&DefaultCreate), m_placement(placement) {} @@ -154,7 +155,7 @@ class ObjectPool size_t m_outstanding = 0; // Allocator, placement ctor: - std::function m_alloc; + std::function m_alloc { &DefaultCreate }; std::function m_placement{ [](T*) {} }; /// From 7feb967115d8afc93490e308f53c16addc089efe Mon Sep 17 00:00:00 2001 From: James Donald Date: Fri, 31 Jul 2015 11:18:45 -0700 Subject: [PATCH 59/60] Default to Release for generators that require CMAKE_BUILD_TYPE --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9bc05895..f90d5a2f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ if(APPLE) set(CMAKE_OSX_ARCHITECTURES "x86_64;i386" CACHE STRING "Mac OS X build architectures" FORCE) endif() +if(NOT (MSVC OR CMAKE_BUILD_TYPE)) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release." FORCE) +endif() + # Need to classify the architecture before we run anything else if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm") set(autowiring_BUILD_ARM ON) From fa6ec5f26bad5aadedf72bfe62fd6e28bd22b9e4 Mon Sep 17 00:00:00 2001 From: Jason Lokerson Date: Fri, 31 Jul 2015 14:58:49 -0700 Subject: [PATCH 60/60] Fix Android build break Android doesn't have `posix_memalign`, detect this case with preprocessor macros and compile accordingly. --- src/autowiring/CreationRulesUnix.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/autowiring/CreationRulesUnix.cpp b/src/autowiring/CreationRulesUnix.cpp index 6eedeb744..210a62e02 100644 --- a/src/autowiring/CreationRulesUnix.cpp +++ b/src/autowiring/CreationRulesUnix.cpp @@ -4,10 +4,14 @@ #include void* autowiring::aligned_malloc(size_t ncb, size_t align) { +#if _POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600 && !defined(__APPLE__) + return memalign(align, ncb); +#else void* pRetVal; - if(posix_memalign(&pRetVal, align, ncb)) + if (posix_memalign(&pRetVal, align, ncb)) return nullptr; return pRetVal; +#endif } void autowiring::aligned_free(void* ptr) {