Skip to content

Commit

Permalink
support caching of VectorTileFeatures resolved for a ThemeLayer (g…
Browse files Browse the repository at this point in the history
…reensopinion#4)

feat: support caching of `VectorTileFeature`s resolved for a `ThemeLayer`
  • Loading branch information
blaugold authored and mvarendorff committed Jan 13, 2022
1 parent 5c56e0f commit 0ca61f0
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 22 deletions.
52 changes: 52 additions & 0 deletions lib/src/themes/feature_resolver.dart
@@ -0,0 +1,52 @@
import 'package:vector_tile_renderer/src/themes/selector.dart';

import '../../vector_tile_renderer.dart';

/// Resolver for resolving the features, that are selected by a
/// [TileLayerSelector].
abstract class LayerFeatureResolver {
/// Resolves and returns the features that the given [selector] selects.
Iterable<LayerFeature> resolveFeatures(TileLayerSelector selector);
}

/// Default implementation of [LayerFeatureResolver] that resolves
/// features from a [tileset].
class DefaultThemeLayerFeatureResolver implements LayerFeatureResolver {
DefaultThemeLayerFeatureResolver(this.tileset);

/// The [Tileset] from which to resolves features.
final Tileset tileset;

@override
Iterable<LayerFeature> resolveFeatures(TileLayerSelector selector) sync* {
for (final layer in selector.select(tileset)) {
for (final feature in selector.layerSelector.features(layer.features)) {
yield LayerFeature(layer, feature);
}
}
}
}

/// A [LayerFeatureResolver] that uses another resolver and caches its results.
class CachingThemeLayerFeatureResolver implements LayerFeatureResolver {
CachingThemeLayerFeatureResolver(this._resolver);

final LayerFeatureResolver _resolver;

final _cache = <TileLayerSelector, List<LayerFeature>>{};

@override
Iterable<LayerFeature> resolveFeatures(TileLayerSelector selector) {
return _cache.putIfAbsent(
selector,
() => _resolver.resolveFeatures(selector).toList(),
);
}
}

class LayerFeature {
LayerFeature(this.layer, this.feature);

final VectorTileLayer layer;
final VectorTileFeature feature;
}
18 changes: 10 additions & 8 deletions lib/src/themes/theme_layers.dart
Expand Up @@ -14,14 +14,16 @@ class DefaultLayer extends ThemeLayer {
final String source;
final String sourceLayer;

DefaultLayer(String id, ThemeLayerType type,
{required this.selector,
required this.style,
required this.source,
required this.sourceLayer,
required double? minzoom,
required double? maxzoom})
: super(id, type, minzoom: minzoom, maxzoom: maxzoom);
DefaultLayer(
String id,
ThemeLayerType type, {
required this.selector,
required this.style,
required this.source,
required this.sourceLayer,
required double? minzoom,
required double? maxzoom,
}) : super(id, type, minzoom: minzoom, maxzoom: maxzoom);

@override
void render(Context context) {
Expand Down
36 changes: 23 additions & 13 deletions lib/src/tileset.dart
@@ -1,5 +1,6 @@
import 'package:vector_tile/vector_tile.dart';

import 'themes/feature_resolver.dart';
import 'themes/theme.dart';
import 'themes/theme_layers.dart';

Expand All @@ -8,16 +9,23 @@ import 'themes/theme_layers.dart';
class Tileset {
final bool preprocessed;
final Map<String, VectorTile> tiles;
late final LayerFeatureResolver _resolver;

Tileset(this.tiles) : this.preprocessed = false;
Tileset(this.tiles) : this.preprocessed = false {
_resolver = DefaultThemeLayerFeatureResolver(this);
}

Tileset._preprocessed(Tileset original)
Tileset._preprocessed(Tileset original, this._resolver)
: this.tiles = original.tiles,
this.preprocessed = true;

VectorTile? tile(String sourceId) => tiles[sourceId];
}

extension InternalTileset on Tileset {
LayerFeatureResolver get resolver => _resolver;
}

/// A pre-processor for [Tileset]s. A pre-processing is an optional step
/// that can reduce CPU overhead during rendering at the cost of higher memory
/// usage.
Expand All @@ -29,17 +37,19 @@ class TilesetPreprocessor {
/// Pre-processes a tileset to eliminate some expensive processing from
/// the rendering stage.
///
/// returns a pre-processed tileset
/// Returns a pre-processed tileset.
Tileset preprocess(Tileset tileset) {
theme.layers.whereType<DefaultLayer>().forEach((themeLayer) {
themeLayer.selector.select(tileset).forEach((layer) {
themeLayer.selector.layerSelector
.features(layer.features)
.forEach((feature) {
feature.decodeGeometry();
});
});
});
return Tileset._preprocessed(tileset);
final featureResolver = tileset.resolver is CachingThemeLayerFeatureResolver
? tileset.resolver
: CachingThemeLayerFeatureResolver(tileset.resolver);

for (final themeLayer in theme.layers.whereType<DefaultLayer>()) {
for (final feature
in featureResolver.resolveFeatures(themeLayer.selector)) {
feature.feature.decodeGeometry();
}
}

return Tileset._preprocessed(tileset, featureResolver);
}
}
2 changes: 1 addition & 1 deletion lib/vector_tile_renderer.dart
Expand Up @@ -9,5 +9,5 @@ export 'src/renderer.dart';
export 'src/themes/provided_themes.dart';
export 'src/themes/theme.dart';
export 'src/themes/theme_reader.dart';
export 'src/tileset.dart';
export 'src/tileset.dart' hide InternalTileset;
export 'src/vector_tile_reader.dart';
7 changes: 7 additions & 0 deletions pubspec.lock
Expand Up @@ -29,6 +29,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.0"
benchmark_harness:
dependency: "direct dev"
description:
name: benchmark_harness
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
bezier:
dependency: "direct main"
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Expand Up @@ -15,4 +15,5 @@ dependencies:
vector_tile: ^0.3.0

dev_dependencies:
benchmark_harness: ^2.0.0
test: ^1.17.5
89 changes: 89 additions & 0 deletions test/benchmark/rendering_test.dart
@@ -0,0 +1,89 @@
import 'dart:ui';

import 'package:benchmark_harness/benchmark_harness.dart';
import 'package:vector_tile_renderer/src/constants.dart';
import 'package:vector_tile_renderer/src/themes/light_theme.dart';
import 'package:vector_tile_renderer/vector_tile_renderer.dart';

import '../src/test_tile.dart';

Picture _renderPicture({
required Theme theme,
required double scale,
required Tileset tileset,
required double zoom,
}) {
assert(scale >= 1 && scale <= 4);

double size = scale * tileSize;
final rect = Rect.fromLTRB(0, 0, size, size);

final recorder = PictureRecorder();
final canvas = Canvas(recorder, rect);
canvas.clipRect(rect);
canvas.scale(scale.toDouble(), scale.toDouble());

Renderer(theme: theme).render(
canvas,
tileset,
zoomScaleFactor: scale,
zoom: zoom,
);

return recorder.endRecording();
}

class RenderPicture extends BenchmarkBase {
RenderPicture({
required this.zoom,
required this.preprocessTile,
}) : super('RenderPicture('
'zoom: $zoom, '
'preprocessTile: $preprocessTile'
')');

static Future<void> setupAll() async {
testTile = await readTestTile();
}

static late final VectorTile testTile;

final double zoom;
final bool preprocessTile;

late final Theme theme;
late final Tileset tileset;

@override
void setup() {
theme = ThemeReader().read(lightThemeData());
final tileset = Tileset({'openmaptiles': testTile});

this.tileset = preprocessTile
? TilesetPreprocessor(theme).preprocess(tileset)
: tileset;
}

@override
void run() => _renderPicture(
theme: theme,
tileset: tileset,
scale: 1,
zoom: zoom,
);
}

Future<void> main() async {
await RenderPicture.setupAll();

final benchmarks = [
for (final zoom in <double>[0, 12, 24]) ...[
RenderPicture(zoom: zoom, preprocessTile: false),
RenderPicture(zoom: zoom, preprocessTile: true),
]
];

for (final benchmark in benchmarks) {
benchmark.report();
}
}

0 comments on commit 0ca61f0

Please sign in to comment.