// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_VIZ_SERVICE_SURFACES_SURFACE_MANAGER_H_
#define COMPONENTS_VIZ_SERVICE_SURFACES_SURFACE_MANAGER_H_

#include <stdint.h>

#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/features.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/scoped_observation_traits.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/types/expected.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/quads/compositor_frame_metadata.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/surface_id.h"
#include "components/viz/service/frame_sinks/frame_sink_observer.h"
#include "components/viz/service/surfaces/surface_observer.h"
#include "components/viz/service/surfaces/surface_reference.h"
#include "ui/latency/latency_info.h"

#if DCHECK_IS_ON()
#include <iosfwd>
#endif

namespace base {
class TickClock;
}  // namespace base

namespace viz {

class Surface;
class SurfaceAllocationGroup;
class SurfaceClient;
class SurfaceManagerDelegate;
class SurfaceRange;

class VIZ_SERVICE_EXPORT SurfaceManager {
 public:
  SurfaceManager(SurfaceManagerDelegate* delegate,
                 std::optional<uint32_t> activation_deadline_in_frames,
                 size_t max_uncommitted_frames);

  SurfaceManager(const SurfaceManager&) = delete;
  SurfaceManager& operator=(const SurfaceManager&) = delete;

  ~SurfaceManager();

#if DCHECK_IS_ON()
  // Returns a string representation of all reachable surface references.
  std::string SurfaceReferencesToString();
#endif

  // Sets an alternative system default frame activation deadline for unit
  // tests. std::nullopt indicates no deadline (in other words, an unlimited
  // deadline).
  void SetActivationDeadlineInFramesForTesting(
      std::optional<uint32_t> deadline);

  std::optional<uint32_t> activation_deadline_in_frames() const {
    return activation_deadline_in_frames_;
  }

  // Sets an alternative base::TickClock to pass into surfaces for surface
  // synchronization deadlines. This allows unit tests to mock the wall clock.
  void SetTickClockForTesting(const base::TickClock* tick_clock);

  // Returns the base::TickClock used to set surface synchronization deadlines.
  const base::TickClock* tick_clock() { return tick_clock_; }

  // Creates a Surface for the given SurfaceClient. The surface will be
  // destroyed when MarkSurfaceForDestruction is called, all of its destruction
  // dependencies are satisfied, and it is not reachable from the root surface.
  // A temporary reference will be added to the new Surface.
  base::expected<Surface*, std::string> CreateSurface(
      base::WeakPtr<SurfaceClient> surface_client,
      const SurfaceInfo& surface_info,
      const SurfaceId& pending_copy_surface_id);

  // Marks |surface_id| for destruction. The surface will get destroyed when
  // it's not reachable from the root or any other surface that is not marked
  // for destruction.
  void MarkSurfaceForDestruction(const SurfaceId& surface_id);

  // Returns a Surface corresponding to the provided |surface_id|.
  Surface* GetSurfaceForId(const SurfaceId& surface_id) const;

  void AddObserver(SurfaceObserver* obs) { observer_list_.AddObserver(obs); }

  void RemoveObserver(SurfaceObserver* obs) {
    observer_list_.RemoveObserver(obs);
  }

  // Called when a Surface is modified, e.g. when a CompositorFrame is
  // activated, its producer confirms that no CompositorFrame will be submitted
  // in response to a BeginFrame, or a CopyOutputRequest is issued.
  //
  // |ack.sequence_number| is only valid if called in response to a BeginFrame.
  bool SurfaceModified(const SurfaceId& surface_id,
                       const BeginFrameAck& ack,
                       SurfaceObserver::HandleInteraction handle_interaction,
                       const std::vector<ui::LatencyInfo>& latency_info = {});

  // Called when a surface has an active frame for the first time.
  void FirstSurfaceActivation(const SurfaceInfo& surface_info);

  // Called when there is new frame in uncommitted queue of the surface.
  void OnSurfaceHasNewUncommittedFrame(Surface* surface);

  // Called when a CompositorFrame within |surface| has activated.
  void SurfaceActivated(Surface* surface);

