910e62b5创建于 1月15日历史提交
// 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 CC_BASE_SYNCED_PROPERTY_H_
#define CC_BASE_SYNCED_PROPERTY_H_

#include "base/memory/ref_counted.h"

namespace cc {

// This class is the basic primitive used for impl-thread scrolling.  Its job is
// to sanely resolve the case where both the main and impl thread are
// concurrently updating the same value (for example, when Javascript sets the
// scroll offset during an ongoing impl-side scroll).
//
// There are three trees (main, pending, and active) and therefore also three
// places with their own idea of the scroll offsets (and analogous properties
// like page scale).  Objects of this class are meant to be held on the Impl
// side, and contain the canonical reference for the pending and active trees,
// as well as keeping track of the latest delta sent to the main thread (which
// is necessary for conflict resolution).

template <typename T>
class SyncedProperty : public base::RefCounted<SyncedProperty<T>> {
 public:
  using BaseT = typename T::BaseType;
  using DeltaT = typename T::DeltaType;

  // Returns the canonical value for the specified tree, including the sum of
  // all deltas.  The pending tree should use this for activation purposes and
  // the active tree should use this for drawing.
  BaseT Current(bool is_active_tree) const {
    if (is_active_tree)
      return T::ApplyDelta(active_base_, active_delta_);
    return T::ApplyDelta(pending_base_, PendingDelta());
  }

  // Sets the value on the impl thread, due to an impl-thread-originating
  // action.  Returns true if this had any effect.  This will remain
  // impl-thread-only information at first, and will get pulled back to the main
  // thread on the next call of PullDeltaForMainThread.
  bool SetCurrent(BaseT current) {
    DeltaT delta = T::DeltaBetweenBases(current, active_base_);
    if (active_delta_ == delta)
      return false;

    active_delta_ = delta;
    return true;
  }

  // Returns the difference between the last value that was committed and
  // activated from the main thread, and the current total value.
  DeltaT Delta() const { return active_delta_; }

  // Returns the latest active tree delta and also makes a note that this value
  // was sent to the main thread.
  DeltaT PullDeltaForMainThread(bool next_bmf) {
    DeltaT& target = next_bmf ? next_reflected_delta_in_main_tree_
                              : reflected_delta_in_main_tree_;
    DCHECK_EQ(target, T::IdentityDelta());
    target = UnsentDelta();
    return target;
  }

  // Push the latest value from the main thread onto pending tree-associated
  // state. Returns true if pushing the value results in different values
  // between the main layer tree and the pending tree.
  bool PushMainToPending(BaseT main_thread_value) {
    reflected_delta_in_pending_tree_ = reflected_delta_in_main_tree_;
    reflected_delta_in_main_tree_ = next_reflected_delta_in_main_tree_;
    next_reflected_delta_in_main_tree_ = T::IdentityDelta();
    pending_base_ = main_thread_value;

    return Current(false) != main_thread_value;
  }

  // Push the value associated with the pending tree to be the active base
  // value. As part of this, subtract the delta reflected in the pending tree
  // from the active tree delta (which will make the delta zero at steady state,
  // or make it contain only the difference since the last send).
  // Returns true if pushing the update results in:
  // 1) Different values on the pending tree and the active tree.
  // 2) An update to the current value on the active tree.
  // The reason for considering the second case only when pushing to the active
  // tree, as opposed to when pushing to the pending tree, is that only the
  // active tree computes state using this value which is not computed on the
  // pending tree and not pushed during activation (aka scrollbar geometries).
  bool PushPendingToActive() {
    BaseT pending_value_before_push = Current(false);
    BaseT active_value_before_push = Current(true);

    active_base_ = pending_base_;
    active_delta_ = PendingDelta();
    reflected_delta_in_pending_tree_ = T::IdentityDelta();
    clobber_active_value_ = false;

    BaseT current_active_value = Current(true);
    return pending_value_before_push != current_active_value ||
           active_value_before_push != current_active_value;
  }

  void AbortCommit(bool next_bmf, bool main_frame_applied_deltas) {
    // Finish processing the delta that was sent to the main thread, and reset
    // the corresponding the delta_in_main_tree_ variable. If
    // main_frame_applied_deltas is true, we send the delta on to the active
    // tree just as would happen for a successful commit. Otherwise, we treat
    // the delta as never having been sent to the main thread and just drop it.
    if (next_bmf) {
      // The previous main frame has not yet run commit; the aborted main frame
      // corresponds to the delta in the "next" slot (if any).  In this case, if
      // the main thread processed the delta from this aborted commit we can
      // simply add the delta to reflected_delta_in_main_tree_.
      if (main_frame_applied_deltas) {
        reflected_delta_in_main_tree_ = T::CombineDeltas(
            reflected_delta_in_main_tree_, next_reflected_delta_in_main_tree_);
      }
      next_reflected_delta_in_main_tree_ = T::IdentityDelta();
    } else {
      // There is no "next" main frame, this abort was for the primary.
      if (main_frame_applied_deltas) {
        DeltaT delta = reflected_delta_in_main_tree_;
        // This simulates the consequences of the sent value getting committed
        // and activated.
        pending_base_ = T::ApplyDelta(pending_base_, delta);
        active_base_ = T::ApplyDelta(active_base_, delta);
        active_delta_ = T::DeltaBetweenDeltas(active_delta_, delta);
      }
      reflected_delta_in_main_tree_ = T::IdentityDelta();
    }
  }

