| // Copyright 2023 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef NINJA_ASYNC_LOOP_TIMERS_H |
| #define NINJA_ASYNC_LOOP_TIMERS_H |
| |
| #include <algorithm> |
| #include <vector> |
| |
| #include "async_loop.h" |
| |
| class AsyncTimer::State { |
| public: |
| State(AsyncLoop& async_loop, AsyncTimer::Callback&& callback) |
| : async_loop_(async_loop), callback_(std::move(callback)) { |
| async_loop_.AttachTimer(this); |
| } |
| |
| ~State() { async_loop_.DetachTimer(this); } |
| |
| AsyncLoop& async_loop() const { return async_loop_; } |
| |
| // Return true if this instance is active. |
| bool active() const { return expiration_ms_ >= 0; } |
| |
| int64_t expiration_ms() const { return expiration_ms_; } |
| |
| void ResetCallback(AsyncTimer::Callback&& callback) { |
| callback_ = std::move(callback); |
| } |
| |
| void SetExpirationMs(int64_t expiration_ms) { |
| expiration_ms_ = expiration_ms; |
| async_loop_.UpdateTimer(this); |
| } |
| |
| void SetDurationMs(int64_t duration_ms) { |
| SetExpirationMs(async_loop_.NowMs() + duration_ms); |
| } |
| |
| void Cancel() { SetExpirationMs(-1); } |
| |
| void Invoke() { |
| expiration_ms_ = -1; |
| callback_(); |
| } |
| |
| private: |
| AsyncLoop& async_loop_; |
| int64_t expiration_ms_ = -1; |
| AsyncTimer::Callback callback_; |
| }; |
| |
| /// Common class used by all AsyncLoop implementation to manage timers. |
| class AsyncLoopTimers { |
| public: |
| void AttachTimer(AsyncTimer::State* timer) { timers_.push_back(timer); } |
| |
| void DetachTimer(AsyncTimer::State* timer) { |
| pending_.erase(timer); |
| timers_.erase(timer); |
| } |
| |
| void UpdateTimer(AsyncTimer::State* timer) { pending_.erase(timer); } |
| |
| /// Compute the next expiration time in milliseconds, or return -1 if |
| /// not timer is active. |
| int64_t ComputeNextExpiration() const { |
| int64_t result = -1; |
| for (const auto* timer : timers_) { |
| if (!timer->active()) |
| continue; |
| if (result < 0) |
| result = timer->expiration_ms(); |
| else if (result > timer->expiration_ms()) |
| result = timer->expiration_ms(); |
| } |
| return result; |
| } |
| |
| /// Compute the set of pending timers at a given time. |
| /// Return true if any timer expired, false otherwise. |
| bool ProcessExpiration(int64_t now_ms) { |
| // First compute the list of pending timers. |
| pending_.clear(); |
| for (auto* timer : timers_) { |
| if (timer->active() && timer->expiration_ms() <= now_ms) |
| pending_.push_back(timer); |
| } |
| |
| if (pending_.empty()) |
| return false; |
| |
| // Then process it. Each callback can end up calling ResetExpiration() |
| // or Destroy() which will remove or deactivate pending timers so do |
| // not assume that each id in the list is valid on each iteration. |
| do { |
| AsyncTimer::State* state = pending_.back(); |
| pending_.pop_back(); |
| state->Invoke(); |
| } while (!pending_.empty()); |
| |
| return true; |
| } |
| |
| // Return true if there are no timers in this set. |
| bool empty() const { return timers_.empty(); } |
| |
| private: |
| struct TimerList : public std::vector<AsyncTimer::State*> { |
| void erase(AsyncTimer::State* state) { |
| auto it = std::find(begin(), end(), state); |
| if (it != end()) { |
| *it = back(); |
| pop_back(); |
| } |
| } |
| }; |
| |
| TimerList timers_; |
| TimerList pending_; |
| }; |
| |
| #endif // NINJA_ASYNC_LOOP_TIMERS_H |