diff options
Diffstat (limited to 'xbmc/threads/Event.h')
-rw-r--r-- | xbmc/threads/Event.h | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/xbmc/threads/Event.h b/xbmc/threads/Event.h new file mode 100644 index 0000000..72ce754 --- /dev/null +++ b/xbmc/threads/Event.h @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/Condition.h" + +#include <initializer_list> +#include <memory> +#include <mutex> +#include <vector> + +// forward declare the CEventGroup +namespace XbmcThreads +{ +class CEventGroup; +} + +/** + * @brief This is an Event class built from a ConditionVariable. The Event adds the state + * that the condition is gating as well as the mutex/lock. + * + * This Event can be 'interruptible' (even though there is only a single place + * in the code that uses this behavior). + * + * This class manages 'spurious returns' from the condition variable. + * + */ + +class CEvent +{ + bool manualReset; + volatile bool signaled; + unsigned int numWaits = 0; + + CCriticalSection groupListMutex; // lock for the groups list + std::unique_ptr<std::vector<XbmcThreads::CEventGroup*>> groups; + + XbmcThreads::ConditionVariable actualCv; + CCriticalSection mutex; + + friend class XbmcThreads::CEventGroup; + + void addGroup(XbmcThreads::CEventGroup* group); + void removeGroup(XbmcThreads::CEventGroup* group); + + // helper for the two wait methods + inline bool prepReturn() + { + bool ret = signaled; + if (!manualReset && numWaits == 0) + signaled = false; + return ret; + } + + CEvent(const CEvent&) = delete; + CEvent& operator=(const CEvent&) = delete; + +public: + inline CEvent(bool manual = false, bool signaled_ = false) + : manualReset(manual), signaled(signaled_) + { + } + + inline void Reset() + { + std::unique_lock<CCriticalSection> lock(mutex); + signaled = false; + } + void Set(); + + /** + * @brief Returns true if Event has been triggered and not reset, false otherwise. + * + */ + inline bool Signaled() + { + std::unique_lock<CCriticalSection> lock(mutex); + return signaled; + } + + /** + * @brief This will wait up to 'duration' for the Event to be + * triggered. The method will return 'true' if the Event + * was triggered. Otherwise it will return false. + * + */ + template<typename Rep, typename Period> + inline bool Wait(std::chrono::duration<Rep, Period> duration) + { + std::unique_lock<CCriticalSection> lock(mutex); + numWaits++; + actualCv.wait(mutex, duration, std::bind(&CEvent::Signaled, this)); + numWaits--; + return prepReturn(); + } + + /** + * @brief This will wait for the Event to be triggered. The method will return + * 'true' if the Event was triggered. If it was either interrupted + * it will return false. Otherwise it will return false. + * + */ + inline bool Wait() + { + std::unique_lock<CCriticalSection> lock(mutex); + numWaits++; + actualCv.wait(mutex, std::bind(&CEvent::Signaled, this)); + numWaits--; + return prepReturn(); + } + + /** + * @brief This is mostly for testing. It allows a thread to make sure there are + * the right amount of other threads waiting. + * + */ + inline int getNumWaits() + { + std::unique_lock<CCriticalSection> lock(mutex); + return numWaits; + } +}; + +namespace XbmcThreads +{ +/** + * @brief CEventGroup is a means of grouping CEvents to wait on them together. + * It is equivalent to WaitOnMultipleObject that returns when "any" Event + * in the group signaled. + * + */ +class CEventGroup +{ + std::vector<CEvent*> events; + CEvent* signaled{}; + XbmcThreads::ConditionVariable actualCv; + CCriticalSection mutex; + + unsigned int numWaits{0}; + + // This is ONLY called from CEvent::Set. + inline void Set(CEvent* child) + { + std::unique_lock<CCriticalSection> l(mutex); + signaled = child; + actualCv.notifyAll(); + } + + friend class ::CEvent; + + CEventGroup(const CEventGroup&) = delete; + CEventGroup& operator=(const CEventGroup&) = delete; + +public: + /** + * @brief Create a CEventGroup from a number of CEvents. + * + */ + CEventGroup(std::initializer_list<CEvent*> events); + + ~CEventGroup(); + + /** + * @brief This will block until any one of the CEvents in the group are + * signaled at which point a pointer to that CEvents will be + * returned. + * + */ + CEvent* wait(); + + /** + * @brief locking is ALWAYS done in this order: + * CEvent::groupListMutex -> CEventGroup::mutex -> CEvent::mutex + * + * Notice that this method doesn't grab the CEvent::groupListMutex at all. This + * is fine. It just grabs the CEventGroup::mutex and THEN the individual + * + */ + template<typename Rep, typename Period> + CEvent* wait(std::chrono::duration<Rep, Period> duration) + { + std::unique_lock<CCriticalSection> lock(mutex); // grab CEventGroup::mutex + numWaits++; + + // ================================================== + // This block checks to see if any child events are + // signaled and sets 'signaled' to the first one it + // finds. + // ================================================== + signaled = nullptr; + for (auto* cur : events) + { + std::unique_lock<CCriticalSection> lock2(cur->mutex); + if (cur->signaled) + signaled = cur; + } + // ================================================== + + if (!signaled) + { + // both of these release the CEventGroup::mutex + if (duration == std::chrono::duration<Rep, Period>::max()) + actualCv.wait(mutex, [this]() { return signaled != nullptr; }); + else + actualCv.wait(mutex, duration, [this]() { return signaled != nullptr; }); + } // at this point the CEventGroup::mutex is reacquired + numWaits--; + + // signaled should have been set by a call to CEventGroup::Set + CEvent* ret = signaled; + if (numWaits == 0) + { + if (signaled) + // This acquires and releases the CEvent::mutex. This is fine since the + // CEventGroup::mutex is already being held + signaled->Wait(std::chrono::duration<Rep, Period>::zero()); // reset the event if needed + signaled = nullptr; // clear the signaled if all the waiters are gone + } + return ret; + } + + /** + * @brief This is mostly for testing. It allows a thread to make sure there are + * the right amount of other threads waiting. + * + */ + inline int getNumWaits() + { + std::unique_lock<CCriticalSection> lock(mutex); + return numWaits; + } +}; +} // namespace XbmcThreads |