Only load tiles when viewed by player (#887)

## Description:
Tiles are only run through putImageData() if they are currently viewed
by the player.

This significantly (usually between 20-80%) reduces the computation time
of putImageData() on large maps.
## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
- [x] I understand that submitting code with bugs that could have been
caught through manual testing blocks releases and new features for all
contributors

## Please put your Discord username so you can be contacted if a bug or
regression is found:

1brucben

---------

Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
1brucben
2025-05-30 23:19:39 +02:00
committed by GitHub
parent a0e00e1fb0
commit 851526ba4e
2 changed files with 44 additions and 48 deletions
+1 -1
View File
@@ -191,7 +191,7 @@ export function createRenderer(
const layers: Layer[] = [
new TerrainLayer(game, transformHandler),
new TerritoryLayer(game, eventBus),
new TerritoryLayer(game, eventBus, transformHandler),
structureLayer,
new UnitLayer(game, eventBus, transformHandler),
new FxLayer(game),
+43 -47
View File
@@ -8,6 +8,7 @@ import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { AlternateViewEvent, DragEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export class TerritoryLayer implements Layer {
@@ -32,7 +33,7 @@ export class TerritoryLayer implements Layer {
private lastDragTime = 0;
private nodrawDragDuration = 200;
private refreshRate = 10;
private refreshRate = 10; //refresh every 10ms
private lastRefresh = 0;
private lastFocusedPlayer: PlayerView | null = null;
@@ -40,6 +41,7 @@ export class TerritoryLayer implements Layer {
constructor(
private game: GameView,
private eventBus: EventBus,
private transformHandler: TransformHandler,
) {
this.theme = game.config().theme();
}
@@ -48,11 +50,10 @@ export class TerritoryLayer implements Layer {
return true;
}
paintPlayerBorder(player: PlayerView) {
player.borderTiles().then((playerBorderTiles) => {
playerBorderTiles.borderTiles.forEach((tile: TileRef) => {
this.paintTerritory(tile, true); // Immediately paint the tile instead of enqueueing
});
async paintPlayerBorder(player: PlayerView) {
const tiles = await player.borderTiles();
tiles.borderTiles.forEach((tile: TileRef) => {
this.paintTerritory(tile, true); // Immediately paint the tile instead of enqueueing
});
}
@@ -128,11 +129,7 @@ export class TerritoryLayer implements Layer {
euclDistFN(centerTile, 9, true),
)) {
if (!this.game.hasOwner(tile)) {
this.paintHighlightCell(
new Cell(this.game.x(tile), this.game.y(tile)),
color,
255,
);
this.paintHighlightTile(tile, color, 255);
}
}
}
@@ -155,16 +152,16 @@ export class TerritoryLayer implements Layer {
const context = this.canvas.getContext("2d");
if (context === null) throw new Error("2d context not supported");
this.context = context;
this.canvas.width = this.game.width();
this.canvas.height = this.game.height();
this.imageData = this.context.getImageData(
0,
0,
this.game.width(),
this.game.height(),
this.canvas.width,
this.canvas.height,
);
this.initImageData();
this.canvas.width = this.game.width();
this.canvas.height = this.game.height();
this.context.putImageData(this.imageData, 0, 0);
// Add a second canvas for highlights
@@ -199,7 +196,19 @@ export class TerritoryLayer implements Layer {
) {
this.lastRefresh = now;
this.renderTerritory();
this.context.putImageData(this.imageData, 0, 0);
const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect();
const vx0 = Math.max(0, topLeft.x);
const vy0 = Math.max(0, topLeft.y);
const vx1 = Math.min(this.game.width() - 1, bottomRight.x);
const vy1 = Math.min(this.game.height() - 1, bottomRight.y);
const w = vx1 - vx0 + 1;
const h = vy1 - vy0 + 1;
if (w > 0 && h > 0) {
this.context.putImageData(this.imageData, 0, 0, vx0, vy0, w, h);
}
}
if (this.alternativeView) {
return;
@@ -245,15 +254,10 @@ export class TerritoryLayer implements Layer {
}
if (!this.game.hasOwner(tile)) {
if (this.game.hasFallout(tile)) {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
this.theme.falloutColor(),
150,
);
this.paintTile(tile, this.theme.falloutColor(), 150);
return;
}
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
this.clearTile(tile);
return;
}
const owner = this.game.owner(tile) as PlayerView;
@@ -273,40 +277,28 @@ export class TerritoryLayer implements Layer {
const lightTile =
(x % 2 === 0 && y % 2 === 0) || (y % 2 === 1 && x % 2 === 1);
const borderColor = lightTile ? borderColors.light : borderColors.dark;
this.paintCell(x, y, borderColor, 255);
this.paintTile(tile, borderColor, 255);
} else {
const useBorderColor = playerIsFocused
? this.theme.focusedBorderColor()
: this.theme.borderColor(owner);
this.paintCell(
this.game.x(tile),
this.game.y(tile),
useBorderColor,
255,
);
this.paintTile(tile, useBorderColor, 255);
}
} else {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
this.theme.territoryColor(owner),
150,
);
this.paintTile(tile, this.theme.territoryColor(owner), 150);
}
}
paintCell(x: number, y: number, color: Colord, alpha: number) {
const index = y * this.game.width() + x;
const offset = index * 4;
paintTile(tile: TileRef, color: Colord, alpha: number) {
const offset = tile * 4;
this.imageData.data[offset] = color.rgba.r;
this.imageData.data[offset + 1] = color.rgba.g;
this.imageData.data[offset + 2] = color.rgba.b;
this.imageData.data[offset + 3] = alpha;
}
clearCell(cell: Cell) {
const index = cell.y * this.game.width() + cell.x;
const offset = index * 4;
clearTile(tile: TileRef) {
const offset = tile * 4;
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
@@ -324,13 +316,17 @@ export class TerritoryLayer implements Layer {
});
}
paintHighlightCell(cell: Cell, color: Colord, alpha: number) {
this.clearCell(cell);
paintHighlightTile(tile: TileRef, color: Colord, alpha: number) {
this.clearTile(tile);
const x = this.game.x(tile);
const y = this.game.y(tile);
this.highlightContext.fillStyle = color.alpha(alpha / 255).toRgbString();
this.highlightContext.fillRect(cell.x, cell.y, 1, 1);
this.highlightContext.fillRect(x, y, 1, 1);
}
clearHighlightCell(cell: Cell) {
this.highlightContext.clearRect(cell.x, cell.y, 1, 1);
clearHighlightTile(tile: TileRef) {
const x = this.game.x(tile);
const y = this.game.y(tile);
this.highlightContext.clearRect(x, y, 1, 1);
}
}