mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
Beast class refactor
This commit is contained in:
@@ -133,6 +133,9 @@
|
||||
<ClInclude Include="..\..\beast\Net.h" />
|
||||
<ClInclude Include="..\..\beast\net\IPEndpoint.h" />
|
||||
<ClInclude Include="..\..\beast\SafeBool.h" />
|
||||
<ClInclude Include="..\..\beast\SmartPtr.h" />
|
||||
<ClInclude Include="..\..\beast\smart_ptr\ContainerDeletePolicy.h" />
|
||||
<ClInclude Include="..\..\beast\smart_ptr\ScopedPointer.h" />
|
||||
<ClInclude Include="..\..\beast\StaticAssert.h" />
|
||||
<ClInclude Include="..\..\beast\Strings.h" />
|
||||
<ClInclude Include="..\..\beast\strings\CharacterFunctions.h" />
|
||||
@@ -228,7 +231,6 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\ScopedValueSetter.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\SortedSet.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\SparseSet.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\DynamicList.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\Variant.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\diagnostic\FatalError.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\diagnostic\FPUFlags.h" />
|
||||
@@ -257,26 +259,17 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\maths\Random.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\maths\Range.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\maths\uint24.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AllocatedBy.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AtomicCounter.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AtomicFlag.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AtomicPointer.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AtomicState.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\CacheLine.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\ContainerDeletePolicy.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStore.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStoreWithoutTLS.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStoreWithTLS.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\GlobalFifoFreeStore.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\GlobalPagedFreeStore.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\MemoryAlignment.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\MemoryBlock.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\OptionalScopedPointer.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\PagedFreeStore.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\RecycledObjectPool.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\SharedFunction.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\SharedObject.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\ScopedPointer.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\SharedSingleton.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\WeakReference.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\SharedPtr.h" />
|
||||
@@ -334,15 +327,10 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\threads\ThreadPool.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\threads\TimeSliceThread.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\threads\WaitableEvent.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\CallQueue.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\DeadlineTimer.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\InterruptibleThread.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Listeners.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\ManualCallQueue.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Semaphore.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\ServiceQueue.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Stoppable.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\ThreadWithCallQueue.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Workers.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\detail\ScopedLock.h" />
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\detail\TrackedMutex.h" />
|
||||
@@ -697,12 +685,6 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\containers\DynamicList.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\diagnostic\Assert.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
@@ -827,36 +809,12 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\FifoFreeStoreWithoutTLS.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\FifoFreeStoreWithTLS.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\GlobalPagedFreeStore.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\MemoryBlock.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\PagedFreeStore.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\StaticObject.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
@@ -1175,36 +1133,12 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\CallQueue.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\DeadlineTimer.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\InterruptibleThread.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Listeners.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\ManualCallQueue.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Semaphore.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
@@ -1223,12 +1157,6 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\ThreadWithCallQueue.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Workers.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
|
||||
@@ -285,6 +285,12 @@
|
||||
<Filter Include="beast\config\stdlib">
|
||||
<UniqueIdentifier>{7243e5e5-ad7e-4d81-8444-d545919e850c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="beast\asio">
|
||||
<UniqueIdentifier>{549430fc-36f6-450e-9d8d-38f4b9e72fb5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="beast\smart_ptr">
|
||||
<UniqueIdentifier>{4e9c54da-1581-41d7-ac75-48140e4a13d4}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\modules\beast_core\beast_core.h">
|
||||
@@ -377,9 +383,6 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\OptionalScopedPointer.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\ScopedPointer.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\WeakReference.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
@@ -704,9 +707,6 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\maths\uint24.h">
|
||||
<Filter>beast_core\maths</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\ContainerDeletePolicy.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\diagnostic\MeasureFunctionCallTime.h">
|
||||
<Filter>beast_core\diagnostic</Filter>
|
||||
</ClInclude>
|
||||
@@ -737,51 +737,15 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\DeadlineTimer.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\CallQueue.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\InterruptibleThread.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Listeners.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\ManualCallQueue.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Semaphore.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\ThreadWithCallQueue.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\thread\Workers.h">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\maths\Math.h">
|
||||
<Filter>beast_core\maths</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\AllocatedBy.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStore.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStoreWithoutTLS.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\FifoFreeStoreWithTLS.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\GlobalFifoFreeStore.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\GlobalPagedFreeStore.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\memory\PagedFreeStore.h">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_asio\sockets\SocketWrapperStrand.h">
|
||||
<Filter>beast_asio\sockets</Filter>
|
||||
</ClInclude>
|
||||
@@ -818,9 +782,6 @@
|
||||
<ClInclude Include="..\..\modules\beast_core\system\SystemStats.h">
|
||||
<Filter>beast_core\system</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\modules\beast_core\containers\DynamicList.h">
|
||||
<Filter>beast_core\containers</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\beast\intrusive\ForwardList.h">
|
||||
<Filter>beast\intrusive</Filter>
|
||||
</ClInclude>
|
||||
@@ -1245,6 +1206,15 @@
|
||||
<ClInclude Include="..\..\beast\config\SelectStdlibConfig.h">
|
||||
<Filter>beast\config</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\beast\smart_ptr\ContainerDeletePolicy.h">
|
||||
<Filter>beast\smart_ptr</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\beast\smart_ptr\ScopedPointer.h">
|
||||
<Filter>beast\smart_ptr</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\beast\SmartPtr.h">
|
||||
<Filter>beast</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\modules\beast_core\containers\AbstractFifo.cpp">
|
||||
@@ -1580,39 +1550,12 @@
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\DeadlineTimer.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\CallQueue.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\InterruptibleThread.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Listeners.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\ManualCallQueue.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Semaphore.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\ThreadWithCallQueue.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\thread\Workers.cpp">
|
||||
<Filter>beast_core\thread</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\FifoFreeStoreWithoutTLS.cpp">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\FifoFreeStoreWithTLS.cpp">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\GlobalPagedFreeStore.cpp">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\memory\PagedFreeStore.cpp">
|
||||
<Filter>beast_core\memory</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_asio\basics\SSLContext.cpp">
|
||||
<Filter>beast_asio\basics</Filter>
|
||||
</ClCompile>
|
||||
@@ -1622,9 +1565,6 @@
|
||||
<ClCompile Include="..\..\modules\beast_core\system\SystemStats.cpp">
|
||||
<Filter>beast_core\system</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_core\containers\DynamicList.cpp">
|
||||
<Filter>beast_core\containers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\modules\beast_asio\async\SharedHandler.cpp">
|
||||
<Filter>beast_asio\async</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -17,27 +17,12 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
namespace
|
||||
{
|
||||
#ifndef BEAST_SMARTPTR_H_INCLUDED
|
||||
#define BEAST_SMARTPTR_H_INCLUDED
|
||||
|
||||
// Size of a page
|
||||
//
|
||||
static const size_t globalPageBytes = 8 * 1024;
|
||||
|
||||
}
|
||||
|
||||
GlobalPagedFreeStore::GlobalPagedFreeStore ()
|
||||
: m_allocator (globalPageBytes)
|
||||
{
|
||||
}
|
||||
|
||||
GlobalPagedFreeStore::~GlobalPagedFreeStore ()
|
||||
{
|
||||
}
|
||||
|
||||
GlobalPagedFreeStore::Ptr GlobalPagedFreeStore::getInstance ()
|
||||
{
|
||||
return SharedSingleton <GlobalPagedFreeStore>::getInstance();
|
||||
}
|
||||
#include "Config.h"
|
||||
|
||||
#include "smart_ptr/ContainerDeletePolicy.h"
|
||||
#include "smart_ptr/ScopedPointer.h"
|
||||
|
||||
#endif
|
||||
@@ -17,8 +17,10 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_CONTAINERDELETEPOLICY_H_INCLUDED
|
||||
#define BEAST_CONTAINERDELETEPOLICY_H_INCLUDED
|
||||
#ifndef BEAST_SMARTPTR_CONTAINERDELETEPOLICY_H_INCLUDED
|
||||
#define BEAST_SMARTPTR_CONTAINERDELETEPOLICY_H_INCLUDED
|
||||
|
||||
namespace beast {
|
||||
|
||||
/** The DeletePolicy provides a way to destroy objects stored in containers.
|
||||
|
||||
@@ -45,4 +47,6 @@ struct ContainerDeletePolicy
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -21,8 +21,14 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_SCOPEDPOINTER_H_INCLUDED
|
||||
#define BEAST_SCOPEDPOINTER_H_INCLUDED
|
||||
#ifndef BEAST_SMARTPTR_SCOPEDPOINTER_H_INCLUDED
|
||||
#define BEAST_SMARTPTR_SCOPEDPOINTER_H_INCLUDED
|
||||
|
||||
#include "ContainerDeletePolicy.h"
|
||||
#include "../Uncopyable.h"
|
||||
#include "../StaticAssert.h"
|
||||
|
||||
namespace beast {
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
@@ -248,4 +254,7 @@ template <typename Type>
|
||||
void deleteAndZero (ScopedPointer<Type>&) { static_bassert (sizeof (Type) == 12345); }
|
||||
#endif
|
||||
|
||||
#endif // BEAST_SCOPEDPOINTER_H_INCLUDED
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -39,7 +39,6 @@ namespace beast {
|
||||
#include "protocol/HandshakeDetectLogicPROXY.cpp"
|
||||
|
||||
# include "http/HTTPParserImpl.h"
|
||||
#include "http/HTTPParser.cpp"
|
||||
#include "http/HTTPClientType.cpp"
|
||||
#include "http/HTTPField.cpp"
|
||||
#include "http/HTTPHeaders.cpp"
|
||||
@@ -61,3 +60,5 @@ namespace beast {
|
||||
#include "system/BoostUnitTests.cpp"
|
||||
|
||||
}
|
||||
|
||||
#include "http/HTTPParser.cpp"
|
||||
|
||||
@@ -89,10 +89,11 @@ namespace beast {
|
||||
# include "http/HTTPMessage.h"
|
||||
# include "http/HTTPRequest.h"
|
||||
# include "http/HTTPResponse.h"
|
||||
# include "http/HTTPParser.h"
|
||||
|
||||
}
|
||||
|
||||
# include "http/HTTPParser.h"
|
||||
|
||||
#include "http/HTTPClientType.h"
|
||||
|
||||
namespace beast {
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
namespace beast {
|
||||
|
||||
HTTPParser::HTTPParser (Type type)
|
||||
: m_type (type)
|
||||
, m_impl (new HTTPParserImpl (
|
||||
@@ -102,3 +104,5 @@ SharedPtr <HTTPResponse> const& HTTPParser::response ()
|
||||
|
||||
return m_response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#ifndef BEAST_ASIO_HTTPPARSER_H_INCLUDED
|
||||
#define BEAST_ASIO_HTTPPARSER_H_INCLUDED
|
||||
|
||||
namespace beast {
|
||||
|
||||
class HTTPParserImpl;
|
||||
|
||||
/** A parser for HTTPRequest and HTTPResponse objects. */
|
||||
@@ -82,4 +84,6 @@ private:
|
||||
SharedPtr <HTTPResponse> m_response;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -134,7 +134,6 @@ namespace beast
|
||||
#include "containers/NamedValueSet.cpp"
|
||||
#include "containers/PropertySet.cpp"
|
||||
#include "containers/Variant.cpp"
|
||||
#include "containers/DynamicList.cpp"
|
||||
|
||||
#include "diagnostic/FatalError.cpp"
|
||||
#include "diagnostic/FPUFlags.cpp"
|
||||
@@ -162,10 +161,6 @@ namespace beast
|
||||
#include "maths/Random.cpp"
|
||||
|
||||
#include "memory/MemoryBlock.cpp"
|
||||
#include "memory/FifoFreeStoreWithTLS.cpp"
|
||||
#include "memory/FifoFreeStoreWithoutTLS.cpp"
|
||||
#include "memory/GlobalPagedFreeStore.cpp"
|
||||
#include "memory/PagedFreeStore.cpp"
|
||||
#include "memory/StaticObject.cpp"
|
||||
|
||||
#include "misc/Main.cpp"
|
||||
@@ -197,13 +192,8 @@ namespace beast
|
||||
|
||||
#include "thread/impl/TrackedMutex.cpp"
|
||||
#include "thread/DeadlineTimer.cpp"
|
||||
#include "thread/InterruptibleThread.cpp"
|
||||
#include "thread/Stoppable.cpp"
|
||||
#include "thread/Semaphore.cpp"
|
||||
#include "thread/CallQueue.cpp"
|
||||
#include "thread/Listeners.cpp"
|
||||
#include "thread/ManualCallQueue.cpp"
|
||||
#include "thread/ThreadWithCallQueue.cpp"
|
||||
#include "thread/Workers.cpp"
|
||||
|
||||
#include "threads/ChildProcess.cpp"
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
// New header-only library modeled more closely according to boost
|
||||
#include "../../beast/CStdInt.h"
|
||||
#include "../../beast/SmartPtr.h"
|
||||
#include "../../beast/StaticAssert.h"
|
||||
#include "../../beast/Uncopyable.h"
|
||||
#include "../../beast/Atomic.h"
|
||||
@@ -155,7 +156,6 @@ class FileOutputStream;
|
||||
#include "thread/TrackedMutex.h"
|
||||
#include "diagnostic/FatalError.h"
|
||||
#include "text/LexicalCast.h"
|
||||
#include "memory/ContainerDeletePolicy.h"
|
||||
#include "maths/Math.h"
|
||||
#include "maths/uint24.h"
|
||||
#include "logging/Logger.h"
|
||||
@@ -180,8 +180,6 @@ class FileOutputStream;
|
||||
#include "containers/SortedSet.h"
|
||||
#include "maths/Range.h"
|
||||
#include "containers/SparseSet.h"
|
||||
# include "containers/DynamicList.h"
|
||||
#include "memory/ScopedPointer.h"
|
||||
#include "files/DirectoryIterator.h"
|
||||
#include "streams/InputStream.h"
|
||||
#include "files/FileInputStream.h"
|
||||
@@ -242,21 +240,8 @@ class FileOutputStream;
|
||||
|
||||
#include "thread/DeadlineTimer.h"
|
||||
|
||||
#include "memory/AllocatedBy.h"
|
||||
#include "memory/PagedFreeStore.h"
|
||||
#include "memory/GlobalPagedFreeStore.h"
|
||||
#include "memory/FifoFreeStoreWithTLS.h"
|
||||
#include "memory/FifoFreeStoreWithoutTLS.h"
|
||||
#include "memory/FifoFreeStore.h"
|
||||
#include "memory/GlobalFifoFreeStore.h"
|
||||
|
||||
#include "thread/Semaphore.h"
|
||||
#include "thread/InterruptibleThread.h"
|
||||
#include "thread/Stoppable.h"
|
||||
#include "thread/CallQueue.h"
|
||||
#include "thread/Listeners.h"
|
||||
#include "thread/ManualCallQueue.h"
|
||||
#include "thread/ThreadWithCallQueue.h"
|
||||
#include "thread/Workers.h"
|
||||
|
||||
}
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
namespace ContainerTests
|
||||
{
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Counts the number of occurrences of each type of operation. */
|
||||
class Counts
|
||||
{
|
||||
public:
|
||||
typedef std::size_t count_type;
|
||||
|
||||
Counts ()
|
||||
: default_ctor (0)
|
||||
, copy_ctor (0)
|
||||
, copy_assign (0)
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
, move_ctor (0)
|
||||
, move_assign (0)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
Counts (Counts const& other)
|
||||
: default_ctor (other.default_ctor)
|
||||
, copy_ctor (other.copy_ctor)
|
||||
, copy_assign (other.copy_assign)
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
, move_ctor (other.move_ctor)
|
||||
, move_assign (other.move_assign)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
Counts& operator= (Counts const& other)
|
||||
{
|
||||
default_ctor = other.default_ctor;
|
||||
copy_ctor = other.copy_ctor;
|
||||
copy_assign = other.copy_assign;
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
move_ctor = other.move_ctor;
|
||||
move_assign = other.move_assign;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend inline Counts operator- (Counts const& lhs, Counts const& rhs)
|
||||
{
|
||||
Counts result;
|
||||
result.default_ctor = lhs.default_ctor - rhs.default_ctor;
|
||||
result.copy_ctor = lhs.copy_ctor - rhs.copy_ctor;
|
||||
result.copy_assign = lhs.copy_assign - rhs.copy_assign;
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
result.move_ctor = lhs.move_ctor - rhs.move_ctor;
|
||||
result.move_assign = lhs.move_assign - rhs.move_assign;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
String toString () const
|
||||
{
|
||||
return String()
|
||||
+ "default_ctor(" + String::fromNumber (default_ctor) + ") "
|
||||
+ ", copy_ctor(" + String::fromNumber (copy_ctor) + ")"
|
||||
+ ", copy_assign(" + String::fromNumber (copy_assign) + ")"
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
+ ", move_ctor(" + String::fromNumber (move_ctor) + ")"
|
||||
+ ", move_assign(" + String::fromNumber (move_assign) + ")"
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
count_type default_ctor;
|
||||
count_type copy_ctor;
|
||||
count_type copy_assign;
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
count_type move_ctor;
|
||||
count_type move_assign;
|
||||
#endif
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Counts the number of element operations performed in a scope. */
|
||||
class ScopedCounts : public Uncopyable
|
||||
{
|
||||
public:
|
||||
ScopedCounts (Counts const& counts)
|
||||
: m_start (counts)
|
||||
, m_counts (counts)
|
||||
{
|
||||
}
|
||||
|
||||
Counts get () const
|
||||
{
|
||||
return m_counts - m_start;
|
||||
}
|
||||
|
||||
private:
|
||||
Counts const m_start;
|
||||
Counts const& m_counts;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* Base for element configurations. */
|
||||
class ElementConfigBase : public Uncopyable
|
||||
{
|
||||
public:
|
||||
typedef std::size_t IdType;
|
||||
};
|
||||
|
||||
/** Provides the element-specific configuration members. */
|
||||
template <class Params>
|
||||
class ElementConfig : public ElementConfigBase
|
||||
{
|
||||
public:
|
||||
static Counts& getCounts ()
|
||||
{
|
||||
static Counts counts;
|
||||
return counts;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Base for elements used in container unit tests. */
|
||||
class ElementBase
|
||||
{
|
||||
public:
|
||||
};
|
||||
|
||||
/** An object placed into a container for unit testing. */
|
||||
template <class Config>
|
||||
class Element : public ElementBase
|
||||
{
|
||||
public:
|
||||
typedef typename Config::IdType IdType;
|
||||
|
||||
Element ()
|
||||
: m_id (0)
|
||||
, m_msg (String::empty)
|
||||
{
|
||||
++Config::getCounts ().default_ctor;
|
||||
}
|
||||
|
||||
explicit Element (IdType id)
|
||||
: m_id (id)
|
||||
, m_msg (String::fromNumber (id))
|
||||
{
|
||||
}
|
||||
|
||||
Element (Element const& other)
|
||||
: m_id (other.m_id)
|
||||
, m_msg (other.m_msg)
|
||||
{
|
||||
++Config::getCounts ().copy_ctor;
|
||||
}
|
||||
|
||||
Element& operator= (Element const& other)
|
||||
{
|
||||
m_id = other.m_id;
|
||||
m_msg = other.m_msg;
|
||||
++Config::getCounts ().copy_assign;
|
||||
}
|
||||
|
||||
#if BEAST_COMPILER_SUPPORTS_MOVE_SEMANTICS
|
||||
Element (Element&& other)
|
||||
: m_id (other.m_id)
|
||||
, m_msg (other.m_msg)
|
||||
{
|
||||
other.m_msg = String::empty;
|
||||
++Config::getCounts ().move_ctor;
|
||||
}
|
||||
|
||||
Element& operator= (Element&& other)
|
||||
{
|
||||
m_id = other.m_id;
|
||||
m_msg = other.m_msg;
|
||||
other.m_msg = String::empty;
|
||||
++Config::getCounts ().move_assign;
|
||||
}
|
||||
#endif
|
||||
|
||||
IdType id () const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
String msg () const
|
||||
{
|
||||
return m_msg;
|
||||
}
|
||||
|
||||
private:
|
||||
IdType m_id;
|
||||
String m_msg;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Base for test state parameters. */
|
||||
class StateBase : public Uncopyable
|
||||
{
|
||||
public:
|
||||
typedef int64 SeedType;
|
||||
};
|
||||
|
||||
/** Provides configuration-specific test state parameters. */
|
||||
template <class Params>
|
||||
class State : public StateBase
|
||||
{
|
||||
public:
|
||||
static Random& random ()
|
||||
{
|
||||
static Random generator (Params::seedValue);
|
||||
return generator;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ConfigBase
|
||||
{
|
||||
};
|
||||
|
||||
template <template <typename, class> class Container,
|
||||
class Params>
|
||||
class Config
|
||||
: public State <Params>
|
||||
, public ElementConfig <Params>
|
||||
{
|
||||
public:
|
||||
typedef Config ConfigType;
|
||||
typedef Params ParamsType;
|
||||
typedef State <Params> StateType;
|
||||
typedef ElementConfig <Params> ElementConfigType;
|
||||
typedef Element <ElementConfigType> ElementType;
|
||||
typedef Container <
|
||||
ElementType,
|
||||
std::allocator <char> > ContainerType;
|
||||
|
||||
typedef StateBase::SeedType SeedType;
|
||||
|
||||
// default seed
|
||||
static SeedType const seedValue = 69;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A generic container test. */
|
||||
template <class Params>
|
||||
class Test : public Params
|
||||
{
|
||||
public:
|
||||
typedef typename Params::StateType StateType;
|
||||
typedef typename Params::ElementConfigType ElementConfigType;
|
||||
typedef typename Params::ContainerType ContainerType;
|
||||
|
||||
void doInsert ()
|
||||
{
|
||||
m_container.reserve (Params::elementCount);
|
||||
for (typename ContainerType::size_type i = 0;
|
||||
i < Params::elementCount; ++i)
|
||||
{
|
||||
m_container.push_back (typename Params::ElementType ());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
typename Params::ContainerType m_container;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class DynamicListTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
struct Params : Config <DynamicList, Params>
|
||||
{
|
||||
static SeedType const seedValue = 42;
|
||||
static ContainerType::size_type const elementCount = 100 * 1000;
|
||||
};
|
||||
|
||||
typedef Test <Params> TestType;
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
TestType test;
|
||||
|
||||
beginTestCase ("insert");
|
||||
|
||||
{
|
||||
ScopedCounts counts (TestType::getCounts ());
|
||||
test.doInsert ();
|
||||
this->logMessage (counts.get ().toString ());
|
||||
}
|
||||
|
||||
pass ();
|
||||
}
|
||||
|
||||
DynamicListTests () : UnitTest ("DynamicList", "beast")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static DynamicListTests dynamicListTests;
|
||||
|
||||
}
|
||||
@@ -1,473 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_CORE_CONTAINERS_DYNAMICLIST_H_INCLUDED
|
||||
#define BEAST_CORE_CONTAINERS_DYNAMICLIST_H_INCLUDED
|
||||
|
||||
template <typename, typename>
|
||||
class DynamicList;
|
||||
|
||||
template <class Container, bool IsConst>
|
||||
class DynamicListIterator
|
||||
: public std::iterator <
|
||||
std::bidirectional_iterator_tag,
|
||||
typename Container::value_type,
|
||||
typename Container::difference_type,
|
||||
typename mpl::IfCond <IsConst,
|
||||
typename Container::const_pointer,
|
||||
typename Container::pointer>::type,
|
||||
typename mpl::IfCond <IsConst,
|
||||
typename Container::const_reference,
|
||||
typename Container::reference>::type>
|
||||
{
|
||||
private:
|
||||
typedef typename mpl::IfCond <IsConst,
|
||||
typename List <typename Container::Item>::const_iterator,
|
||||
typename List <typename Container::Item>::iterator>::type iterator_type;
|
||||
|
||||
typedef typename mpl::IfCond <IsConst,
|
||||
typename Container::const_pointer,
|
||||
typename Container::pointer>::type pointer;
|
||||
|
||||
typedef typename mpl::IfCond <IsConst,
|
||||
typename Container::const_reference,
|
||||
typename Container::reference>::type reference;
|
||||
|
||||
public:
|
||||
DynamicListIterator ()
|
||||
{
|
||||
}
|
||||
|
||||
DynamicListIterator (iterator_type iter)
|
||||
: m_iter (iter)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicListIterator (DynamicListIterator const& other)
|
||||
: m_iter (other.m_iter)
|
||||
{
|
||||
}
|
||||
|
||||
template <bool OtherIsConst>
|
||||
DynamicListIterator (DynamicListIterator <Container, OtherIsConst> const& other)
|
||||
: m_iter (other.m_iter)
|
||||
{
|
||||
}
|
||||
|
||||
DynamicListIterator& operator= (DynamicListIterator const& other)
|
||||
{
|
||||
m_iter = other.m_iter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <bool OtherIsConst>
|
||||
DynamicListIterator& operator= (DynamicListIterator <
|
||||
Container, OtherIsConst> const& other)
|
||||
{
|
||||
m_iter = other.m_iter;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <bool OtherIsConst>
|
||||
bool operator== (DynamicListIterator <Container, OtherIsConst> const& other) const
|
||||
{
|
||||
return m_iter == other.m_iter;
|
||||
}
|
||||
|
||||
template <bool OtherIsConst>
|
||||
bool operator!= (DynamicListIterator <Container, OtherIsConst> const& other) const
|
||||
{
|
||||
return ! ((*this) == other);
|
||||
}
|
||||
|
||||
reference operator* () const
|
||||
{
|
||||
return dereference ();
|
||||
}
|
||||
|
||||
pointer operator-> () const
|
||||
{
|
||||
return &dereference ();
|
||||
}
|
||||
|
||||
DynamicListIterator& operator++ ()
|
||||
{
|
||||
increment ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicListIterator operator++ (int)
|
||||
{
|
||||
DynamicListIterator const result (*this);
|
||||
increment ();
|
||||
return result;
|
||||
}
|
||||
|
||||
DynamicListIterator& operator-- ()
|
||||
{
|
||||
decrement ();
|
||||
return *this;
|
||||
}
|
||||
|
||||
DynamicListIterator operator-- (int)
|
||||
{
|
||||
DynamicListIterator const result (*this);
|
||||
decrement ();
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename, typename>
|
||||
friend class DynamicList;
|
||||
|
||||
reference dereference () const
|
||||
{
|
||||
return *(m_iter->get ());
|
||||
}
|
||||
|
||||
void increment ()
|
||||
{
|
||||
++m_iter;
|
||||
}
|
||||
|
||||
void decrement ()
|
||||
{
|
||||
--m_iter;
|
||||
}
|
||||
|
||||
iterator_type m_iter;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A list that uses a very small number of dynamic allocations.
|
||||
|
||||
Once an element is allocated, its address does not change. Elements
|
||||
can be erased, and they are placed onto a deleted list for re-use.
|
||||
Allocations occur in configurable batches.
|
||||
|
||||
Iterators to elements never become invalid, they can be safely
|
||||
stored elsewhere, as long as the underlying element is not erased.
|
||||
|
||||
T may support these concepts:
|
||||
DefaultConstructible
|
||||
MoveConstructible (C++11)
|
||||
|
||||
T must support these concepts:
|
||||
Destructible
|
||||
*/
|
||||
template <typename T,
|
||||
class Allocator = std::allocator <char> >
|
||||
class DynamicList
|
||||
{
|
||||
private:
|
||||
typedef PARAMETER_TYPE (T) TParam;
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
defaultBlocksize = 1000
|
||||
};
|
||||
|
||||
typedef T value_type;
|
||||
typedef Allocator allocator_type;
|
||||
typedef std::size_t size_type;
|
||||
typedef std::ptrdiff_t difference_type;
|
||||
typedef T* pointer;
|
||||
typedef T& reference;
|
||||
typedef T const* const_pointer;
|
||||
typedef T const& const_reference;
|
||||
|
||||
typedef DynamicList <
|
||||
T,
|
||||
Allocator> container_type;
|
||||
|
||||
typedef DynamicListIterator <container_type, false> iterator;
|
||||
typedef DynamicListIterator <container_type, true> const_iterator;
|
||||
|
||||
public:
|
||||
explicit DynamicList (
|
||||
size_type blocksize = defaultBlocksize,
|
||||
Allocator const& allocator = Allocator ())
|
||||
: m_allocator (allocator)
|
||||
, m_blocksize (blocksize)
|
||||
, m_capacity (0)
|
||||
{
|
||||
}
|
||||
|
||||
~DynamicList ()
|
||||
{
|
||||
clear ();
|
||||
shrink_to_fit ();
|
||||
}
|
||||
|
||||
allocator_type get_allocator () const noexcept
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
reference front () noexcept
|
||||
{
|
||||
return *m_items.front ().get ();
|
||||
}
|
||||
|
||||
const_reference front () const noexcept
|
||||
{
|
||||
return *m_items.front ().get ();
|
||||
}
|
||||
|
||||
reference back () noexcept
|
||||
{
|
||||
return *m_items.back ().get ();
|
||||
}
|
||||
|
||||
const_reference back () const noexcept
|
||||
{
|
||||
return *m_items.back ().get ();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
iterator begin () noexcept
|
||||
{
|
||||
return iterator (m_items.begin ());
|
||||
}
|
||||
|
||||
const_iterator begin () const noexcept
|
||||
{
|
||||
return const_iterator (m_items.begin ());
|
||||
}
|
||||
|
||||
const_iterator cbegin () const noexcept
|
||||
{
|
||||
return const_iterator (m_items.cbegin ());
|
||||
}
|
||||
|
||||
iterator end () noexcept
|
||||
{
|
||||
return iterator (m_items.end ());
|
||||
}
|
||||
|
||||
const_iterator end () const noexcept
|
||||
{
|
||||
return const_iterator (m_items.end ());
|
||||
}
|
||||
|
||||
const_iterator cend () const noexcept
|
||||
{
|
||||
return const_iterator (m_items.cend ());
|
||||
}
|
||||
|
||||
iterator iterator_to (T& value) noexcept
|
||||
{
|
||||
std::ptrdiff_t const offset (
|
||||
(std::ptrdiff_t)(((Item const*)0)->get ()));
|
||||
Item& item (*addBytesToPointer (((Item*)&value), -offset));
|
||||
return iterator (m_items.iterator_to (item));
|
||||
}
|
||||
|
||||
const_iterator const_iterator_to (T const& value) const noexcept
|
||||
{
|
||||
std::ptrdiff_t const offset (
|
||||
(std::ptrdiff_t)(((Item const*)0)->get ()));
|
||||
Item const& item (*addBytesToPointer (((Item const*)&value), -offset));
|
||||
return const_iterator (m_items.const_iterator_to (item));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
bool empty () const noexcept
|
||||
{
|
||||
return m_items.empty ();
|
||||
}
|
||||
|
||||
size_type size () const noexcept
|
||||
{
|
||||
return m_items.size ();
|
||||
}
|
||||
|
||||
size_type max_size () const noexcept
|
||||
{
|
||||
return std::numeric_limits <size_type>::max ();
|
||||
}
|
||||
|
||||
void reserve (size_type new_cap) noexcept
|
||||
{
|
||||
new_cap = m_blocksize * (
|
||||
(new_cap + m_blocksize - 1) / m_blocksize);
|
||||
if (new_cap > max_size ())
|
||||
Throw (std::length_error ("new_cap > max_size"), __FILE__, __LINE__);
|
||||
if (new_cap <= m_capacity)
|
||||
return;
|
||||
size_type const n (new_cap / m_blocksize);
|
||||
m_handles.reserve (n);
|
||||
for (size_type i = m_handles.size (); i < n; ++i)
|
||||
m_handles.push_back (static_cast <Item*> (std::malloc (
|
||||
m_blocksize * sizeof (Item))));
|
||||
m_capacity = new_cap;
|
||||
}
|
||||
|
||||
size_type capacity () const noexcept
|
||||
{
|
||||
return m_capacity;
|
||||
}
|
||||
|
||||
void shrink_to_fit ()
|
||||
{
|
||||
// Special case when all allocated
|
||||
// items are part of the free list.
|
||||
if (m_items.empty ())
|
||||
m_free.clear ();
|
||||
|
||||
size_type const used (m_items.size () + m_free.size ());
|
||||
size_type const handles ((used + m_blocksize - 1) / m_blocksize);
|
||||
m_capacity = handles * m_blocksize;
|
||||
for (size_type i = m_handles.size (); i-- > handles;)
|
||||
{
|
||||
std::free (m_handles [i]);
|
||||
m_handles.erase (m_handles.begin () + i);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void clear ()
|
||||
{
|
||||
// Might want to skip this if is_pod<T> is true
|
||||
for (typename List <Item>::iterator iter = m_items.begin ();
|
||||
iter != m_items.end ();)
|
||||
{
|
||||
Item& item (*iter++);
|
||||
item.get ()->~T ();
|
||||
m_free.push_back (item);
|
||||
}
|
||||
}
|
||||
|
||||
/** Allocate a new default-constructed element and return the iterator.
|
||||
If there are deleted elements in the free list, the new element
|
||||
may not be created at the end of the storage area.
|
||||
*/
|
||||
iterator emplace_back ()
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T ());
|
||||
}
|
||||
|
||||
template <class A1>
|
||||
iterator emplace_back (A1 a1)
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (a1));
|
||||
}
|
||||
|
||||
template <class A1, class A2>
|
||||
iterator emplace_back (A1 a1, A2 a2)
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (a1, a2));
|
||||
}
|
||||
|
||||
template <class A1, class A2, class A3>
|
||||
iterator emplace_back (A1 a1, A2 a2, A3 a3)
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (a1, a2, a3));
|
||||
}
|
||||
|
||||
template <class A1, class A2, class A3, class A4>
|
||||
iterator emplace_back (A1 a1, A2 a2, A3 a3, A4 a4)
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (a1, a2, a3, a4));
|
||||
}
|
||||
|
||||
template <class A1, class A2, class A3, class A4, class A5>
|
||||
iterator emplace_back (A1 a1, A2 a2, A3 a3, A4 a4, A5 a5)
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (a1, a2, a3, a4, a5));
|
||||
}
|
||||
|
||||
/** Allocate a new copy-constructed element and return the index. */
|
||||
iterator push_back (TParam value) noexcept
|
||||
{
|
||||
return iterator_to (*new (alloc ()->get ()) T (value));
|
||||
}
|
||||
|
||||
/** Erase the element at the specified position. */
|
||||
iterator erase (iterator pos)
|
||||
{
|
||||
Item& item (*pos.m_iter);
|
||||
item.get ()->~T ();
|
||||
pos = m_items.erase (m_items.iterator_to (item));
|
||||
m_free.push_front (item);
|
||||
return pos;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class, bool>
|
||||
friend class DynamicListIterator;
|
||||
|
||||
struct Item : List <Item>::Node
|
||||
{
|
||||
typedef T value_type;
|
||||
|
||||
T* get () noexcept
|
||||
{
|
||||
return reinterpret_cast <T*> (&storage [0]);
|
||||
}
|
||||
|
||||
T const* get () const noexcept
|
||||
{
|
||||
return reinterpret_cast <T const*> (&storage [0]);
|
||||
}
|
||||
|
||||
private:
|
||||
// Lets hope this is padded correctly
|
||||
uint8 storage [sizeof (T)];
|
||||
};
|
||||
|
||||
Item* alloc () noexcept
|
||||
{
|
||||
Item* item;
|
||||
if (m_free.empty ())
|
||||
{
|
||||
if (m_capacity <= m_items.size ())
|
||||
reserve (m_items.size () + 1);
|
||||
|
||||
size_type const index (m_items.size () / m_blocksize);
|
||||
size_type const offset (m_items.size () - index * m_blocksize);
|
||||
item = m_handles [index] + offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
item = &m_free.pop_front ();
|
||||
}
|
||||
|
||||
m_items.push_back (*item);
|
||||
return item;
|
||||
}
|
||||
|
||||
typedef std::vector <Item*> blocks_t;
|
||||
|
||||
Allocator m_allocator;
|
||||
size_type m_blocksize;
|
||||
size_type m_capacity;
|
||||
std::vector <Item*> m_handles;
|
||||
List <Item> m_items;
|
||||
List <Item> m_free;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_ALLOCATEDBY_H_INCLUDED
|
||||
#define BEAST_ALLOCATEDBY_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
Customized allocation for heap objects.
|
||||
|
||||
Derived classes will use the specified allocator for new and delete.
|
||||
|
||||
@param AllocatorType The type of allocator to use.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
template <class AllocatorType>
|
||||
class AllocatedBy
|
||||
{
|
||||
public:
|
||||
static inline void* operator new (size_t bytes, AllocatorType& allocator) noexcept
|
||||
{
|
||||
return allocator.allocate (bytes);
|
||||
}
|
||||
|
||||
static inline void* operator new (size_t bytes, AllocatorType* allocator) noexcept
|
||||
{
|
||||
return allocator->allocate (bytes);
|
||||
}
|
||||
|
||||
static inline void operator delete (void* p, AllocatorType&) noexcept
|
||||
{
|
||||
AllocatorType::deallocate (p);
|
||||
}
|
||||
|
||||
static inline void operator delete (void* p, AllocatorType*) noexcept
|
||||
{
|
||||
AllocatorType::deallocate (p);
|
||||
}
|
||||
|
||||
static inline void operator delete (void* p) noexcept
|
||||
{
|
||||
AllocatorType::deallocate (p);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,38 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_FIFOFREESTORE_H_INCLUDED
|
||||
#define BEAST_FIFOFREESTORE_H_INCLUDED
|
||||
|
||||
/** Selected free store based on compilation settings.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
// VFALCO NOTE Disabled this because it seems that the TLS
|
||||
// implementation has a leak. Although the other
|
||||
// one also seems to have a leak.
|
||||
//
|
||||
//#if BEAST_USE_BOOST_FEATURES
|
||||
#if 0
|
||||
typedef FifoFreeStoreWithTLS FifoFreeStoreType;
|
||||
#else
|
||||
typedef FifoFreeStoreWithoutTLS FifoFreeStoreType;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,198 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// Implementation notes
|
||||
//
|
||||
// - A Page is a large allocation from a global PageAllocator.
|
||||
//
|
||||
// - Each thread maintains an 'active' page from which it makes allocations.
|
||||
//
|
||||
// - When the active page is full, a new one takes it's place.
|
||||
//
|
||||
// - Page memory is deallocated when it is not active and no longer referenced.
|
||||
//
|
||||
// - Each instance of FifoFreeStoreWithTLS maintains its own set of per-thread active pages,
|
||||
// but uses a global PageAllocator. This reduces memory consumption without
|
||||
// affecting performance.
|
||||
//
|
||||
|
||||
#if BEAST_USE_BOOST_FEATURES
|
||||
|
||||
// This precedes every allocation
|
||||
//
|
||||
struct FifoFreeStoreWithTLS::Header
|
||||
{
|
||||
FifoFreeStoreWithTLS::Page* page;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class FifoFreeStoreWithTLS::Page : LeakChecked <Page>, public Uncopyable
|
||||
{
|
||||
public:
|
||||
explicit Page (const size_t bytes) : m_refs (1)
|
||||
{
|
||||
m_end = reinterpret_cast <char*> (this) + bytes;
|
||||
m_free = reinterpret_cast <char*> (
|
||||
Memory::pointerAdjustedForAlignment (this + 1));
|
||||
}
|
||||
|
||||
~Page ()
|
||||
{
|
||||
bassert (! m_refs.isSignaled ());
|
||||
}
|
||||
|
||||
inline bool release ()
|
||||
{
|
||||
bassert (! m_refs.isSignaled ());
|
||||
|
||||
return m_refs.release ();
|
||||
}
|
||||
|
||||
void* allocate (size_t bytes)
|
||||
{
|
||||
bassert (bytes > 0);
|
||||
|
||||
char* p = Memory::pointerAdjustedForAlignment (m_free);
|
||||
char* free = p + bytes;
|
||||
|
||||
if (free <= m_end)
|
||||
{
|
||||
m_free = free;
|
||||
|
||||
m_refs.addref ();
|
||||
}
|
||||
else
|
||||
{
|
||||
p = 0;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private:
|
||||
AtomicCounter m_refs; // reference count
|
||||
char* m_free; // next free byte
|
||||
char* m_end; // last free byte + 1
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class FifoFreeStoreWithTLS::PerThreadData : LeakChecked <PerThreadData>, public Uncopyable
|
||||
{
|
||||
public:
|
||||
explicit PerThreadData (FifoFreeStoreWithTLS* allocator)
|
||||
: m_allocator (*allocator)
|
||||
, m_active (m_allocator.newPage ())
|
||||
{
|
||||
}
|
||||
|
||||
~PerThreadData ()
|
||||
{
|
||||
if (m_active->release ())
|
||||
m_allocator.deletePage (m_active);
|
||||
}
|
||||
|
||||
inline void* allocate (const size_t bytes)
|
||||
{
|
||||
const size_t headerBytes = Memory::sizeAdjustedForAlignment (sizeof (Header));
|
||||
const size_t bytesNeeded = headerBytes + bytes;
|
||||
|
||||
if (bytesNeeded > m_allocator.m_pages->getPageBytes ())
|
||||
fatal_error ("the memory request was too large");
|
||||
|
||||
Header* header;
|
||||
|
||||
header = reinterpret_cast <Header*> (m_active->allocate (bytesNeeded));
|
||||
|
||||
if (!header)
|
||||
{
|
||||
if (m_active->release ())
|
||||
deletePage (m_active);
|
||||
|
||||
m_active = m_allocator.newPage ();
|
||||
|
||||
header = reinterpret_cast <Header*> (m_active->allocate (bytesNeeded));
|
||||
}
|
||||
|
||||
header->page = m_active;
|
||||
|
||||
return reinterpret_cast <char*> (header) + headerBytes;
|
||||
}
|
||||
|
||||
private:
|
||||
FifoFreeStoreWithTLS& m_allocator;
|
||||
Page* m_active;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline FifoFreeStoreWithTLS::Page* FifoFreeStoreWithTLS::newPage ()
|
||||
{
|
||||
return new (m_pages->allocate ()) Page (m_pages->getPageBytes ());
|
||||
}
|
||||
|
||||
inline void FifoFreeStoreWithTLS::deletePage (Page* page)
|
||||
{
|
||||
// Safe, because each thread maintains its own active page.
|
||||
page->~Page ();
|
||||
PagedFreeStoreType::deallocate (page);
|
||||
}
|
||||
|
||||
FifoFreeStoreWithTLS::FifoFreeStoreWithTLS ()
|
||||
: m_pages (PagedFreeStoreType::getInstance ())
|
||||
{
|
||||
//bassert (m_pages->getPageBytes () >= sizeof (Page) + Memory::allocAlignBytes);
|
||||
}
|
||||
|
||||
FifoFreeStoreWithTLS::~FifoFreeStoreWithTLS ()
|
||||
{
|
||||
// Clean up this thread's data before we release
|
||||
// the reference to the global page allocator.
|
||||
m_tsp.reset (0);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void* FifoFreeStoreWithTLS::allocate (const size_t bytes)
|
||||
{
|
||||
PerThreadData* data = m_tsp.get ();
|
||||
|
||||
if (!data)
|
||||
{
|
||||
data = new PerThreadData (this);
|
||||
m_tsp.reset (data);
|
||||
}
|
||||
|
||||
return data->allocate (bytes);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void FifoFreeStoreWithTLS::deallocate (void* p)
|
||||
{
|
||||
const size_t headerBytes = Memory::sizeAdjustedForAlignment (sizeof (Header));
|
||||
Header* const header = reinterpret_cast <Header*> (reinterpret_cast <char*> (p) - headerBytes);
|
||||
Page* const page = header->page;
|
||||
|
||||
if (page->release ())
|
||||
deletePage (page);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,69 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_FIFOFREESTOREWITHTLS_H_INCLUDED
|
||||
#define BEAST_FIFOFREESTOREWITHTLS_H_INCLUDED
|
||||
|
||||
#if BEAST_USE_BOOST_FEATURES
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
Lock-free and mostly wait-free FIFO memory allocator.
|
||||
|
||||
This allocator is suitable for use with CallQueue and Listeners. It is
|
||||
expected that over time, deallocations will occur in roughly the same order
|
||||
as allocations.
|
||||
|
||||
@note This implementation uses Thread Local Storage to further improve
|
||||
performance. However, it requires boost style thread_specific_ptr.
|
||||
|
||||
@invariant allocate() and deallocate() are fully concurrent.
|
||||
|
||||
@invariant The ABA problem is handled automatically.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API FifoFreeStoreWithTLS : public LeakChecked <FifoFreeStoreWithTLS>
|
||||
{
|
||||
public:
|
||||
FifoFreeStoreWithTLS ();
|
||||
~FifoFreeStoreWithTLS ();
|
||||
|
||||
void* allocate (const size_t bytes);
|
||||
static void deallocate (void* const p);
|
||||
|
||||
private:
|
||||
typedef GlobalPagedFreeStore PagedFreeStoreType;
|
||||
struct Header;
|
||||
|
||||
class Page;
|
||||
|
||||
inline Page* newPage ();
|
||||
static inline void deletePage (Page* page);
|
||||
|
||||
private:
|
||||
class PerThreadData;
|
||||
boost::thread_specific_ptr <PerThreadData> m_tsp;
|
||||
|
||||
PagedFreeStoreType::Ptr m_pages;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,241 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// This precedes every allocation
|
||||
struct FifoFreeStoreWithoutTLS::Header
|
||||
{
|
||||
union
|
||||
{
|
||||
FifoFreeStoreWithoutTLS::Block* block; // backpointer to the page
|
||||
|
||||
char pad [Memory::allocAlignBytes];
|
||||
};
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class FifoFreeStoreWithoutTLS::Block : public Uncopyable
|
||||
{
|
||||
public:
|
||||
explicit Block (const size_t bytes) : m_refs (1)
|
||||
{
|
||||
m_end = reinterpret_cast <char*> (this) + bytes;
|
||||
m_free = reinterpret_cast <char*> (
|
||||
Memory::pointerAdjustedForAlignment (this + 1));
|
||||
}
|
||||
|
||||
~Block ()
|
||||
{
|
||||
bassert (!m_refs.isSignaled ());
|
||||
}
|
||||
|
||||
inline void addref ()
|
||||
{
|
||||
m_refs.addref ();
|
||||
}
|
||||
|
||||
inline bool release ()
|
||||
{
|
||||
return m_refs.release ();
|
||||
}
|
||||
|
||||
enum Result
|
||||
{
|
||||
success, // successful allocation
|
||||
ignore, // disregard the block
|
||||
consumed // block is consumed (1 thread sees this)
|
||||
};
|
||||
|
||||
Result allocate (size_t bytes, void* pBlock)
|
||||
{
|
||||
bassert (bytes > 0);
|
||||
|
||||
Result result;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
char* base = m_free.get ();
|
||||
|
||||
if (base)
|
||||
{
|
||||
char* p = Memory::pointerAdjustedForAlignment (base);
|
||||
char* free = p + bytes;
|
||||
|
||||
if (free <= m_end)
|
||||
{
|
||||
// Try to commit the allocation
|
||||
if (m_free.compareAndSet (free, base))
|
||||
{
|
||||
* (reinterpret_cast <void**> (pBlock)) = p;
|
||||
result = success;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Someone changed m_free, retry.
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mark the block consumed.
|
||||
if (m_free.compareAndSet (0, base))
|
||||
{
|
||||
// Only one caller sees this, the rest get 'ignore'
|
||||
result = consumed;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Happens with another concurrent allocate(), retry.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Block is consumed, ignore it.
|
||||
result = ignore;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
AtomicCounter m_refs; // reference count
|
||||
AtomicPointer <char> m_free; // next free byte or 0 if inactive.
|
||||
char* m_end; // last free byte + 1
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
inline FifoFreeStoreWithoutTLS::Block* FifoFreeStoreWithoutTLS::newBlock ()
|
||||
{
|
||||
return new (m_pages->allocate ()) Block (m_pages->getPageBytes ());
|
||||
}
|
||||
|
||||
inline void FifoFreeStoreWithoutTLS::deleteBlock (Block* b)
|
||||
{
|
||||
// It is critical that we do not call the destructor,
|
||||
// because due to the lock-free implementation, a Block
|
||||
// can be accessed for a short time after it is deleted.
|
||||
/* b->~Block (); */ // DO NOT CALL!!!
|
||||
|
||||
PagedFreeStoreType::deallocate (b);
|
||||
}
|
||||
|
||||
FifoFreeStoreWithoutTLS::FifoFreeStoreWithoutTLS ()
|
||||
: m_pages (GlobalPagedFreeStore::getInstance ())
|
||||
{
|
||||
if (m_pages->getPageBytes () < sizeof (Block) + 256)
|
||||
fatal_error ("the block size is too small");
|
||||
|
||||
m_active = newBlock ();
|
||||
}
|
||||
|
||||
FifoFreeStoreWithoutTLS::~FifoFreeStoreWithoutTLS ()
|
||||
{
|
||||
deleteBlock (m_active);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void* FifoFreeStoreWithoutTLS::allocate (const size_t bytes)
|
||||
{
|
||||
const size_t actual = sizeof (Header) + bytes;
|
||||
|
||||
if (actual > m_pages->getPageBytes ())
|
||||
fatal_error ("the memory request was too large");
|
||||
|
||||
Header* h;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Get an active block.
|
||||
Block* b = m_active;
|
||||
|
||||
while (!b)
|
||||
{
|
||||
Thread::yield ();
|
||||
b = m_active;
|
||||
}
|
||||
|
||||
// (*) It is possible for the block to get a final release here
|
||||
// In this case it will have been put in the garbage, and
|
||||
// m_active will not match.
|
||||
|
||||
// Acquire a reference.
|
||||
b->addref ();
|
||||
|
||||
// Is it still active?
|
||||
if (m_active == b)
|
||||
{
|
||||
// Yes so try to allocate from it.
|
||||
const Block::Result result = b->allocate (actual, &h);
|
||||
|
||||
if (result == Block::success)
|
||||
{
|
||||
// Keep the reference and return the allocation.
|
||||
h->block = b;
|
||||
break;
|
||||
}
|
||||
else if (result == Block::consumed)
|
||||
{
|
||||
// Remove block from active.
|
||||
m_active = 0;
|
||||
|
||||
// Take away the reference we added
|
||||
b->release ();
|
||||
|
||||
// Take away the original active reference.
|
||||
if (b->release ())
|
||||
deleteBlock (b);
|
||||
|
||||
// Install a fresh empty active block.
|
||||
m_active = newBlock ();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b->release ())
|
||||
deleteBlock (b);
|
||||
}
|
||||
|
||||
// Try again.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Block became inactive, so release our reference.
|
||||
b->release ();
|
||||
|
||||
// (*) It is possible for this to be a duplicate final release.
|
||||
}
|
||||
}
|
||||
|
||||
return h + 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void FifoFreeStoreWithoutTLS::deallocate (void* p)
|
||||
{
|
||||
Block* const b = (reinterpret_cast <Header*> (p) - 1)->block;
|
||||
|
||||
if (b->release ())
|
||||
deleteBlock (b);
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_FIFOFREESTOREWITHOUTTLS_H_INCLUDED
|
||||
#define BEAST_FIFOFREESTOREWITHOUTTLS_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
Lock-free FIFO memory allocator.
|
||||
|
||||
This allocator is suitable for use with CallQueue and Listeners. It is
|
||||
expected that over time, deallocations will occur in roughly the same order
|
||||
as allocations.
|
||||
|
||||
@note This version of the fifo free store uses less memory and doesn't require
|
||||
thread specific storage. However, it runs slower. The performance
|
||||
differences are negligible for desktop class applications.
|
||||
|
||||
@invariant allocate() and deallocate() are fully concurrent.
|
||||
|
||||
@invariant The ABA problem is handled automatically.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API FifoFreeStoreWithoutTLS : LeakChecked <FifoFreeStoreWithoutTLS>
|
||||
{
|
||||
public:
|
||||
explicit FifoFreeStoreWithoutTLS ();
|
||||
~FifoFreeStoreWithoutTLS ();
|
||||
|
||||
void* allocate (const size_t bytes);
|
||||
static void deallocate (void* const p);
|
||||
|
||||
private:
|
||||
typedef GlobalPagedFreeStore PagedFreeStoreType;
|
||||
|
||||
struct Header;
|
||||
|
||||
class Block;
|
||||
|
||||
inline Block* newBlock ();
|
||||
static inline void deleteBlock (Block* b);
|
||||
|
||||
private:
|
||||
Block* volatile m_active;
|
||||
PagedFreeStoreType::Ptr m_pages;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_GLOBALFIFOFREESTORE_H_INCLUDED
|
||||
#define BEAST_GLOBALFIFOFREESTORE_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
A @ref FifoFreeStoreType singleton.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
template <class Tag>
|
||||
class GlobalFifoFreeStore
|
||||
{
|
||||
public:
|
||||
inline void* allocate (size_t bytes)
|
||||
{
|
||||
return m_allocator.allocate (bytes);
|
||||
}
|
||||
|
||||
static inline void deallocate (void* const p)
|
||||
{
|
||||
FifoFreeStoreType::deallocate (p);
|
||||
}
|
||||
|
||||
typedef SharedPtr <SharedSingleton <GlobalFifoFreeStore> > Ptr;
|
||||
|
||||
static Ptr getInstance ()
|
||||
{
|
||||
return SharedSingleton <GlobalFifoFreeStore>::getInstance();
|
||||
}
|
||||
|
||||
public:
|
||||
GlobalFifoFreeStore ()
|
||||
{
|
||||
}
|
||||
|
||||
~GlobalFifoFreeStore ()
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
FifoFreeStoreType m_allocator;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,59 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_GLOBALPAGEDFREESTORE_H_INCLUDED
|
||||
#define BEAST_GLOBALPAGEDFREESTORE_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
A PagedFreeStore singleton.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API GlobalPagedFreeStore : public LeakChecked <GlobalPagedFreeStore>
|
||||
{
|
||||
public:
|
||||
GlobalPagedFreeStore ();
|
||||
~GlobalPagedFreeStore ();
|
||||
|
||||
public:
|
||||
inline size_t getPageBytes ()
|
||||
{
|
||||
return m_allocator.getPageBytes ();
|
||||
}
|
||||
|
||||
inline void* allocate ()
|
||||
{
|
||||
return m_allocator.allocate ();
|
||||
}
|
||||
|
||||
static inline void deallocate (void* const p)
|
||||
{
|
||||
PagedFreeStore::deallocate (p);
|
||||
}
|
||||
|
||||
typedef SharedPtr <SharedSingleton <GlobalPagedFreeStore> > Ptr;
|
||||
|
||||
static Ptr getInstance ();
|
||||
|
||||
private:
|
||||
PagedFreeStore m_allocator;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,227 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#define LOG_GC 0
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// This is the upper limit on the amount of physical memory an instance of the
|
||||
// allocator will allow. Going over this limit means that consumers cannot keep
|
||||
// up with producers, and application logic should be re-examined.
|
||||
//
|
||||
// TODO: ENFORCE THIS GLOBALLY? MEASURE IN KILOBYTES AND FORCE KILOBYTE PAGE SIZES
|
||||
#define HARD_LIMIT 1
|
||||
const size_t hardLimitMegaBytes = 256;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Implementation notes
|
||||
|
||||
- There are two pools, the 'hot' pool and the 'cold' pool.
|
||||
|
||||
- When a new page is needed we pop from the 'fresh' stack of the hot pool.
|
||||
|
||||
- When a page is deallocated it is pushed to the 'garbage' stack of the hot pool.
|
||||
|
||||
- Every so often, a garbage collection is performed on a separate thread.
|
||||
During collection, fresh and garbage are swapped in the cold pool.
|
||||
Then, the hot and cold pools are atomically swapped.
|
||||
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
struct PagedFreeStore::Page : Pages::Node, LeakChecked <Page>
|
||||
{
|
||||
explicit Page (PagedFreeStore* const allocator)
|
||||
: m_allocator (*allocator)
|
||||
{
|
||||
}
|
||||
|
||||
PagedFreeStore& getAllocator () const
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
private:
|
||||
PagedFreeStore& m_allocator;
|
||||
};
|
||||
|
||||
inline void* PagedFreeStore::fromPage (Page* const p)
|
||||
{
|
||||
return reinterpret_cast <char*> (p) +
|
||||
Memory::sizeAdjustedForAlignment (sizeof (Page));
|
||||
}
|
||||
|
||||
inline PagedFreeStore::Page* PagedFreeStore::toPage (void* const p)
|
||||
{
|
||||
return reinterpret_cast <Page*> (
|
||||
(reinterpret_cast <char*> (p) -
|
||||
Memory::sizeAdjustedForAlignment (sizeof (Page))));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
PagedFreeStore::PagedFreeStore (const size_t pageBytes)
|
||||
: m_timer (this)
|
||||
, m_pageBytes (pageBytes)
|
||||
, m_pageBytesAvailable (pageBytes - Memory::sizeAdjustedForAlignment (sizeof (Page)))
|
||||
, m_newPagesLeft (int ((hardLimitMegaBytes * 1024 * 1024) / m_pageBytes))
|
||||
#if LOG_GC
|
||||
, m_swaps (0)
|
||||
#endif
|
||||
{
|
||||
m_hot = m_pool1;
|
||||
m_cold = m_pool2;
|
||||
|
||||
m_timer.setExpiration (1.0);
|
||||
}
|
||||
|
||||
PagedFreeStore::~PagedFreeStore ()
|
||||
{
|
||||
m_timer.cancel ();
|
||||
|
||||
#if LOG_GC
|
||||
bassert (!m_used.isSignaled ());
|
||||
#endif
|
||||
|
||||
dispose (m_pool1);
|
||||
dispose (m_pool2);
|
||||
|
||||
#if LOG_GC
|
||||
bassert (!m_total.isSignaled ());
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void* PagedFreeStore::allocate ()
|
||||
{
|
||||
Page* page = m_hot->fresh->pop_front ();
|
||||
|
||||
if (!page)
|
||||
{
|
||||
#if HARD_LIMIT
|
||||
const bool exhausted = m_newPagesLeft.release ();
|
||||
|
||||
if (exhausted)
|
||||
fatal_error ("the limit of memory allocations was reached");
|
||||
|
||||
#endif
|
||||
|
||||
void* storage = ::malloc (m_pageBytes);
|
||||
|
||||
if (!storage)
|
||||
fatal_error ("a memory allocation failed");
|
||||
|
||||
page = new (storage) Page (this);
|
||||
|
||||
#if LOG_GC
|
||||
m_total.addref ();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if LOG_GC
|
||||
m_used.addref ();
|
||||
#endif
|
||||
|
||||
return fromPage (page);
|
||||
}
|
||||
|
||||
void PagedFreeStore::deallocate (void* const p)
|
||||
{
|
||||
Page* const page = toPage (p);
|
||||
PagedFreeStore& allocator = page->getAllocator ();
|
||||
|
||||
allocator.m_hot->garbage->push_front (page);
|
||||
|
||||
#if LOG_GC
|
||||
allocator.m_used.release ();
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
// Perform garbage collection.
|
||||
//
|
||||
void PagedFreeStore::onDeadlineTimer (DeadlineTimer&)
|
||||
{
|
||||
// Physically free one page.
|
||||
// This will reduce the working set over time after a spike.
|
||||
{
|
||||
Page* page = m_cold->garbage->pop_front ();
|
||||
|
||||
if (page)
|
||||
{
|
||||
page->~Page ();
|
||||
::free (page);
|
||||
m_newPagesLeft.addref ();
|
||||
#ifdef LOG_GC
|
||||
m_total.release ();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
std::swap (m_cold->fresh, m_cold->garbage);
|
||||
|
||||
// Swap atomically with respect to m_hot
|
||||
Pool* temp = m_hot;
|
||||
m_hot = m_cold; // atomic
|
||||
m_cold = temp;
|
||||
|
||||
#if LOG_GC
|
||||
String s;
|
||||
s << "swap " << String (++m_swaps);
|
||||
s << " (" << String (m_used.get ()) << "/"
|
||||
<< String (m_total.get ()) << " of "
|
||||
<< String (m_newPagesLeft.get ()) << ")";
|
||||
Logger::outputDebugString (s);
|
||||
#endif
|
||||
|
||||
m_timer.setExpiration (1.0);
|
||||
}
|
||||
|
||||
void PagedFreeStore::dispose (Pages& pages)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
Page* const page = pages.pop_front ();
|
||||
|
||||
if (page)
|
||||
{
|
||||
page->~Page ();
|
||||
::free (page);
|
||||
|
||||
#if LOG_GC
|
||||
m_total.release ();
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PagedFreeStore::dispose (Pool& pool)
|
||||
{
|
||||
dispose (pool.fresh_c);
|
||||
dispose (pool.garbage_c);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_PAGEDFREESTORE_H_INCLUDED
|
||||
#define BEAST_PAGEDFREESTORE_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
Lock-free memory allocator for fixed size pages.
|
||||
|
||||
The ABA problem (http://en.wikipedia.org/wiki/ABA_problem) is avoided by
|
||||
treating freed pages as garbage, and performing a collection every second.
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API PagedFreeStore : private DeadlineTimer::Listener
|
||||
{
|
||||
public:
|
||||
explicit PagedFreeStore (const size_t pageBytes);
|
||||
~PagedFreeStore ();
|
||||
|
||||
// The available bytes per page is a little bit less
|
||||
// than requested in the constructor, due to overhead.
|
||||
//
|
||||
inline size_t getPageBytes () const
|
||||
{
|
||||
return m_pageBytesAvailable;
|
||||
}
|
||||
|
||||
inline void* allocate (const size_t bytes)
|
||||
{
|
||||
if (bytes > m_pageBytes)
|
||||
fatal_error ("the size is too large");
|
||||
|
||||
return allocate ();
|
||||
}
|
||||
|
||||
void* allocate ();
|
||||
static void deallocate (void* const p);
|
||||
|
||||
private:
|
||||
void* newPage ();
|
||||
void onDeadlineTimer (DeadlineTimer&);
|
||||
|
||||
private:
|
||||
struct Page;
|
||||
typedef LockFreeStack <Page> Pages;
|
||||
|
||||
struct Pool
|
||||
{
|
||||
Pool()
|
||||
: fresh (&fresh_c.get())
|
||||
, garbage (&garbage_c.get())
|
||||
{
|
||||
}
|
||||
|
||||
Pages* fresh;
|
||||
Pages* garbage;
|
||||
CacheLine::Padded <Pages> fresh_c;
|
||||
CacheLine::Padded <Pages> garbage_c;
|
||||
};
|
||||
|
||||
static inline void* fromPage (Page* const p);
|
||||
static inline Page* toPage (void* const p);
|
||||
|
||||
void dispose (Pages& pages);
|
||||
void dispose (Pool& pool);
|
||||
|
||||
private:
|
||||
DeadlineTimer m_timer;
|
||||
const size_t m_pageBytes;
|
||||
const size_t m_pageBytesAvailable;
|
||||
CacheLine::Aligned <Pool> m_pool1; // pair of pools
|
||||
CacheLine::Aligned <Pool> m_pool2;
|
||||
Pool* volatile m_cold; // pool which is cooling down
|
||||
Pool* volatile m_hot; // pool we are currently using
|
||||
AtomicCounter m_newPagesLeft; // limit of system allocations
|
||||
|
||||
#if 1
|
||||
int m_swaps;
|
||||
AtomicCounter m_total;
|
||||
AtomicCounter m_used;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -20,18 +20,6 @@
|
||||
#ifndef BEAST_STATICOBJECT_H_INCLUDED
|
||||
#define BEAST_STATICOBJECT_H_INCLUDED
|
||||
|
||||
//
|
||||
// A full suite of thread-safe objects designed for static storage duration.
|
||||
//
|
||||
// Wraps an object with a thread-safe initialization preamble so that it can
|
||||
// properly exist with static storage duration.
|
||||
//
|
||||
// Implementation notes:
|
||||
//
|
||||
// This is accomplished by omitting the constructor and relying on the C++
|
||||
// specification that plain data types with static storage duration are filled
|
||||
// with zeroes before any other initialization code executes.
|
||||
//
|
||||
// Spec: N2914=09-0104
|
||||
//
|
||||
// [3.6.2] Initialization of non-local objects
|
||||
@@ -40,84 +28,8 @@
|
||||
// duration (3.7.2) shall be zero-initialized (8.5) before any
|
||||
// other initialization takes place.
|
||||
//
|
||||
// Requirements:
|
||||
//
|
||||
// Object must be constructible without parameters.
|
||||
// The StaticObject must be declared with static storage duration or
|
||||
// the behavior is undefined.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// Object* getInstance ()
|
||||
// {
|
||||
// static StaticObject <Object> instance;
|
||||
// return instance->getObject ();
|
||||
// }
|
||||
//
|
||||
|
||||
namespace Static
|
||||
{
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Holds an object with static storage duration.
|
||||
// The owner determines if and when the object is constructed and destroyed.
|
||||
// Caller is responsible for synchronization.
|
||||
//
|
||||
template <class ObjectType, class Tag>
|
||||
class Storage
|
||||
{
|
||||
public:
|
||||
static inline void construct ()
|
||||
{
|
||||
new (getObjectPtr ()) ObjectType;
|
||||
}
|
||||
|
||||
static inline void destroy ()
|
||||
{
|
||||
getObjectPtr ()->~ObjectType ();
|
||||
}
|
||||
|
||||
static inline ObjectType* getObjectPtr ()
|
||||
{
|
||||
return reinterpret_cast <ObjectType*> (s_storage);
|
||||
}
|
||||
|
||||
static inline ObjectType& getObject ()
|
||||
{
|
||||
return *getObjectPtr ();
|
||||
}
|
||||
|
||||
inline ObjectType* operator-> () const
|
||||
{
|
||||
return getObjectPtr ();
|
||||
}
|
||||
|
||||
inline ObjectType& operator* () const
|
||||
{
|
||||
return getObject ();
|
||||
}
|
||||
|
||||
inline operator ObjectType* () const
|
||||
{
|
||||
return getObjectPtr ();
|
||||
}
|
||||
|
||||
// TODO: Crashes on iOS if not accessed before usage
|
||||
static char s_storage [sizeof (ObjectType)];
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
template <class ObjectType, class Tag>
|
||||
char Storage <ObjectType, Tag>::s_storage [sizeof (ObjectType)];
|
||||
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace detail
|
||||
{
|
||||
namespace detail {
|
||||
|
||||
extern void staticObjectWait (std::size_t n);
|
||||
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
CallQueue::CallQueue (String name)
|
||||
: m_name (name)
|
||||
{
|
||||
}
|
||||
|
||||
CallQueue::~CallQueue ()
|
||||
{
|
||||
// Someone forget to close the queue.
|
||||
bassert (m_closed.isSignaled ());
|
||||
|
||||
// Can't destroy queue with unprocessed calls.
|
||||
bassert (m_queue.empty ());
|
||||
}
|
||||
|
||||
bool CallQueue::isAssociatedWithCurrentThread () const
|
||||
{
|
||||
return Thread::getCurrentThreadId () == m_id;
|
||||
}
|
||||
|
||||
// Adds a call to the queue of execution.
|
||||
void CallQueue::queuep (Work* c)
|
||||
{
|
||||
// If this goes off it means calls are being made after the
|
||||
// queue is closed, and probably there is no one around to
|
||||
// process it.
|
||||
bassert (!m_closed.isSignaled ());
|
||||
|
||||
if (m_queue.push_back (c))
|
||||
signal ();
|
||||
}
|
||||
|
||||
// Append the Work to the queue. If this call is made from the same
|
||||
// thread as the last thread that called synchronize(), then the call
|
||||
// will execute synchronously.
|
||||
//
|
||||
void CallQueue::callp (Work* c)
|
||||
{
|
||||
queuep (c);
|
||||
|
||||
// If we are called on the process thread and we are not
|
||||
// recursed into doSynchronize, then process the queue. This
|
||||
// makes calls from the process thread synchronous.
|
||||
//
|
||||
// NOTE: The value of isBeingSynchronized is invalid/volatile unless
|
||||
// this thread is the last process thread.
|
||||
//
|
||||
// NOTE: There is a small window of opportunity where we
|
||||
// might get an undesired synchronization if new thread
|
||||
// calls synchronize() concurrently.
|
||||
//
|
||||
if (isAssociatedWithCurrentThread () &&
|
||||
m_isBeingSynchronized.trySignal ())
|
||||
{
|
||||
doSynchronize ();
|
||||
|
||||
m_isBeingSynchronized.reset ();
|
||||
}
|
||||
}
|
||||
|
||||
bool CallQueue::synchronize ()
|
||||
{
|
||||
bool did_something;
|
||||
|
||||
// Detect recursion into doSynchronize(), and
|
||||
// break ties for concurrent calls atomically.
|
||||
//
|
||||
if (m_isBeingSynchronized.trySignal ())
|
||||
{
|
||||
// Remember this thread.
|
||||
m_id = Thread::getCurrentThreadId ();
|
||||
|
||||
did_something = doSynchronize ();
|
||||
|
||||
m_isBeingSynchronized.reset ();
|
||||
}
|
||||
else
|
||||
{
|
||||
did_something = false;
|
||||
}
|
||||
|
||||
return did_something;
|
||||
}
|
||||
|
||||
// Can still have pending calls, just can't put new ones in.
|
||||
void CallQueue::close ()
|
||||
{
|
||||
m_closed.signal ();
|
||||
|
||||
synchronize ();
|
||||
}
|
||||
|
||||
// Process everything in the queue. The list of pending calls is
|
||||
// acquired atomically. New calls may enter the queue while we are
|
||||
// processing.
|
||||
//
|
||||
// Returns true if any functors were called.
|
||||
//
|
||||
bool CallQueue::doSynchronize ()
|
||||
{
|
||||
bool did_something;
|
||||
|
||||
// Reset since we are emptying the queue. Since we loop
|
||||
// until the queue is empty, it is possible for us to exit
|
||||
// this function with an empty queue and signaled state.
|
||||
//
|
||||
reset ();
|
||||
|
||||
Work* call = m_queue.pop_front ();
|
||||
|
||||
if (call)
|
||||
{
|
||||
did_something = true;
|
||||
|
||||
// This method of processing one at a time has the desired
|
||||
// side effect of synchronizing nested calls to us from a functor.
|
||||
//
|
||||
for (;;)
|
||||
{
|
||||
call->operator () ();
|
||||
delete call;
|
||||
|
||||
call = m_queue.pop_front ();
|
||||
|
||||
if (call == 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
did_something = false;
|
||||
}
|
||||
|
||||
return did_something;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class CallQueueTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
callsPerThread = 50000
|
||||
};
|
||||
|
||||
struct TestQueue : CallQueue
|
||||
{
|
||||
explicit TestQueue (Thread& thread)
|
||||
: CallQueue (thread.getThreadName())
|
||||
, m_thread (thread)
|
||||
{
|
||||
}
|
||||
|
||||
void synchronize ()
|
||||
{
|
||||
CallQueue::synchronize();
|
||||
}
|
||||
|
||||
void signal ()
|
||||
{
|
||||
m_thread.notify ();
|
||||
}
|
||||
|
||||
void reset ()
|
||||
{
|
||||
}
|
||||
|
||||
void close ()
|
||||
{
|
||||
CallQueue::close();
|
||||
}
|
||||
|
||||
Thread& m_thread;
|
||||
};
|
||||
|
||||
struct TestThread : Thread
|
||||
{
|
||||
explicit TestThread (int id)
|
||||
: Thread ("#" + String::fromNumber (id))
|
||||
, m_queue (*this)
|
||||
{
|
||||
startThread ();
|
||||
}
|
||||
|
||||
void stop ()
|
||||
{
|
||||
m_queue.call (&TestThread::doStop, this);
|
||||
m_queue.close ();
|
||||
}
|
||||
|
||||
void doStop ()
|
||||
{
|
||||
signalThreadShouldExit ();
|
||||
notify ();
|
||||
}
|
||||
|
||||
void func1 ()
|
||||
{
|
||||
m_string = m_string + String::fromNumber (m_random.nextInt(10));
|
||||
if (m_string.length() > 100)
|
||||
m_string = String::empty;
|
||||
}
|
||||
|
||||
void func2 ()
|
||||
{
|
||||
m_string = m_string + String::fromNumber (m_random.nextInt());
|
||||
if (m_string.length() > 100)
|
||||
m_string = String::empty;
|
||||
}
|
||||
|
||||
void func3 ()
|
||||
{
|
||||
m_string = m_string + m_string;
|
||||
if (m_string.length() > 100)
|
||||
m_string = m_string.substring (m_random.nextInt (30));
|
||||
}
|
||||
|
||||
void run ()
|
||||
{
|
||||
while (! threadShouldExit ())
|
||||
{
|
||||
wait ();
|
||||
|
||||
m_queue.synchronize();
|
||||
}
|
||||
}
|
||||
|
||||
Random m_random;
|
||||
TestQueue m_queue;
|
||||
String m_string;
|
||||
};
|
||||
|
||||
void testThreads (std::size_t nThreads)
|
||||
{
|
||||
beginTestCase (String::fromNumber (nThreads) + " threads");
|
||||
|
||||
OwnedArray <TestThread> threads;
|
||||
threads.ensureStorageAllocated (nThreads);
|
||||
|
||||
for (std::size_t i = 0; i < nThreads; ++i)
|
||||
threads.add (new TestThread (i + 1));
|
||||
|
||||
Time const startTime (Time::getCurrentTime());
|
||||
|
||||
for (std::size_t i = 0; i < callsPerThread * nThreads; ++i)
|
||||
{
|
||||
int const n (random().nextInt (threads.size()));
|
||||
int const f (random().nextInt (3));
|
||||
switch (f)
|
||||
{
|
||||
default:
|
||||
bassertfalse;
|
||||
case 0: threads[n]->m_queue.call (&TestThread::func1, threads[n]); break;
|
||||
case 1: threads[n]->m_queue.call (&TestThread::func2, threads[n]); break;
|
||||
case 2: threads[n]->m_queue.call (&TestThread::func3, threads[n]); break;
|
||||
};
|
||||
}
|
||||
|
||||
for (int i = 0; i < threads.size(); ++i)
|
||||
threads[i]->stop ();
|
||||
|
||||
for (int i = 0; i < threads.size(); ++i)
|
||||
threads[i]->waitForThreadToExit();
|
||||
|
||||
double const secondsElapsed ((Time::getCurrentTime() - startTime).inSeconds ());
|
||||
|
||||
pass ();
|
||||
|
||||
std::size_t const totalCalls (callsPerThread * nThreads);
|
||||
double const callsPerSecond = (totalCalls / secondsElapsed);
|
||||
logMessage (String::fromNumber (callsPerSecond + 0.5, 0) + " calls/second (in " +
|
||||
String::fromNumber (secondsElapsed, 1) + " seconds)");
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
testThreads (8);
|
||||
testThreads (64);
|
||||
}
|
||||
|
||||
CallQueueTests () : UnitTest ("CallQueue", "beast", runManual)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static CallQueueTests callQueueTests;
|
||||
@@ -1,506 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_CALLQUEUE_H_INCLUDED
|
||||
#define BEAST_CALLQUEUE_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
A FIFO for calling functors asynchronously.
|
||||
|
||||
This object is an alternative to traditional locking techniques used to
|
||||
implement concurrent systems. Instead of acquiring a mutex to change shared
|
||||
data, a functor is queued for later execution (usually on another thread). The
|
||||
execution of the functor applies the transformation to the shared state that
|
||||
was formerly performed within a lock (i.e. CriticalSection).
|
||||
|
||||
For read operations on shared data, instead of acquiring a mutex and
|
||||
accessing the data directly, copies are made (one for each thread), and the
|
||||
thread accesses its copy without acquiring a lock. One thread owns the master
|
||||
copy of the shared state. Requests for changing shared state are made by other
|
||||
threads by posting functors to the master thread's CallQueue. The master
|
||||
thread notifies other threads of changes by posting functors to their
|
||||
respective associated CallQueue, using the Listeners interface.
|
||||
|
||||
The purpose of the functor is to encapsulate one mutation of shared state to
|
||||
guarantee progress towards a consensus of the concurrent data among
|
||||
participating threads. Functors should execute quickly, ideally in constant
|
||||
time. Dynamically allocated objects of class type passed as functor parameters
|
||||
should, in general, be reference counted. The ConcurrentObject class is ideal
|
||||
for meeting this requirement, and has the additional benefit that the workload
|
||||
of deletion is performed on a separate, provided thread. This queue is not a
|
||||
replacement for a thread pool or job queue type system.
|
||||
|
||||
A CallQueue is considered signaled when one or more functors are present.
|
||||
Functors are executed during a call to synchronize(). The operation of
|
||||
executing functors via the call to synchronize() is called synchronizing
|
||||
the queue. It can more generally be thought of as synchronizing multiple
|
||||
copies of shared data between threads.
|
||||
|
||||
Although there is some extra work required to set up and maintain this
|
||||
system, the benefits are significant. Since shared data is only synchronized
|
||||
at well defined times, the programmer can reason and make strong statements
|
||||
about the correctness of the concurrent system. For example, if an
|
||||
AudioIODeviceCallback synchronizes the CallQueue only at the beginning of its
|
||||
execution, it is guaranteed that shared data will remain the same throughout
|
||||
the remainder of the function.
|
||||
|
||||
Because shared data is accessed for reading without a lock, upper bounds
|
||||
on the run time performance can easily be calculated and assured. Compare
|
||||
this with the use of a mutex - the run time performance experiences a
|
||||
combinatorial explosion of possibilities depending on the complex interaction
|
||||
of multiple threads.
|
||||
|
||||
Since a CallQueue is almost always used to invoke parameterized member
|
||||
functions of objects, the call() function comes in a variety of convenient
|
||||
forms to make usage easy:
|
||||
|
||||
@code
|
||||
|
||||
void func1 (int);
|
||||
|
||||
struct Object
|
||||
{
|
||||
void func2 (void);
|
||||
void func3 (String name);
|
||||
|
||||
static void func4 ();
|
||||
};
|
||||
|
||||
CallQueue fifo ("Example");
|
||||
|
||||
void example ()
|
||||
{
|
||||
fifo.call (func1, 42); // same as: func1 (42)
|
||||
|
||||
Object* object = new Object;
|
||||
|
||||
fifo.call (&Object::func2, object); // same as: object->func2 ()
|
||||
|
||||
fifo.call (&Object::func3, // same as: object->funcf ("Label")
|
||||
object,
|
||||
"Label");
|
||||
|
||||
fifo.call (&Object::func4); // even static members can be called.
|
||||
|
||||
fifo.callf (functional::bind (&Object::func2, // same as: object->func2 ()
|
||||
object));
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
@invariant Functors can be added from any thread at any time, to any queue
|
||||
which is not closed.
|
||||
|
||||
@invariant When synchronize() is called, functors are called and deleted.
|
||||
|
||||
@invariant The thread from which synchronize() is called is considered the
|
||||
thread associated with the CallQueue.
|
||||
|
||||
@invariant Functors queued by the same thread always execute in the same
|
||||
order they were queued.
|
||||
|
||||
@invariant Functors are guaranteed to execute. It is an error if the
|
||||
CallQueue is deleted while there are functors in it.
|
||||
|
||||
Normally, you will not use CallQueue directly, but one of its subclasses
|
||||
instead. The CallQueue is one of a handful of objects that work together to
|
||||
implement this system of concurrent data access.
|
||||
|
||||
For performance considerations, this implementation is wait-free for
|
||||
producers and mostly wait-free for consumers. It also uses a lock-free
|
||||
and wait-free (in the fast path) custom memory allocator.
|
||||
|
||||
@see GuiCallQueue, ManualCallQueue, MessageThread, ThreadWithCallQueue
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API CallQueue
|
||||
{
|
||||
public:
|
||||
//============================================================================
|
||||
|
||||
/** Type of allocator to use.
|
||||
|
||||
@internal
|
||||
*/
|
||||
typedef FifoFreeStoreType AllocatorType;
|
||||
|
||||
/** Abstract nullary functor in a @ref CallQueue.
|
||||
|
||||
Custom implementations may derive from this object for efficiency instead
|
||||
of using the automatic binding functions.
|
||||
*/
|
||||
class Work : public LockFreeQueue <Work>::Node,
|
||||
public AllocatedBy <AllocatorType>
|
||||
{
|
||||
public:
|
||||
virtual ~Work () { }
|
||||
|
||||
/** Calls the functor.
|
||||
|
||||
This executes during the queue's call to synchronize().
|
||||
*/
|
||||
virtual void operator () () = 0;
|
||||
};
|
||||
|
||||
//============================================================================
|
||||
|
||||
/** Create the CallQueue.
|
||||
|
||||
The queue starts out open and empty.
|
||||
|
||||
@param name A string to identify the queue during debugging.
|
||||
*/
|
||||
explicit CallQueue (String name);
|
||||
|
||||
/** Destroy the CallQueue.
|
||||
|
||||
@invariant Destroying a queue that contains functors results in undefined
|
||||
behavior.
|
||||
|
||||
@note It is customary to call close() on the CallQueue early in the
|
||||
shutdown process to catch functors going into the queue late.
|
||||
*/
|
||||
virtual ~CallQueue ();
|
||||
|
||||
//============================================================================
|
||||
|
||||
/** Add a functor and possibly synchronize.
|
||||
|
||||
Use this when you want to perform the bind yourself.
|
||||
|
||||
@param f The functor to add, typically the return value of a call
|
||||
to bind().
|
||||
|
||||
@see call
|
||||
*/
|
||||
template <class Functor>
|
||||
void callf (Functor f)
|
||||
{
|
||||
callp (new (m_allocator) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
/** Add a function call and possibly synchronize.
|
||||
|
||||
Parameters are evaluated immediately and added to the queue as a packaged
|
||||
functor. If the current thread of execution is the same as the thread
|
||||
associated with the CallQueue, synchronize() is called automatically. This
|
||||
behavior can be avoided by using queue() instead.
|
||||
|
||||
@param f The function to call followed by up to eight parameters,
|
||||
evaluated immediately. The parameter list must match the function
|
||||
signature. For class member functions, the first argument must be a
|
||||
pointer to the class object.
|
||||
|
||||
@see queue
|
||||
|
||||
@todo Provide an example of when synchronize() is needed in call().
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Fn>
|
||||
void call (Fn f)
|
||||
{ callf (functional::bind (f)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Fn, class T1>
|
||||
void call (Fn f, T1 t1)
|
||||
{ callf (functional::bind (f, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Fn, class T1, class T2>
|
||||
void call (Fn f, T1 t1, T2 t2)
|
||||
{ callf (functional::bind (f, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Fn, class T1, class T2, class T3>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3)
|
||||
{ callf (functional::bind (f, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Fn, class T1, class T2, class T3, class T4>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ callf (functional::bind (f, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ callf (functional::bind (f, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ callf (functional::bind (f, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ callf (functional::bind (f, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void call (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ callf (functional::bind (f, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/** Add a functor without synchronizing.
|
||||
|
||||
Use this when you want to perform the bind yourself.
|
||||
|
||||
@param f The functor to add, typically the return value of a call
|
||||
to bind().
|
||||
|
||||
@see queue
|
||||
*/
|
||||
template <class Functor>
|
||||
void queuef (Functor f)
|
||||
{
|
||||
queuep (new (m_allocator) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
/** Add a function call without synchronizing.
|
||||
|
||||
Parameters are evaluated immediately, then the resulting functor is added
|
||||
to the queue. This is used to postpone the call to synchronize() when
|
||||
there would be adverse side effects to executing the function immediately.
|
||||
In this example, we use queue() instead of call() to avoid a deadlock:
|
||||
|
||||
@code
|
||||
|
||||
struct SharedState; // contains data shared between threads
|
||||
|
||||
SharedData <SharedState> sharedState;
|
||||
|
||||
void stateChanged ()
|
||||
{
|
||||
SharedData <SharedState>::ConstAccess state (sharedState);
|
||||
|
||||
// (read state)
|
||||
}
|
||||
|
||||
CallQueue fifo;
|
||||
|
||||
void changeState ()
|
||||
{
|
||||
SharedData <State>::Access state (sharedState);
|
||||
|
||||
// (read and write state)
|
||||
|
||||
fifo.call (&stateChanged); // BUG: DEADLOCK because of the implicit synchronize().
|
||||
|
||||
fifo.queue (&stateChanged); // Okay, synchronize() will be called later,
|
||||
// after the write lock is released.
|
||||
}
|
||||
|
||||
@endcode
|
||||
|
||||
@param f The function to call followed by up to eight parameters,
|
||||
evaluated immediately. The parameter list must match the
|
||||
function signature. For non-static class member functions,
|
||||
the first argument must be a pointer an instance of the class.
|
||||
|
||||
@see call
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Fn>
|
||||
void queue (Fn f)
|
||||
{ queuef (functional::bind (f)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Fn, class T1>
|
||||
void queue (Fn f, T1 t1)
|
||||
{ queuef (functional::bind (f, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Fn, class T1, class T2>
|
||||
void queue (Fn f, T1 t1, T2 t2)
|
||||
{ queuef (functional::bind (f, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Fn, class T1, class T2, class T3>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3)
|
||||
{ queuef (functional::bind (f, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Fn, class T1, class T2, class T3, class T4>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ queuef (functional::bind (f, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ queuef (functional::bind (f, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ queuef (functional::bind (f, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ queuef (functional::bind (f, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Fn, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void queue (Fn f, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ queuef (functional::bind (f, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
protected:
|
||||
//============================================================================
|
||||
/** Synchronize the queue.
|
||||
|
||||
A synchronize operation calls all functors in the queue. If a functor
|
||||
causes additional functors to be added, they are eventually executed
|
||||
before synchronize() returns. Derived class call this when the queue is
|
||||
signaled, and optionally at any other time. Calling this function from
|
||||
more than one thread simultaneously is undefined.
|
||||
|
||||
@return true if any functors were executed.
|
||||
*/
|
||||
bool synchronize ();
|
||||
|
||||
/** Close the queue.
|
||||
|
||||
Functors may not be added after this routine is called. This is used for
|
||||
diagnostics, to track down spurious calls during application shutdown
|
||||
or exit. Derived classes may call this if the appropriate time is known.
|
||||
|
||||
The queue is synchronized after it is closed.
|
||||
*/
|
||||
void close ();
|
||||
|
||||
/** Called when the queue becomes signaled.
|
||||
|
||||
A queue is signaled on the transition from empty to non-empty. Derived
|
||||
classes implement this function to perform a notification so that
|
||||
synchronize() will be called. For example, by triggering a WaitableEvent.
|
||||
|
||||
@note Due to the implementation the queue can remain signaled for one
|
||||
extra cycle. This does not happen under load and is not an issue
|
||||
in practice.
|
||||
*/
|
||||
virtual void signal () = 0;
|
||||
|
||||
/** Called when the queue is reset.
|
||||
|
||||
A queue is reset when it was previously signaled and then becomes empty
|
||||
as a result of a call to synchronize.
|
||||
*/
|
||||
virtual void reset () = 0;
|
||||
|
||||
public:
|
||||
//============================================================================
|
||||
|
||||
/** Add a raw call.
|
||||
|
||||
@internal
|
||||
|
||||
Custom implementations use this to control the allocation.
|
||||
|
||||
@param c The call to add. The memory must come from the allocator.
|
||||
*/
|
||||
void callp (Work* c);
|
||||
|
||||
/** Queue a raw call.
|
||||
|
||||
Custom implementations use this to control the allocation.
|
||||
|
||||
@param c The call to add. The memory must come from the allocator.
|
||||
*/
|
||||
void queuep (Work* c);
|
||||
|
||||
/** Retrieve the allocator.
|
||||
|
||||
@return The allocator to use when allocating a raw Work object.
|
||||
*/
|
||||
inline AllocatorType& getAllocator ()
|
||||
{
|
||||
return m_allocator;
|
||||
}
|
||||
|
||||
/** See if the caller is on the association thread.
|
||||
|
||||
@return `true` if the calling thread of execution is associated with the
|
||||
queue.
|
||||
*/
|
||||
bool isAssociatedWithCurrentThread () const;
|
||||
|
||||
/** See if the queue is being synchronized.
|
||||
|
||||
This is used for diagnostics.
|
||||
|
||||
@note This must be called from the associated thread or else the return
|
||||
value is undefined.
|
||||
|
||||
@return `true` if the call stack contains synchronize() for this queue.
|
||||
*/
|
||||
bool isBeingSynchronized () const
|
||||
{
|
||||
return m_isBeingSynchronized.isSignaled ();
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Functor>
|
||||
class CallType : public Work
|
||||
{
|
||||
public:
|
||||
explicit CallType (Functor f) : m_f (f) { }
|
||||
void operator () ()
|
||||
{
|
||||
m_f ();
|
||||
}
|
||||
|
||||
private:
|
||||
Functor m_f;
|
||||
};
|
||||
|
||||
bool doSynchronize ();
|
||||
|
||||
private:
|
||||
String const m_name;
|
||||
Thread::ThreadID m_id;
|
||||
LockFreeQueue <Work> m_queue;
|
||||
AtomicFlag m_closed;
|
||||
AtomicFlag m_isBeingSynchronized;
|
||||
AllocatorType m_allocator;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -30,8 +30,9 @@ public:
|
||||
The listener is called on an auxiliary thread. It is suggested
|
||||
not to perform any time consuming operations during the call.
|
||||
*/
|
||||
// VFALCO TODO Allow construction with a specific ThreadWithCallQueue&
|
||||
// on which to notify the listener.
|
||||
// VFALCO TODO Perhaps allow construction using a ServiceQueue to use
|
||||
// for notifications.
|
||||
//
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
InterruptibleThread::ThreadHelper::ThreadHelper (String name,
|
||||
InterruptibleThread* owner)
|
||||
: Thread (name)
|
||||
, m_owner (owner)
|
||||
{
|
||||
}
|
||||
|
||||
InterruptibleThread* InterruptibleThread::ThreadHelper::getOwner () const
|
||||
{
|
||||
return m_owner;
|
||||
}
|
||||
|
||||
void InterruptibleThread::ThreadHelper::run ()
|
||||
{
|
||||
m_owner->run ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
InterruptibleThread::InterruptibleThread (String name)
|
||||
: m_thread (name, this)
|
||||
, m_entryPoint (nullptr)
|
||||
, m_state (stateRun)
|
||||
{
|
||||
}
|
||||
|
||||
InterruptibleThread::~InterruptibleThread ()
|
||||
{
|
||||
m_runEvent.signal ();
|
||||
|
||||
join ();
|
||||
}
|
||||
|
||||
void InterruptibleThread::start (EntryPoint* const entryPoint)
|
||||
{
|
||||
m_entryPoint = entryPoint;
|
||||
|
||||
m_thread.startThread ();
|
||||
|
||||
// Prevent data race with member variables
|
||||
//
|
||||
m_runEvent.signal ();
|
||||
}
|
||||
|
||||
void InterruptibleThread::join ()
|
||||
{
|
||||
m_thread.signalThreadShouldExit();
|
||||
m_thread.notify();
|
||||
interrupt();
|
||||
m_thread.stopThread (-1);
|
||||
}
|
||||
|
||||
// Block until there is an interruption.
|
||||
// This counts as an interruption point.
|
||||
//
|
||||
void InterruptibleThread::wait ()
|
||||
{
|
||||
// Can only be called from the thread of execution.
|
||||
bassert (isTheCurrentThread ());
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Impossible for us to already be in the wait state.
|
||||
bassert (m_state != stateWait);
|
||||
|
||||
// See if we are interrupted.
|
||||
if (m_state.tryChangeState (stateInterrupt, stateRun))
|
||||
{
|
||||
// We were interrupted, so the wait is satisfied.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try to get into the wait state.
|
||||
if (m_state.tryChangeState (stateRun, stateWait))
|
||||
{
|
||||
bassert (m_state == stateWait);
|
||||
|
||||
// Got into the wait state so block until interrupt.
|
||||
m_thread.wait ();
|
||||
|
||||
// Event is signalled means we were
|
||||
// interrupted, so the wait is satisfied.
|
||||
bassert (m_state != stateWait || m_thread.threadShouldExit ());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InterruptibleThread::interrupt ()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
int const state (m_state);
|
||||
|
||||
if (state == stateInterrupt ||
|
||||
m_state.tryChangeState (stateRun, stateInterrupt))
|
||||
{
|
||||
// We got into the interrupt state, the thead
|
||||
// will see this at the next interruption point.
|
||||
//
|
||||
// Thread will see this at next interruption point.
|
||||
//
|
||||
break;
|
||||
}
|
||||
else if (m_state.tryChangeState (stateWait, stateRun))
|
||||
{
|
||||
m_thread.notify ();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the thead function should stop
|
||||
// its activities as soon as possible and return.
|
||||
//
|
||||
bool InterruptibleThread::interruptionPoint ()
|
||||
{
|
||||
// Can only be called from the thread of execution.
|
||||
bassert (isTheCurrentThread ());
|
||||
|
||||
// Impossible for this to be called in the wait state.
|
||||
bassert (m_state != stateWait);
|
||||
|
||||
bool const interrupted (m_state.tryChangeState (stateInterrupt, stateRun));
|
||||
|
||||
return interrupted;
|
||||
}
|
||||
|
||||
InterruptibleThread::id InterruptibleThread::getId () const
|
||||
{
|
||||
return m_threadId;
|
||||
}
|
||||
|
||||
bool InterruptibleThread::isTheCurrentThread () const
|
||||
{
|
||||
return m_thread.getCurrentThreadId () == m_threadId;
|
||||
}
|
||||
|
||||
void InterruptibleThread::setPriority (int priority)
|
||||
{
|
||||
m_thread.setPriority (priority);
|
||||
}
|
||||
|
||||
InterruptibleThread* InterruptibleThread::getCurrentThread ()
|
||||
{
|
||||
InterruptibleThread* result = nullptr;
|
||||
|
||||
Thread* const thread = Thread::getCurrentThread ();
|
||||
|
||||
if (thread != nullptr)
|
||||
{
|
||||
ThreadHelper* const helper = dynamic_cast <ThreadHelper*> (thread);
|
||||
|
||||
bassert (helper != nullptr);
|
||||
|
||||
result = helper->getOwner ();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void InterruptibleThread::run ()
|
||||
{
|
||||
m_threadId = m_thread.getThreadId ();
|
||||
|
||||
m_runEvent.wait ();
|
||||
|
||||
m_entryPoint->threadRun ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool CurrentInterruptibleThread::interruptionPoint ()
|
||||
{
|
||||
bool interrupted = false;
|
||||
|
||||
InterruptibleThread* const interruptibleThread (InterruptibleThread::getCurrentThread ());
|
||||
|
||||
bassert (interruptibleThread != nullptr);
|
||||
|
||||
interrupted = interruptibleThread->interruptionPoint ();
|
||||
|
||||
return interrupted;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class InterruptibleThreadTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
callsPerThread = 100000
|
||||
};
|
||||
|
||||
struct TestThread : InterruptibleThread::EntryPoint
|
||||
{
|
||||
explicit TestThread (int id)
|
||||
: m_thread ("#" + String::fromNumber (id))
|
||||
{
|
||||
m_thread.start (this);
|
||||
}
|
||||
|
||||
void threadRun ()
|
||||
{
|
||||
while (! m_thread.peekThread().threadShouldExit())
|
||||
{
|
||||
String s;
|
||||
|
||||
while (!m_thread.interruptionPoint ())
|
||||
{
|
||||
s = s + String::fromNumber (m_random.nextInt ());
|
||||
|
||||
if (s.length () > 100)
|
||||
s = String::empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Random m_random;
|
||||
InterruptibleThread m_thread;
|
||||
};
|
||||
|
||||
void testThreads (std::size_t nThreads)
|
||||
{
|
||||
beginTestCase (String::fromNumber (nThreads) + " threads");
|
||||
|
||||
OwnedArray <TestThread> threads;
|
||||
threads.ensureStorageAllocated (nThreads);
|
||||
|
||||
for (std::size_t i = 0; i < nThreads; ++i)
|
||||
threads.add (new TestThread (i + 1));
|
||||
|
||||
for (std::size_t i = 0; i < callsPerThread * nThreads; ++i)
|
||||
{
|
||||
int const n (random().nextInt (threads.size()));
|
||||
threads[n]->m_thread.interrupt();
|
||||
}
|
||||
|
||||
pass ();
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
testThreads (8);
|
||||
testThreads (64);
|
||||
}
|
||||
|
||||
InterruptibleThreadTests () : UnitTest ("InterruptibleThread", "beast")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static InterruptibleThreadTests interruptibleThreadTests;
|
||||
@@ -1,180 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_INTERRUPTIBLETHREAD_H_INCLUDED
|
||||
#define BEAST_INTERRUPTIBLETHREAD_H_INCLUDED
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A thread with soft interruption support.
|
||||
|
||||
The thread must periodically call interruptionPoint(), which returns `true`
|
||||
the first time an interruption has occurred since the last call to
|
||||
interruptionPoint().
|
||||
|
||||
To create a thread, derive your class from InterruptibleThread::EntryPoint
|
||||
and implement the threadRun() function. Then, call run() with your object.
|
||||
|
||||
@ingroup beast_core
|
||||
*/
|
||||
class BEAST_API InterruptibleThread
|
||||
{
|
||||
public:
|
||||
/** InterruptibleThread entry point.
|
||||
*/
|
||||
class EntryPoint
|
||||
{
|
||||
public:
|
||||
virtual ~EntryPoint () { }
|
||||
|
||||
virtual void threadRun () = 0;
|
||||
};
|
||||
|
||||
public:
|
||||
typedef Thread::ThreadID id;
|
||||
|
||||
/** Construct an interruptible thread.
|
||||
|
||||
The name is used for debugger diagnostics.
|
||||
|
||||
@param name The name of the thread.
|
||||
*/
|
||||
explicit InterruptibleThread (String name);
|
||||
|
||||
/** Destroy the interruptible thread.
|
||||
|
||||
This will signal an interrupt and wait until the thread exits.
|
||||
*/
|
||||
~InterruptibleThread ();
|
||||
|
||||
/** Start the thread.
|
||||
*/
|
||||
void start (EntryPoint* const entryPoint);
|
||||
|
||||
/** Wait for the thread to exit.
|
||||
*/
|
||||
void join ();
|
||||
|
||||
/** Wait for interrupt.
|
||||
This call blocks until the thread is interrupted.
|
||||
May only be called by the thread of execution.
|
||||
*/
|
||||
void wait ();
|
||||
|
||||
/** Interrupt the thread of execution.
|
||||
|
||||
This can be called from any thread.
|
||||
*/
|
||||
void interrupt ();
|
||||
|
||||
/** Determine if an interruption is requested.
|
||||
|
||||
After the function returns `true`, the interrupt status is cleared.
|
||||
Subsequent calls will return `false` until another interrupt is requested.
|
||||
|
||||
May only be called by the thread of execution.
|
||||
|
||||
@see CurrentInterruptibleThread::interruptionPoint
|
||||
|
||||
@return `true` if an interrupt was requested.
|
||||
*/
|
||||
bool interruptionPoint ();
|
||||
|
||||
/** Get the ID of the associated thread.
|
||||
|
||||
@return The ID of the thread.
|
||||
*/
|
||||
id getId () const;
|
||||
|
||||
/** Determine if this is the thread of execution.
|
||||
|
||||
@note The return value is undefined if the thread is not running.
|
||||
|
||||
@return `true` if the caller is this thread of execution.
|
||||
*/
|
||||
bool isTheCurrentThread () const;
|
||||
|
||||
/** Adjust the thread priority.
|
||||
|
||||
@note This only affects some platforms.
|
||||
|
||||
@param priority A number from 0..10
|
||||
*/
|
||||
void setPriority (int priority);
|
||||
|
||||
/** Get the InterruptibleThread for the thread of execution.
|
||||
|
||||
This will return `nullptr` when called from the message thread, or from
|
||||
a thread of execution that is not an InterruptibleThread.
|
||||
*/
|
||||
static InterruptibleThread* getCurrentThread ();
|
||||
|
||||
// private
|
||||
Thread& peekThread ()
|
||||
{
|
||||
return m_thread;
|
||||
}
|
||||
|
||||
private:
|
||||
class ThreadHelper : public Thread
|
||||
{
|
||||
public:
|
||||
ThreadHelper (String name, InterruptibleThread* owner);
|
||||
|
||||
InterruptibleThread* getOwner () const;
|
||||
|
||||
void run ();
|
||||
|
||||
private:
|
||||
InterruptibleThread* const m_owner;
|
||||
};
|
||||
|
||||
void run ();
|
||||
|
||||
ThreadHelper m_thread;
|
||||
EntryPoint* m_entryPoint;
|
||||
WaitableEvent m_runEvent;
|
||||
id m_threadId;
|
||||
|
||||
enum
|
||||
{
|
||||
stateRun,
|
||||
stateInterrupt,
|
||||
stateWait
|
||||
};
|
||||
|
||||
AtomicState m_state;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** Global operations on the current InterruptibleThread.
|
||||
|
||||
Calling members of the class from a thread of execution which is not an
|
||||
InterruptibleThread results in undefined behavior.
|
||||
*/
|
||||
class CurrentInterruptibleThread
|
||||
{
|
||||
public:
|
||||
/** Call the current thread's interrupt point function.
|
||||
*/
|
||||
static bool interruptionPoint ();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,758 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
// CallQueue item to process a Call for a particular listener.
|
||||
// This is used to avoid bind overhead.
|
||||
//
|
||||
class ListenersBase::CallWork : public CallQueue::Work
|
||||
{
|
||||
public:
|
||||
inline CallWork (ListenersBase::Call* const c, void* const listener)
|
||||
: m_call (c), m_listener (listener)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () ()
|
||||
{
|
||||
m_call->operator () (m_listener);
|
||||
}
|
||||
|
||||
private:
|
||||
ListenersBase::Call::Ptr m_call;
|
||||
void* const m_listener;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// CallQueue item to process a Call for a group.
|
||||
// This is used to avoid bind overhead.
|
||||
//
|
||||
class ListenersBase::GroupWork : public CallQueue::Work
|
||||
{
|
||||
public:
|
||||
inline GroupWork (Group* group,
|
||||
ListenersBase::Call* c,
|
||||
const timestamp_t timestamp)
|
||||
: m_group (group)
|
||||
, m_call (c)
|
||||
, m_timestamp (timestamp)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () ()
|
||||
{
|
||||
m_group->do_call (m_call, m_timestamp);
|
||||
}
|
||||
|
||||
private:
|
||||
Group::Ptr m_group;
|
||||
ListenersBase::Call::Ptr m_call;
|
||||
const timestamp_t m_timestamp;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// CallQueue item to process a call for a particular listener.
|
||||
// This is used to avoid bind overhead.
|
||||
//
|
||||
class ListenersBase::GroupWork1 : public CallQueue::Work
|
||||
{
|
||||
public:
|
||||
inline GroupWork1 (Group* group,
|
||||
ListenersBase::Call* c,
|
||||
const timestamp_t timestamp,
|
||||
void* const listener)
|
||||
: m_group (group)
|
||||
, m_call (c)
|
||||
, m_timestamp (timestamp)
|
||||
, m_listener (listener)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () ()
|
||||
{
|
||||
m_group->do_call1 (m_call, m_timestamp, m_listener);
|
||||
}
|
||||
|
||||
private:
|
||||
Group::Ptr m_group;
|
||||
ListenersBase::Call::Ptr m_call;
|
||||
const timestamp_t m_timestamp;
|
||||
void* const m_listener;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// A Proxy maintains a list of Entry.
|
||||
// Each Entry holds a group and the current Call (which can be updated).
|
||||
//
|
||||
struct ListenersBase::Proxy::Entry : Entries::Node,
|
||||
SharedObject,
|
||||
AllocatedBy <AllocatorType>
|
||||
{
|
||||
typedef SharedPtr <Entry> Ptr;
|
||||
|
||||
explicit Entry (Group* g)
|
||||
: group (g)
|
||||
{
|
||||
}
|
||||
|
||||
~Entry ()
|
||||
{
|
||||
bassert (call.get () == 0);
|
||||
}
|
||||
|
||||
Group::Ptr group;
|
||||
AtomicPointer <Call> call;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// A Group maintains a list of Entry.
|
||||
//
|
||||
struct ListenersBase::Group::Entry : List <Entry>::Node,
|
||||
AllocatedBy <AllocatorType>
|
||||
{
|
||||
Entry (void* const l, const timestamp_t t)
|
||||
: listener (l)
|
||||
, timestamp (t)
|
||||
{
|
||||
}
|
||||
|
||||
void* const listener;
|
||||
const timestamp_t timestamp;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Group
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// - A list of listeners associated with the same CallQueue.
|
||||
//
|
||||
// - The list is only iterated on the CallQueue's thread.
|
||||
//
|
||||
// - It is safe to add or remove listeners from the group
|
||||
// at any time.
|
||||
//
|
||||
|
||||
ListenersBase::Group::Group (CallQueue& callQueue)
|
||||
: m_fifo (callQueue)
|
||||
, m_listener (0)
|
||||
{
|
||||
}
|
||||
|
||||
ListenersBase::Group::~Group ()
|
||||
{
|
||||
// If this goes off it means a Listener forgot to remove itself.
|
||||
bassert (m_list.empty ());
|
||||
|
||||
// shouldn't be deleting group during a call
|
||||
bassert (m_listener == 0);
|
||||
}
|
||||
|
||||
// Add the listener with the given timestamp.
|
||||
// The listener will only get calls with higher timestamps.
|
||||
// The caller must prevent duplicates.
|
||||
//
|
||||
void ListenersBase::Group::add (void* listener,
|
||||
const timestamp_t timestamp,
|
||||
AllocatorType& allocator)
|
||||
{
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_mutex);
|
||||
|
||||
bassert (!contains (listener));
|
||||
|
||||
// Should never be able to get here while in call()
|
||||
bassert (m_listener == 0);
|
||||
|
||||
// Add the listener and remember the time stamp so we don't
|
||||
// send it calls that were queued earlier than the add().
|
||||
m_list.push_back (*new (allocator) Entry (listener, timestamp));
|
||||
}
|
||||
|
||||
// Removes the listener from the group if it exists.
|
||||
// Returns true if the listener was removed.
|
||||
//
|
||||
bool ListenersBase::Group::remove (void* listener)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_mutex);
|
||||
|
||||
// Should never be able to get here while in call()
|
||||
bassert (m_listener == 0);
|
||||
|
||||
for (List <Entry>::iterator iter = m_list.begin (); iter != m_list.end (); ++iter)
|
||||
{
|
||||
Entry* entry = & (*iter);
|
||||
|
||||
if (entry->listener == listener)
|
||||
{
|
||||
m_list.erase (m_list.iterator_to (*entry));
|
||||
delete entry;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// Used for assertions.
|
||||
// The caller must synchronize.
|
||||
//
|
||||
bool ListenersBase::Group::contains (void* const listener) /*const*/
|
||||
{
|
||||
for (List <Entry>::iterator iter = m_list.begin (); iter != m_list.end (); iter++)
|
||||
if (iter->listener == listener)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ListenersBase::Group::call (Call* const c, const timestamp_t timestamp)
|
||||
{
|
||||
bassert (!empty ());
|
||||
m_fifo.callp (new (m_fifo.getAllocator ()) GroupWork (this, c, timestamp));
|
||||
}
|
||||
|
||||
void ListenersBase::Group::queue (Call* const c, const timestamp_t timestamp)
|
||||
{
|
||||
bassert (!empty ());
|
||||
m_fifo.queuep (new (m_fifo.getAllocator ()) GroupWork (this, c, timestamp));
|
||||
}
|
||||
|
||||
void ListenersBase::Group::call1 (Call* const c,
|
||||
const timestamp_t timestamp,
|
||||
void* const listener)
|
||||
{
|
||||
m_fifo.callp (new (m_fifo.getAllocator ()) GroupWork1 (
|
||||
this, c, timestamp, listener));
|
||||
}
|
||||
|
||||
void ListenersBase::Group::queue1 (Call* const c,
|
||||
const timestamp_t timestamp,
|
||||
void* const listener)
|
||||
{
|
||||
m_fifo.queuep (new (m_fifo.getAllocator ()) GroupWork1 (
|
||||
this, c, timestamp, listener));
|
||||
}
|
||||
|
||||
// Queues a reference to the Call on the thread queue of each listener
|
||||
// that is currently in our list. The thread queue must be in the
|
||||
// stack's call chain, either directly from CallQueue::synchronize(),
|
||||
// or from Proxy::do_call() called from CallQueue::synchronize().
|
||||
//
|
||||
void ListenersBase::Group::do_call (Call* const c, const timestamp_t timestamp)
|
||||
{
|
||||
if (!empty ())
|
||||
{
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_mutex);
|
||||
|
||||
// Recursion not allowed.
|
||||
bassert (m_listener == 0);
|
||||
|
||||
// The body of the loop MUST NOT cause listeners to get called.
|
||||
// Therefore, we don't have to worry about listeners removing
|
||||
// themselves while iterating the list.
|
||||
//
|
||||
for (List <Entry>::iterator iter = m_list.begin (); iter != m_list.end ();)
|
||||
{
|
||||
Entry* entry = & (*iter++);
|
||||
|
||||
// Since it is possible for a listener to be added after a
|
||||
// Call gets queued but before it executes, this prevents listeners
|
||||
// from seeing Calls created before they were added.
|
||||
//
|
||||
if (timestamp > entry->timestamp)
|
||||
{
|
||||
m_listener = entry->listener;
|
||||
|
||||
// The thread queue's synchronize() function MUST be in our call
|
||||
// stack to guarantee that these calls will not execute immediately.
|
||||
// They will be handled by the tail recusion unrolling in the
|
||||
// thread queue.
|
||||
bassert (m_fifo.isBeingSynchronized ());
|
||||
|
||||
m_fifo.callp (new (m_fifo.getAllocator ()) CallWork (c, m_listener));
|
||||
|
||||
m_listener = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// last listener was removed before we got here,
|
||||
// and the parent listener list may have been deleted.
|
||||
}
|
||||
}
|
||||
|
||||
void ListenersBase::Group::do_call1 (Call* const c, const timestamp_t timestamp,
|
||||
void* const listener)
|
||||
{
|
||||
if (!empty ())
|
||||
{
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_mutex);
|
||||
|
||||
// Recursion not allowed.
|
||||
bassert (m_listener == 0);
|
||||
|
||||
for (List <Entry>::iterator iter = m_list.begin (); iter != m_list.end ();)
|
||||
{
|
||||
Entry* entry = & (*iter++);
|
||||
|
||||
if (entry->listener == listener)
|
||||
{
|
||||
if (timestamp > entry->timestamp)
|
||||
{
|
||||
m_listener = entry->listener;
|
||||
|
||||
bassert (m_fifo.isBeingSynchronized ());
|
||||
|
||||
m_fifo.callp (new (m_fifo.getAllocator ()) CallWork (c, m_listener));
|
||||
|
||||
m_listener = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Listener was removed
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// Proxy
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// CallQueue item for processing a an Entry for a Proxy.
|
||||
// This is used to avoid bind overhead.
|
||||
//
|
||||
class ListenersBase::Proxy::Work : public CallQueue::Work
|
||||
{
|
||||
public:
|
||||
inline Work (Entry* const entry, const timestamp_t timestamp)
|
||||
: m_entry (entry)
|
||||
, m_timestamp (timestamp)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () ()
|
||||
{
|
||||
ListenersBase::Call* c = m_entry->call.exchange (0);
|
||||
Group* group = m_entry->group;
|
||||
if (!group->empty ())
|
||||
group->do_call (c, m_timestamp);
|
||||
c->decReferenceCount ();
|
||||
}
|
||||
|
||||
private:
|
||||
Entry::Ptr m_entry;
|
||||
const timestamp_t m_timestamp;
|
||||
};
|
||||
|
||||
// Holds a Call, and gets put in the CallQueue in place of the Call.
|
||||
// The Call may be replaced if it hasn't been processed yet.
|
||||
// A Proxy exists for the lifetime of the Listeners.
|
||||
//
|
||||
ListenersBase::Proxy::Proxy (void const* const member, const size_t bytes)
|
||||
: m_bytes (bytes)
|
||||
{
|
||||
if (bytes > maxMemberBytes)
|
||||
fatal_error ("the Proxy member is too large");
|
||||
|
||||
memcpy (m_member, member, bytes);
|
||||
}
|
||||
|
||||
ListenersBase::Proxy::~Proxy ()
|
||||
{
|
||||
// If the proxy is getting destroyed it means:
|
||||
// - the listeners object is getting destroyed
|
||||
// - all listeners must have removed themselves
|
||||
// - all thread queues have been fully processed
|
||||
// Therefore, our entries should be gone.
|
||||
|
||||
// NO it is possible for an empty Group, for which
|
||||
// the parent listeners object has been destroyed,
|
||||
// to still exist in a thread queue!!!
|
||||
|
||||
// But all listeners should have removed themselves
|
||||
// so our list of groups should still be empty.
|
||||
bassert (m_entries.empty ());
|
||||
}
|
||||
|
||||
// Adds the group to the Proxy.
|
||||
// Caller must have the proxies mutex.
|
||||
// Caller is responsible for preventing duplicates.
|
||||
//
|
||||
void ListenersBase::Proxy::add (Group* group, AllocatorType& allocator)
|
||||
{
|
||||
Entry* entry (new (allocator) Entry (group));
|
||||
|
||||
// Manual addref and put raw pointer in list
|
||||
entry->incReferenceCount ();
|
||||
m_entries.push_back (*entry);
|
||||
}
|
||||
|
||||
// Removes the group from the Proxy.
|
||||
// Caller must have the proxies mutex.
|
||||
// Caller is responsible for making sure the group exists.
|
||||
void ListenersBase::Proxy::remove (Group* group)
|
||||
{
|
||||
for (Entries::iterator iter = m_entries.begin (); iter != m_entries.end ();)
|
||||
{
|
||||
Entry* entry = & (*iter++);
|
||||
|
||||
if (entry->group == group)
|
||||
{
|
||||
// remove from list and manual release
|
||||
m_entries.erase (m_entries.iterator_to (*entry));
|
||||
entry->decReferenceCount ();
|
||||
|
||||
// Entry might still be in the empty group's thread queue
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For each group, updates the call.
|
||||
// Queues each group that isn't already queued.
|
||||
// Caller must acquire the group read lock.
|
||||
//
|
||||
void ListenersBase::Proxy::update (Call* const c, const timestamp_t timestamp)
|
||||
{
|
||||
// why would we even want to be called?
|
||||
bassert (!m_entries.empty ());
|
||||
|
||||
// With the read lock, this list can't change on us unless someone
|
||||
// adds a listener to a new thread queue in response to a call.
|
||||
for (Entries::iterator iter = m_entries.begin (); iter != m_entries.end ();)
|
||||
{
|
||||
Entry* entry = & (*iter++);
|
||||
|
||||
// Manually add a reference since we use a raw pointer
|
||||
c->incReferenceCount ();
|
||||
|
||||
// Atomically exchange the new call for the old one
|
||||
Call* old = entry->call.exchange (c);
|
||||
|
||||
// If no old call then they need to be queued
|
||||
if (!old)
|
||||
{
|
||||
CallQueue& callQueue = entry->group->getCallQueue ();
|
||||
callQueue.callp (new (callQueue.getAllocator ()) Work (entry, timestamp));
|
||||
}
|
||||
else
|
||||
{
|
||||
old->decReferenceCount ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ListenersBase::Proxy::match (void const* const member, const size_t bytes) const
|
||||
{
|
||||
return m_bytes == bytes && memcmp (member, m_member, bytes) == 0;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// ListenersBase
|
||||
//
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
ListenersBase::ListenersBase ()
|
||||
: m_timestamp (0)
|
||||
, m_allocator (AllocatorType::getInstance ())
|
||||
, m_callAllocator (CallAllocatorType::getInstance ())
|
||||
{
|
||||
}
|
||||
|
||||
ListenersBase::~ListenersBase ()
|
||||
{
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
|
||||
// If this goes off it means a Listener forgot to remove.
|
||||
bassert (group->empty ());
|
||||
|
||||
group->decReferenceCount ();
|
||||
}
|
||||
|
||||
// Proxies are never deleted until here.
|
||||
for (Proxies::iterator iter = m_proxies.begin (); iter != m_proxies.end ();)
|
||||
delete & (*iter++);
|
||||
}
|
||||
|
||||
void ListenersBase::add_void (void* const listener, CallQueue& callQueue)
|
||||
{
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_groups_mutex);
|
||||
|
||||
#if BEAST_DEBUG
|
||||
|
||||
// Make sure the listener has not already been added
|
||||
// SHOULD USE const_iterator!
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
|
||||
// We can be in do_call() on another thread now, but it
|
||||
// doesn't modify the list, and we have the write lock.
|
||||
bassert (!group->contains (listener));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// See if we already have a Group for this thread queue.
|
||||
Group::Ptr group;
|
||||
|
||||
// SHOULD USE const_iterator
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group::Ptr cur = & (*iter++);
|
||||
|
||||
if (&cur->getCallQueue () == &callQueue)
|
||||
{
|
||||
group = cur;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!group)
|
||||
{
|
||||
group = new (m_allocator) Group (callQueue);
|
||||
|
||||
// Add it to the list, and give it a manual ref
|
||||
// since the list currently uses raw pointers.
|
||||
group->incReferenceCount ();
|
||||
m_groups.push_back (*group);
|
||||
|
||||
// Tell existing proxies to add the group
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_proxies_mutex);
|
||||
|
||||
for (Proxies::iterator iter = m_proxies.begin (); iter != m_proxies.end ();)
|
||||
(iter++)->add (group, *m_allocator);
|
||||
}
|
||||
|
||||
// Add the listener to the group with the current timestamp
|
||||
group->add (listener, m_timestamp, *m_allocator);
|
||||
|
||||
// Increment the timestamp within the mutex so
|
||||
// future calls will be newer than this listener.
|
||||
++m_timestamp;
|
||||
}
|
||||
|
||||
void ListenersBase::remove_void (void* const listener)
|
||||
{
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_groups_mutex);
|
||||
|
||||
// Make sure the listener exists
|
||||
#if BEAST_DEBUG
|
||||
{
|
||||
bool exists = false;
|
||||
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
|
||||
// this should never happen while we hold the mutex
|
||||
bassert (!group->empty ());
|
||||
|
||||
if (group->contains (listener))
|
||||
{
|
||||
bassert (!exists); // added twice?
|
||||
|
||||
exists = true;
|
||||
// keep going to make sure there are no empty groups
|
||||
}
|
||||
}
|
||||
|
||||
bassert (exists);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Find the group and remove
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group::Ptr group = & (*iter++);
|
||||
|
||||
// If the listener is in there, take it out.
|
||||
if (group->remove (listener))
|
||||
{
|
||||
// Are we the last listener?
|
||||
if (group->empty ())
|
||||
{
|
||||
// Tell proxies to remove the group
|
||||
{
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_proxies_mutex);
|
||||
|
||||
for (Proxies::iterator iter = m_proxies.begin (); iter != m_proxies.end ();)
|
||||
{
|
||||
Proxy* proxy = & (*iter++);
|
||||
proxy->remove (group);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove it from the list and manually release
|
||||
// the reference since the list uses raw pointers.
|
||||
m_groups.erase (m_groups.iterator_to (*group));
|
||||
group->decReferenceCount ();
|
||||
|
||||
// It is still possible for the group to exist at this
|
||||
// point in a thread queue but it will get processed,
|
||||
// do nothing, and release its own final reference.
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenersBase::callp (Call::Ptr cp)
|
||||
{
|
||||
Call* c = cp;
|
||||
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_groups_mutex);
|
||||
|
||||
// can't be const iterator because queue() might cause called functors
|
||||
// to modify the list.
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
(iter++)->call (c, m_timestamp);
|
||||
}
|
||||
|
||||
void ListenersBase::queuep (Call::Ptr cp)
|
||||
{
|
||||
Call* c = cp;
|
||||
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_groups_mutex);
|
||||
|
||||
// can't be const iterator because queue() might cause called functors
|
||||
// to modify the list.
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
(iter++)->queue (c, m_timestamp);
|
||||
}
|
||||
|
||||
void ListenersBase::call1p_void (void* const listener, Call* c)
|
||||
{
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_groups_mutex);
|
||||
|
||||
// can't be const iterator because queue() might cause called functors
|
||||
// to modify the list.
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
|
||||
if (group->contains (listener))
|
||||
{
|
||||
group->call1 (c, m_timestamp, listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ListenersBase::queue1p_void (void* const listener, Call* c)
|
||||
{
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_groups_mutex);
|
||||
|
||||
// can't be const iterator because queue() might cause called functors
|
||||
// to modify the list.
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
|
||||
if (group->contains (listener))
|
||||
{
|
||||
group->queue1 (c, m_timestamp, listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for an existing Proxy that matches the pointer to
|
||||
// member and replace it's Call, or create a new Proxy for it.
|
||||
//
|
||||
void ListenersBase::updatep (void const* const member,
|
||||
const size_t bytes, Call::Ptr cp)
|
||||
{
|
||||
Call* c = cp;
|
||||
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_groups_mutex);
|
||||
|
||||
if (!m_groups.empty ())
|
||||
{
|
||||
Proxy* proxy;
|
||||
|
||||
{
|
||||
ReadWriteMutex::ScopedReadLockType lock (m_proxies_mutex);
|
||||
|
||||
// See if there's already a proxy
|
||||
proxy = find_proxy (member, bytes);
|
||||
}
|
||||
|
||||
// Possibly create one
|
||||
if (!proxy)
|
||||
{
|
||||
ReadWriteMutex::ScopedWriteLockType lock (m_proxies_mutex);
|
||||
|
||||
// Have to search for it again in case someone else added it
|
||||
proxy = find_proxy (member, bytes);
|
||||
|
||||
if (!proxy)
|
||||
{
|
||||
// Create a new empty proxy
|
||||
proxy = new (m_allocator) Proxy (member, bytes);
|
||||
|
||||
// Add all current groups to the Proxy.
|
||||
// We need the group read lock for this (caller provided).
|
||||
for (Groups::iterator iter = m_groups.begin (); iter != m_groups.end ();)
|
||||
{
|
||||
Group* group = & (*iter++);
|
||||
proxy->add (group, *m_allocator);
|
||||
}
|
||||
|
||||
// Add it to the list.
|
||||
m_proxies.push_front (*proxy);
|
||||
}
|
||||
}
|
||||
|
||||
// Requires the group read lock
|
||||
proxy->update (c, m_timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
// Searches for a proxy that matches the pointer to member.
|
||||
// Caller synchronizes.
|
||||
//
|
||||
ListenersBase::Proxy* ListenersBase::find_proxy (const void* member, size_t bytes)
|
||||
{
|
||||
for (Proxies::iterator iter = m_proxies.begin (); iter != m_proxies.end ();)
|
||||
{
|
||||
Proxy* proxy = & (*iter++);
|
||||
|
||||
if (proxy->match (member, bytes))
|
||||
return proxy;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,797 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_LISTENERS_H_INCLUDED
|
||||
#define BEAST_LISTENERS_H_INCLUDED
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
A group of concurrent Listeners.
|
||||
|
||||
A Listener is an object of class type which inherits from a defined
|
||||
interface, and registers on a provided instance of Listeners to receive
|
||||
asynchronous notifications of changes to concurrent states. Another way of
|
||||
defining Listeners, is that it is similar to a Juce ListenerList but with
|
||||
the provision that the Listener registers with the CallQueue upon which the
|
||||
notification should be made.
|
||||
|
||||
Listeners makes extensive use of CallQueue for providing the notifications,
|
||||
and provides a higher level facility for implementing the concurrent
|
||||
synchronization strategy outlined in CallQueue. Therefore, the same notes
|
||||
which apply to functors in CallQueue also apply to Listener member
|
||||
invocations. Their execution time should be brief, limited in scope to
|
||||
updating the recipient's view of a shared state, and use reference counting
|
||||
for parameters of class type.
|
||||
|
||||
To use this system, first declare your Listener interface:
|
||||
|
||||
@code
|
||||
|
||||
struct Listener
|
||||
{
|
||||
// Sent on every output block
|
||||
virtual void onOutputLevelChanged (const float outputLevel) { }
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
Now set up the place where you want to send the notifications. In this
|
||||
example, we will set up the AudioIODeviceCallback to notify anyone who is
|
||||
interested about changes in the current audio output level. We will use
|
||||
this to implement a VU meter:
|
||||
|
||||
@code
|
||||
|
||||
Listeners <Listener> listeners;
|
||||
|
||||
// (Process audio data)
|
||||
|
||||
// Calculate output level
|
||||
float outputLevel = calcOutputLevel ();
|
||||
|
||||
// Notify listeners
|
||||
listeners.call (&Listener::onOutputLevelChanged, outputLevel);
|
||||
|
||||
@endcode
|
||||
|
||||
To receive notifications, derive from Listener and then add yourself to the
|
||||
Listeners object using the desired CallQueue.
|
||||
|
||||
@code
|
||||
|
||||
// We want notifications on the message thread
|
||||
GuiCallQueue fifo;
|
||||
|
||||
struct VUMeter : public Listener, public Component
|
||||
{
|
||||
VUMeter () : m_outputLevel (0)
|
||||
{
|
||||
listeners.add (this, fifo);
|
||||
}
|
||||
|
||||
~VUMeter ()
|
||||
{
|
||||
listeners.remove (this);
|
||||
}
|
||||
|
||||
void onOutputLevelChanged (float outputLevel)
|
||||
{
|
||||
// Update our copy of the output level shared state.
|
||||
m_outputLevel = outputLevel;
|
||||
|
||||
// Now trigger a redraw of the control.
|
||||
repaint ();
|
||||
}
|
||||
|
||||
float m_outputLevel;
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
In this example, the VUMeter constructs with the output level set to zero,
|
||||
and must wait for a notification before it shows up to date data. For a
|
||||
simple VU meter, this is likely not a problem. But if the shared state
|
||||
contains complex information, such as dynamically allocated objects with
|
||||
rich data, then we need a more solid system.
|
||||
|
||||
We will add some classes to create a complete robust example of the use of
|
||||
Listeners to synchronize shared state:
|
||||
|
||||
@code
|
||||
|
||||
// Handles audio device output.
|
||||
class AudioDeviceOutput : public AudioIODeviceCallback
|
||||
{
|
||||
public:
|
||||
struct Listener
|
||||
{
|
||||
// Sent on every output block.
|
||||
virtual void onOutputLevelChanged (float outputLevel) { }
|
||||
};
|
||||
|
||||
AudioDeviceOutput () : AudioDeviceOutput ("Audio CallQueue")
|
||||
{
|
||||
}
|
||||
|
||||
~AudioDeviceOutput ()
|
||||
{
|
||||
m_fifo.close ();
|
||||
}
|
||||
|
||||
void addListener (Listener* listener, CallQueue& callQueue)
|
||||
{
|
||||
// Acquire read access to the shared state.
|
||||
SharedData <State>::ConstAccess state (m_state);
|
||||
|
||||
// Add the listener.
|
||||
m_listeners.add (listener, callQueue);
|
||||
|
||||
// Queue an update for the listener to receive the initial state.
|
||||
m_listeners.queue1 (listener,
|
||||
&Listener::onOutputLevelChanged,
|
||||
state->outputLevel);
|
||||
}
|
||||
|
||||
void removeListener (Listener* listener)
|
||||
{
|
||||
m_listeners.remove (listener);
|
||||
}
|
||||
|
||||
protected:
|
||||
void audioDeviceIOCallback (const float** inputChannelData,
|
||||
int numInputChannels,
|
||||
float** outputChannelData,
|
||||
int numOutputChannels,
|
||||
int numSamples)
|
||||
{
|
||||
// Synchronize our call queue. Not needed for this example but
|
||||
// included here as a best-practice for audio device I/O callbacks.
|
||||
m_fifo.synchronize ();
|
||||
|
||||
// (Process audio data)
|
||||
|
||||
// Calculate output level.
|
||||
float newOutputLevel = calcOutputLevel ();
|
||||
|
||||
// Update shared state.
|
||||
{
|
||||
SharedData <State>::Access state (m_state);
|
||||
|
||||
m_state->outputLevel = newOutputLevel;
|
||||
}
|
||||
|
||||
// Notify listeners.
|
||||
listeners.call (&Listener::onOutputLevelChanged, newOutputLevel);
|
||||
}
|
||||
|
||||
private:
|
||||
struct State
|
||||
{
|
||||
State () : outputLevel (0) { }
|
||||
|
||||
float outputLevel;
|
||||
};
|
||||
|
||||
SharedData <State> m_state;
|
||||
|
||||
ManualCallQueue m_fifo;
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
Although the rigor demonstrated in the example above is not strictly
|
||||
required when the shared state consists only of a single float, it
|
||||
becomes necessary when there are dynamically allocated objects with complex
|
||||
interactions in the shared state.
|
||||
|
||||
@see CallQueue
|
||||
|
||||
@class Listeners
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API ListenersBase
|
||||
{
|
||||
public:
|
||||
struct ListenersStructureTag { };
|
||||
|
||||
typedef GlobalFifoFreeStore <ListenersStructureTag> AllocatorType;
|
||||
|
||||
typedef GlobalFifoFreeStore <ListenersBase> CallAllocatorType;
|
||||
|
||||
class Call : public SharedObject,
|
||||
public AllocatedBy <CallAllocatorType>
|
||||
{
|
||||
public:
|
||||
typedef SharedPtr <Call> Ptr;
|
||||
virtual void operator () (void* const listener) = 0;
|
||||
};
|
||||
|
||||
private:
|
||||
typedef unsigned long timestamp_t;
|
||||
|
||||
class Group;
|
||||
typedef List <Group> Groups;
|
||||
|
||||
class Proxy;
|
||||
typedef List <Proxy> Proxies;
|
||||
|
||||
class CallWork;
|
||||
class GroupWork;
|
||||
class GroupWork1;
|
||||
|
||||
// Maintains a list of listeners registered on the same CallQueue
|
||||
//
|
||||
class Group : public Groups::Node,
|
||||
public SharedObject,
|
||||
public AllocatedBy <AllocatorType>
|
||||
{
|
||||
public:
|
||||
typedef SharedPtr <Group> Ptr;
|
||||
|
||||
explicit Group (CallQueue& callQueue);
|
||||
~Group ();
|
||||
void add (void* listener, const timestamp_t timestamp,
|
||||
AllocatorType& allocator);
|
||||
bool remove (void* listener);
|
||||
bool contains (void* const listener);
|
||||
void call (Call* const c, const timestamp_t timestamp);
|
||||
void queue (Call* const c, const timestamp_t timestamp);
|
||||
void call1 (Call* const c, const timestamp_t timestamp,
|
||||
void* const listener);
|
||||
void queue1 (Call* const c, const timestamp_t timestamp,
|
||||
void* const listener);
|
||||
void do_call (Call* const c, const timestamp_t timestamp);
|
||||
void do_call1 (Call* const c, const timestamp_t timestamp,
|
||||
void* const listener);
|
||||
|
||||
bool empty () const
|
||||
{
|
||||
return m_list.empty ();
|
||||
}
|
||||
CallQueue& getCallQueue () const
|
||||
{
|
||||
return m_fifo;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Entry;
|
||||
|
||||
CallQueue& m_fifo;
|
||||
List <Entry> m_list;
|
||||
void* m_listener;
|
||||
CacheLine::Aligned <ReadWriteMutex> m_mutex;
|
||||
};
|
||||
|
||||
// A Proxy is keyed to a unique pointer-to-member of a
|
||||
// ListenerClass and is used to consolidate multiple unprocessed
|
||||
// Calls into a single call to prevent excess messaging. It is up
|
||||
// to the user of the class to decide when this behavior is appropriate.
|
||||
//
|
||||
class Proxy : public Proxies::Node,
|
||||
public AllocatedBy <AllocatorType>
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
maxMemberBytes = 16
|
||||
};
|
||||
|
||||
Proxy (void const* const member, const size_t bytes);
|
||||
~Proxy ();
|
||||
|
||||
void add (Group* group, AllocatorType& allocator);
|
||||
void remove (Group* group);
|
||||
void update (Call* const c, const timestamp_t timestamp);
|
||||
|
||||
bool match (void const* const member, const size_t bytes) const;
|
||||
|
||||
private:
|
||||
class Work;
|
||||
struct Entry;
|
||||
typedef List <Entry> Entries;
|
||||
char m_member [maxMemberBytes];
|
||||
const size_t m_bytes;
|
||||
Entries m_entries;
|
||||
};
|
||||
|
||||
protected:
|
||||
ListenersBase ();
|
||||
~ListenersBase ();
|
||||
|
||||
inline CallAllocatorType& getCallAllocator ()
|
||||
{
|
||||
return *m_callAllocator;
|
||||
}
|
||||
|
||||
void add_void (void* const listener, CallQueue& callQueue);
|
||||
void remove_void (void* const listener);
|
||||
|
||||
void callp (Call::Ptr c);
|
||||
void queuep (Call::Ptr c);
|
||||
void call1p_void (void* const listener, Call* c);
|
||||
void queue1p_void (void* const listener, Call* c);
|
||||
void updatep (void const* const member,
|
||||
const size_t bytes, Call::Ptr cp);
|
||||
|
||||
private:
|
||||
Proxy* find_proxy (const void* member, size_t bytes);
|
||||
|
||||
private:
|
||||
Groups m_groups;
|
||||
Proxies m_proxies;
|
||||
timestamp_t m_timestamp;
|
||||
CacheLine::Aligned <ReadWriteMutex> m_groups_mutex;
|
||||
CacheLine::Aligned <ReadWriteMutex> m_proxies_mutex;
|
||||
AllocatorType::Ptr m_allocator;
|
||||
CallAllocatorType::Ptr m_callAllocator;
|
||||
};
|
||||
|
||||
/*============================================================================*/
|
||||
|
||||
template <class ListenerClass>
|
||||
class Listeners : public ListenersBase
|
||||
{
|
||||
private:
|
||||
template <class Functor>
|
||||
class CallType : public Call
|
||||
{
|
||||
public:
|
||||
CallType (Functor f) : m_f (f)
|
||||
{
|
||||
}
|
||||
|
||||
void operator () (void* const listener)
|
||||
{
|
||||
ListenerClass* object = static_cast <ListenerClass*> (listener);
|
||||
m_f.operator () (object);
|
||||
}
|
||||
|
||||
private:
|
||||
Functor m_f;
|
||||
};
|
||||
|
||||
template <class Functor>
|
||||
inline void callf (Functor f)
|
||||
{
|
||||
callp (new (getCallAllocator ()) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
template <class Functor>
|
||||
inline void queuef (Functor f)
|
||||
{
|
||||
queuep (new (getCallAllocator ()) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
inline void call1p (ListenerClass* const listener, Call::Ptr c)
|
||||
{
|
||||
call1p_void (listener, c);
|
||||
}
|
||||
|
||||
inline void queue1p (ListenerClass* const listener, Call::Ptr c)
|
||||
{
|
||||
queue1p_void (listener, c);
|
||||
}
|
||||
|
||||
template <class Functor>
|
||||
inline void call1f (ListenerClass* const listener, Functor f)
|
||||
{
|
||||
call1p (listener, new (getCallAllocator ()) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
template <class Functor>
|
||||
inline void queue1f (ListenerClass* const listener, Functor f)
|
||||
{
|
||||
queue1p (listener, new (getCallAllocator ()) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
template <class Member, class Functor>
|
||||
inline void updatef (Member member, Functor f)
|
||||
{
|
||||
updatep (reinterpret_cast <void*> (&member), sizeof (Member),
|
||||
new (getCallAllocator ()) CallType <Functor> (f));
|
||||
}
|
||||
|
||||
public:
|
||||
/** Add a listener.
|
||||
|
||||
The specified listener is associated with the specified CallQueue and
|
||||
added to the list.
|
||||
|
||||
Invariants:
|
||||
|
||||
- All other members of Listeners are blocked during add().
|
||||
|
||||
- The listener is guaranteed to receive every subsequent call.
|
||||
|
||||
- The listener must not already exist in the list.
|
||||
|
||||
- Safe to call from any thread.
|
||||
|
||||
@param listener The listener to add.
|
||||
|
||||
@param callQueue The CallQueue to associate with the listener.
|
||||
*/
|
||||
void add (ListenerClass* const listener, CallQueue& callQueue)
|
||||
{
|
||||
add_void (listener, callQueue);
|
||||
}
|
||||
|
||||
/** Remove a listener.
|
||||
|
||||
The specified listener, which must have been previously added, is removed
|
||||
from the list. A listener always needs to remove itself before the
|
||||
associated CallQueue is closed.
|
||||
|
||||
Invariants:
|
||||
|
||||
- All other members of Listeners are blocked during remove().
|
||||
|
||||
- The listener is guaranteed not to receive calls after remove() returns.
|
||||
|
||||
- Safe to call from any thread.
|
||||
|
||||
@param listener The listener to remove.
|
||||
*/
|
||||
void remove (ListenerClass* const listener)
|
||||
{
|
||||
remove_void (listener);
|
||||
}
|
||||
|
||||
/** Call a member function on every added listener, on its associated
|
||||
CallQueue.
|
||||
|
||||
A listener's CallQueue will be synchronized if this function is called
|
||||
from it's associated thread.
|
||||
|
||||
Invariants:
|
||||
|
||||
- A listener that later removes itself afterwards may not get called.
|
||||
|
||||
- Calls from the same thread always execute in order.
|
||||
|
||||
- A listener can remove itself even if it has a pending call.
|
||||
|
||||
@param mf The member function to call. This may be followed by up to 8
|
||||
arguments.
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Mf>
|
||||
inline void call (Mf mf)
|
||||
{ callf (functional::bind (mf, placeholders::_1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Mf, class T1>
|
||||
void call (Mf mf, T1 t1)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Mf, class T1, class T2>
|
||||
void call (Mf mf, T1 t1, T2 t2)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Mf, class T1, class T2, class T3>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Mf, class T1, class T2, class T3, class T4>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void call (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ callf (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/** Queue a member function on every added listener, without synchronizing.
|
||||
|
||||
Operates like call(), but no CallQueue synchronization takes place. This
|
||||
can be necessary when the call to queue() is made inside a held lock.
|
||||
|
||||
@param mf The member function to call. This may be followed by up to 8
|
||||
arguments.
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Mf>
|
||||
inline void queue (Mf mf)
|
||||
{ queuef (functional::bind (mf, placeholders::_1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Mf, class T1>
|
||||
void queue (Mf mf, T1 t1)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Mf, class T1, class T2>
|
||||
void queue (Mf mf, T1 t1, T2 t2)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Mf, class T1, class T2, class T3>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Mf, class T1, class T2, class T3, class T4>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void queue (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ queuef (functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/** Call a member function on every added listener, replacing pending
|
||||
calls to the same member.
|
||||
|
||||
This operates like call(), except that if there are pending unprocessed
|
||||
calls to the same member function,they will be replaced, with the previous
|
||||
parameters destroyed normally. This functionality is useful for
|
||||
high frequency notifications of non critical data, where the recipient
|
||||
may not catch up often enough. For example, the output level of the
|
||||
AudioIODeviceCallback in the example is a candidate for the use of
|
||||
update().
|
||||
|
||||
@param mf The member function to call. This may be followed by up to 8
|
||||
arguments.
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Mf>
|
||||
inline void update (Mf mf)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Mf, class T1>
|
||||
void update (Mf mf, T1 t1)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Mf, class T1, class T2>
|
||||
void update (Mf mf, T1 t1, T2 t2)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Mf, class T1, class T2, class T3>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Mf, class T1, class T2, class T3, class T4>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void update (Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ updatef (mf, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/** Call a member function on a specific listener.
|
||||
|
||||
Like call(), except that one listener is targeted only. This is useful when
|
||||
builing complex behaviors during the addition of a listener, such as
|
||||
providing an initial state.
|
||||
|
||||
@param listener The listener to call.
|
||||
|
||||
@param mf The member function to call. This may be followed by up
|
||||
to 8 arguments.
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Mf>
|
||||
inline void call1 (ListenerClass* const listener, Mf mf)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Mf, class T1>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Mf, class T1, class T2>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Mf, class T1, class T2, class T3>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Mf, class T1, class T2, class T3, class T4>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void call1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ call1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/** Queue a member function on a specific listener.
|
||||
|
||||
Like call1(), except that no CallQueue synchronization takes place.
|
||||
|
||||
@param listener The listener to call.
|
||||
|
||||
@param mf The member function to call. This may be followed by up
|
||||
to 8 arguments.
|
||||
*/
|
||||
/** @{ */
|
||||
#if BEAST_VARIADIC_MAX >= 1
|
||||
template <class Mf>
|
||||
inline void queue1 (ListenerClass* const listener, Mf mf)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 2
|
||||
template <class Mf, class T1>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 3
|
||||
template <class Mf, class T1, class T2>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 4
|
||||
template <class Mf, class T1, class T2, class T3>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 5
|
||||
template <class Mf, class T1, class T2, class T3, class T4>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 6
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 7
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 8
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7)); }
|
||||
#endif
|
||||
|
||||
#if BEAST_VARIADIC_MAX >= 9
|
||||
template <class Mf, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8>
|
||||
void queue1 (ListenerClass* const listener, Mf mf, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8)
|
||||
{ queue1f (listener, functional::bind (mf, placeholders::_1, t1, t2, t3, t4, t5, t6, t7, t8)); }
|
||||
#endif
|
||||
/** @} */
|
||||
};
|
||||
/** @} */
|
||||
|
||||
#endif
|
||||
@@ -1,54 +0,0 @@
|
||||
/*============================================================================*/
|
||||
/*
|
||||
VFLib: https://github.com/vinniefalco/VFLib
|
||||
|
||||
Copyright (C) 2008 by Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
This library contains portions of other open source products covered by
|
||||
separate licenses. Please see the corresponding source files for specific
|
||||
terms.
|
||||
|
||||
VFLib is provided under the terms of The MIT License (MIT):
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
/*============================================================================*/
|
||||
|
||||
ManualCallQueue::ManualCallQueue (String name)
|
||||
: CallQueue (name)
|
||||
{
|
||||
}
|
||||
|
||||
void ManualCallQueue::close ()
|
||||
{
|
||||
CallQueue::close ();
|
||||
}
|
||||
|
||||
bool ManualCallQueue::synchronize ()
|
||||
{
|
||||
return CallQueue::synchronize ();
|
||||
}
|
||||
|
||||
void ManualCallQueue::signal ()
|
||||
{
|
||||
}
|
||||
|
||||
void ManualCallQueue::reset ()
|
||||
{
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*============================================================================*/
|
||||
/*
|
||||
VFLib: https://github.com/vinniefalco/VFLib
|
||||
|
||||
Copyright (C) 2008 by Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
This library contains portions of other open source products covered by
|
||||
separate licenses. Please see the corresponding source files for specific
|
||||
terms.
|
||||
|
||||
VFLib is provided under the terms of The MIT License (MIT):
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
*/
|
||||
/*============================================================================*/
|
||||
|
||||
#ifndef VF_MANUALCALLQUEUE_VFHEADER
|
||||
#define VF_MANUALCALLQUEUE_VFHEADER
|
||||
|
||||
/*============================================================================*/
|
||||
/**
|
||||
A CallQueue that requires periodic manual synchronization.
|
||||
|
||||
To use this, declare an instance and then place calls into it as usual.
|
||||
Every so often, you must call synchronize() from the thread you want to
|
||||
associate with the queue. Typically this is done within an
|
||||
AudioIODeviceCallback:
|
||||
|
||||
@code
|
||||
|
||||
class AudioIODeviceCallbackWithCallQueue
|
||||
: public AudioIODeviceCallback
|
||||
, public CallQueue
|
||||
{
|
||||
public:
|
||||
AudioIODeviceCallbackWithCallQueue () : m_fifo ("Audio CallQueue")
|
||||
{
|
||||
}
|
||||
|
||||
void audioDeviceIOCallback (const float** inputChannelData,
|
||||
int numInputChannels,
|
||||
float** outputChannelData,
|
||||
int numOutputChannels,
|
||||
int numSamples)
|
||||
{
|
||||
CallQueue::synchronize ();
|
||||
|
||||
// do audio i/o
|
||||
}
|
||||
|
||||
void signal () { } // No action required
|
||||
void reset () { } // No action required
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
The close() function is provided for diagnostics. Call it as early as
|
||||
possible based on the exit or shutdown logic of your application. If calls
|
||||
are put into the queue after it is closed, it will generate an exception so
|
||||
you can track it down.
|
||||
|
||||
@see CallQueue
|
||||
|
||||
@ingroup vf_concurrent
|
||||
*/
|
||||
class ManualCallQueue : public CallQueue
|
||||
{
|
||||
public:
|
||||
/** Create a ManualCallQueue.
|
||||
|
||||
@param name A string used to help identify the associated
|
||||
thread for debugging.
|
||||
*/
|
||||
explicit ManualCallQueue (String name);
|
||||
|
||||
/** Close the queue. If calls are placed into a closed queue, an exception
|
||||
is thrown.
|
||||
*/
|
||||
void close ();
|
||||
|
||||
/** Synchronize the queue by calling all pending functors.
|
||||
|
||||
@return `true` if any functors were called.
|
||||
*/
|
||||
bool synchronize ();
|
||||
|
||||
private:
|
||||
void signal ();
|
||||
void reset ();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,283 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
ThreadWithCallQueue::ThreadWithCallQueue (String name)
|
||||
: CallQueue (name)
|
||||
, m_thread (name)
|
||||
, m_entryPoints (nullptr)
|
||||
, m_calledStart (false)
|
||||
, m_calledStop (false)
|
||||
, m_shouldStop (false)
|
||||
{
|
||||
}
|
||||
|
||||
ThreadWithCallQueue::~ThreadWithCallQueue ()
|
||||
{
|
||||
stop (true);
|
||||
}
|
||||
|
||||
ThreadWithCallQueue::EntryPoints* ThreadWithCallQueue::getDefaultEntryPoints () noexcept
|
||||
{
|
||||
static EntryPoints entryPoints;
|
||||
|
||||
return &entryPoints;
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::start (EntryPoints* const entryPoints)
|
||||
{
|
||||
{
|
||||
// This is mostly for diagnostics
|
||||
// TODO: Atomic flag for this whole thing
|
||||
CriticalSection::ScopedLockType lock (m_mutex);
|
||||
|
||||
// start() MUST be called.
|
||||
bassert (!m_calledStart);
|
||||
m_calledStart = true;
|
||||
}
|
||||
|
||||
m_entryPoints = entryPoints;
|
||||
|
||||
m_thread.start (this);
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::stop (bool const wait)
|
||||
{
|
||||
// can't call stop(true) from within a thread function
|
||||
bassert (!wait || !m_thread.isTheCurrentThread ());
|
||||
|
||||
{
|
||||
CriticalSection::ScopedLockType lock (m_mutex);
|
||||
|
||||
// start() MUST be called.
|
||||
bassert (m_calledStart);
|
||||
|
||||
// TODO: Atomic for this
|
||||
if (!m_calledStop)
|
||||
{
|
||||
m_calledStop = true;
|
||||
|
||||
{
|
||||
CriticalSection::ScopedUnlockType unlock (m_mutex); // getting fancy
|
||||
|
||||
call (&ThreadWithCallQueue::doStop, this);
|
||||
|
||||
// in theory something could slip in here
|
||||
|
||||
close ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wait)
|
||||
m_thread.join ();
|
||||
}
|
||||
|
||||
// Should be called periodically by the idle function.
|
||||
// There are three possible results:
|
||||
//
|
||||
// #1 Returns false. The idle function may continue or return.
|
||||
// #2 Returns true. The idle function should return as soon as possible.
|
||||
// #3 Throws a Thread::Interruption exception.
|
||||
//
|
||||
// If interruptionPoint returns true or throws, it must
|
||||
// not be called again before the thread has the opportunity to reset.
|
||||
//
|
||||
bool ThreadWithCallQueue::interruptionPoint ()
|
||||
{
|
||||
return m_thread.interruptionPoint ();
|
||||
}
|
||||
|
||||
// Interrupts the idle function by queueing a call that does nothing.
|
||||
void ThreadWithCallQueue::interrupt ()
|
||||
{
|
||||
//call (&ThreadWithCallQueue::doNothing);
|
||||
m_thread.interrupt();
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::doNothing ()
|
||||
{
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::signal ()
|
||||
{
|
||||
m_thread.interrupt ();
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::reset ()
|
||||
{
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::doStop ()
|
||||
{
|
||||
m_shouldStop = true;
|
||||
}
|
||||
|
||||
void ThreadWithCallQueue::threadRun ()
|
||||
{
|
||||
m_entryPoints->threadInit ();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
CallQueue::synchronize ();
|
||||
|
||||
if (m_shouldStop)
|
||||
break;
|
||||
|
||||
bool interrupted = m_entryPoints->threadIdle ();
|
||||
|
||||
if (!interrupted)
|
||||
interrupted = interruptionPoint ();
|
||||
|
||||
if (!interrupted)
|
||||
m_thread.wait ();
|
||||
}
|
||||
|
||||
m_entryPoints->threadExit ();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class ThreadWithCallQueueTests : public UnitTest
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
callsPerThread = 20000
|
||||
};
|
||||
|
||||
struct TestThread : ThreadWithCallQueue, ThreadWithCallQueue::EntryPoints
|
||||
{
|
||||
explicit TestThread (int id)
|
||||
: ThreadWithCallQueue ("#" + String::fromNumber (id))
|
||||
, interruptedOnce (false)
|
||||
{
|
||||
start (this);
|
||||
}
|
||||
|
||||
bool interruptedOnce;
|
||||
|
||||
void threadInit ()
|
||||
{
|
||||
}
|
||||
|
||||
void threadExit ()
|
||||
{
|
||||
}
|
||||
|
||||
bool threadIdle ()
|
||||
{
|
||||
bool interrupted;
|
||||
|
||||
String s;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
interrupted = interruptionPoint ();
|
||||
if (interrupted)
|
||||
{
|
||||
interruptedOnce = true;
|
||||
break;
|
||||
}
|
||||
|
||||
s = s + String::fromNumber (m_random.nextInt ());
|
||||
|
||||
if (s.length () > 100)
|
||||
s = String::empty;
|
||||
}
|
||||
|
||||
bassert (interrupted);
|
||||
|
||||
return interrupted;
|
||||
}
|
||||
|
||||
Random m_random;
|
||||
};
|
||||
|
||||
void func1 ()
|
||||
{
|
||||
}
|
||||
|
||||
void func2 ()
|
||||
{
|
||||
}
|
||||
|
||||
void func3 ()
|
||||
{
|
||||
}
|
||||
|
||||
void testThreads (int nThreads)
|
||||
{
|
||||
beginTestCase (String::fromNumber (nThreads) + " threads");
|
||||
|
||||
OwnedArray <TestThread> threads;
|
||||
threads.ensureStorageAllocated (nThreads);
|
||||
|
||||
for (int i = 0; i < nThreads; ++i)
|
||||
threads.add (new TestThread (i + 1));
|
||||
|
||||
for (int i = 0; i < 100000; ++i)
|
||||
{
|
||||
int const n (random().nextInt (threads.size()));
|
||||
int const f (random().nextInt (3));
|
||||
switch (f)
|
||||
{
|
||||
default:
|
||||
bassertfalse;
|
||||
#if 0
|
||||
case 0: threads[n]->call (&ThreadWithCallQueueTests::func1, this); break;
|
||||
case 1: threads[n]->call (&ThreadWithCallQueueTests::func2, this); break;
|
||||
case 2: threads[n]->call (&ThreadWithCallQueueTests::func3, this); break;
|
||||
#else
|
||||
case 0: threads[n]->interrupt(); break;
|
||||
case 1: threads[n]->interrupt(); break;
|
||||
case 2: threads[n]->interrupt(); break;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#if 0
|
||||
for (int i = 0; i < threads.size(); ++i)
|
||||
{
|
||||
expect (threads[i]->interruptedOnce);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
for (int i = 0; i < threads.size(); ++i)
|
||||
threads[i]->stop (false);
|
||||
for (int i = 0; i < threads.size(); ++i)
|
||||
threads[i]->stop (true);
|
||||
#endif
|
||||
|
||||
pass ();
|
||||
}
|
||||
|
||||
void runTest ()
|
||||
{
|
||||
testThreads (5);
|
||||
testThreads (50);
|
||||
}
|
||||
|
||||
ThreadWithCallQueueTests () : UnitTest ("ThreadWithCallQueue", "beast")
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static ThreadWithCallQueueTests threadWithCallQueueTests;
|
||||
@@ -1,155 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of Beast: https://github.com/vinniefalco/Beast
|
||||
Copyright 2013, Vinnie Falco <vinnie.falco@gmail.com>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef BEAST_THREADWITHCALLQUEUE_H_INCLUDED
|
||||
#define BEAST_THREADWITHCALLQUEUE_H_INCLUDED
|
||||
|
||||
/** An InterruptibleThread with a CallQueue.
|
||||
|
||||
This combines an InterruptibleThread with a CallQueue, allowing functors to
|
||||
be queued for asynchronous execution on the thread.
|
||||
|
||||
The thread runs an optional user-defined idle function, which must regularly
|
||||
check for an interruption using the InterruptibleThread interface. When an
|
||||
interruption is signaled, the idle function returns and the CallQueue is
|
||||
synchronized. Then, the idle function is resumed.
|
||||
|
||||
When the ThreadWithCallQueue first starts up, an optional user-defined
|
||||
initialization function is executed on the thread. When the thread exits,
|
||||
a user-defined exit function may be executed on the thread.
|
||||
|
||||
@see CallQueue
|
||||
|
||||
@ingroup beast_concurrent
|
||||
*/
|
||||
class BEAST_API ThreadWithCallQueue
|
||||
: public CallQueue
|
||||
, private InterruptibleThread::EntryPoint
|
||||
, LeakChecked <ThreadWithCallQueue>
|
||||
{
|
||||
public:
|
||||
/** Entry points for a ThreadWithCallQueue.
|
||||
*/
|
||||
class EntryPoints
|
||||
{
|
||||
public:
|
||||
virtual ~EntryPoints () { }
|
||||
|
||||
virtual void threadInit () { }
|
||||
|
||||
virtual void threadExit () { }
|
||||
|
||||
virtual bool threadIdle ()
|
||||
{
|
||||
bool interrupted = false;
|
||||
|
||||
return interrupted;
|
||||
}
|
||||
};
|
||||
|
||||
/** Create a thread.
|
||||
|
||||
@param name The name of the InterruptibleThread and CallQueue, used
|
||||
for diagnostics when debugging.
|
||||
*/
|
||||
explicit ThreadWithCallQueue (String name);
|
||||
|
||||
/** Retrieve the default entry points.
|
||||
|
||||
The default entry points do nothing.
|
||||
*/
|
||||
static EntryPoints* getDefaultEntryPoints () noexcept;
|
||||
|
||||
/** Destroy a ThreadWithCallQueue.
|
||||
|
||||
If the thread is still running it is stopped. The destructor blocks
|
||||
until the thread exits cleanly.
|
||||
*/
|
||||
~ThreadWithCallQueue ();
|
||||
|
||||
/** Start the thread, with optional entry points.
|
||||
|
||||
If `entryPoints` is specified then the thread runs using those
|
||||
entry points. If ommitted, the default entry simply do nothing.
|
||||
This is useful for creating a thread whose sole activities are
|
||||
performed through the call queue.
|
||||
|
||||
@param entryPoints An optional pointer to @ref EntryPoints.
|
||||
*/
|
||||
void start (EntryPoints* const entryPoints = getDefaultEntryPoints ());
|
||||
|
||||
/* Stop the thread.
|
||||
|
||||
Stops the thread and optionally wait until it exits. It is safe to call
|
||||
this function at any time and as many times as desired.
|
||||
|
||||
After a call to stop () the CallQueue is closed, and attempts to queue new
|
||||
functors will throw a runtime exception. Existing functors will still
|
||||
execute.
|
||||
|
||||
Any listeners registered on the CallQueue need to be removed
|
||||
before stop is called
|
||||
|
||||
@invariant The caller is not on the associated thread.
|
||||
|
||||
@param wait `true` if the function should wait until the thread exits
|
||||
before returning.
|
||||
*/
|
||||
|
||||
void stop (bool const wait);
|
||||
|
||||
/** Determine if the thread needs interruption.
|
||||
|
||||
Should be called periodically by the idle function. If interruptionPoint
|
||||
returns true or throws, it must not be called again until the idle function
|
||||
returns and is re-entered.
|
||||
|
||||
@invariant No previous calls to interruptionPoint() made after the idle
|
||||
function entry point returned `true`.
|
||||
|
||||
@return `false` if the idle function may continue, or `true` if the
|
||||
idle function must return as soon as possible.
|
||||
*/
|
||||
bool interruptionPoint ();
|
||||
|
||||
/* Interrupts the idle function.
|
||||
*/
|
||||
void interrupt ();
|
||||
|
||||
private:
|
||||
static void doNothing ();
|
||||
|
||||
void signal ();
|
||||
|
||||
void reset ();
|
||||
|
||||
void doStop ();
|
||||
|
||||
void threadRun ();
|
||||
|
||||
private:
|
||||
InterruptibleThread m_thread;
|
||||
EntryPoints* m_entryPoints;
|
||||
bool m_calledStart;
|
||||
bool m_calledStop;
|
||||
bool m_shouldStop;
|
||||
CriticalSection m_mutex;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user