  // Values sent to the main thread and not yet resolved in the pending or
  // active tree.
  DeltaT reflected_delta_in_main_tree() const {
    return reflected_delta_in_main_tree_;
  }
  DeltaT next_reflected_delta_in_main_tree() const {
    return next_reflected_delta_in_main_tree_;
  }
  // Values as last pushed to the pending or active tree respectively, with no
  // impl-thread delta applied.
  BaseT PendingBase() const { return pending_base_; }
  BaseT ActiveBase() const { return active_base_; }

  // The new delta we would use if we decide to activate now.  This delta
  // excludes the amount that we know is reflected in the pending tree.
  DeltaT PendingDelta() const {
    if (clobber_active_value_)
      return T::IdentityDelta();

    return T::DeltaBetweenDeltas(active_delta_,
                                 reflected_delta_in_pending_tree_);
  }

  DeltaT UnsentDelta() const {
    return T::DeltaBetweenDeltas(PendingDelta(), reflected_delta_in_main_tree_);
  }

  void set_clobber_active_value() { clobber_active_value_ = true; }

 private:
  friend class base::RefCounted<SyncedProperty<T>>;
  ~SyncedProperty() = default;

  // Value last committed to the pending tree.
  BaseT pending_base_ = T::IdentityBase();
  // Value last committed to the active tree on the last activation.
  BaseT active_base_ = T::IdentityBase();
  // The difference between |active_base_| and the user-perceived value.
  DeltaT active_delta_ = T::IdentityDelta();
  // A value sent to the main thread on a BeginMainFrame, but not yet applied to
  // the resulting pending tree.
  DeltaT reflected_delta_in_main_tree_ = T::IdentityDelta();
  // A value sent to the main thread on a BeginMainFrame at a time when
  // the previous BeginMainFrame is in the ready-to-commit state.
  DeltaT next_reflected_delta_in_main_tree_ = T::IdentityDelta();
  // The value that was sent to the main thread for BeginMainFrame for the
  // current pending tree. This is always identity outside of the
  // BeginMainFrame to activation interval.
  DeltaT reflected_delta_in_pending_tree_ = T::IdentityDelta();
  // When true the pending delta is always identity so that it does not change
  // and will clobber the active value on push.
  bool clobber_active_value_ = false;
};

// SyncedProperty's delta-based conflict resolution logic makes sense for any
// mathematical group.  In practice, there are two that are useful:
// 1. Numbers/classes with addition and subtraction operations, and
//    identity = constructor() (like gfx::Vector2dF for scroll offset and
//    scroll delta)
// 2. Real numbers with multiplication and division operations, and
//    identity = 1 (like page scale)

template <typename BaseT, typename DeltaT = BaseT>
class AdditionGroup {
 public:
  using BaseType = BaseT;
  using DeltaType = DeltaT;
  static constexpr BaseT IdentityBase() { return BaseT(); }
  static constexpr DeltaT IdentityDelta() { return DeltaT(); }
  static BaseT ApplyDelta(BaseT v, DeltaT delta) { return v + delta; }
  static DeltaT DeltaBetweenBases(BaseT v1, BaseT v2) { return v1 - v2; }
  static DeltaT DeltaBetweenDeltas(DeltaT d1, DeltaT d2) { return d1 - d2; }
  static DeltaT CombineDeltas(DeltaT d1, DeltaT d2) { return d1 + d2; }
};

class ScaleGroup {
 public:
  using BaseType = float;
  using DeltaType = float;
  static constexpr float IdentityBase() { return 1.f; }
  static constexpr float IdentityDelta() { return 1.f; }
  static float ApplyDelta(float v, float delta) { return v * delta; }
  static float DeltaBetweenBases(float v1, float v2) { return v1 / v2; }
  static float DeltaBetweenDeltas(float d1, float d2) { return d1 / d2; }
  static float CombineDeltas(float d1, float d2) { return d1 * d2; }
};

}  // namespace cc

#endif  // CC_BASE_SYNCED_PROPERTY_H_