1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164

cc / paint / skottie_serialization_history.cc [blame]

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

#include "cc/paint/skottie_serialization_history.h"

#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "cc/paint/skottie_wrapper.h"

namespace cc {

SkottieSerializationHistory::SkottieFrameDataId::SkottieFrameDataId(
    const SkottieFrameData& frame_data)
    : paint_image_id(frame_data.image ? frame_data.image.stable_id()
                                      : PaintImage::kInvalidId),
      quality(frame_data.quality) {}

bool SkottieSerializationHistory::SkottieFrameDataId::operator==(
    const SkottieFrameDataId& other) const {
  return paint_image_id == other.paint_image_id && quality == other.quality;
}

bool SkottieSerializationHistory::SkottieFrameDataId::operator!=(
    const SkottieFrameDataId& other) const {
  return !(*this == other);
}

SkottieSerializationHistory::SkottieWrapperHistory::SkottieWrapperHistory(
    const SkottieFrameDataMap& initial_images,
    const SkottieTextPropertyValueMap& initial_text_map)
    : accumulated_text_map_(initial_text_map) {
  for (const auto& image_asset_pair : initial_images) {
    DVLOG(1) << "Received initial image for asset " << image_asset_pair.first;
    last_frame_data_per_asset_.emplace(
        /*asset id*/ image_asset_pair.first,
        SkottieFrameDataId(image_asset_pair.second));
  }
}

SkottieSerializationHistory::SkottieWrapperHistory::SkottieWrapperHistory(
    const SkottieWrapperHistory& other) = default;

SkottieSerializationHistory::SkottieWrapperHistory&
SkottieSerializationHistory::SkottieWrapperHistory::operator=(
    const SkottieWrapperHistory& other) = default;

SkottieSerializationHistory::SkottieWrapperHistory::~SkottieWrapperHistory() =
    default;

void SkottieSerializationHistory::SkottieWrapperHistory::FilterNewState(
    SkottieFrameDataMap& images,
    SkottieTextPropertyValueMap& text_map) {
  ++current_sequence_id_;
  FilterNewFrameImages(images);
  FilterNewTextPropertyValues(text_map);
}

void SkottieSerializationHistory::SkottieWrapperHistory::FilterNewFrameImages(
    SkottieFrameDataMap& images) {
  auto images_iter = images.begin();
  while (images_iter != images.end()) {
    const SkottieResourceIdHash& asset_id = images_iter->first;
    const SkottieFrameData& frame_data = images_iter->second;
    SkottieFrameDataId new_frame_data_id(frame_data);
    auto [result_iterator, is_new_insertion] =
        last_frame_data_per_asset_.emplace(asset_id, new_frame_data_id);
    SkottieFrameDataId& existing_frame_data_id = result_iterator->second;

    bool asset_has_updated_frame_data =
        is_new_insertion || existing_frame_data_id != new_frame_data_id;
    if (asset_has_updated_frame_data) {
      DVLOG(1) << "New image available for asset " << asset_id;
      existing_frame_data_id = std::move(new_frame_data_id);
      ++images_iter;
    } else {
      DVLOG(4) << "No update to image for asset" << asset_id;
      images_iter = images.erase(images_iter);
    }
  }
}

void SkottieSerializationHistory::SkottieWrapperHistory::
    FilterNewTextPropertyValues(SkottieTextPropertyValueMap& text_map_in) {
  auto text_map_in_iter = text_map_in.begin();
  while (text_map_in_iter != text_map_in.end()) {
    const SkottieResourceIdHash& node = text_map_in_iter->first;
    const SkottieTextPropertyValue& new_text_property_val =
        text_map_in_iter->second;
    auto [accumulated_iter, is_new_insertion] =
        accumulated_text_map_.insert(*text_map_in_iter);
    SkottieTextPropertyValue& old_text_property_val = accumulated_iter->second;
    if (!is_new_insertion && old_text_property_val == new_text_property_val) {
      DVLOG(4) << "No update to text property value for node" << node;
      text_map_in_iter = text_map_in.erase(text_map_in_iter);
    } else {
      DVLOG(1) << "New text available for node " << node;
      old_text_property_val = new_text_property_val;
      ++text_map_in_iter;
    }
  }
}

SkottieSerializationHistory::SkottieSerializationHistory(int purge_period)
    : purge_period_(purge_period) {}

SkottieSerializationHistory::~SkottieSerializationHistory() = default;

void SkottieSerializationHistory::FilterNewSkottieFrameState(
    const SkottieWrapper& skottie,
    SkottieFrameDataMap& images,
    SkottieTextPropertyValueMap& text_map) {
  DCHECK(skottie.is_valid());
  base::AutoLock lock(mutex_);
  auto [result_iterator, is_new_insertion] =
      history_per_animation_.try_emplace(skottie.id(), images, text_map);
  if (is_new_insertion) {
    DVLOG(1) << "Encountered new SkottieWrapper with id " << skottie.id()
             << " and " << images.size() << " images";
  } else {
    SkottieWrapperHistory& skottie_history_found = result_iterator->second;
    skottie_history_found.FilterNewState(images, text_map);
  }
}

void SkottieSerializationHistory::RequestInactiveAnimationsPurge() {
  base::AutoLock lock(mutex_);
  // Since RequestInactiveAnimationsPurge() is called frequently in a
  // time-sensitive part of the code and purging stale history is not an urgent
  // operation, only do a purge check once in a while. (Even then, a purge check
  // actually isn't that expensive)
  //
  // If there is some odd corner case where a Skottie animation's history gets
  // purged while it is still active somehow, user functionality will not be
  // broken. The animation's history will just be recreated with a clean slate
  // the next time its state is registered with this class.
  ++purge_period_counter_;
  if (purge_period_counter_ < purge_period_)
    return;

  purge_period_counter_ = 0;
  auto animation_history_iter = history_per_animation_.begin();
  while (animation_history_iter != history_per_animation_.end()) {
    SkottieWrapperHistory& skottie_wrapper_history =
        animation_history_iter->second;
    if (skottie_wrapper_history.current_sequence_id() ==
        skottie_wrapper_history.sequence_id_at_last_purge_check()) {
      DVLOG(1) << "Purging Skottie animation with id "
               << animation_history_iter->first
               << ". No update to animation's state since last purge check.";
      animation_history_iter =
          history_per_animation_.erase(animation_history_iter);
    } else {
      skottie_wrapper_history.update_sequence_id_at_last_purge_check();
      ++animation_history_iter;
    }
  }
}

}  // namespace cc