blob: 9310c2d0e411e9473f9a5abae243e1c16a84c3f0 [file]
// Copyright 2025 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_UI_SCENIC_LIB_DISPLAY_INTERNAL_CHECK_CONFIG_CACHE_H_
#define SRC_UI_SCENIC_LIB_DISPLAY_INTERNAL_CHECK_CONFIG_CACHE_H_
#include <lib/syslog/cpp/macros.h>
#include <cstddef>
#include <functional>
#include <list>
#include <unordered_map>
#include "src/ui/scenic/lib/display/internal/display_equivalence.h"
namespace display::internal {
// Helper for `class BoundedLruCache`, to allow the std::unordered_map key to be a reference.
template <typename T>
struct ReferenceHasher {
// Not necessary for `CheckConfigCache`, but useful if the key type should allow heteregeneous
// lookup, e.g. both `const char*` and `std::string`.
using is_transparent = void;
size_t operator()(std::reference_wrapper<const T> key) const { return std::hash<T>{}(key.get()); }
size_t operator()(const T& key) const { return std::hash<T>{}(key); }
};
// Helper for `class BoundedLruCache`, to allow the std::unordered_map key to be a reference.
template <typename T>
struct ReferenceEquality {
// Not necessary for `CheckConfigCache`, but useful if the key type should allow heteregeneous
// lookup, e.g. both `const char*` and `std::string`.
using is_transparent = void;
bool operator()(std::reference_wrapper<const T> lhs, std::reference_wrapper<const T> rhs) const {
return lhs.get() == rhs.get();
}
bool operator()(std::reference_wrapper<const T> lhs, const T& rhs) const {
return lhs.get() == rhs;
}
};
// `BoundedLruCache` implements a bounded-size cache where the least recently used entry is evicted
// when a new one is added. Accessing an existing entry, either by `Get()` or setting a new value
// with `Put()`, causes that entry to become the most recently used.
//
// This implementation is optimized for cases where the keys are quite large compared to the values
// (although it will work fine in the opposite case, too). A naive implementation would store two
// copies of each key: one in `map_` for lookup and one in `lru_list_` for eviction. Instead, the
// key exists only in `lru_list_`, and `map_` is keyed by a reference to that key.
//
// Both the key and value types must be copy-constructable.
//
// Thread-safety: This class is thread-unsafe; concurrent access must be externally synchronized.
template <typename K, typename V>
class BoundedLruCache {
public:
using Key = K;
using Value = V;
static_assert(std::is_copy_constructible_v<Key>);
static_assert(std::is_copy_constructible_v<Value>);
// Public so `Iterator` can be public.
struct CacheNode {
const Key key;
Value value;
};
// Allows iteration of cache entries in MRU order; see `begin()`, `end()`.
using Iterator = typename std::list<CacheNode>::const_iterator;
explicit BoundedLruCache(size_t capacity) : capacity_(capacity) {
FX_CHECK(capacity_ > 0) << capacity_;
}
// Not moveable, not copyable.
BoundedLruCache(const BoundedLruCache& other) = delete;
BoundedLruCache(BoundedLruCache&& other) = delete;
BoundedLruCache& operator=(const BoundedLruCache& other) = delete;
BoundedLruCache& operator=(BoundedLruCache&& other) = delete;
void Put(const Key& key, const Value& value) {
auto map_it = map_.find(key);
// Key already exists: update value and move to front.
if (map_it != map_.end()) {
map_it->second->value = value;
lru_list_.splice(lru_list_.begin(), lru_list_, map_it->second);
return;
}
// Key is new. Check for capacity and evict if necessary.
if (lru_list_.size() == capacity_) {
// The key to evict is in the node at the back of the list.
const Key& lru_key = lru_list_.back().key;
bool evicted_lru = map_.erase(lru_key);
FX_DCHECK(evicted_lru);
lru_list_.pop_back();
}
// Insert the new element at the front of the list
lru_list_.push_front({key, value});
// The key now lives in lru_list_.begin()->key.
// Insert a reference to that key into the map.
map_.emplace(std::cref(lru_list_.begin()->key), lru_list_.begin());
}
std::optional<Value> Get(const Key& key) {
auto map_it = map_.find(key);
if (map_it == map_.end()) {
return std::nullopt;
}
// Move the accessed node to the front of the list
lru_list_.splice(lru_list_.begin(), lru_list_, map_it->second);
return map_it->second->value;
}
// Iterators in MRU (most recently used) order.
Iterator begin() const { return lru_list_.begin(); }
Iterator end() const { return lru_list_.end(); }
size_t size() const {
FX_DCHECK(map_.size() == lru_list_.size());
return map_.size();
}
private:
using MapKey = std::reference_wrapper<const Key>;
using LruListIterator = typename std::list<CacheNode>::iterator;
using HashMap =
std::unordered_map<MapKey, LruListIterator, ReferenceHasher<Key>, ReferenceEquality<Key>>;
const size_t capacity_;
std::list<CacheNode> lru_list_;
HashMap map_;
};
// Caches the results of `fuchsia.hardware.display.Coordinator/CheckConfig()`, so that it is
// unnecessary to make subsequent calls for equivalent configs (using the notion of equivalence
// defined by `DisplayEquivalence`). Keeps track of which "equivs" were used most recently, in
// order to maintain a maximum cache size by trimming equivs that have not been used for a while.
using CheckConfigCache = BoundedLruCache<DisplayEquivalence, bool>;
} // namespace display::internal
#endif // SRC_UI_SCENIC_LIB_DISPLAY_INTERNAL_CHECK_CONFIG_CACHE_H_