summaryrefslogtreecommitdiff
path: root/src/mbgl/util/thread.hpp
blob: d05748a0035df0ddedb86445355fc97fa387b723 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#ifndef MBGL_UTIL_THREAD
#define MBGL_UTIL_THREAD

#include <future>
#include <thread>
#include <atomic>
#include <utility>
#include <functional>

#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/thread_context.hpp>
#include <mbgl/platform/platform.hpp>

namespace mbgl {
namespace util {

// Manages a thread with Object.

// Upon creation of this object, it launches a thread, creates an object of type Object in that
// thread, and then calls .start(); on that object. When the Thread<> object is destructed, the
// Object's .stop() function is called, and the destructor waits for thread termination. The
// Thread<> constructor blocks until the thread and the Object are fully created, so after the
// object creation, it's safe to obtain the Object stored in this thread.

template <class Object>
class Thread {
public:
    template <class... Args>
    Thread(const ThreadContext&, Args&&... args);
    ~Thread();

    // Invoke object->fn(args...) in the runloop thread.
    template <typename Fn, class... Args>
    void invoke(Fn fn, Args&&... args) {
        loop->invoke(bind(fn), std::forward<Args>(args)...);
    }

    // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread.
    template <typename Fn, class Cb, class... Args>
    std::unique_ptr<WorkRequest>
    invokeWithCallback(Fn fn, Cb&& callback, Args&&... args) {
        return loop->invokeWithCallback(bind(fn), callback, std::forward<Args>(args)...);
    }

    // Invoke object->fn(args...) in the runloop thread, and wait for the result.
    template <class R, typename Fn, class... Args>
    R invokeSync(Fn fn, Args&&... args) {
        std::packaged_task<R ()> task(std::bind(fn, object, args...));
        std::future<R> future = task.get_future();
        loop->invoke(std::move(task));
        return future.get();
    }

    // Invoke object->fn(args...) in the runloop thread, and wait for it to complete.
    template <typename Fn, class... Args>
    void invokeSync(Fn fn, Args&&... args) {
        std::packaged_task<void ()> task(std::bind(fn, object, args...));
        std::future<void> future = task.get_future();
        loop->invoke(std::move(task));
        future.get();
    }

private:
    Thread(const Thread&) = delete;
    Thread(Thread&&) = delete;
    Thread& operator=(const Thread&) = delete;
    Thread& operator=(Thread&&) = delete;

    template <typename Fn>
    auto bind(Fn fn) {
        return [fn, this] (auto &&... args) {
            return (object->*fn)(std::forward<decltype(args)>(args)...);
        };
    }

    template <typename P, std::size_t... I>
    void run(ThreadContext, P&& params, std::index_sequence<I...>);

    std::promise<void> running;
    std::promise<void> joinable;

    std::thread thread;

    Object* object = nullptr;
    RunLoop* loop = nullptr;
};

template <class Object>
template <class... Args>
Thread<Object>::Thread(const ThreadContext& context, Args&&... args) {
    // Note: We're using std::tuple<> to store the arguments because GCC 4.9 has a bug
    // when expanding parameters packs captured in lambdas.
    std::tuple<Args...> params = std::forward_as_tuple(::std::forward<Args>(args)...);

    thread = std::thread([&] {
        #if defined( __APPLE__)
        pthread_setname_np(context.name.c_str());
        #elif defined(__linux__)
        pthread_setname_np(pthread_self(), context.name.c_str());
        #endif

        if (context.priority == ThreadPriority::Low) {
            platform::makeThreadLowPriority();
        }

        run(context, std::move(params), std::index_sequence_for<Args...>{});
    });

    running.get_future().get();
}

template <class Object>
template <typename P, std::size_t... I>
void Thread<Object>::run(ThreadContext context, P&& params, std::index_sequence<I...>) {
    ThreadContext::Set(&context);

    RunLoop loop_(RunLoop::Type::New);
    loop = &loop_;

    Object object_(std::get<I>(std::forward<P>(params))...);
    object = &object_;

    running.set_value();
    loop_.run();

    loop = nullptr;
    object = nullptr;

    ThreadContext::Set(nullptr);

    joinable.get_future().get();
}

template <class Object>
Thread<Object>::~Thread() {
    loop->stop();
    joinable.set_value();
    thread.join();
}

} // namespace util
} // namespace mbgl

#endif