Skip to content
Open
Show file tree
Hide file tree
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
37 changes: 37 additions & 0 deletions .github/workflows/perf-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Comment Benchmark Results
on:
workflow_run:
workflows: [Performance Benchmarks]
types: [completed]

permissions:
actions: read
pull-requests: write

jobs:
comment:
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Download benchmark comment artifact
id: download
continue-on-error: true
uses: actions/download-artifact@v4
with:
name: perf-comment
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: perf-report

- name: Read PR number
if: steps.download.outcome == 'success'
id: pr
run: echo "number=$(cat perf-report/pr-number.txt)" >> "$GITHUB_OUTPUT"

- name: Comment benchmark results on PR
if: steps.download.outcome == 'success'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: benchmark-results
path: perf-report/comment.md
number: ${{ steps.pr.outputs.number }}
63 changes: 63 additions & 0 deletions .github/workflows/perf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Performance Benchmarks
on:
pull_request:
branches: [master]

workflow_dispatch:

permissions:
contents: read

jobs:
perf:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- uses: actions/setup-node@v5
with:
node-version: "24"
cache: "npm"
- name: Install dependencies
run: npm ci

- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-chromium-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- name: Install Playwright system deps
if: steps.playwright-cache.outputs.cache-hit == 'true'
run: npx playwright install-deps chromium

- name: Run A/B perf benchmarks against master
id: perf
continue-on-error: true
run: npm run perf:ab -- --base origin/master --head HEAD --rounds 3 --threshold 0.25 --markdown-out perf-report/comment.md

- name: Save PR number
if: always() && github.event_name == 'pull_request'
run: |
mkdir -p perf-report
echo "${{ github.event.pull_request.number }}" > perf-report/pr-number.txt

- name: Upload benchmark comment artifact
if: always() && github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: perf-comment
path: |
perf-report/comment.md
perf-report/pr-number.txt
if-no-files-found: ignore

