summaryrefslogtreecommitdiff
path: root/src/mbgl/util/thread.hpp
blob: e97872a5027c2e7fe2f736a2d58a619be4c03116 (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#ifndef MBGL_UTIL_THREAD
#define MBGL_UTIL_THREAD

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

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

namespace {

template <::std::size_t...>
struct index_sequence {};

template <::std::size_t N, ::std::size_t... I>
struct integer_sequence : integer_sequence<N - 1, N - 1, I...> {};

template <::std::size_t... I>
struct integer_sequence<0, I...> {
    using type = index_sequence<I...>;
};

}

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.

enum class ThreadPriority : bool {
    Regular,
    Low,
};

template <class Object>
class Thread {
public:
    template <class... Args>
    Thread(const std::string& name, ThreadPriority priority, Args&&... args);
    ~Thread();

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

    // Invoke object->fn(args...) in the runloop thread, then invoke callback(result) in the current thread.
    template <typename Fn, class R, class... Args>
    void invokeWithResult(Fn fn, std::function<void (R)>&& callback, Args&&... args) {
        loop->invokeWithResult(std::bind(fn, object, args...), std::move(callback));
    }

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

    // 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));
        return future.get();
    }

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

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

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

    std::thread thread;

    Object* object;
    RunLoop* loop;
};

template <class Object>
template <class... Args>
Thread<Object>::Thread(const std::string& name, ThreadPriority priority, 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([&] {
        #ifdef __APPLE__
        pthread_setname_np(name.c_str());
        #else
        (void(name));
        #endif

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

        constexpr auto seq = typename integer_sequence<sizeof...(Args)>::type();
        run(std::move(params), seq);
    });

    running.get_future().get();
}

template <class Object>
template <typename P, std::size_t... I>
void Thread<Object>::run(P&& params, index_sequence<I...>) {
    uv::loop l;

    {
        RunLoop loop_(l.get());
        loop = &loop_;

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

        running.set_value();
        l.run();

        loop = nullptr;
        object = nullptr;
    }

    // Run the loop again to ensure that async close callbacks have been called.
    l.run();

    joinable.get_future().get();
}

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

}
}

#endif