blob: 119b2874b856ca7450c2934e07c85c43f80e918b [file] [log] [blame]
// Copyright 2019 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.
#include "components/performance_manager/public/frame_priority/frame_priority.h"
#include <cstring>
#include <utility>
namespace performance_manager {
namespace frame_priority {
int ReasonCompare(const char* reason1, const char* reason2) {
if (reason1 == reason2)
return 0;
if (reason1 == nullptr)
return -1;
if (reason2 == nullptr)
return 1;
return ::strcmp(reason1, reason2);
}
/////////////////////////////////////////////////////////////////////
// PriorityAndReason
int PriorityAndReason::Compare(const PriorityAndReason& other) const {
if (priority_ > other.priority_)
return 1;
if (priority_ < other.priority_)
return -1;
return ReasonCompare(reason_, other.reason_);
}
bool PriorityAndReason::operator==(const PriorityAndReason& other) const {
return Compare(other) == 0;
}
bool PriorityAndReason::operator!=(const PriorityAndReason& other) const {
return Compare(other) != 0;
}
bool PriorityAndReason::operator<=(const PriorityAndReason& other) const {
return Compare(other) <= 0;
}
bool PriorityAndReason::operator>=(const PriorityAndReason& other) const {
return Compare(other) >= 0;
}
bool PriorityAndReason::operator<(const PriorityAndReason& other) const {
return Compare(other) < 0;
}
bool PriorityAndReason::operator>(const PriorityAndReason& other) const {
return Compare(other) > 0;
}
/////////////////////////////////////////////////////////////////////
// Vote
Vote::Vote() = default;
Vote::Vote(const FrameNode* frame_node,
base::TaskPriority priority,
const char* reason)
: frame_node_(frame_node), priority_(priority), reason_(reason) {}
Vote::Vote(const Vote& rhs) = default;
Vote& Vote::operator=(const Vote& rhs) = default;
Vote::~Vote() = default;
bool Vote::operator==(const Vote& vote) const {
return frame_node_ == vote.frame_node_ && priority_ == vote.priority_ &&
::strcmp(reason_, vote.reason_) == 0;
}
bool Vote::operator!=(const Vote& vote) const {
return !(*this == vote);
}
bool Vote::IsValid() const {
return frame_node_ && reason_;
}
/////////////////////////////////////////////////////////////////////
// VoteReceipt
VoteReceipt::VoteReceipt() = default;
VoteReceipt::VoteReceipt(VoteReceipt&& rhs) {
Take(std::move(rhs));
}
VoteReceipt& VoteReceipt::operator=(VoteReceipt&& rhs) {
Take(std::move(rhs));
return *this;
}
VoteReceipt::~VoteReceipt() {
Reset();
}
bool VoteReceipt::HasVote() const {
return vote_;
}
bool VoteReceipt::HasVote(const AcceptedVote* vote) const {
return vote_ == vote;
}
VoteConsumer* VoteReceipt::GetConsumer() const {
return vote_->consumer();
}
VoterId VoteReceipt::GetVoterId() const {
return vote_->voter_id();
}
const Vote& VoteReceipt::GetVote() const {
return vote_->vote();
}
void VoteReceipt::ChangeVote(base::TaskPriority priority, const char* reason) {
DCHECK(vote_);
// Do nothing if the vote hasn't actually changed.
const auto& vote = vote_->vote();
if (vote.priority() == priority && vote.reason() == reason)
return;
*this = vote_->ChangeVote(std::move(*this), priority, reason);
}
void VoteReceipt::Reset() {
if (vote_) {
vote_->InvalidateVote(this);
vote_ = nullptr;
}
}
void VoteReceipt::MoveVote(AcceptedVote* old_vote, AcceptedVote* new_vote) {
DCHECK(old_vote);
DCHECK(new_vote);
DCHECK_EQ(vote_, old_vote);
vote_ = new_vote;
// The vote should already be associated with this receipt (as the vote
// initiated the move).
DCHECK(vote_->HasReceipt(this));
}
VoteReceipt::VoteReceipt(AcceptedVote* vote) : vote_(vote) {
// The vote should be valid and not be associated with any receipt.
DCHECK(vote->IsValid());
DCHECK(vote->HasReceipt(nullptr));
// Associate the vote with this newly issued receipt.
vote->SetReceipt(this);
}
void VoteReceipt::Take(VoteReceipt&& rhs) {
vote_ = rhs.vote_;
// Update the back-pointer from the vote.
if (vote_)
vote_->MoveReceipt(&rhs, this);
rhs.vote_ = nullptr;
}
/////////////////////////////////////////////////////////////////////
// AcceptedVote
AcceptedVote::AcceptedVote() = default;
AcceptedVote::AcceptedVote(VoteConsumer* consumer,
VoterId voter_id,
const Vote& vote)
: consumer_(consumer),
voter_id_(voter_id),
vote_(vote),
invalidated_(false) {
DCHECK(consumer);
DCHECK_NE(kInvalidVoterId, voter_id_);
DCHECK(vote.IsValid());
}
AcceptedVote::AcceptedVote(AcceptedVote&& rhs) {
Take(std::move(rhs));
}
AcceptedVote& AcceptedVote::operator=(AcceptedVote&& rhs) {
Take(std::move(rhs));
return *this;
}
AcceptedVote::~AcceptedVote() {
// A vote should not be destroyed while it has an outstanding receipt. It is
// up to Voters to destroy their votes when they go out of scope, and
// consumers must outlive voters.
DCHECK(!receipt_);
}
bool AcceptedVote::HasReceipt() const {
return receipt_;
}
bool AcceptedVote::HasReceipt(const VoteReceipt* receipt) const {
return receipt_ == receipt;
}
bool AcceptedVote::IsValid() const {
return consumer_ && voter_id_ != kInvalidVoterId && vote_.IsValid() &&
!invalidated_;
}
VoteReceipt AcceptedVote::IssueReceipt() {
return VoteReceipt(this);
}
void AcceptedVote::UpdateVote(const Vote& vote) {
DCHECK_EQ(vote_.frame_node(), vote.frame_node());
DCHECK(vote_.priority() != vote.priority() ||
vote_.reason() != vote.reason());
vote_ = vote;
}
void AcceptedVote::SetReceipt(VoteReceipt* receipt) {
// A receipt can only be set on a vote once in its lifetime.
DCHECK(!receipt_);
DCHECK(!invalidated_);
receipt_ = receipt;
// The receipt should already be associated with this vote (the association
// is initiated by the receipt).
DCHECK(receipt_->HasVote(this));
}
void AcceptedVote::MoveReceipt(VoteReceipt* old_receipt,
VoteReceipt* new_receipt) {
DCHECK(old_receipt);
DCHECK(new_receipt);
DCHECK_EQ(receipt_, old_receipt);
receipt_ = new_receipt;
// The receipt should already be associated with this vote (its calling for
// the move).
DCHECK(receipt_->HasVote(this));
}
VoteReceipt AcceptedVote::ChangeVote(VoteReceipt receipt,
base::TaskPriority priority,
const char* reason) {
DCHECK_EQ(receipt_, &receipt);
DCHECK(!invalidated_);
DCHECK(vote_.priority() != priority || vote_.reason() != reason);
// Explicitly save a copy of |vote_| as the consumer might overwrite it
// directly.
Vote old_vote = vote_;
// Notify the consumer of the new vote.
Vote new_vote = Vote(old_vote.frame_node(), priority, reason);
receipt = consumer_->ChangeVote(std::move(receipt), this, new_vote);
// Ensure that the returned receipt refers to a vote with the expected
// properties.
const Vote& returned_vote = receipt.GetVote();
DCHECK_EQ(new_vote.frame_node(), returned_vote.frame_node());
DCHECK_EQ(new_vote.priority(), returned_vote.priority());
DCHECK_EQ(new_vote.reason(), returned_vote.reason());
return receipt;
}
void AcceptedVote::InvalidateVote(VoteReceipt* receipt) {
DCHECK(receipt);
DCHECK_EQ(receipt_, receipt);
DCHECK(!invalidated_);
// Care has to be taken not to access |receipt|, as it is running its
// destructor. It is only passed as a parameter to ensure that the proper
// receipt is notifying this vote.
receipt_ = nullptr;
invalidated_ = true;
consumer_->VoteInvalidated(this);
}
void AcceptedVote::Take(AcceptedVote&& rhs) {
// An AcceptedVote can't be overwritten while it has a pending receipt.
DCHECK(!receipt_);
consumer_ = std::exchange(rhs.consumer_, nullptr);
voter_id_ = std::exchange(rhs.voter_id_, kInvalidVoterId);
vote_ = std::exchange(rhs.vote_, Vote());
receipt_ = std::exchange(rhs.receipt_, nullptr);
invalidated_ = std::exchange(rhs.invalidated_, true);
// Update the back-pointer from the receipt.
if (receipt_)
receipt_->MoveVote(&rhs, this);
}
/////////////////////////////////////////////////////////////////////
// VotingChannel
VotingChannel::VotingChannel() = default;
VotingChannel::VotingChannel(VotingChannel&& rhs) {
Take(std::move(rhs));
}
VotingChannel& VotingChannel::operator=(VotingChannel&& rhs) {
Take(std::move(rhs));
return *this;
}
VotingChannel::~VotingChannel() {
Reset();
}
VoteReceipt VotingChannel::SubmitVote(const Vote& vote) {
// Pass the vote along to the consumer with the bound |voter_id_|.
return factory_->consumer_->SubmitVote(voter_id_, vote);
}
bool VotingChannel::IsValid() const {
return factory_ && voter_id_ != kInvalidVoterId;
}
void VotingChannel::Reset() {
if (!factory_)
return;
DCHECK_NE(kInvalidVoterId, voter_id_);
factory_->OnVotingChannelDestroyed();
factory_ = nullptr;
voter_id_ = kInvalidVoterId;
}
VotingChannel::VotingChannel(VotingChannelFactory* factory, VoterId voter_id)
: factory_(factory), voter_id_(voter_id) {}
void VotingChannel::Take(VotingChannel&& rhs) {
Reset();
factory_ = std::exchange(rhs.factory_, nullptr);
voter_id_ = std::exchange(rhs.voter_id_, kInvalidVoterId);
}
/////////////////////////////////////////////////////////////////////
// VotingChannelFactory
VotingChannelFactory::VotingChannelFactory(VoteConsumer* consumer)
: consumer_(consumer) {
DCHECK(consumer);
}
VotingChannelFactory::~VotingChannelFactory() {
// We expect all voters to have severed their VotingChannels before we are
// torn down.
DCHECK_EQ(0u, voting_channels_outstanding_);
}
VotingChannel VotingChannelFactory::BuildVotingChannel() {
++voting_channels_outstanding_;
VoterId new_voter_id = ++voting_channels_issued_;
return VotingChannel(this, new_voter_id);
}
void VotingChannelFactory::OnVotingChannelDestroyed() {
DCHECK_LT(0u, voting_channels_outstanding_);
--voting_channels_outstanding_;
}
/////////////////////////////////////////////////////////////////////
// VoteConsumer
VoteConsumer::VoteConsumer() = default;
VoteConsumer::~VoteConsumer() = default;
/////////////////////////////////////////////////////////////////////
// VoteConsumerDefaultImpl
VoteConsumerDefaultImpl::VoteConsumerDefaultImpl() = default;
VoteConsumerDefaultImpl::~VoteConsumerDefaultImpl() = default;
VoteReceipt VoteConsumerDefaultImpl::ChangeVote(VoteReceipt receipt,
AcceptedVote* old_vote,
const Vote& new_vote) {
// The receipt and vote should be entangled, and the vote should be valid.
DCHECK(receipt.HasVote(old_vote));
DCHECK(old_vote->IsValid());
// Tear down the old vote before submitting a new one in order to prevent
// the voter from having 2 simultaneous votes for the same frame.
auto voter_id = receipt.GetVoterId();
receipt.Reset();
return SubmitVote(voter_id, new_vote);
}
} // namespace frame_priority
} // namespace performance_manager