  // Called when |surface| is being destroyed.
  void SurfaceDestroyed(Surface* surface);

  // Called when a Surface's CompositorFrame producer has received a BeginFrame
  // and, thus, is expected to produce damage soon.
  void SurfaceDamageExpected(const SurfaceId& surface_id,
                             const BeginFrameArgs& args);

  // Invalidate a frame_sink_id that might still have associated sequences,
  // possibly because a renderer process has crashed.
  void InvalidateFrameSinkId(const FrameSinkId& frame_sink_id);

  // Returns the top level root SurfaceId. Surfaces that are not reachable
  // from the top level root may be garbage collected. It will not be a valid
  // SurfaceId and will never correspond to a surface.
  const SurfaceId& GetRootSurfaceId() const;

  // Returns SurfaceIds of currently alive Surfaces. This may include ids of
  // Surfaces that are about to be destroyed.
  std::vector<SurfaceId> GetCreatedSurfaceIds() const;

  // Adds all surface references in |references|. This will remove any temporary
  // references for child surface in a surface reference.
  void AddSurfaceReferences(const std::vector<SurfaceReference>& references);

  // Removes all surface references in |references| then runs garbage
  // collection to delete unreachable surfaces.
  void RemoveSurfaceReferences(const std::vector<SurfaceReference>& references);

  // Garbage collects all destroyed surfaces that aren't live.
  void GarbageCollectSurfaces();

  // Returns all surfaces referenced by parent |surface_id|. Will return an
  // empty set if |surface_id| is unknown or has no references.
  const base::flat_set<SurfaceId>& GetSurfacesReferencedByParent(
      const SurfaceId& surface_id) const;

  // Returns all surfaces that have a reference to child |surface_id|. Will
  // return an empty set if |surface_id| is unknown or has no references to it.
  base::flat_set<SurfaceId> GetSurfacesThatReferenceChildForTesting(
      const SurfaceId& surface_id) const;

  // Gets the earliest timestamp when the surface with ID `surface_id` gets
  // embedded through `AddSurfaceReferences()`, if it's already embedded.
  // Returns an empty base::TimeTicks() if the surface hasn't been embedded yet.
  base::TimeTicks GetSurfaceReferencedTimestamp(
      const SurfaceId& surface_id) const;

  // Returns the primary surface if it exists. Otherwise, this will return the
  // most recent surface in |surface_range|. If no surface exists, this will
  // return nullptr.
  Surface* GetLatestInFlightSurface(const SurfaceRange& surface_range);

  // Called by SurfaceAggregator notifying us that it will use |surface| in the
  // next display frame. We will notify SurfaceObservers accordingly.
  void SurfaceWillBeDrawn(Surface* surface);

  // Removes temporary reference to |surface_id| and older surfaces.
  void DropTemporaryReference(const SurfaceId& surface_id);

  // Returns the corresponding SurfaceAllocationGroup for |surface_id|. A
  // SurfaceAllocationGroup will be created for |surface_id| if one doesn't
  // exist yet. If there is already a SurfaceAllocationGroup that matches the
  // embed token of |surface_id| but its submitter doesn't match |surface_id|'s
  // FrameSinkId, nullptr will be returned. In any other case, the returned
  // value will always be a valid SurfaceAllocationGroup.
  SurfaceAllocationGroup* GetOrCreateAllocationGroupForSurfaceId(
      const SurfaceId& surface_id);

  // Similar to GetOrCreateAllocationGroupForSurfaceId, but will not attempt to
  // create the allocation group if it does not already exist.
  SurfaceAllocationGroup* GetAllocationGroupForSurfaceId(
      const SurfaceId& surface_id);

  // Called by allocation groups when they're ready to destroy and need garbage
  // collection.
  void SetAllocationGroupsNeedGarbageCollection();

  // Returns whether there is any surface blocked on a surface from
  // |frame_sink_id|.
  bool HasBlockedEmbedder(const FrameSinkId& frame_sink_id) const;

  // Indicates that the set of frame sinks being aggregated for display has
  // changed since the previous aggregation.
  void AggregatedFrameSinksChanged();

