Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support caching of VectorTileFeatures resolved for a ThemeLayer #4

Merged
merged 3 commits into from Jan 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
}
31 changes: 17 additions & 14 deletions lib/src/themes/theme_layers.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:vector_tile/vector_tile_feature.dart';

import '../context.dart';
import '../tileset.dart';
import 'selector.dart';
import 'style.dart';
import 'theme.dart';
Expand All @@ -11,23 +12,25 @@ class DefaultLayer extends ThemeLayer {
final TileLayerSelector selector;
final Style style;

DefaultLayer(String id, ThemeLayerType type,
{required this.selector,
required this.style,
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 double? minzoom,
required double? maxzoom,
}) : super(id, type, minzoom: minzoom, maxzoom: maxzoom);

@override
void render(Context context) {
selector.select(context.tileset).forEach((layer) {
selector.layerSelector.features(layer.features).forEach((feature) {
context.featureRenderer.render(context, type, style, layer, feature);
if (!context.tileset.preprocessed) {
_releaseMemory(feature);
}
});
});
for (final feature
in context.tileset.resolver.resolveFeatures(this.selector)) {
context.featureRenderer
.render(context, type, style, feature.layer, feature.feature);
if (!context.tileset.preprocessed) {
_releaseMemory(feature.feature);
}
}
}

void _releaseMemory(VectorTileFeature feature) {
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"
boolean_selector:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Expand Up @@ -13,4 +13,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;
greensopinion marked this conversation as resolved.
Show resolved Hide resolved

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();
}
}