Skip to content

Commit

Permalink
feat: support caching of VectorTileFeatures resolved for a `ThemeLa…
Browse files Browse the repository at this point in the history
…yer`
  • Loading branch information
blaugold committed Jan 1, 2022
1 parent c04cbaf commit d9a37f1
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 29 deletions.
2 changes: 1 addition & 1 deletion lib/src/renderer.dart
Expand Up @@ -13,7 +13,7 @@ class Renderer {
final FeatureDispatcher featureRenderer;

Renderer({required this.theme, Logger? logger})
: this.logger = logger ?? Logger.noop(),
: logger = logger ?? Logger.noop(),
featureRenderer = FeatureDispatcher(logger ?? Logger.noop());

/// renders the given tile to the canvas
Expand Down
57 changes: 57 additions & 0 deletions lib/src/themes/feature_resolver.dart
@@ -0,0 +1,57 @@
import '../../vector_tile_renderer.dart';
import 'theme_layers.dart';

/// Resolver for resolving the [VectorTileFeature]s a [ThemeLayer] should
/// render.
abstract class ThemeLayerFeatureResolver {
/// Resolves and returns the [VectorTileFeature]s within the given [tileset]
/// that the given [themeLayer] should render.
Iterable<ResolvedThemeLayerFeature> resolveFeatures(DefaultLayer themeLayer);
}

/// Default implementation of [ThemeLayerFeatureResolver].
class DefaultThemeLayerFeatureResolver implements ThemeLayerFeatureResolver {
DefaultThemeLayerFeatureResolver(this.tileset);

final Tileset tileset;

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

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

/// The resolver whose results will be cached.
final ThemeLayerFeatureResolver resolver;

final _cache = <DefaultLayer, List<ResolvedThemeLayerFeature>>{};

@override
Iterable<ResolvedThemeLayerFeature> resolveFeatures(DefaultLayer themeLayer) {
return _cache.putIfAbsent(
themeLayer,
() => resolver.resolveFeatures(themeLayer).toList(),
);
}
}

/// Wrapper around [VectorTileFeature] that includes the containing
/// [VectorTileLayer].
class ResolvedThemeLayerFeature {
ResolvedThemeLayerFeature(this.layer, this.feature);

final VectorTileLayer layer;
final VectorTileFeature feature;
}
30 changes: 16 additions & 14 deletions lib/src/themes/theme_layers.dart
Expand Up @@ -11,23 +11,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.themeLayerFeatureResolver.resolveFeatures(this)) {
context.featureRenderer
.render(context, type, style, feature.layer, feature.feature);
if (!context.tileset.preprocessed) {
_releaseMemory(feature.feature);
}
}
}

void _releaseMemory(VectorTileFeature feature) {
Expand Down
37 changes: 23 additions & 14 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,11 +9,17 @@ import 'themes/theme_layers.dart';
class Tileset {
final bool preprocessed;
final Map<String, VectorTile> tiles;
late final ThemeLayerFeatureResolver themeLayerFeatureResolver;

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

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

VectorTile? tile(String sourceId) => tiles[sourceId];
Expand All @@ -30,16 +37,18 @@ class TilesetPreprocessor {
/// the rendering stage.
///
/// 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);
Tileset preprocess(Tileset tileset, {bool themeLayerFeatures = true}) {
final themeLayerFeatureResolver = themeLayerFeatures
? CachingThemeLayerFeatureResolver(tileset.themeLayerFeatureResolver)
: tileset.themeLayerFeatureResolver;

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

return Tileset._preprocessed(tileset, themeLayerFeatureResolver);
}
}
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
105 changes: 105 additions & 0 deletions test/benchmark/rendering_test.dart
@@ -0,0 +1,105 @@
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,
this.preprocessTile = false,
this.preprocessThemeLayerFeatures = false,
}) : super('RenderPicture('
'zoom: $zoom, '
'preprocessTile: $preprocessTile, '
'preprocessThemeLayerFeatures: $preprocessThemeLayerFeatures'
')');

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

static late final VectorTile testTile;

final double zoom;
final bool preprocessTile;
final bool preprocessThemeLayerFeatures;

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,
themeLayerFeatures: preprocessThemeLayerFeatures,
)
: 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),
RenderPicture(
zoom: zoom,
preprocessTile: false,
preprocessThemeLayerFeatures: false,
),
RenderPicture(
zoom: zoom,
preprocessTile: true,
preprocessThemeLayerFeatures: true,
),
]
];

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

0 comments on commit d9a37f1

Please sign in to comment.