// 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 "third_party/blink/renderer/modules/webcodecs/codec_pressure_gauge.h"

#include "build/build_config.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"

#if !BUILDFLAG(IS_WIN)
#define USE_SHARED_INSTANCE
#endif

// These numbers were picked as roughly 1/4th of the empirical lower limit at
// which we start getting errors when allocating new codecs. Some platforms have
// a decoder limit and an encoder limit, whereas others have a common shared
// limit. These estimates are conservative, but they take into account the fact
// that the true limits are OS-wide, while these thresholds are per-process. It
// also takes into account that we never actually gate codec creation, and we
// only vary the eagerness with which we will try to reclaim codecs instead.
#if BUILDFLAG(IS_WIN)
constexpr int kDecoderPressureThreshold = 6;
constexpr int kEncoderPressureThreshold = 0;
#elif BUILDFLAG(IS_CHROMEOS)
constexpr int kSharedPressureThreshold = 3;
#elif BUILDFLAG(IS_MAC)
constexpr int kSharedPressureThreshold = 24;
#elif BUILDFLAG(IS_ANDROID)
constexpr int kSharedPressureThreshold = 4;
#else
// By default (e.g. for Linux, Fuschia, Chromecast...), any codec with pressure
// should be reclaimable, regardless of global presure.
constexpr int kSharedPressureThreshold = 0;
#endif

namespace blink {

CodecPressureGauge::CodecPressureGauge(size_t pressure_threshold)
    : pressure_threshold_(pressure_threshold) {}

// static
CodecPressureGauge& CodecPressureGauge::GetInstance(
    ReclaimableCodec::CodecType type) {
#if defined(USE_SHARED_INSTANCE)
  return SharedInstance();
#else
  switch (type) {
    case ReclaimableCodec::CodecType::kDecoder:
      return DecoderInstance();
    case ReclaimableCodec::CodecType::kEncoder:
      return EncoderInstance();
  }
#endif
}

#if defined(USE_SHARED_INSTANCE)
// static
CodecPressureGauge& CodecPressureGauge::SharedInstance() {
  DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecPressureGauge, shared_instance,
                                  (kSharedPressureThreshold));

  return shared_instance;
}
#else
// static
CodecPressureGauge& CodecPressureGauge::DecoderInstance() {
  DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecPressureGauge, decoder_instance,
                                  (kDecoderPressureThreshold));

  return decoder_instance;
}

// static
CodecPressureGauge& CodecPressureGauge::EncoderInstance() {
  DEFINE_THREAD_SAFE_STATIC_LOCAL(CodecPressureGauge, encoder_instance,
                                  (kEncoderPressureThreshold));

  return encoder_instance;
}
#endif

#undef USE_SHARED_INSTANCE

void CodecPressureGauge::Increment() {
  base::AutoLock locker(lock_);
  DCHECK(pressure_callbacks_.size());

  ++global_pressure_;

  CheckForThresholdChanges_Locked();
}

void CodecPressureGauge::Decrement() {
  base::AutoLock locker(lock_);
  DCHECK(pressure_callbacks_.size());

  DCHECK(global_pressure_);
  --global_pressure_;

  CheckForThresholdChanges_Locked();
}

std::pair<CodecPressureGauge::PressureCallbackId, bool>
CodecPressureGauge::RegisterPressureCallback(
    PressureThresholdChangedCallback pressure_callback) {
  base::AutoLock locker(lock_);
  PressureCallbackId id = next_pressure_callback_id_++;

  auto result = pressure_callbacks_.insert(id, std::move(pressure_callback));
  DCHECK(result.is_new_entry);

  return std::make_pair(id, global_pressure_exceeded_);
}

void CodecPressureGauge::UnregisterPressureCallback(
    PressureCallbackId callback_id,
    size_t pressure_released) {
  base::AutoLock locker(lock_);

  DCHECK(pressure_callbacks_.Contains(callback_id));
  pressure_callbacks_.erase(callback_id);

  DCHECK_GE(global_pressure_, pressure_released);
  global_pressure_ -= pressure_released;

  // Make sure we still have callbacks left if we have leftover pressure.
  DCHECK(!global_pressure_ || pressure_callbacks_.size());

  CheckForThresholdChanges_Locked();
}

void CodecPressureGauge::CheckForThresholdChanges_Locked() {
  lock_.AssertAcquired();

  bool pressure_exceeded = global_pressure_ > pressure_threshold_;

  if (pressure_exceeded == global_pressure_exceeded_)
    return;

  global_pressure_exceeded_ = pressure_exceeded;

  // Notify all callbacks of pressure threshold changes.
  // Note: we normally should make a copy of |pressure_callbacks_| and release
  // |lock_|, to avoid deadlocking on reentrant calls. However, the only
  // callbacks registered are from CodecPressureManagers, which do not
  // reentranly call into this class.
  for (auto& callback : pressure_callbacks_.Values())
    callback.Run(global_pressure_exceeded_);
}

}  // namespace blink
