blob: c7ef585f87656a407d5dcca5e14febe53d27964c [file] [log] [blame]
Andy Hungce9b6632020-04-28 20:15:17 -07001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <android-base/thread_annotations.h>
20#include <chrono>
21#include <map>
22#include <mutex>
23#include <thread>
24
25namespace android::mediametrics {
26
27class TimedAction {
28public:
29 TimedAction() : mThread{[this](){threadLoop();}} {}
30
31 ~TimedAction() {
32 quit();
33 }
34
35 // TODO: return a handle for cancelling the action?
36 template <typename T> // T is in units of std::chrono::duration.
37 void postIn(const T& time, std::function<void()> f) {
38 postAt(std::chrono::steady_clock::now() + time, f);
39 }
40
41 template <typename T> // T is in units of std::chrono::time_point
42 void postAt(const T& targetTime, std::function<void()> f) {
43 std::lock_guard l(mLock);
44 if (mQuit) return;
45 if (mMap.empty() || targetTime < mMap.begin()->first) {
46 mMap.emplace_hint(mMap.begin(), targetTime, std::move(f));
47 mCondition.notify_one();
48 } else {
49 mMap.emplace(targetTime, std::move(f));
50 }
51 }
52
53 void clear() {
54 std::lock_guard l(mLock);
55 mMap.clear();
56 }
57
58 void quit() {
59 {
60 std::lock_guard l(mLock);
61 if (mQuit) return;
62 mQuit = true;
63 mMap.clear();
64 mCondition.notify_all();
65 }
66 mThread.join();
67 }
68
69 size_t size() const {
70 std::lock_guard l(mLock);
71 return mMap.size();
72 }
73
74private:
75 void threadLoop() NO_THREAD_SAFETY_ANALYSIS { // thread safety doesn't cover unique_lock
76 std::unique_lock l(mLock);
77 while (!mQuit) {
78 auto sleepUntilTime = std::chrono::time_point<std::chrono::steady_clock>::max();
79 if (!mMap.empty()) {
80 sleepUntilTime = mMap.begin()->first;
81 if (sleepUntilTime <= std::chrono::steady_clock::now()) {
82 auto node = mMap.extract(mMap.begin()); // removes from mMap.
83 l.unlock();
84 node.mapped()();
85 l.lock();
86 continue;
87 }
88 }
89 mCondition.wait_until(l, sleepUntilTime);
90 }
91 }
92
93 mutable std::mutex mLock;
94 std::condition_variable mCondition GUARDED_BY(mLock);
95 bool mQuit GUARDED_BY(mLock) = false;
96 std::multimap<std::chrono::time_point<std::chrono::steady_clock>, std::function<void()>>
97 mMap GUARDED_BY(mLock); // multiple functions could execute at the same time.
98
99 // needs to be initialized after the variables above, done in constructor initializer list.
100 std::thread mThread;
101};
102
103} // namespace android::mediametrics