Commit da88f1d0 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #2828 from Hixie/lru

Replace use of LruMap with a pure LinkedHashMap
parents 924480fc ea04c910
......@@ -3,10 +3,10 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'dart:ui' show hashValues;
import 'package:mojo/mojo/url_response.mojom.dart';
import 'package:quiver/collection.dart';
import 'fetch.dart';
import 'image_decoder.dart';
......@@ -25,6 +25,12 @@ import 'image_resource.dart';
/// share the same cache as all the other image loading codepaths that used the
/// [imageCache].
abstract class ImageProvider { // ignore: one_member_abstracts
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const ImageProvider();
/// Subclasses must implement this method by having it asynchronously return
/// an [ImageInfo] that represents the image provided by this [ImageProvider].
Future<ImageInfo> loadImage();
/// Subclasses must implement the `==` operator so that the image cache can
......@@ -88,22 +94,34 @@ const int _kDefaultSize = 1000;
class ImageCache {
ImageCache._();
final LruMap<ImageProvider, ImageResource> _cache =
new LruMap<ImageProvider, ImageResource>(maximumSize: _kDefaultSize);
final LinkedHashMap<ImageProvider, ImageResource> _cache =
new LinkedHashMap<ImageProvider, ImageResource>();
/// 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 => _cache.maximumSize;
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. However, doing this is not very efficient.
// (the quiver library does it one at a time rather than using clear())
void set maximumSize(int value) { _cache.maximumSize = value; }
/// cache.
void set maximumSize(int value) {
assert(value != null);
assert(value >= 0);
if (value == maximumSize)
return;
_maximumSize = value;
if (maximumSize == 0) {
_cache.clear();
} else {
while (_cache.length > maximumSize)
_cache.remove(_cache.keys.first);
}
}
/// Calls the [ImageProvider.loadImage] method on the given image provider, if
/// necessary, and returns an [ImageResource] that encapsulates a [Future] for
......@@ -113,9 +131,20 @@ class ImageCache {
/// cache, then the [ImageResource] object is immediately usable and the
/// provider is not invoked.
ImageResource loadProvider(ImageProvider provider) {
return _cache.putIfAbsent(provider, () {
return new ImageResource(provider.loadImage());
});
ImageResource result = _cache[provider];
if (result != null) {
_cache.remove(provider);
} else {
if (_cache.length == maximumSize && maximumSize > 0)
_cache.remove(_cache.keys.first);
result = new ImageResource(provider.loadImage());;
}
if (maximumSize > 0) {
assert(_cache.length < maximumSize);
_cache[provider] = result;
}
assert(_cache.length <= maximumSize);
return result;
}
/// Fetches the given URL, associating it with the given scale.
......
......@@ -8,7 +8,6 @@ dependencies:
collection: '>=1.4.0 <2.0.0'
intl: '>=0.12.4+2 <0.13.0'
vector_math: '>=1.4.5 <2.0.0'
quiver: '>=0.21.4 <0.22.0'
sky_engine:
path: ../../bin/cache/pkg/sky_engine
......
// Copyright 2016 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 'package:flutter/services.dart';
import 'package:test/test.dart';
import 'mocks_for_image_cache.dart';
void main() {
test('Image cache resizing', () async {
imageCache.maximumSize = 2;
TestImageInfo a = (await imageCache.loadProvider(new TestProvider(1, 1)).first);
TestImageInfo b = (await imageCache.loadProvider(new TestProvider(2, 2)).first);
TestImageInfo c = (await imageCache.loadProvider(new TestProvider(3, 3)).first);
TestImageInfo d = (await imageCache.loadProvider(new TestProvider(1, 4)).first);
expect(a.value, equals(1));
expect(b.value, equals(2));
expect(c.value, equals(3));
expect(d.value, equals(4));
imageCache.maximumSize = 0;
TestImageInfo e = (await imageCache.loadProvider(new TestProvider(1, 5)).first);
expect(e.value, equals(5));
TestImageInfo f = (await imageCache.loadProvider(new TestProvider(1, 6)).first);
expect(f.value, equals(6));
imageCache.maximumSize = 3;
TestImageInfo g = (await imageCache.loadProvider(new TestProvider(1, 7)).first);
expect(g.value, equals(7));
TestImageInfo h = (await imageCache.loadProvider(new TestProvider(1, 8)).first);
expect(h.value, equals(7));
});
}
// Copyright 2016 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 'package:flutter/services.dart';
import 'package:test/test.dart';
import 'mocks_for_image_cache.dart';
void main() {
test('Image cache', () async {
imageCache.maximumSize = 3;
TestImageInfo a = (await imageCache.loadProvider(new TestProvider(1, 1)).first);
expect(a.value, equals(1));
TestImageInfo b = (await imageCache.loadProvider(new TestProvider(1, 2)).first);
expect(b.value, equals(1));
TestImageInfo c = (await imageCache.loadProvider(new TestProvider(1, 3)).first);
expect(c.value, equals(1));
TestImageInfo d = (await imageCache.loadProvider(new TestProvider(1, 4)).first);
expect(d.value, equals(1));
TestImageInfo e = (await imageCache.loadProvider(new TestProvider(1, 5)).first);
expect(e.value, equals(1));
TestImageInfo f = (await imageCache.loadProvider(new TestProvider(1, 6)).first);
expect(f.value, equals(1));
expect(f, equals(a));
// cache still only has one entry in it: 1(1)
TestImageInfo g = (await imageCache.loadProvider(new TestProvider(2, 7)).first);
expect(g.value, equals(7));
// cache has two entries in it: 1(1), 2(7)
TestImageInfo h = (await imageCache.loadProvider(new TestProvider(1, 8)).first);
expect(h.value, equals(1));
// cache still has two entries in it: 2(7), 1(1)
TestImageInfo i = (await imageCache.loadProvider(new TestProvider(3, 9)).first);
expect(i.value, equals(9));
// cache has three entries in it: 2(7), 1(1), 3(9)
TestImageInfo j = (await imageCache.loadProvider(new TestProvider(1, 10)).first);
expect(j.value, equals(1));
// cache still has three entries in it: 2(7), 3(9), 1(1)
TestImageInfo k = (await imageCache.loadProvider(new TestProvider(4, 11)).first);
expect(k.value, equals(11));
// cache has three entries: 3(9), 1(1), 4(11)
TestImageInfo l = (await imageCache.loadProvider(new TestProvider(1, 12)).first);
expect(l.value, equals(1));
// cache has three entries: 3(9), 4(11), 1(1)
TestImageInfo m = (await imageCache.loadProvider(new TestProvider(2, 13)).first);
expect(m.value, equals(13));
// cache has three entries: 4(11), 1(1), 2(13)
TestImageInfo n = (await imageCache.loadProvider(new TestProvider(3, 14)).first);
expect(n.value, equals(14));
// cache has three entries: 1(1), 2(13), 3(14)
TestImageInfo o = (await imageCache.loadProvider(new TestProvider(4, 15)).first);
expect(o.value, equals(15));
// cache has three entries: 2(13), 3(14), 4(15)
TestImageInfo p = (await imageCache.loadProvider(new TestProvider(1, 16)).first);
expect(p.value, equals(16));
// cache has three entries: 3(14), 4(15), 1(16)
});
}
// Copyright 2016 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 'dart:async';
import 'dart:ui' as ui show Image;
import 'package:flutter/services.dart';
class TestImageInfo implements ImageInfo {
const TestImageInfo(this.value) : image = null, scale = null;
@override
final ui.Image image; // ignored in test
@override
final double scale; // ignored in test
final int value;
@override
String toString() => '$runtimeType($value)';
}
class TestProvider extends ImageProvider {
const TestProvider(this.equalityValue, this.imageValue);
final int imageValue;
final int equalityValue;
@override
Future<ImageInfo> loadImage() async {
return new TestImageInfo(imageValue);
}
@override
bool operator ==(dynamic other) {
if (other is! TestProvider)
return false;
final TestProvider typedOther = other;
return equalityValue == typedOther.equalityValue;
}
@override
int get hashCode => equalityValue.hashCode;
@override
String toString() => '$runtimeType($equalityValue, $imageValue)';
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment