summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2019-12-09 11:37:36 -0500
committerNat Goodspeed <nat@lindenlab.com>2020-03-25 15:28:17 -0400
commit960593fd5eedcc63632fe4e0e3b71ac4afc91d11 (patch)
tree163b9529522195e9a87cbb699639a6fec221f369
parent69fbe647abe3942ced02f14f0adc477630d9dd7e (diff)
DRTVWR-494: Add LLMainThreadTask to perform work on the main thread.
If already running on the main thread, LLMaintThreadTask simply runs the work inline. Otherwise it queues it for the main thread using LLEventTimer, using std::future to retrieve the result.
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rw-r--r--indra/llcommon/llmainthreadtask.cpp22
-rw-r--r--indra/llcommon/llmainthreadtask.h102
3 files changed, 125 insertions, 0 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 98e1c00ce3..55c44446b4 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -189,6 +189,7 @@ set(llcommon_HEADER_FILES
lllistenerwrapper.h
llliveappconfig.h
lllivefile.h
+ llmainthreadtask.h
llmd5.h
llmemory.h
llmemorystream.h
diff --git a/indra/llcommon/llmainthreadtask.cpp b/indra/llcommon/llmainthreadtask.cpp
new file mode 100644
index 0000000000..e0d70cacd8
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.cpp
@@ -0,0 +1,22 @@
+/**
+ * @file llmainthreadtask.cpp
+ * @author Nat Goodspeed
+ * @date 2019-12-05
+ * @brief Implementation for llmainthreadtask.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llmainthreadtask.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+
+// This file is required by our CMake integration-test machinery. It
+// contributes no code to the viewer executable.
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h
new file mode 100644
index 0000000000..526374981a
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.h
@@ -0,0 +1,102 @@
+/**
+ * @file llmainthreadtask.h
+ * @author Nat Goodspeed
+ * @date 2019-12-04
+ * @brief LLMainThreadTask dispatches work to the main thread. When invoked on
+ * the main thread, it performs the work inline.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLMAINTHREADTASK_H)
+#define LL_LLMAINTHREADTASK_H
+
+#include "lleventtimer.h"
+#include "llthread.h"
+#include "lockstatic.h"
+#include "llmake.h"
+#include <future>
+#include <type_traits> // std::result_of
+#include <boost/signals2/dummy_mutex.hpp>
+
+class LLMainThreadTask
+{
+private:
+ // Don't instantiate this class -- use dispatch() instead.
+ LLMainThreadTask() {}
+ // If our caller doesn't explicitly pass a LockStatic<something>, make a
+ // fake one.
+ struct Static
+ {
+ boost::signals2::dummy_mutex mMutex;
+ };
+ typedef llthread::LockStatic<Static> LockStatic;
+
+public:
+ /// dispatch() is the only way to invoke this functionality.
+ /// If you call it with a LockStatic<something>, dispatch() unlocks it
+ /// before blocking for the result.
+ template <typename Static, typename CALLABLE>
+ static auto dispatch(llthread::LockStatic<Static>& lk, CALLABLE&& callable)
+ -> decltype(callable())
+ {
+ if (on_main_thread())
+ {
+ // we're already running on the main thread, perfect
+ return callable();
+ }
+ else
+ {
+ // It's essential to construct LLEventTimer subclass instances on
+ // the heap because, on completion, LLEventTimer deletes them.
+ // Once we enable C++17, we can use Class Template Argument
+ // Deduction. Until then, use llmake_heap().
+ auto* task = llmake_heap<Task>(std::forward<CALLABLE>(callable));
+ // The moment we construct a new LLEventTimer subclass object, its
+ // tick() method might get called. However, its tick() method
+ // might depend on something locked by the passed LockStatic.
+ // Unlock it so tick() can proceed.
+ lk.unlock();
+ auto future = task->mTask.get_future();
+ // Now simply block on the future.
+ return future.get();
+ }
+ }
+
+ /// You can call dispatch() without a LockStatic<something>.
+ template <typename CALLABLE>
+ static auto dispatch(CALLABLE&& callable) -> decltype(callable())
+ {
+ LockStatic lk;
+ return dispatch(lk, std::forward<CALLABLE>(callable));
+ }
+
+private:
+ template <typename CALLABLE>
+ struct Task: public LLEventTimer
+ {
+ Task(CALLABLE&& callable):
+ // no wait time: call tick() next chance we get
+ LLEventTimer(0),
+ mTask(std::forward<CALLABLE>(callable))
+ {}
+ BOOL tick() override
+ {
+ // run the task on the main thread, will populate the future
+ // obtained by get_future()
+ mTask();
+ // tell LLEventTimer we're done (one shot)
+ return TRUE;
+ }
+ // Given arbitrary CALLABLE, which might be a lambda, how are we
+ // supposed to obtain its signature for std::packaged_task? It seems
+ // redundant to have to add an argument list to engage result_of, then
+ // add the argument list again to complete the signature. At least we
+ // only support a nullary CALLABLE.
+ std::packaged_task<typename std::result_of<CALLABLE()>::type()> mTask;
+ };
+};
+
+#endif /* ! defined(LL_LLMAINTHREADTASK_H) */