mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-25 01:19:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			257 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			257 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2015 Dolphin Emulator Project
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| #pragma once
 | |
| 
 | |
| #include <atomic>
 | |
| #include <mutex>
 | |
| #include <thread>
 | |
| 
 | |
| #include "Common/Event.h"
 | |
| #include "Common/Flag.h"
 | |
| 
 | |
| namespace Common
 | |
| {
 | |
| // This class provides a synchronized loop.
 | |
| // It's a thread-safe way to trigger a new iteration without busy loops.
 | |
| // It's optimized for high-usage iterations which usually are already running while it's triggered
 | |
| // often.
 | |
| // Be careful when using Wait() and Wakeup() at the same time. Wait() may block forever while
 | |
| // Wakeup() is called regularly.
 | |
| class BlockingLoop
 | |
| {
 | |
| public:
 | |
|   enum class StopMode
 | |
|   {
 | |
|     NonBlock,
 | |
|     Block,
 | |
|     BlockAndGiveUp,
 | |
|   };
 | |
| 
 | |
|   BlockingLoop() { m_stopped.Set(); }
 | |
|   ~BlockingLoop() { Stop(StopMode::BlockAndGiveUp); }
 | |
|   // Triggers to rerun the payload of the Run() function at least once again.
 | |
|   // This function will never block and is designed to finish as fast as possible.
 | |
|   void Wakeup()
 | |
|   {
 | |
|     // Already running, so no need for a wakeup.
 | |
|     // This is the common case, so try to get this as fast as possible.
 | |
|     if (m_running_state.load() >= STATE_NEED_EXECUTION)
 | |
|       return;
 | |
| 
 | |
|     // Mark that new data is available. If the old state will rerun the payload
 | |
|     // itself, we don't have to set the event to interrupt the worker.
 | |
|     if (m_running_state.exchange(STATE_NEED_EXECUTION) != STATE_SLEEPING)
 | |
|       return;
 | |
| 
 | |
|     // Else as the worker thread may sleep now, we have to set the event.
 | |
|     m_new_work_event.Set();
 | |
|   }
 | |
| 
 | |
|   // Wait for a complete payload run after the last Wakeup() call.
 | |
|   // If stopped, this returns immediately.
 | |
|   void Wait()
 | |
|   {
 | |
|     // already done
 | |
|     if (IsDone())
 | |
|       return;
 | |
| 
 | |
|     // notifying this event will only wake up one thread, so use a mutex here to
 | |
|     // allow only one waiting thread. And in this way, we get an event free wakeup
 | |
|     // but for the first thread for free
 | |
|     std::lock_guard<std::mutex> lk(m_wait_lock);
 | |
| 
 | |
|     // Wait for the worker thread to finish.
 | |
|     while (!IsDone())
 | |
|     {
 | |
|       m_done_event.Wait();
 | |
|     }
 | |
| 
 | |
|     // As we wanted to wait for the other thread, there is likely no work remaining.
 | |
|     // So there is no need for a busy loop any more.
 | |
|     m_may_sleep.Set();
 | |
|   }
 | |
| 
 | |
|   // Wait for a complete payload run after the last Wakeup() call.
 | |
|   // This version will call a yield function every 100ms.
 | |
|   // If stopped, this returns immediately.
 | |
|   template <class Rep, class Period, typename Functor>
 | |
|   void WaitYield(const std::chrono::duration<Rep, Period>& rel_time, Functor yield_func)
 | |
|   {
 | |
|     // already done
 | |
|     if (IsDone())
 | |
|       return;
 | |
| 
 | |
|     // notifying this event will only wake up one thread, so use a mutex here to
 | |
|     // allow only one waiting thread. And in this way, we get an event free wakeup
 | |
|     // but for the first thread for free
 | |
|     std::lock_guard<std::mutex> lk(m_wait_lock);
 | |
| 
 | |
|     // Wait for the worker thread to finish.
 | |
|     while (!IsDone())
 | |
|     {
 | |
|       if (!m_done_event.WaitFor(rel_time))
 | |
|         yield_func();
 | |
|     }
 | |
| 
 | |
|     // As we wanted to wait for the other thread, there is likely no work remaining.
 | |
|     // So there is no need for a busy loop any more.
 | |
|     m_may_sleep.Set();
 | |
|   }
 | |
| 
 | |
|   // Half start the worker.
 | |
|   // So this object is in a running state and Wait() will block until the worker calls Run().
 | |
|   // This may be called from any thread and is supposed to be called at least once before Wait() is
 | |
|   // used.
 | |
|   void Prepare()
 | |
|   {
 | |
|     // There is a race condition if the other threads call this function while
 | |
|     // the loop thread is initializing. Using this lock will ensure a valid state.
 | |
|     std::lock_guard<std::mutex> lk(m_prepare_lock);
 | |
| 
 | |
|     if (!m_stopped.TestAndClear())
 | |
|       return;
 | |
|     m_running_state.store(
 | |
|         STATE_LAST_EXECUTION);  // so the payload will only be executed once without any Wakeup call
 | |
|     m_shutdown.Clear();
 | |
|     m_may_sleep.Set();
 | |
|   }
 | |
| 
 | |
|   // Main loop of this object.
 | |
|   // The payload callback is called at least as often as it's needed to match the Wakeup()
 | |
|   // requirements.
 | |
|   // The optional timeout parameter is a timeout for how periodically the payload should be called.
 | |
|   // Use timeout = 0 to run without a timeout at all.
 | |
|   template <class F>
 | |
|   void Run(F payload, int64_t timeout = 0)
 | |
|   {
 | |
|     // Asserts that Prepare is called at least once before we enter the loop.
 | |
|     // But a good implementation should call this before already.
 | |
|     Prepare();
 | |
| 
 | |
|     while (!m_shutdown.IsSet())
 | |
|     {
 | |
|       payload();
 | |
| 
 | |
|       switch (m_running_state.load())
 | |
|       {
 | |
|       case STATE_NEED_EXECUTION:
 | |
|         // We won't get notified while we are in the STATE_NEED_EXECUTION state, so maybe Wakeup was
 | |
|         // called.
 | |
|         // So we have to assume on finishing the STATE_NEED_EXECUTION state, that there may be some
 | |
|         // remaining tasks.
 | |
|         // To process this tasks, we call the payload again within the STATE_LAST_EXECUTION state.
 | |
|         m_running_state--;
 | |
|         break;
 | |
| 
 | |
|       case STATE_LAST_EXECUTION:
 | |
|         // If we're still in the STATE_LAST_EXECUTION state, then Wakeup wasn't called within the
 | |
|         // last
 | |
|         // execution of the payload. This means we should be ready now.
 | |
|         // But bad luck, Wakeup may have been called right now. So break and rerun the payload
 | |
|         // if the state was touched.
 | |
|         if (m_running_state-- != STATE_LAST_EXECUTION)
 | |
|           break;
 | |
| 
 | |
|         // Else we're likely in the STATE_DONE state now, so wakeup the waiting threads right now.
 | |
|         // However, if we're not in the STATE_DONE state any more, the event should also be
 | |
|         // triggered so that we'll skip the next waiting call quite fast.
 | |
|         m_done_event.Set();
 | |
|         [[fallthrough]];
 | |
| 
 | |
|       case STATE_DONE:
 | |
|         // We're done now. So time to check if we want to sleep or if we want to stay in a busy
 | |
|         // loop.
 | |
|         if (m_may_sleep.TestAndClear())
 | |
|         {
 | |
|           // Try to set the sleeping state.
 | |
|           if (m_running_state-- != STATE_DONE)
 | |
|             break;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           // Busy loop.
 | |
|           break;
 | |
|         }
 | |
|         [[fallthrough]];
 | |
| 
 | |
|       case STATE_SLEEPING:
 | |
|         // Just relax
 | |
|         if (timeout > 0)
 | |
|         {
 | |
|           m_new_work_event.WaitFor(std::chrono::milliseconds(timeout));
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           m_new_work_event.Wait();
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Shutdown down, so get a safe state
 | |
|     m_running_state.store(STATE_DONE);
 | |
|     m_stopped.Set();
 | |
| 
 | |
|     // Wake up the last Wait calls.
 | |
|     m_done_event.Set();
 | |
|   }
 | |
| 
 | |
|   // Quits the main loop.
 | |
|   // By default, it will wait until the main loop quits.
 | |
|   // Be careful to not use the blocking way within the payload of the Run() method.
 | |
|   void Stop(StopMode mode = StopMode::Block)
 | |
|   {
 | |
|     if (m_stopped.IsSet())
 | |
|       return;
 | |
| 
 | |
|     m_shutdown.Set();
 | |
| 
 | |
|     // We have to interrupt the sleeping call to let the worker shut down soon.
 | |
|     Wakeup();
 | |
| 
 | |
|     switch (mode)
 | |
|     {
 | |
|     case StopMode::NonBlock:
 | |
|       break;
 | |
|     case StopMode::Block:
 | |
|       Wait();
 | |
|       break;
 | |
|     case StopMode::BlockAndGiveUp:
 | |
|       WaitYield(std::chrono::milliseconds(100), [&] {
 | |
|         // If timed out, assume no one will come along to call Run, so force a break
 | |
|         m_stopped.Set();
 | |
|       });
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   bool IsRunning() const { return !m_stopped.IsSet() && !m_shutdown.IsSet(); }
 | |
|   bool IsDone() const { return m_stopped.IsSet() || m_running_state.load() <= STATE_DONE; }
 | |
|   // This function should be triggered regularly over time so
 | |
|   // that we will fall back from the busy loop to sleeping.
 | |
|   void AllowSleep() { m_may_sleep.Set(); }
 | |
| 
 | |
| private:
 | |
|   std::mutex m_wait_lock;
 | |
|   std::mutex m_prepare_lock;
 | |
| 
 | |
|   Flag m_stopped;   // If this is set, Wait() shall not block.
 | |
|   Flag m_shutdown;  // If this is set, the loop shall end.
 | |
| 
 | |
|   Event m_new_work_event;
 | |
|   Event m_done_event;
 | |
| 
 | |
|   enum RUNNING_TYPE
 | |
|   {
 | |
|     STATE_SLEEPING = 0,
 | |
|     STATE_DONE = 1,
 | |
|     STATE_LAST_EXECUTION = 2,
 | |
|     STATE_NEED_EXECUTION = 3
 | |
|   };
 | |
|   std::atomic<int> m_running_state;  // must be of type RUNNING_TYPE
 | |
| 
 | |
|   Flag m_may_sleep;  // If this is set, we fall back from the busy loop to an event based
 | |
|                      // synchronization.
 | |
| };
 | |
| }  // namespace Common
 |