summaryrefslogtreecommitdiff
path: root/include/mbgl/util/work_task_impl.hpp
blob: a2f55e00c554025025a3b9c1c24ab83c48c15b38 (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
#pragma once

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

#include <mutex>

namespace mbgl {

template <class F, class P>
class WorkTaskImpl : public WorkTask {
public:
    WorkTaskImpl(F&& f, P&& p, std::shared_ptr<std::atomic<bool>> canceled_)
      : canceled(std::move(canceled_)),
        func(std::move(f)),
        params(std::move(p)) {
    }

    void operator()() override {
        // Lock the mutex while processing so that cancel() will block.
        std::lock_guard<std::recursive_mutex> lock(mutex);
        if (!*canceled) {
            invoke(std::make_index_sequence<std::tuple_size<P>::value>{});
        }
    }

    // If the task has not yet begun, this will cancel it.
    // If the task is in progress, this will block until it completed. (Currently
    // necessary because of shared state, but should be removed.) It will also
    // cancel the after callback.
    // If the task has completed, but the after callback has not executed, this
    // will cancel the after callback.
    // If the task has completed and the after callback has executed, this will
    // do nothing.
    void cancel() override {
        std::lock_guard<std::recursive_mutex> lock(mutex);
        *canceled = true;
    }

private:
    template <std::size_t... I>
    void invoke(std::index_sequence<I...>) {
        func(std::move(std::get<I>(std::forward<P>(params)))...);
    }

    std::recursive_mutex mutex;
    std::shared_ptr<std::atomic<bool>> canceled;

    F func;
    P params;
};

template <class Fn, class... Args>
std::shared_ptr<WorkTask> WorkTask::make(Fn&& fn, Args&&... args) {
    auto flag = std::make_shared<std::atomic<bool>>();
    *flag = false;

    auto tuple = std::make_tuple(std::move(args)...);
    return std::make_shared<WorkTaskImpl<Fn, decltype(tuple)>>(
        std::move(fn),
        std::move(tuple),
        flag);
}

namespace detail {
template <class Tuple, size_t... Indexes>
auto packageArgumentsAndCallback(std::shared_ptr<std::atomic<bool>> flag,
                                 Tuple&& args,
                                 std::index_sequence<Indexes...>) {
    auto callback = std::get<sizeof...(Indexes)>(args);

    // Create a lambda L1 that invokes another lambda L2 on the current RunLoop R, that calls
    // the callback C. Both lambdas check the flag before proceeding. L1 needs to check the flag
    // because if the request was cancelled, then R might have been destroyed. L2 needs to check
    // the flag because the request may have been cancelled after L2 was invoked but before it
    // began executing.
    auto after = [flag, current = util::RunLoop::Get(), callback1 = std::move(callback)] (auto&&... results1) {
        if (!*flag) {
            current->invoke([flag, callback2 = std::move(callback1)] (auto&&... results2) {
                if (!*flag) {
                    callback2(std::move(results2)...);
                }
            }, std::move(results1)...);
        }
    };

    return std::make_tuple(std::move(std::get<Indexes>(args))..., after);
}
} // namespace detail

template <class Fn, class... Args>
std::shared_ptr<WorkTask> WorkTask::makeWithCallback(Fn&& fn, Args&&... args) {
    auto flag = std::make_shared<std::atomic<bool>>();
    *flag = false;

    auto tuple = detail::packageArgumentsAndCallback(flag,
        std::forward_as_tuple(std::forward<Args>(args)...),
        std::make_index_sequence<sizeof...(Args) - 1>());

    return std::make_shared<WorkTaskImpl<Fn, decltype(tuple)>>(
        std::move(fn),
        std::move(tuple),
        flag);
}

} // namespace mbgl