// Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #pragma once #include #include #include #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 on using Wait() and Wakeup() at the same time. Wait() may block forever while Wakeup() is called regulary. class BlockingLoop { public: BlockingLoop() { m_stopped.Set(); } ~BlockingLoop() { Stop(); } // 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 (m_stopped.IsSet() || m_running_state.load() <= STATE_DONE) 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 lk(m_wait_lock); // Wait for the worker thread to finish. while (!m_stopped.IsSet() && m_running_state.load() > STATE_DONE) { 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(); } // 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 call 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 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(); } // Mainloop of this object. // The payload callback is called at least as often as it's needed to match the Wakeup() requirements. // The optional timeout parameters is a timeout how periodicly the payload should be called. // Use timeout = 0 to run without a timeout at all. template 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, than Wakeup wasn't called within the last // execution of payload. This means we should be ready now. // But bad luck, Wakeup might have be called right now. So break and rerun the payload // if the state was touched right now. 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(); 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; } 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 mainloop. // By default, it will wait until the Mainloop quits. // Be careful to not use the blocking way within the payload of the Run() method. void Stop(bool block = true) { if (m_stopped.IsSet()) return; m_shutdown.Set(); // We have to interrupt the sleeping call to let the worker shut down soon. Wakeup(); if (block) Wait(); } bool IsRunning() const { return !m_stopped.IsSet() && !m_shutdown.IsSet(); } // This functions should be triggered by regulary by time. So we will fall back from // the busy loop to the sleeping way. void AllowSleep() { m_may_sleep.Set(); } private: std::mutex m_wait_lock; std::mutex m_prepare_lock; Flag m_stopped; // This one is set, Wait() shall not block. Flag m_shutdown; // If this one is set, the loop shall be quit. 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 m_running_state; // must be of type RUNNING_TYPE Flag m_may_sleep; // If this one is set, we fall back from the busy loop to an event based synchronization. }; }