  // Add and Remove FrameSinkObserver
  void AddFrameSinkObserver(FrameSinkObserver* obs);
  void RemoveFrameSinkObserver(FrameSinkObserver* obs);

  // Checks whether FrameSinkManager has view `transition_token`.
  bool FrameSinkManagerHasViewTransitionToken(
      const blink::ViewTransitionToken& transition_token);

  using CommitPredicate =
      base::FunctionRef<bool(const SurfaceId&, const BeginFrameId&)>;
  // Commits all surfaces in range and their referenced surfaces. For each
  // surface processed calls `predicate` for each uncommitted frame from oldest
  // to newest. If predicate returns true, surface is committed. If not the
  // surface processing stops and we go to the next surface.
  // |range| is passed by value because CommitFramesRecursively can
  // synchronously activate a caller's pending frame, replacing
  // active_frame_data_ and freeing the referenced_surfaces vector that the
  // caller passed |range| out of.
  void CommitFramesInRangeRecursively(SurfaceRange range,
                                      const CommitPredicate& predicate);

 private:
  friend class CompositorFrameSinkSupportTestBase;
  friend class FrameSinkManagerTest;
  friend class HitTestAggregatorTest;
  friend class SurfaceSynchronizationTest;
  friend class SurfaceReferencesTest;
  friend class SurfaceSynchronizationTest;

  using SurfaceIdSet = std::unordered_set<SurfaceId, SurfaceIdHash>;

  // The reason for removing a temporary reference.
  // These values are persisted to logs. Entries should not be renumbered and
  // numeric values should never be reused.
  enum class RemovedReason {
    EMBEDDED = 0,  // The surface was embedded.
    DROPPED = 1,   // The surface won't be embedded so it was dropped.
    SKIPPED = 2,   // A newer surface was embedded and the surface was skipped.
    EXPIRED = 4,   // The surface was never embedded and expired.
    COPIED = 5,    // The surface was copied.
    COUNT
  };

  struct TemporaryReferenceData {
    // Used to track old surface references, will be marked as true on first
    // timer tick and will be true on second timer tick.
    bool marked_as_old = false;
  };

  // Returns set of surfaces that cannot be garbage-collected.
  SurfaceIdSet GetLiveSurfaces();

  // Adds a reference from |parent_id| to |child_id| without dealing with
  // temporary references.
  void AddSurfaceReferenceImpl(const SurfaceReference& reference);

  // Removes a reference from a |parent_id| to |child_id|.
  void RemoveSurfaceReferenceImpl(const SurfaceReference& reference);

  // Returns whether |surface_id| has a temporary reference or not.
  bool HasTemporaryReference(const SurfaceId& surface_id) const;

  // Adds a temporary reference to |surface_id|. The reference will not have an
  // owner initially.
  void AddTemporaryReference(const SurfaceId& surface_id);

  // Removes temporary reference to |surface_id| and older surfaces. The
  // |reason| for removing will be recorded with UMA.
  void RemoveTemporaryReferenceImpl(const SurfaceId& surface_id,
                                    RemovedReason reason);

  // Marks and then expires old temporary references. This function is run
  // periodically by a timer.
  void ExpireOldTemporaryReferences();

  // Removes the surface from the surface map and destroys it.
  void DestroySurfaceInternal(const SurfaceId& surface_id);

#if DCHECK_IS_ON()
  // Recursively prints surface references starting at |surface_id| to |str|.
  void SurfaceReferencesToStringImpl(const SurfaceId& surface_id,
                                     std::string indent,
                                     std::stringstream* str);
#endif

  // Returns true if |surface_id| is in the garbage collector's queue.
  bool IsMarkedForDestruction(const SurfaceId& surface_id);

  // Garbage-collects the allocation groups if they have signalled that they are
  // ready for destruction.
  void MaybeGarbageCollectAllocationGroups();

  // This returns true if early-acks for frame activation during interaction is
  // enabled and if the number of frames since ack and the last interactive
  // frame is below the cooldown threshold. This is only true for the Surfaces
  // which are not currently being interacted with.
  bool ShouldAckNonInteractiveFrame(const CompositorFrameMetadata& ack) const;

