blob: f5173bcdb686be30e2d546b2857cdaeba637ebf5 [file] [log] [blame]
// Copyright 2014 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 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 PullDeltaToMainThread (which happens right
// before the commit).
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() {
reflected_delta_in_main_tree_ = PendingDelta();
return reflected_delta_in_main_tree_;
}
// 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_ = 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;
}
// This simulates the consequences of the sent value getting committed and
// activated.
void AbortCommit() {
pending_base_ = T::ApplyDelta(pending_base_, reflected_delta_in_main_tree_);
active_base_ = T::ApplyDelta(active_base_, reflected_delta_in_main_tree_);
active_delta_ =
T::DeltaBetweenDeltas(active_delta_, reflected_delta_in_main_tree_);
reflected_delta_in_main_tree_ = T::IdentityDelta();
}
// 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_);
}
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();
// The value sent to the main thread on the last BeginMainFrame. This is
// always identity outside of the BeginMainFrame to (aborted)commit interval.
DeltaT 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; }
};
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; }
};
} // namespace cc
#endif // CC_BASE_SYNCED_PROPERTY_H_