blob: dac94d87f37747af9ac74e891f6dcb80e0ec662c [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 SRC_MEDIA_AUDIO_LIB_PROCESSING_COEFFICIENT_TABLE_CACHE_H_
#define SRC_MEDIA_AUDIO_LIB_PROCESSING_COEFFICIENT_TABLE_CACHE_H_
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <map>
#include <memory>
#include <mutex>
#include <utility>
#include "src/lib/fxl/synchronization/thread_annotations.h"
#include "src/media/audio/lib/processing/coefficient_table.h"
namespace media_audio {
// A cache of `CoefficientTables`. These use a lot of memory so we try to reuse them as much as
// possible. For example, different filters might use the same underlying coefficient table with
// slightly different filter parameters. Additionally, different samplers might use the same filter.
//
// `InputT` defines the set of inputs that are used to construct the CoefficientTable.
template <class InputT>
class CoefficientTableCache {
public:
// Thread-safe reference-counted pointer to a cached `CoefficientTable`.
// This is like a `std::shared_ptr`, except the destructor runs atomically with a `Get` call to
// simplify cache garbage collection.
class SharedPtr {
public:
SharedPtr() = default;
SharedPtr(SharedPtr&& r) noexcept : ptr_(r.ptr_), drop_(std::move(r.drop_)) {
r.ptr_ = nullptr;
}
~SharedPtr() {
if (ptr_) {
drop_();
}
}
SharedPtr(const SharedPtr& r) = delete;
SharedPtr& operator=(const SharedPtr& r) = delete;
SharedPtr& operator=(SharedPtr&& r) noexcept {
if (ptr_) {
drop_();
}
ptr_ = r.ptr_;
drop_ = std::move(r.drop_);
r.ptr_ = nullptr;
return *this;
}
CoefficientTable* get() const { return ptr_; }
explicit operator bool() const { return ptr_ != nullptr; }
private:
friend class CoefficientTableCache<InputT>;
SharedPtr(CoefficientTable* p, fit::function<void()> drop) : ptr_(p), drop_(std::move(drop)) {
FX_CHECK(p);
}
CoefficientTable* ptr_{nullptr};
fit::function<void()> drop_;
};
explicit CoefficientTableCache(fit::function<CoefficientTable*(const InputT&)> create_table)
: create_table_(std::move(create_table)) {}
// Returns a cached table for the given inputs, or if a cached tabled does not exist, a new table
// is created and stored in the cache.
SharedPtr Get(InputT inputs) {
return AddEntry(inputs, [this, inputs]() { return create_table_(inputs); });
}
// Similar to `Get`, but uses the given table rather than creating a new one.
SharedPtr Add(InputT inputs, CoefficientTable* table) {
return AddEntry(inputs, [table]() { return table; });
}
private:
SharedPtr AddEntry(InputT inputs, fit::function<CoefficientTable*()> create_table) {
// Don't use a locker here so we can release mutex_ before creating a new table.
// This allows multiple threads to create tables concurrently.
mutex_.lock();
// `std::map` guarantees that iterators are not invalidated until erased.
// We hold onto this iterator until the reference is dropped.
auto lookup_result = cache_.insert(std::make_pair(inputs, std::make_unique<Entry>()));
auto it = lookup_result.first;
std::lock_guard<std::mutex> entry_locker(it->second->mutex);
mutex_.unlock();
it->second->ref_cnt++;
if (!it->second->table) {
FX_DCHECK(lookup_result.second);
FX_CHECK(it->second->ref_cnt == 1);
it->second->table = create_table();
} else {
FX_DCHECK(!lookup_result.second);
}
return SharedPtr(it->second->table, [this, it]() {
std::lock_guard<std::mutex> cache_locker(mutex_);
it->second->mutex.lock();
it->second->ref_cnt--;
if (it->second->ref_cnt == 0) {
delete it->second->table;
it->second->mutex.unlock();
cache_.erase(it);
} else {
it->second->mutex.unlock();
}
});
}
struct Entry {
std::mutex mutex;
size_t ref_cnt FXL_GUARDED_BY(mutex) = 0;
CoefficientTable* table FXL_GUARDED_BY(mutex) = nullptr;
};
std::mutex mutex_;
std::map<InputT, std::unique_ptr<Entry>> cache_ FXL_GUARDED_BY(mutex_);
fit::function<CoefficientTable*(InputT)> create_table_;
};
// `LazySharedCoefficientTable` is a wrapper around `CoefficientTables` that are constructed lazily.
// This is a simple way to construct a `CoefficientTable` table in any thread (such as the FIDL loop
// thread) but delay the potentially-expensive step of building the table until the table is
// actually needed, possibly on another thread.
template <class InputT>
class LazySharedCoefficientTable {
public:
using CacheT = CoefficientTableCache<InputT>;
LazySharedCoefficientTable(CacheT* cache, InputT inputs) : cache_(cache), inputs_(inputs) {}
LazySharedCoefficientTable(const LazySharedCoefficientTable&) = delete;
LazySharedCoefficientTable& operator=(const LazySharedCoefficientTable&) = delete;
CoefficientTable* get() {
if (unlikely(!ptr_)) {
ptr_ = cache_->Get(inputs_);
}
return ptr_.get();
}
CoefficientTable& operator*() { return *get(); }
private:
CacheT* cache_;
InputT inputs_;
typename CacheT::SharedPtr ptr_;
};
} // namespace media_audio
#endif // SRC_MEDIA_AUDIO_LIB_PROCESSING_COEFFICIENT_TABLE_CACHE_H_