// Copyright 2018 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.

#ifndef UI_LATENCY_SKIPPED_FRAME_TRACKER_H_
#define UI_LATENCY_SKIPPED_FRAME_TRACKER_H_

#include "base/macros.h"
#include "base/time/time.h"

namespace ui {

// SkippedFrameTracker tracks skipped BeginFrames. It can be used by sources
// attempting to produce at the display rate. It properly handles
// non-consecutive BeginFrames and tracks when the source is actualy trying to
// produce, rather than passively receiving BeginFrames.
class SkippedFrameTracker {
 public:
  // SkippedFrameTracker calls Client::AddFrameProduced from FinishFrame
  // when necessary and with the correct values.
  class Client {
   public:
    virtual ~Client() = default;
    virtual void AddFrameProduced(base::TimeTicks source_timestamp,
                                  base::TimeDelta amount_produced,
                                  base::TimeDelta amount_skipped) = 0;
  };

  // SkippedFrameTracker will call |client|->AddFrameProduced
  // with the appropriate info automatically as frames are produced.
  explicit SkippedFrameTracker(Client* client);

  // BeginFrame and FinishFrame must be called for each BeginFrame received.
  // In order for this class to detect idle periods properly, the source must
  // call Begin+FinishFrame without calling WillProduceFrame before going
  // idle. This is necessary since there is otherwise no way to tell if a
  // non-consecutive BeginFrame occured a) because we were slow or b) because
  // we weren't trying to produce a frame.
  void BeginFrame(base::TimeTicks frame_time, base::TimeDelta interval);
  void FinishFrame();

  // WillProduceFrame should be called when the source knows it wants to
  // produce a frame. DidProduceFrame should be called when the source has
  // actually submitted the frame. WillNotProduceFrame should be called when
  // the source knows if doesn't need a new frame.
  // It is okay for DidProduceFrame to be called without WillProduceFrame,
  // which can happen in cases where a frame is "pulled" from later in the
  // pipeline rather than pushed from the source. Such calls to DidProduceFrame
  // will be ignored.
  void WillProduceFrame();
  void DidProduceFrame();
  void WillNotProduceFrame();

 protected:
  Client* client_;

  bool inside_begin_frame_ = false;
  base::TimeTicks frame_time_;
  base::TimeDelta interval_;
  bool did_produce_this_frame_ = false;

  base::TimeTicks will_produce_frame_time_;

  enum class ActiveState {
    // Idle: The initial and idle state.
    // Goto WillProduceFirst on 1st call to WillProduceFrame.
    Idle,
    // WillProduceFirst: Producing the first frame out of idle.
    // Goto WasActive on first FinishFrame after a DidProduceFrame.
    // Counts missing BeginFrames as skipped: NO.
    WillProduceFirst,
    // WillProduce: Producing the (N > 1)'th frame of constant activity.
    // Goto WasActive on first FinishFrame after a DidProduceFrame.
    // Counts missing BeginFrames as skipped: YES.
    WillProduce,
    // WasActive: An intermediate state to determine if we are idle or not.
    // Goto WillProduce on WillProduceFrame.
    // Otherwise, goto Idle on next FinishFrame.
    WasActive,
  };
  ActiveState active_state_ = ActiveState::Idle;

  DISALLOW_COPY_AND_ASSIGN(SkippedFrameTracker);
};

}  // namespace ui

#endif  // UI_LATENCY_SKIPPED_FRAME_TRACKER_H_
