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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// OneShotTimer and RepeatingTimer provide a simple timer API. As the names
// suggest, OneShotTimer calls you back once after a time delay expires.
// RepeatingTimer on the other hand calls you back periodically with the
// prescribed time interval.
//
// OneShotTimer and RepeatingTimer both cancel the timer when they go out of
// scope, which makes it easy to ensure that you do not get called when your
// object has gone out of scope. Just instantiate a OneShotTimer or
// RepeatingTimer as a member variable of the class for which you wish to
// receive timer events.
//
// Sample RepeatingTimer usage:
//
// class MyClass {
// public:
// void StartDoingStuff() {
// timer_.Start(TimeDelta::FromSeconds(1), this, &MyClass::DoStuff);
// }
// void StopDoingStuff() {
// timer_.Stop();
// }
// private:
// void DoStuff() {
// // This method is called every second to do stuff.
// ...
// }
// base::RepeatingTimer<MyClass> timer_;
// };
//
// Both OneShotTimer and RepeatingTimer also support a Reset method, which
// allows you to easily defer the timer event until the timer delay passes once
// again. So, in the above example, if 0.5 seconds have already passed,
// calling Reset on timer_ would postpone DoStuff by another 1 second. In
// other words, Reset is shorthand for calling Stop and then Start again with
// the same arguments.
#ifndef BASE_TIMER_H_
#define BASE_TIMER_H_
// IMPORTANT: If you change timer code, make sure that all tests (including
// disabled ones) from timer_unittests.cc pass locally. Some are disabled
// because they're flaky on the buildbot, but when you run them locally you
// should be able to tell the difference.
#include "base/logging.h"
#include "base/task.h"
#include "base/time.h"
class MessageLoop;
namespace base {
//-----------------------------------------------------------------------------
// This class is an implementation detail of OneShotTimer and RepeatingTimer.
// Please do not use this class directly.
//
// This class exists to share code between BaseTimer<T> template instantiations.
//
class BaseTimer_Helper {
public:
// Stops the timer.
~BaseTimer_Helper() {
OrphanDelayedTask();
}
// Returns true if the timer is running (i.e., not stopped).
bool IsRunning() const {
return !!delayed_task_;
}
// Returns the current delay for this timer. May only call this method when
// the timer is running!
TimeDelta GetCurrentDelay() const {
DCHECK(IsRunning());
return delayed_task_->delay_;
}
protected:
BaseTimer_Helper() {}
// We have access to the timer_ member so we can orphan this task.
class TimerTask : public mozilla::Runnable {
public:
explicit TimerTask(TimeDelta delay) : delay_(delay) {
// timer_ is set in InitiateDelayedTask.
}
virtual ~TimerTask() {}
BaseTimer_Helper* timer_;
TimeDelta delay_;
};
// Used to orphan delayed_task_ so that when it runs it does nothing.
void OrphanDelayedTask();
// Used to initiated a new delayed task. This has the side-effect of
// orphaning delayed_task_ if it is non-null.
void InitiateDelayedTask(TimerTask* timer_task);
RefPtr<TimerTask> delayed_task_;
DISALLOW_COPY_AND_ASSIGN(BaseTimer_Helper);
};
//-----------------------------------------------------------------------------
// This class is an implementation detail of OneShotTimer and RepeatingTimer.
// Please do not use this class directly.
template <class Receiver, bool kIsRepeating>
class BaseTimer : public BaseTimer_Helper {
public:
typedef void (Receiver::*ReceiverMethod)();
// Call this method to start the timer. It is an error to call this method
// while the timer is already running.
void Start(TimeDelta delay, Receiver* receiver, ReceiverMethod method) {
DCHECK(!IsRunning());
InitiateDelayedTask(new TimerTask(delay, receiver, method));
}
// Call this method to stop the timer. It is a no-op if the timer is not
// running.
void Stop() {
OrphanDelayedTask();
}
// Call this method to reset the timer delay of an already running timer.
void Reset() {
DCHECK(IsRunning());
InitiateDelayedTask(static_cast<TimerTask*>(delayed_task_.get())->Clone());
}
private:
typedef BaseTimer<Receiver, kIsRepeating> SelfType;
class TimerTask : public BaseTimer_Helper::TimerTask {
public:
TimerTask(TimeDelta delay, Receiver* receiver, ReceiverMethod method)
: BaseTimer_Helper::TimerTask(delay),
receiver_(receiver),
method_(method) {
}
virtual ~TimerTask() {
// This task may be getting cleared because the MessageLoop has been
// destructed. If so, don't leave the Timer with a dangling pointer
// to this now-defunct task.
ClearBaseTimer();
}
NS_IMETHOD Run() override {
if (!timer_) // timer_ is null if we were orphaned.
return NS_OK;
if (kIsRepeating)
ResetBaseTimer();
else
ClearBaseTimer();
DispatchToMethod(receiver_, method_, Tuple0());
return NS_OK;
}
TimerTask* Clone() const {
return new TimerTask(delay_, receiver_, method_);
}
private:
// Inform the Base that the timer is no longer active.
void ClearBaseTimer() {
if (timer_) {
SelfType* self = static_cast<SelfType*>(timer_);
// It is possible that the Timer has already been reset, and that this
// Task is old. So, if the Timer points to a different task, assume
// that the Timer has already taken care of properly setting the task.
if (self->delayed_task_ == this)
self->delayed_task_ = nullptr;
// By now the delayed_task_ in the Timer does not point to us anymore.
// We should reset our own timer_ because the Timer can not do this
// for us in its destructor.
timer_ = NULL;
}
}
// Inform the Base that we're resetting the timer.
void ResetBaseTimer() {
DCHECK(timer_);
DCHECK(kIsRepeating);
SelfType* self = static_cast<SelfType*>(timer_);
self->Reset();
}
Receiver* receiver_;
ReceiverMethod method_;
};
};
//-----------------------------------------------------------------------------
// A simple, one-shot timer. See usage notes at the top of the file.
template <class Receiver>
class OneShotTimer : public BaseTimer<Receiver, false> {};
//-----------------------------------------------------------------------------
// A simple, repeating timer. See usage notes at the top of the file.
template <class Receiver>
class RepeatingTimer : public BaseTimer<Receiver, true> {};
//-----------------------------------------------------------------------------
// A Delay timer is like The Button from Lost. Once started, you have to keep
// calling Reset otherwise it will call the given method in the MessageLoop
// thread.
//
// Once created, it is inactive until Reset is called. Once |delay| seconds have
// passed since the last call to Reset, the callback is made. Once the callback
// has been made, it's inactive until Reset is called again.
//
// If destroyed, the timeout is canceled and will not occur even if already
// inflight.
template <class Receiver>
class DelayTimer {
public:
typedef void (Receiver::*ReceiverMethod)();
DelayTimer(TimeDelta delay, Receiver* receiver, ReceiverMethod method)
: receiver_(receiver),
method_(method),
delay_(delay) {
}
void Reset() {
DelayFor(delay_);
}
private:
void DelayFor(TimeDelta delay) {
trigger_time_ = Time::Now() + delay;
// If we already have a timer that will expire at or before the given delay,
// then we have nothing more to do now.
if (timer_.IsRunning() && timer_.GetCurrentDelay() <= delay)
return;
// The timer isn't running, or will expire too late, so restart it.
timer_.Stop();
timer_.Start(delay, this, &DelayTimer<Receiver>::Check);
}
void Check() {
if (trigger_time_.is_null())
return;
// If we have not waited long enough, then wait some more.
const Time now = Time::Now();
if (now < trigger_time_) {
DelayFor(trigger_time_ - now);
return;
}
(receiver_->*method_)();
}
Receiver *const receiver_;
const ReceiverMethod method_;
const TimeDelta delay_;
OneShotTimer<DelayTimer<Receiver> > timer_;
Time trigger_time_;
};
} // namespace base
#endif // BASE_TIMER_H_
|