blob: f008b61a6f5cb24647536b49c644c34edfdb0587 [file] [log] [blame]
// Copyright 2015 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.
import 'image_stream.dart';
const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 10485760; // 10 MiB
/// Class for the [imageCache] object.
///
/// Implements a least-recently-used cache of up to 1000 images. The maximum
/// size can be adjusted using [maximumSize]. Images that are actively in use
/// (i.e. to which the application is holding references, either via
/// [ImageStream] objects, [ImageStreamCompleter] objects, [ImageInfo] objects,
/// or raw [dart:ui.Image] objects) may get evicted from the cache (and thus
/// need to be refetched from the network if they are referenced in the
/// [putIfAbsent] method), but the raw bits are kept in memory for as long as
/// the application is using them.
///
/// The [putIfAbsent] method is the main entry-point to the cache API. It
/// returns the previously cached [ImageStreamCompleter] for the given key, if
/// available; if not, it calls the given callback to obtain it first. In either
/// case, the key is moved to the "most recently used" position.
///
/// Generally this class is not used directly. The [ImageProvider] class and its
/// subclasses automatically handle the caching of images.
class ImageCache {
final Map<Object, ImageStreamCompleter> _pendingImages = <Object, ImageStreamCompleter>{};
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
/// Maximum number of entries to store in the cache.
///
/// Once this many entries have been cached, the least-recently-used entry is
/// evicted when adding a new entry.
int get maximumSize => _maximumSize;
int _maximumSize = _kDefaultSize;
/// Changes the maximum cache size.
///
/// If the new size is smaller than the current number of elements, the
/// extraneous elements are evicted immediately. Setting this to zero and then
/// returning it to its original value will therefore immediately clear the
/// cache.
set maximumSize(int value) {
assert(value != null);
assert(value >= 0);
if (value == maximumSize)
return;
_maximumSize = value;
if (maximumSize == 0) {
_cache.clear();
_currentSizeBytes = 0;
} else {
_checkCacheSize();
}
}
/// The current number of cached entries.
int get currentSize => _cache.length;
/// Maximum size of entries to store in the cache in bytes.
///
/// Once more than this amount of bytes have been cached, the
/// least-recently-used entry is evicted until there are fewer than the
/// maximum bytes.
int get maximumSizeBytes => _maximumSizeBytes;
int _maximumSizeBytes = _kDefaultSizeBytes;
/// Changes the maximum cache bytes.
///
/// If the new size is smaller than the current size in bytes, the
/// extraneous elements are evicted immediately. Setting this to zero and then
/// returning it to its original value will therefore immediately clear the
/// cache.
set maximumSizeBytes(int value) {
assert(value != null);
assert(value >= 0);
if (value == _maximumSizeBytes)
return;
_maximumSizeBytes = value;
if (_maximumSizeBytes == 0) {
_cache.clear();
_currentSizeBytes = 0;
} else {
_checkCacheSize();
}
}
/// The current size of cached entries in bytes.
int get currentSizeBytes => _currentSizeBytes;
int _currentSizeBytes = 0;
/// Evicts all entries from the cache.
///
/// This is useful if, for instance, the root asset bundle has been updated
/// and therefore new images must be obtained.
///
/// Images which have not finished loading yet will not be removed from the
/// cache, and when they complete they will be inserted as normal.
void clear() {
_cache.clear();
_currentSizeBytes = 0;
}
/// Evicts a single entry from the cache, returning true if successful.
///
/// The [key] must be equal to an object used to cache an image in
/// [ImageCache.putIfAbsent].
///
/// If the key is not immediately available, as is common, consider using
/// [ImageProvider.evict] to call this method indirectly instead.
///
/// See also:
///
/// * [ImageProvider], for providing images to the [Image] widget.
bool evict(Object key) {
final _CachedImage image = _cache.remove(key);
if (image != null) {
_currentSizeBytes -= image.sizeBytes;
return true;
}
return false;
}
/// Returns the previously cached [ImageStream] for the given key, if available;
/// if not, calls the given callback to obtain it first. In either case, the
/// key is moved to the "most recently used" position.
///
/// The arguments must not be null. The `loader` cannot return null.
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader()) {
assert(key != null);
assert(loader != null);
ImageStreamCompleter result = _pendingImages[key];
// Nothing needs to be done because the image hasn't loaded yet.
if (result != null)
return result;
// Remove the provider from the list so that we can move it to the
// recently used position below.
final _CachedImage image = _cache.remove(key);
if (image != null) {
_cache[key] = image;
return image.completer;
}
result = loader();
void listener(ImageInfo info, bool syncCall) {
// Images that fail to load don't contribute to cache size.
final int imageSize = info.image == null ? 0 : info.image.height * info.image.width * 4;
final _CachedImage image = new _CachedImage(result, imageSize);
_currentSizeBytes += imageSize;
_pendingImages.remove(key);
_cache[key] = image;
result.removeListener(listener);
_checkCacheSize();
}
if (maximumSize > 0 && maximumSizeBytes > 0) {
_pendingImages[key] = result;
result.addListener(listener);
}
return result;
}
// Remove images from the cache until both the length and bytes are below
// maximum, or the cache is empty.
void _checkCacheSize() {
while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
final Object key = _cache.keys.first;
final _CachedImage image = _cache[key];
_currentSizeBytes -= image.sizeBytes;
_cache.remove(key);
}
assert(_currentSizeBytes >= 0);
assert(_cache.length <= maximumSize);
assert(_currentSizeBytes <= maximumSizeBytes);
}
}
class _CachedImage {
_CachedImage(this.completer, this.sizeBytes);
final ImageStreamCompleter completer;
final int sizeBytes;
}