  // Can be nullptr.
  const raw_ptr<SurfaceManagerDelegate> delegate_;

  std::optional<uint32_t> activation_deadline_in_frames_;

  base::flat_map<base::UnguessableToken,
                 std::unique_ptr<SurfaceAllocationGroup>>
      embed_token_to_allocation_group_;
  base::flat_map<
      FrameSinkId,
      std::vector<raw_ptr<SurfaceAllocationGroup, VectorExperimental>>>
      frame_sink_id_to_allocation_groups_;
  base::flat_map<SurfaceId, std::unique_ptr<Surface>> surface_map_;
  // TODO(crbug.com/484371187): Investigate if reentrancy can be removed.
  base::ObserverList<
      SurfaceObserver,
      /*check_empty=*/false,
      base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>
      observer_list_;
  SEQUENCE_CHECKER(sequence_checker_);

  base::flat_map<SurfaceId, base::TimeTicks> surfaces_to_destroy_;

  // Root SurfaceId that references display root surfaces. There is no Surface
  // with this id, it's for bookkeeping purposes only.
  const SurfaceId root_surface_id_;

  // Always empty set that is returned when there is no entry in |references_|
  // for a SurfaceId.
  const base::flat_set<SurfaceId> empty_surface_id_set_;

  // Used for setting deadlines for surface synchronization.
  raw_ptr<const base::TickClock> tick_clock_;

  // Keeps track of surface references for a surface. The graph of references is
  // stored in parent to child direction. i.e the map stores all direct children
  // of the surface specified by |SurfaceId|.
  std::unordered_map<SurfaceId, base::flat_set<SurfaceId>, SurfaceIdHash>
      references_;

  // A map of surfaces that have temporary references.
  std::unordered_map<SurfaceId, TemporaryReferenceData, SurfaceIdHash>
      temporary_references_;

  // A map of pair(the timestamp of the first time a surface gets referenced,
  // the number of references that surface has).
  std::unordered_map<SurfaceId,
                     std::pair<base::TimeTicks, uint32_t>,
                     SurfaceIdHash>
      surface_referenced_timestamps_;

  // Range tracking information for temporary references. Each map entry is an
  // is an ordered list of SurfaceIds that have temporary references with the
  // same FrameSinkId. A SurfaceId can be reconstructed with:
  //   SurfaceId surface_id(key, value[index]);
  // The LocalSurfaceIds are stored in the order the surfaces are created in. If
  // a reference is added to a later SurfaceId then all temporary references up
  // to that point will be removed. This is to handle clients getting out of
  // sync, for example the embedded client producing new SurfaceIds faster than
  // the embedding client can use them.
  std::unordered_map<FrameSinkId, std::vector<LocalSurfaceId>, FrameSinkIdHash>
      temporary_reference_ranges_;

  std::optional<BeginFrameId> last_interactive_frame_;

  // Timer to remove old temporary references that aren't removed after an
  // interval of time. The timer will started/stopped so it only runs if there
  // are temporary references. Also the timer isn't used with Android WebView.
  std::optional<base::RepeatingTimer> expire_timer_;

  bool allocation_groups_need_garbage_collection_ = false;

  // Maximum length of uncommitted queue, zero means all frames are committed
  // automatically.
  const size_t max_uncommitted_frames_;

  std::optional<uint64_t>
      cooldown_frames_for_ack_on_activation_during_interaction_;
};

}  // namespace viz

namespace base {

template <>
struct ScopedObservationTraits<viz::SurfaceManager, viz::FrameSinkObserver> {
  static void AddObserver(viz::SurfaceManager* source,
                          viz::FrameSinkObserver* observer) {
    source->AddFrameSinkObserver(observer);
  }
  static void RemoveObserver(viz::SurfaceManager* source,
                             viz::FrameSinkObserver* observer) {
    source->RemoveFrameSinkObserver(observer);
  }
};

}  // namespace base

#endif  // COMPONENTS_VIZ_SERVICE_SURFACES_SURFACE_MANAGER_H_
