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 1 commit
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
59 changes: 59 additions & 0 deletions lib/src/themes/feature_resolver.dart
@@ -0,0 +1,59 @@
import '../../vector_tile_renderer.dart';
import 'theme_layers.dart';

/// Resolver for resolving the [VectorTileFeature]s a [ThemeLayer] should
/// render.
abstract class ThemeLayerFeatureResolver {
blaugold marked this conversation as resolved.
Show resolved Hide resolved
/// Resolves and returns the [VectorTileFeature]s that the given [themeLayer]
/// should render.
Iterable<ResolvedThemeLayerFeature> resolveFeatures(DefaultLayer themeLayer);
}

/// Default implementation of [ThemeLayerFeatureResolver] that resolves the
/// features contained in a specific [tileset] for a [ThemeLayer].
class DefaultThemeLayerFeatureResolver implements ThemeLayerFeatureResolver {
DefaultThemeLayerFeatureResolver(this.tileset);

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

@override
Iterable<ResolvedThemeLayerFeature> resolveFeatures(
DefaultLayer themeLayer,
blaugold marked this conversation as resolved.
Show resolved Hide resolved
) 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;
blaugold marked this conversation as resolved.
Show resolved Hide resolved

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 {
blaugold marked this conversation as resolved.
Show resolved Hide resolved
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
42 changes: 27 additions & 15 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;
blaugold marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -29,17 +36,22 @@ class TilesetPreprocessor {
/// Pre-processes a tileset to eliminate some expensive processing from
/// 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);
/// The [themeLayerFeatures] options controls whether for each theme layer,
blaugold marked this conversation as resolved.
Show resolved Hide resolved
/// the features it will render are pre-processed.
///
/// Returns a pre-processed tileset.
Tileset preprocess(Tileset tileset, {bool themeLayerFeatures = true}) {
final themeLayerFeatureResolver = themeLayerFeatures
? CachingThemeLayerFeatureResolver(tileset.themeLayerFeatureResolver)
blaugold marked this conversation as resolved.
Show resolved Hide resolved
: 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,
blaugold marked this conversation as resolved.
Show resolved Hide resolved
this.preprocessThemeLayerFeatures = false,
}) : super('RenderPicture('
'zoom: $zoom, '
'preprocessTile: $preprocessTile, '
'preprocessThemeLayerFeatures: $preprocessThemeLayerFeatures'
')');

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