summaryrefslogtreecommitdiffstats
path: root/xbmc/threads/Event.h
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/threads/Event.h')
-rw-r--r--xbmc/threads/Event.h239
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