- name: Fail job on performance regression
if: steps.perf.outcome == 'failure'
run: exit 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/coverage
/playwright-report
/test-results
/perf-report
/_bmad
/_bmad-output
/memory
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"preview": "vite preview",
"test": "vitest",
"test:e2e": "playwright test",
"perf:ab": "node tests/perf/ab.mjs",
"lint": "biome check --write",
"prepare": "simple-git-hooks"
},
Expand Down
34 changes: 18 additions & 16 deletions public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -484,8 +484,9 @@ async function generate(options) {
AddedLabels.initiate();
Names.getMapName();

WARN && console.warn(`TOTAL: ${rn((performance.now() - timeStart) / 1000, 2)}s`);
showStatistics();
const totalMs = performance.now() - timeStart;
WARN && console.warn(`TOTAL: ${rn(totalMs / 1000, 2)}s`);
showStatistics(totalMs);
} catch (error) {
ERROR && console.error(error);
const parsedError = parseError(error);
Expand Down Expand Up @@ -532,7 +533,7 @@ function setSeed(precreatedSeed) {
}

function addLakesInDeepDepressions() {
TIME && console.time("addLakesInDeepDepressions");
TIME && timeStart("addLakesInDeepDepressions");
const elevationLimit = +ensureEl("lakeElevationLimitOutput").value;
if (elevationLimit === 80) return;

Expand Down Expand Up @@ -588,7 +589,7 @@ function addLakesInDeepDepressions() {
features.push({ i: f, land: false, border: false, type: "lake" });
}

TIME && console.timeEnd("addLakesInDeepDepressions");
TIME && timeEnd("addLakesInDeepDepressions");
}

// near sea lakes usually get a lot of water inflow, most of them should break threshold and flow out to sea (see Ancylus Lake)
Expand All @@ -598,7 +599,7 @@ function openNearSeaLakes() {
const cells = grid.cells;
const features = grid.features;
if (!features.find(f => f.type === "lake")) return; // no lakes
TIME && console.time("openLakes");
TIME && timeStart("openLakes");
const LIMIT = 22; // max height that can be breached by water

for (const i of cells.i) {
Expand Down Expand Up @@ -631,7 +632,7 @@ function openNearSeaLakes() {
features[lakeFeatureId].type = "ocean"; // mark former lake as ocean
}

TIME && console.timeEnd("openLakes");
TIME && timeEnd("openLakes");
}

// define map size and position based on template and random factor
Expand Down Expand Up @@ -712,7 +713,7 @@ function calculateMapCoordinates() {
// temperature model, trying to follow real-world data
// based on http://www-das.uwyo.edu/~geerts/cwx/notes/chap16/Image64.gif
function calculateTemperatures() {
TIME && console.time("calculateTemperatures");
TIME && timeStart("calculateTemperatures");
const cells = grid.cells;
cells.temp = new Int8Array(cells.i.length); // temperature array

Expand Down Expand Up @@ -756,12 +757,12 @@ function calculateTemperatures() {
return rn((height / 1000) * 6.5);
}

TIME && console.timeEnd("calculateTemperatures");
TIME && timeEnd("calculateTemperatures");
}

// simplest precipitation model
function generatePrecipitation() {
TIME && console.time("generatePrecipitation");
TIME && timeStart("generatePrecipitation");
d3.select("#prec").selectAll("*").remove();
const { cells, cellsX, cellsY } = grid;
cells.prec = new Uint8Array(cells.i.length); // precipitation array
Expand Down Expand Up @@ -920,12 +921,12 @@ function generatePrecipitation() {
.text("\u21C8");
})();

TIME && console.timeEnd("generatePrecipitation");
TIME && timeEnd("generatePrecipitation");
}

// recalculate Voronoi Graph to pack cells
function reGraph() {
TIME && console.time("reGraph");
TIME && timeStart("reGraph");
const { cells: gridCells, points, features } = grid;
const newCells = { p: [], g: [], h: [] }; // store new data
const spacing2 = grid.spacing ** 2;
Expand Down Expand Up @@ -975,7 +976,7 @@ function reGraph() {
}
);

TIME && console.timeEnd("reGraph");
TIME && timeEnd("reGraph");
}

function isWetLand(moisture, temperature, height) {
Expand All @@ -986,7 +987,7 @@ function isWetLand(moisture, temperature, height) {

// assess cells suitability to calculate population and rand cells for culture center and burgs placement
function rankCells() {
TIME && console.time("rankCells");
TIME && timeStart("rankCells");
const { cells, features } = pack;
cells.s = new Int16Array(cells.i.length); // cell suitability array
cells.pop = new Float32Array(cells.i.length); // cell population array
Expand Down Expand Up @@ -1039,11 +1040,11 @@ function rankCells() {
cells.pop[i] = cells.s[i] > 0 ? (cells.s[i] * cells.area[i]) / meanArea : 0;
}

TIME && console.timeEnd("rankCells");
TIME && timeEnd("rankCells");
}

// show map stats on generation complete
function showStatistics() {
function showStatistics(totalMs) {
const heightmap = ensureEl("templateInput").value;
const isTemplate = heightmap in heightmapTemplates;
const heightmapType = isTemplate ? "template" : "precreated";
Expand All @@ -1069,7 +1070,8 @@ function showStatistics() {
INFO && console.info(stats);

// Dispatch event for test automation and external integrations
window.dispatchEvent(new CustomEvent("map:generated", { detail: { seed, mapId } }));
const detail = typeof totalMs === "number" ? { seed, mapId, totalMs } : { seed, mapId };
window.dispatchEvent(new CustomEvent("map:generated", { detail }));
}

const regenerateMap = debounce(async function (config) {
Expand Down
10 changes: 5 additions & 5 deletions src/controllers/heightmap-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { heightmapTemplates } from "@/data/heightmap-templates";
import { GraphOverride } from "@/generators/graph-override";
import { removeEmblem } from "@/renderers/draw-emblems";
import { moveCircle, removeCircle } from "@/renderers/overlays/brush-circle";
import { downloadFile, getFileName, uploadFile } from "@/utils";
import { downloadFile, getFileName, timeEnd, timeStart, uploadFile } from "@/utils";
import {
ensureEl,
findEl,
Expand Down Expand Up @@ -477,7 +477,7 @@ function finalizeHeightmap(): void {

function regenerateErasedData(): void {
INFO && console.group("Edit Heightmap");
TIME && console.time("regenerateErasedData");
TIME && timeStart("regenerateErasedData");

// remove data
pack.cultures = [];
Expand Down Expand Up @@ -541,7 +541,7 @@ function regenerateErasedData(): void {
Military.generate();
Markers.generate();
Zones.generate();
TIME && console.timeEnd("regenerateErasedData");
TIME && timeEnd("regenerateErasedData");
INFO && console.groupEnd();
}

Expand Down Expand Up @@ -577,7 +577,7 @@ export const createAvailableLandCellFinder = (cells: {

function restoreRiskedData(): void {
INFO && console.group("Edit Heightmap");
TIME && console.time("restoreRiskedData");
TIME && timeStart("restoreRiskedData");
const erosionAllowed = ensureEl<HTMLInputElement>("allowErosion").checked;

// assign pack data to grid cells
Expand Down Expand Up @@ -802,7 +802,7 @@ function restoreRiskedData(): void {
Ice.generate();
select("#ice").selectAll("*").remove();

TIME && console.timeEnd("restoreRiskedData");
TIME && timeEnd("restoreRiskedData");
INFO && console.groupEnd();
}

Expand Down
6 changes: 3 additions & 3 deletions src/generators/biomes-generator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mean } from "d3";
import { rn } from "../utils";
import { rn, timeEnd, timeStart } from "../utils";

export interface Biome {
i: number;
Expand Down Expand Up @@ -103,7 +103,7 @@ class BiomesGenerator {
}

define(): void {
TIME && console.time("defineBiomes");
TIME && timeStart("defineBiomes");
if (!pack.biomes?.length) pack.biomes = this.getDefault();

const { fl: flux, r: riverIds, h: heights, c: neighbors, g: gridReference } = pack.cells;
Expand All @@ -128,7 +128,7 @@ class BiomesGenerator {
pack.cells.biome[cellId] = this.getId(moisture, temperature, height, Boolean(riverIds[cellId]));
}

TIME && console.timeEnd("defineBiomes");
TIME && timeEnd("defineBiomes");
}

getId(moisture: number, temperature: number, height: number, hasRiver: boolean) {
Expand Down
10 changes: 5 additions & 5 deletions src/generators/burgs-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { quadtree } from "d3-quadtree";
import { Emblems } from "@/generators/emblems-generator";
import type { BurgGroup } from "@/types/burg-groups";
import type { Emblem } from "@/types/emblems";
import { each, ensureEl, findClosestCell, gauss, minmax, normalize, P, rn } from "../utils";
import { each, ensureEl, findClosestCell, gauss, minmax, normalize, P, rn, timeEnd, timeStart } from "../utils";
import { type CultureType, DEFAULT_CULTURE_TYPE } from "./cultures-generator";
import { NON_NAVIGABLE_LAKE_GROUPS } from "./features";
import type { Label } from "./labels-generator";
Expand Down Expand Up @@ -52,7 +52,7 @@ type PortCandidate = {

class BurgModule {
generate() {
TIME && console.time("generateBurgs");
TIME && timeStart("generateBurgs");
const { cells } = pack;

let burgs: Burg[] = [0 as any]; // burgs array
Expand Down Expand Up @@ -151,7 +151,7 @@ class BurgModule {
pack.burgs = burgs;
this.assignPorts();

TIME && console.timeEnd("generateBurgs");
TIME && timeEnd("generateBurgs");

function getCapitalsNumber() {
let number = (ensureEl("statesNumber") as HTMLInputElement).valueAsNumber;
Expand Down Expand Up @@ -524,7 +524,7 @@ class BurgModule {
}

specify() {
TIME && console.time("specifyBurgs");
TIME && timeStart("specifyBurgs");

pack.burgs.forEach(burg => {
if (!burg.i || burg.removed || burg.lock) return;
Expand All @@ -543,7 +543,7 @@ class BurgModule {
this.defineGroup(burg, populations);
});

TIME && console.timeEnd("specifyBurgs");
TIME && timeEnd("specifyBurgs");
}

private createWatabouCityLinks(burg: Burg) {
Expand Down
Loading
Loading