Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 73 additions & 11 deletions src/client/graphics/layers/RailroadLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export class RailroadLayer implements Layer {
private existingRailroads = new Map<TileRef, RailRef>();
private nextRailIndexToCheck = 0;
private railTileList: TileRef[] = [];
private railTileIndex = new Map<TileRef, number>();
private lastRailColorUpdate = 0;
private readonly railColorIntervalMs = 50;

constructor(
private game: GameView,
Expand All @@ -49,7 +52,21 @@ export class RailroadLayer implements Layer {
}

updateRailColors() {
const maxTilesPerFrame = this.railTileList.length / 60;
if (this.railTileList.length === 0) {
return;
}
// Throttle color checks so we do not re-evaluate on every frame
const now = performance.now();
if (now - this.lastRailColorUpdate < this.railColorIntervalMs) {
return;
}
this.lastRailColorUpdate = now;

// Spread work over multiple frames to avoid large bursts when many rails exist
const maxTilesPerFrame = Math.max(
1,
Math.ceil(this.railTileList.length / 120),
);
let checked = 0;

while (checked < maxTilesPerFrame && this.railTileList.length > 0) {
Expand All @@ -58,15 +75,14 @@ export class RailroadLayer implements Layer {
if (railRef) {
const currentOwner = this.game.owner(tile)?.id() ?? null;
if (railRef.lastOwnerId !== currentOwner) {
// Repaint only when the owner changed to keep colors in sync
railRef.lastOwnerId = currentOwner;
this.paintRail(railRef.tile);
}
}

this.nextRailIndexToCheck++;
if (this.nextRailIndexToCheck >= this.railTileList.length) {
this.nextRailIndexToCheck = 0;
}
this.nextRailIndexToCheck =
(this.nextRailIndexToCheck + 1) % this.railTileList.length;
checked++;
}
}
Expand Down Expand Up @@ -95,22 +111,49 @@ export class RailroadLayer implements Layer {
}

renderLayer(context: CanvasRenderingContext2D) {
this.updateRailColors();
const scale = this.transformHandler.scale;
if (scale <= 1) {
return;
}
if (this.existingRailroads.size === 0) {
return;
}
this.updateRailColors();
const rawAlpha = (scale - 1) / (2 - 1); // maps 1->0, 2->1
const alpha = Math.max(0, Math.min(1, rawAlpha));

const [topLeft, bottomRight] = this.transformHandler.screenBoundingRect();
const padding = 2; // small margin so edges do not pop
const visLeft = Math.max(0, topLeft.x - padding);
const visTop = Math.max(0, topLeft.y - padding);
const visRight = Math.min(this.game.width(), bottomRight.x + padding);
const visBottom = Math.min(this.game.height(), bottomRight.y + padding);
const visWidth = Math.max(0, visRight - visLeft);
const visHeight = Math.max(0, visBottom - visTop);
if (visWidth === 0 || visHeight === 0) {
return;
}

const srcX = visLeft * 2;
const srcY = visTop * 2;
const srcW = visWidth * 2;
const srcH = visHeight * 2;

const dstX = -this.game.width() / 2 + visLeft;
const dstY = -this.game.height() / 2 + visTop;

context.save();
context.globalAlpha = alpha;
context.drawImage(
this.canvas,
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height(),
srcX,
srcY,
srcW,
srcH,
dstX,
dstY,
visWidth,
visHeight,
);
context.restore();
}
Expand Down Expand Up @@ -139,6 +182,7 @@ export class RailroadLayer implements Layer {
numOccurence: 1,
lastOwnerId: currentOwner,
});
this.railTileIndex.set(railRoad.tile, this.railTileList.length);
this.railTileList.push(railRoad.tile);
this.paintRail(railRoad);
}
Expand All @@ -150,7 +194,7 @@ export class RailroadLayer implements Layer {

if (!ref || ref.numOccurence <= 0) {
this.existingRailroads.delete(railRoad.tile);
this.railTileList = this.railTileList.filter((t) => t !== railRoad.tile);
this.removeRailTile(railRoad.tile);
if (this.context === undefined) throw new Error("Not initialized");
if (this.game.isWater(railRoad.tile)) {
this.context.clearRect(
Expand All @@ -170,6 +214,24 @@ export class RailroadLayer implements Layer {
}
}

private removeRailTile(tile: TileRef) {
const idx = this.railTileIndex.get(tile);
if (idx === undefined) return;

const lastIndex = this.railTileList.length - 1;
const lastTile = this.railTileList[lastIndex];

this.railTileList[idx] = lastTile;
this.railTileIndex.set(lastTile, idx);

this.railTileList.pop();
this.railTileIndex.delete(tile);

if (this.nextRailIndexToCheck >= this.railTileList.length) {
this.nextRailIndexToCheck = 0;
}
}

paintRail(railRoad: RailTile) {
if (this.context === undefined) throw new Error("Not initialized");
const { tile } = railRoad;
Expand Down
Loading