diff --git a/Source/Core/AudioCommon/AOSoundStream.h b/Source/Core/AudioCommon/AOSoundStream.h index 5900a8c8b5..8d8cc5203e 100644 --- a/Source/Core/AudioCommon/AOSoundStream.h +++ b/Source/Core/AudioCommon/AOSoundStream.h @@ -5,7 +5,9 @@ #pragma once #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" +#include "Common/StdMutex.h" +#include "Common/StdThread.h" #if defined(HAVE_AO) && HAVE_AO #include diff --git a/Source/Core/AudioCommon/DSoundStream.cpp b/Source/Core/AudioCommon/DSoundStream.cpp index 2ffbe9794b..df96a9551f 100644 --- a/Source/Core/AudioCommon/DSoundStream.cpp +++ b/Source/Core/AudioCommon/DSoundStream.cpp @@ -9,6 +9,8 @@ #include "AudioCommon/AudioCommon.h" #include "AudioCommon/DSoundStream.h" +#include "Common/StdThread.h" +#include "Common/Thread.h" bool DSound::CreateBuffer() { diff --git a/Source/Core/AudioCommon/DSoundStream.h b/Source/Core/AudioCommon/DSoundStream.h index 331c4581d3..d885a17664 100644 --- a/Source/Core/AudioCommon/DSoundStream.h +++ b/Source/Core/AudioCommon/DSoundStream.h @@ -5,7 +5,8 @@ #pragma once #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" +#include "Common/StdThread.h" #ifdef _WIN32 #include diff --git a/Source/Core/AudioCommon/OpenALStream.cpp b/Source/Core/AudioCommon/OpenALStream.cpp index b2f820c453..1c1a802528 100644 --- a/Source/Core/AudioCommon/OpenALStream.cpp +++ b/Source/Core/AudioCommon/OpenALStream.cpp @@ -5,6 +5,8 @@ #include "AudioCommon/aldlist.h" #include "AudioCommon/DPL2Decoder.h" #include "AudioCommon/OpenALStream.h" +#include "Common/StdThread.h" +#include "Common/Thread.h" #if defined HAVE_OPENAL && HAVE_OPENAL diff --git a/Source/Core/AudioCommon/OpenALStream.h b/Source/Core/AudioCommon/OpenALStream.h index 0c3f932a38..940bb090f4 100644 --- a/Source/Core/AudioCommon/OpenALStream.h +++ b/Source/Core/AudioCommon/OpenALStream.h @@ -5,7 +5,8 @@ #pragma once #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" +#include "Common/StdThread.h" #include "Core/Core.h" #include "Core/HW/AudioInterface.h" #include "Core/HW/SystemTimers.h" diff --git a/Source/Core/AudioCommon/OpenSLESStream.h b/Source/Core/AudioCommon/OpenSLESStream.h index 08deec6eb1..9ba190728d 100644 --- a/Source/Core/AudioCommon/OpenSLESStream.h +++ b/Source/Core/AudioCommon/OpenSLESStream.h @@ -5,7 +5,8 @@ #pragma once #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" +#include "Common/StdThread.h" class OpenSLESStream final : public SoundStream { diff --git a/Source/Core/AudioCommon/XAudio2Stream.cpp b/Source/Core/AudioCommon/XAudio2Stream.cpp index d5964f0e0e..81af045300 100644 --- a/Source/Core/AudioCommon/XAudio2Stream.cpp +++ b/Source/Core/AudioCommon/XAudio2Stream.cpp @@ -5,6 +5,7 @@ #include #include "AudioCommon/AudioCommon.h" #include "AudioCommon/XAudio2Stream.h" +#include "Common/Event.h" #ifndef XAUDIO2_DLL #error You are building this module against the wrong version of DirectX. You probably need to remove DXSDK_DIR from your include path. diff --git a/Source/Core/AudioCommon/XAudio2Stream.h b/Source/Core/AudioCommon/XAudio2Stream.h index be76d919e8..9083abdc3b 100644 --- a/Source/Core/AudioCommon/XAudio2Stream.h +++ b/Source/Core/AudioCommon/XAudio2Stream.h @@ -11,7 +11,7 @@ #include #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" #ifdef _WIN32 diff --git a/Source/Core/AudioCommon/XAudio2_7Stream.cpp b/Source/Core/AudioCommon/XAudio2_7Stream.cpp index 816d83d47b..8739e919f5 100644 --- a/Source/Core/AudioCommon/XAudio2_7Stream.cpp +++ b/Source/Core/AudioCommon/XAudio2_7Stream.cpp @@ -9,6 +9,7 @@ #include "AudioCommon/AudioCommon.h" #include "AudioCommon/XAudio2_7Stream.h" +#include "Common/Event.h" #ifdef HAVE_DXSDK #include diff --git a/Source/Core/AudioCommon/XAudio2_7Stream.h b/Source/Core/AudioCommon/XAudio2_7Stream.h index e1e3182be9..99c0ca690f 100644 --- a/Source/Core/AudioCommon/XAudio2_7Stream.h +++ b/Source/Core/AudioCommon/XAudio2_7Stream.h @@ -14,7 +14,7 @@ #include #include "AudioCommon/SoundStream.h" -#include "Common/Thread.h" +#include "Common/Event.h" #ifdef _WIN32 diff --git a/Source/Core/Common/Event.h b/Source/Core/Common/Event.h new file mode 100644 index 0000000000..b60a7cacd3 --- /dev/null +++ b/Source/Core/Common/Event.h @@ -0,0 +1,76 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +// Multithreaded event class. This allows waiting in a thread for an event to +// be triggered in another thread. While waiting, the CPU will be available for +// other tasks. +// * Set(): triggers the event and wakes up the waiting thread. +// * Wait(): waits for the event to be triggered. +// * Reset(): tries to reset the event before the waiting thread sees it was +// triggered. Usually a bad idea. + +#pragma once + +#ifdef _WIN32 +#include +#endif + +#include "Common/Flag.h" +#include "Common/StdConditionVariable.h" +#include "Common/StdMutex.h" + +namespace Common { + +// Windows uses a specific implementation because std::condition_variable has +// terrible performance for this kind of workload with MSVC++ 2013. +#ifndef _WIN32 +class Event final +{ +public: + void Set() + { + if (m_flag.TestAndSet()) + { + std::lock_guard lk(m_mutex); + m_condvar.notify_one(); + } + } + + void Wait() + { + if (m_flag.TestAndClear()) + return; + + std::unique_lock lk(m_mutex); + m_condvar.wait(lk, [&]{ return m_flag.IsSet(); }); + m_flag.Clear(); + } + + void Reset() + { + // no other action required, since wait loops on + // the predicate and any lingering signal will get + // cleared on the first iteration + m_flag.Clear(); + } + +private: + Flag m_flag; + std::condition_variable m_condvar; + std::mutex m_mutex; +}; +#else +class Event final +{ +public: + void Set() { m_event.set(); } + void Wait() { m_event.wait(); m_event.reset(); } + void Reset() { m_event.reset(); } + +private: + concurrency::event m_event; +}; +#endif + +} // namespace Common diff --git a/Source/Core/Common/Flag.h b/Source/Core/Common/Flag.h new file mode 100644 index 0000000000..89b61c7805 --- /dev/null +++ b/Source/Core/Common/Flag.h @@ -0,0 +1,66 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +// Abstraction for a simple flag that can be toggled in a multithreaded way. +// +// Simple API: +// * Set(bool = true): sets the Flag +// * IsSet(): tests if the flag is set +// * Clear(): clears the flag (equivalent to Set(false)). +// +// More advanced features: +// * TestAndSet(bool = true): sets the flag to the given value. If a change was +// needed (the flag did not already have this value) +// the function returns true. Else, false. +// * TestAndClear(): alias for TestAndSet(false). + +#pragma once + +#include + +namespace Common { + +class Flag final +{ +public: + // Declared as explicit since we do not want "= true" to work on a flag + // object - it should be made explicit that a flag is *not* a normal + // variable. + explicit Flag(bool initial_value = false) : m_val(initial_value) {} + + void Set(bool val = true) + { + m_val.store(val); + } + + void Clear() + { + Set(false); + } + + bool IsSet() const + { + return m_val.load(); + } + + bool TestAndSet(bool val = true) + { + bool expected = !val; + return m_val.compare_exchange_strong(expected, val); + } + + bool TestAndClear() + { + return TestAndSet(false); + } + +private: + // We are not using std::atomic_bool here because MSVC sucks as of VC++ + // 2013 and does not implement the std::atomic_bool(bool) constructor. + // + // Re-evaluate next time we upgrade that piece of shit. + std::atomic m_val; +}; + +} // namespace Common diff --git a/Source/Core/Common/Thread.h b/Source/Core/Common/Thread.h index d108359dcd..54045caf26 100644 --- a/Source/Core/Common/Thread.h +++ b/Source/Core/Common/Thread.h @@ -32,45 +32,6 @@ int CurrentThreadId(); void SetThreadAffinity(std::thread::native_handle_type thread, u32 mask); void SetCurrentThreadAffinity(u32 mask); -class Event -{ -public: - Event() - : is_set(false) - {} - - void Set() - { - std::lock_guard lk(m_mutex); - if (!is_set) - { - is_set = true; - m_condvar.notify_one(); - } - } - - void Wait() - { - std::unique_lock lk(m_mutex); - m_condvar.wait(lk, [&]{ return is_set; }); - is_set = false; - } - - void Reset() - { - std::unique_lock lk(m_mutex); - // no other action required, since wait loops on - // the predicate and any lingering signal will get - // cleared on the first iteration - is_set = false; - } - -private: - volatile bool is_set; - std::condition_variable m_condvar; - std::mutex m_mutex; -}; - // TODO: doesn't work on windows with (count > 2) class Barrier { diff --git a/Source/Core/Core/DSP/DSPCore.cpp b/Source/Core/Core/DSP/DSPCore.cpp index 520db9ac49..45063d4d47 100644 --- a/Source/Core/Core/DSP/DSPCore.cpp +++ b/Source/Core/Core/DSP/DSPCore.cpp @@ -24,10 +24,10 @@ ====================================================================*/ #include "Common/Common.h" +#include "Common/Event.h" #include "Common/FileUtil.h" #include "Common/Hash.h" #include "Common/MemoryUtil.h" -#include "Common/Thread.h" #include "Core/DSP/DSPAnalyzer.h" #include "Core/DSP/DSPCore.h" diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index 613974b675..820617eb33 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -5,7 +5,8 @@ #include "AudioCommon/AudioCommon.h" #include "Common/Common.h" -#include "Common/Thread.h" +#include "Common/Event.h" +#include "Common/StdMutex.h" #include "Core/Core.h" #include "Core/DSPEmulator.h" diff --git a/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp b/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp index 697e69586d..8ada666527 100644 --- a/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp +++ b/Source/Core/Core/HW/DSPLLE/DSPLLE.cpp @@ -7,9 +7,11 @@ #include "Common/Common.h" #include "Common/CommonPaths.h" #include "Common/CPUDetect.h" +#include "Common/Event.h" #include "Common/IniFile.h" #include "Common/LogManager.h" -#include "Common/Thread.h" +#include "Common/StdMutex.h" +#include "Common/StdThread.h" #include "Core/ConfigManager.h" #include "Core/Core.h" diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 6d16cef7c5..f0060a4c70 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -5,8 +5,10 @@ #include #include "Common/Common.h" +#include "Common/Event.h" +#include "Common/StdMutex.h" +#include "Common/StdThread.h" #include "Common/StringUtil.h" -#include "Common/Thread.h" #include "Common/Timer.h" #include "Core/ConfigManager.h" diff --git a/Source/Core/DolphinWX/Debugger/CodeWindow.h b/Source/Core/DolphinWX/Debugger/CodeWindow.h index 90ddff938c..74977026ff 100644 --- a/Source/Core/DolphinWX/Debugger/CodeWindow.h +++ b/Source/Core/DolphinWX/Debugger/CodeWindow.h @@ -14,7 +14,7 @@ #include #include "Common/CommonTypes.h" -#include "Common/Thread.h" +#include "Common/Event.h" #include "DolphinWX/Globals.h" class CFrame; diff --git a/Source/Core/DolphinWX/Frame.h b/Source/Core/DolphinWX/Frame.h index 8f7e0732c7..2c98fda566 100644 --- a/Source/Core/DolphinWX/Frame.h +++ b/Source/Core/DolphinWX/Frame.h @@ -22,7 +22,7 @@ #include #include "Common/CommonTypes.h" -#include "Common/Thread.h" +#include "Common/Event.h" #include "DolphinWX/Globals.h" #include "InputCommon/GCPadStatus.h" diff --git a/Source/Core/DolphinWX/MainAndroid.cpp b/Source/Core/DolphinWX/MainAndroid.cpp index 4ce8ce0710..ab82e7def1 100644 --- a/Source/Core/DolphinWX/MainAndroid.cpp +++ b/Source/Core/DolphinWX/MainAndroid.cpp @@ -27,9 +27,9 @@ #include "Common/Common.h" #include "Common/CommonPaths.h" #include "Common/CPUDetect.h" +#include "Common/Event.h" #include "Common/FileUtil.h" #include "Common/LogManager.h" -#include "Common/Thread.h" #include "Core/BootManager.h" #include "Core/ConfigManager.h" #include "Core/Core.h" diff --git a/Source/Core/DolphinWX/MainNoGUI.cpp b/Source/Core/DolphinWX/MainNoGUI.cpp index c8f57f9ef6..cfd176e27a 100644 --- a/Source/Core/DolphinWX/MainNoGUI.cpp +++ b/Source/Core/DolphinWX/MainNoGUI.cpp @@ -10,8 +10,8 @@ #include #include "Common/Common.h" +#include "Common/Event.h" #include "Common/LogManager.h" -#include "Common/Thread.h" #include "Core/BootManager.h" #include "Core/ConfigManager.h" diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt index 78f9938e1c..42c0a8aaed 100644 --- a/Source/UnitTests/Common/CMakeLists.txt +++ b/Source/UnitTests/Common/CMakeLists.txt @@ -1,5 +1,7 @@ add_dolphin_test(BitFieldTest BitFieldTest.cpp common) add_dolphin_test(CommonFuncsTest CommonFuncsTest.cpp common) +add_dolphin_test(EventTest EventTest.cpp common) add_dolphin_test(FifoQueueTest FifoQueueTest.cpp common) add_dolphin_test(FixedSizeQueueTest FixedSizeQueueTest.cpp common) +add_dolphin_test(FlagTest FlagTest.cpp common) add_dolphin_test(MathUtilTest MathUtilTest.cpp common) diff --git a/Source/UnitTests/Common/EventTest.cpp b/Source/UnitTests/Common/EventTest.cpp new file mode 100644 index 0000000000..41949a893d --- /dev/null +++ b/Source/UnitTests/Common/EventTest.cpp @@ -0,0 +1,42 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include + +#include "Common/Event.h" + +using Common::Event; + +TEST(Event, MultiThreaded) +{ + Event has_sent, can_send; + int shared_obj; + const int ITERATIONS_COUNT = 100000; + + auto sender = [&]() { + for (int i = 0; i < ITERATIONS_COUNT; ++i) + { + can_send.Wait(); + shared_obj = i; + has_sent.Set(); + } + }; + + auto receiver = [&]() { + for (int i = 0; i < ITERATIONS_COUNT; ++i) { + has_sent.Wait(); + EXPECT_EQ(i, shared_obj); + can_send.Set(); + } + }; + + std::thread sender_thread(sender); + std::thread receiver_thread(receiver); + + can_send.Set(); + + sender_thread.join(); + receiver_thread.join(); +} diff --git a/Source/UnitTests/Common/FlagTest.cpp b/Source/UnitTests/Common/FlagTest.cpp new file mode 100644 index 0000000000..a3a0c3d929 --- /dev/null +++ b/Source/UnitTests/Common/FlagTest.cpp @@ -0,0 +1,91 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include +#include +#include + +#include "Common/Flag.h" + +using Common::Flag; + +TEST(Flag, Simple) +{ + Flag f; + EXPECT_FALSE(f.IsSet()); + + f.Set(); + EXPECT_TRUE(f.IsSet()); + + f.Clear(); + EXPECT_FALSE(f.IsSet()); + + f.Set(false); + EXPECT_FALSE(f.IsSet()); + + EXPECT_TRUE(f.TestAndSet()); + EXPECT_TRUE(f.TestAndClear()); + + Flag f2(true); + EXPECT_TRUE(f2.IsSet()); +} + +TEST(Flag, MultiThreaded) +{ + Flag f; + int count = 0; + const int ITERATIONS_COUNT = 100000; + + auto setter = [&f]() { + for (int i = 0; i < ITERATIONS_COUNT; ++i) + { + while (f.IsSet()); + f.Set(); + } + }; + + auto clearer = [&f, &count]() { + for (int i = 0; i < ITERATIONS_COUNT; ++i) + { + while (!f.IsSet()); + count++; + f.Clear(); + } + }; + + std::thread setter_thread(setter); + std::thread clearer_thread(clearer); + + setter_thread.join(); + clearer_thread.join(); + + EXPECT_EQ(ITERATIONS_COUNT, count); +} + +TEST(Flag, SpinLock) +{ + // Uses a flag to implement basic spinlocking using TestAndSet. + Flag f; + int count = 0; + const int ITERATIONS_COUNT = 5000; + const int THREADS_COUNT = 50; + + auto adder_func = [&]() { + for (int i = 0; i < ITERATIONS_COUNT; ++i) + { + // Acquire the spinlock. + while (!f.TestAndSet()); + count++; + f.Clear(); + } + }; + + std::array threads; + for (auto& th : threads) + th = std::thread(adder_func); + for (auto& th : threads) + th.join(); + + EXPECT_EQ(ITERATIONS_COUNT * THREADS_COUNT, count); +}