From 718f4e36209ad42c2c8b4dded7dab632c00314e6 Mon Sep 17 00:00:00 2001 From: darthmaim Date: Tue, 9 Jul 2024 21:33:45 +0200 Subject: [PATCH] Preload area spanning animation --- packages/tyria/dev/index.html | 2 ++ packages/tyria/src/Tyria.ts | 37 +++++++++++++++++++++----- packages/tyria/src/layer.ts | 4 ++- packages/tyria/src/layers/TileLayer.ts | 4 +-- packages/tyria/src/render-queue.ts | 13 ++++++--- 5 files changed, 46 insertions(+), 14 deletions(-) diff --git a/packages/tyria/dev/index.html b/packages/tyria/dev/index.html index b88d43e..550e515 100644 --- a/packages/tyria/dev/index.html +++ b/packages/tyria/dev/index.html @@ -74,6 +74,7 @@ + diff --git a/packages/tyria/src/Tyria.ts b/packages/tyria/src/Tyria.ts index 4c9778a..16979ad 100644 --- a/packages/tyria/src/Tyria.ts +++ b/packages/tyria/src/Tyria.ts @@ -2,8 +2,8 @@ import { HandlerManager } from './handlers/manager'; import { ImageManager } from './image-manager'; import { Layer, LayerPreloadContext, LayerRenderContext } from './layer'; import { TyriaMapOptions } from './options'; -import { RenderQueue, RenderQueuePriority } from './render-queue'; -import { Point, View, ViewOptions } from './types'; +import { RenderQueue, RenderQueuePriority, RenderReason } from './render-queue'; +import { Bounds, Point, View, ViewOptions } from './types'; import { add, clamp, easeInOutCubic, multiply, subtract } from './util'; export class Tyria { @@ -79,11 +79,11 @@ export class Tyria { return [x * nativeZoomScale / zoomScale, y * nativeZoomScale / zoomScale]; } - queueRender(priority?: RenderQueuePriority) { - this.renderQueue.queue(priority); + queueRender(priority?: RenderQueuePriority, reason?: RenderReason) { + this.renderQueue.queue(priority, reason); } - #render() { + #render(reason: RenderReason) { // we are doing it, cancel any pending renders this.renderQueue.cancel(); @@ -100,6 +100,8 @@ export class Tyria { // preload images this.#preloadImages(); + console.log('render', reason); + performance.mark('render-start', { detail: { view: this.view }}); // calculate the global transform of the map @@ -125,6 +127,7 @@ export class Tyria { dpr, debug: this.debug, }, + reason, project: this.project.bind(this), unproject: this.unproject.bind(this), } @@ -311,6 +314,18 @@ export class Tyria { return { center, zoom }; } + /** Gets the area visible in the viewport */ + #getViewportArea(view: View) { + const dpr = window.devicePixelRatio ?? 1; + const viewportHalfSizePx: Point = [this.canvas.width / dpr / 2, this.canvas.height / dpr / 2]; + const centerPx = this.project(view.center); + + const topLeft = this.unproject(subtract(centerPx, viewportHalfSizePx)); + const bottomRight = this.unproject(add(centerPx, viewportHalfSizePx)); + + return [topLeft, bottomRight]; + } + /** Instantly jumps to the provided view */ jumpTo(view: ViewOptions) { this.debugLastViewOptions = view; @@ -344,6 +359,14 @@ export class Tyria { // preload target view this.preload(target); + const startArea = this.#getViewportArea(start); + const targetArea = this.#getViewportArea(target); + const combinedArea: Bounds = [ + [Math.min(startArea[0][0], targetArea[0][0]), Math.min(startArea[0][1], targetArea[0][1])] as Point, + [Math.max(startArea[1][0], targetArea[1][0]), Math.max(startArea[1][1], targetArea[1][1])] as Point + ]; + this.preload(this.resolveView({ contain: combinedArea })); + // if we are not moving, don't move if(target.zoom === start.zoom && target.center[0] === start.center[0] && target.center[1] === start.center[1]) { return; @@ -393,7 +416,7 @@ export class Tyria { this.currentEase = { frame, duration, start: performance.now() } } - this.queueRender(); + this.queueRender('next-frame', 'ease'); } /** The currently running easing */ @@ -419,7 +442,7 @@ export class Tyria { // if the animation is not yet finished, queue another frame, // otherwise unset the current one if(progress < 1) { - this.queueRender(); + this.queueRender('next-frame', 'ease'); } else { this.currentEase = undefined; } diff --git a/packages/tyria/src/layer.ts b/packages/tyria/src/layer.ts index 42bab88..2f89e1c 100644 --- a/packages/tyria/src/layer.ts +++ b/packages/tyria/src/layer.ts @@ -1,4 +1,5 @@ import { ImageGetOptions } from "./image-manager"; +import { RenderReason } from "./render-queue"; import { Point } from "./types"; export interface Layer { @@ -9,6 +10,7 @@ export interface Layer { export interface LayerRenderContext { context: CanvasRenderingContext2D, + reason: RenderReason, state: { /** center of the map in map coordinates */ center: Point, @@ -33,4 +35,4 @@ export interface LayerRenderContext { getImage: (src: string, options?: ImageGetOptions) => ImageBitmap | undefined, } -export type LayerPreloadContext = Omit; +export type LayerPreloadContext = Omit; diff --git a/packages/tyria/src/layers/TileLayer.ts b/packages/tyria/src/layers/TileLayer.ts index fd2b6ef..5fa3e39 100644 --- a/packages/tyria/src/layers/TileLayer.ts +++ b/packages/tyria/src/layers/TileLayer.ts @@ -66,7 +66,7 @@ export class TileLayer implements Layer { } } - render({ context, state, project, getImage }: LayerRenderContext) { + render({ context, state, project, getImage, reason }: LayerRenderContext) { performance.mark('tile-layer-render-start'); // get tiles in viewport @@ -114,7 +114,7 @@ export class TileLayer implements Layer { // try to get the tile from the cache const src = this.options.source(x, y, zoom); - const tile = getImage(src, { priority: 2 - distance }); + const tile = getImage(src, { priority: 2 - distance, cacheOnly: reason === 'ease' }); if(tile) { // draw tile diff --git a/packages/tyria/src/render-queue.ts b/packages/tyria/src/render-queue.ts index cbb65e6..89f0885 100644 --- a/packages/tyria/src/render-queue.ts +++ b/packages/tyria/src/render-queue.ts @@ -1,21 +1,26 @@ export type RenderQueuePriority = 'next-frame' | 'low-priority'; +export type RenderReason = 'render' | 'ease'; + export class RenderQueue { - #render: () => void; + #render: (reason: RenderReason) => void; #renderQueued: false | RenderQueuePriority = false; #renderQueueFrame?: number; #renderQueueTimeout?: number; + #reason: RenderReason; constructor(render: () => void) { this.#render = render; } - queue(priority: RenderQueuePriority = 'next-frame') { + queue(priority: RenderQueuePriority = 'next-frame', reason: RenderReason = 'render') { // don't queue if it is already queued with same or higher priority if(this.#renderQueued === priority || this.#renderQueued === 'next-frame') { return; } + this.#reason = reason; + if(priority === 'next-frame') { // cancel low priority request if we get a high priority one if(this.#renderQueued === 'low-priority') { @@ -25,13 +30,13 @@ export class RenderQueue { // request render in next animation frame this.#renderQueueFrame = requestAnimationFrame(() => { this.#renderQueueFrame = undefined; - this.#render(); + this.#render(this.#reason); }); } else { // render in 80ms (5 frames at ~60fps), so we can collect some more queueRenders until then (for example from image loading promises resolving) this.#renderQueueTimeout = setTimeout(() => { this.#renderQueueTimeout = undefined; - this.#render(); + this.#render(this.#reason); }, 80); }