mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 07:50:45 +00:00
improve astar perf (#1268)
## Description: Created test that has astar pathfind from top left to bottom right of giant world map. * Before these changes: took ~950ms * replaced queue with fastqueue library: ~600ms * Changes heuristic to be more greedy (1.1 * dist => 2 * dist): ~90ms Resulting in a roughly 10x improvement. Other paths also saw improvements as well, although not as dramatic. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [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: evan
This commit is contained in:
@@ -8,3 +8,4 @@ resources/images/.DS_Store
|
||||
resources/.DS_Store
|
||||
.env*
|
||||
.DS_Store
|
||||
.clinic/
|
||||
|
||||
+7
-1
@@ -14,7 +14,13 @@ export default {
|
||||
"ts-jest",
|
||||
{
|
||||
useESM: true,
|
||||
tsconfig: "tsconfig.jest.json",
|
||||
tsconfig: {
|
||||
target: "ES2020",
|
||||
module: "es2022",
|
||||
moduleResolution: "node",
|
||||
experimentalDecorators: true,
|
||||
types: ["jest", "node"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Generated
+34
@@ -34,6 +34,7 @@
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^4.21.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fastpriorityqueue": "^0.7.5",
|
||||
"google-auth-library": "^9.14.0",
|
||||
"googleapis": "^143.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
@@ -68,6 +69,7 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@eslint/compat": "^1.2.7",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/chai": "^4.3.17",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
@@ -79,6 +81,7 @@
|
||||
"@types/systeminformation": "^3.23.1",
|
||||
"@types/ws": "^8.5.11",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-base64-loader": "^1.0.0",
|
||||
"canvas": "^3.1.0",
|
||||
"chai": "^5.1.1",
|
||||
@@ -8925,6 +8928,13 @@
|
||||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/benchmark": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/benchmark/-/benchmark-2.1.5.tgz",
|
||||
"integrity": "sha512-cKio2eFB3v7qmKcvIHLUMw/dIx/8bhWPuzpzRT4unCPRTD8VdA9Zb0afxpcxOqR4PixRS7yT42FqGS8BYL8g1w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
@@ -10912,6 +10922,17 @@
|
||||
"integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/benchmark": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
|
||||
"integrity": "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4",
|
||||
"platform": "^1.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@@ -14011,6 +14032,12 @@
|
||||
"node": ">= 4.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fastpriorityqueue": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/fastpriorityqueue/-/fastpriorityqueue-0.7.5.tgz",
|
||||
"integrity": "sha512-3Pa0n9gwy8yIbEsT3m2j/E9DXgWvvjfiZjjqcJ+AdNKTAlVMIuFYrYG5Y3RHEM8O6cwv9hOpOWY/NaMfywoQVA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
|
||||
@@ -19518,6 +19545,13 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/platform": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
|
||||
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
|
||||
"tunnel": "npm run build-prod && npm run start:server",
|
||||
"test": "jest",
|
||||
"perf": "npx tsx tests/perf/*.ts",
|
||||
"test:coverage": "jest --coverage",
|
||||
"format": "prettier --ignore-unknown --write .",
|
||||
"lint": "eslint",
|
||||
@@ -27,6 +28,7 @@
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@eslint/compat": "^1.2.7",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/chai": "^4.3.17",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/jest": "^30.0.0",
|
||||
@@ -38,6 +40,7 @@
|
||||
"@types/systeminformation": "^3.23.1",
|
||||
"@types/ws": "^8.5.11",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-base64-loader": "^1.0.0",
|
||||
"canvas": "^3.1.0",
|
||||
"chai": "^5.1.1",
|
||||
@@ -105,6 +108,7 @@
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^4.21.1",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"fastpriorityqueue": "^0.7.5",
|
||||
"google-auth-library": "^9.14.0",
|
||||
"googleapis": "^143.0.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||
import FastPriorityQueue from "fastpriorityqueue";
|
||||
import { AStar, PathFindResultType } from "./AStar";
|
||||
|
||||
/**
|
||||
@@ -12,11 +12,11 @@ export interface GraphAdapter<NodeType> {
|
||||
}
|
||||
|
||||
export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
private fwdOpenSet: PriorityQueue<{
|
||||
private fwdOpenSet: FastPriorityQueue<{
|
||||
tile: NodeType;
|
||||
fScore: number;
|
||||
}>;
|
||||
private bwdOpenSet: PriorityQueue<{
|
||||
private bwdOpenSet: FastPriorityQueue<{
|
||||
tile: NodeType;
|
||||
fScore: number;
|
||||
}>;
|
||||
@@ -39,15 +39,15 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
private graph: GraphAdapter<NodeType>,
|
||||
private directionChangePenalty: number = 0,
|
||||
) {
|
||||
this.fwdOpenSet = new PriorityQueue((a, b) => a.fScore - b.fScore);
|
||||
this.bwdOpenSet = new PriorityQueue((a, b) => a.fScore - b.fScore);
|
||||
this.fwdOpenSet = new FastPriorityQueue((a, b) => a.fScore < b.fScore);
|
||||
this.bwdOpenSet = new FastPriorityQueue((a, b) => a.fScore < b.fScore);
|
||||
this.sources = Array.isArray(src) ? src : [src];
|
||||
this.closestSource = this.findClosestSource(dst);
|
||||
|
||||
// Initialize forward search with source point(s)
|
||||
this.sources.forEach((startPoint) => {
|
||||
this.fwdGScore.set(startPoint, 0);
|
||||
this.fwdOpenSet.enqueue({
|
||||
this.fwdOpenSet.add({
|
||||
tile: startPoint,
|
||||
fScore: this.heuristic(startPoint, dst),
|
||||
});
|
||||
@@ -55,7 +55,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
|
||||
// Initialize backward search from destination
|
||||
this.bwdGScore.set(dst, 0);
|
||||
this.bwdOpenSet.enqueue({
|
||||
this.bwdOpenSet.add({
|
||||
tile: dst,
|
||||
fScore: this.heuristic(dst, this.findClosestSource(dst)),
|
||||
});
|
||||
@@ -85,7 +85,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
}
|
||||
|
||||
// Process forward search
|
||||
const fwdCurrent = this.fwdOpenSet.dequeue()!.tile;
|
||||
const fwdCurrent = this.fwdOpenSet.poll()!.tile;
|
||||
|
||||
// Check if we've found a meeting point
|
||||
if (this.bwdGScore.has(fwdCurrent)) {
|
||||
@@ -96,7 +96,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
this.expandNode(fwdCurrent, true);
|
||||
|
||||
// Process backward search
|
||||
const bwdCurrent = this.bwdOpenSet.dequeue()!.tile;
|
||||
const bwdCurrent = this.bwdOpenSet.poll()!.tile;
|
||||
|
||||
// Check if we've found a meeting point
|
||||
if (this.fwdGScore.has(bwdCurrent)) {
|
||||
@@ -145,7 +145,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
const fScore =
|
||||
totalG +
|
||||
this.heuristic(neighbor, isForward ? this.dst : this.closestSource);
|
||||
openSet.enqueue({ tile: neighbor, fScore: fScore });
|
||||
openSet.add({ tile: neighbor, fScore: fScore });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export class SerialAStar<NodeType> implements AStar<NodeType> {
|
||||
private heuristic(a: NodeType, b: NodeType): number {
|
||||
const posA = this.graph.position(a);
|
||||
const posB = this.graph.position(b);
|
||||
return 1.1 * (Math.abs(posA.x - posB.x) + Math.abs(posA.y - posB.y));
|
||||
return 2 * (Math.abs(posA.x - posB.x) + Math.abs(posA.y - posB.y));
|
||||
}
|
||||
|
||||
private getDirection(from: NodeType, to: NodeType): string {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import Benchmark from "benchmark";
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { PathFinder } from "../../src/core/pathfinding/PathFinding";
|
||||
import { setup } from "../util/Setup";
|
||||
|
||||
const game = await setup(
|
||||
"giantworldmap",
|
||||
{},
|
||||
[],
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
);
|
||||
|
||||
new Benchmark.Suite()
|
||||
.add("top-left-to-bottom-right", () => {
|
||||
PathFinder.Mini(game, 10_000_000_000, true, 1).nextTile(
|
||||
game.ref(0, 0),
|
||||
game.ref(4077, 1929),
|
||||
);
|
||||
})
|
||||
.add("hawaii to svalbard", () => {
|
||||
PathFinder.Mini(game, 10_000_000_000, true, 1).nextTile(
|
||||
game.ref(186, 800),
|
||||
game.ref(2205, 52),
|
||||
);
|
||||
})
|
||||
.add("black sea to california", () => {
|
||||
PathFinder.Mini(game, 10_000_000_000, true, 1).nextTile(
|
||||
game.ref(2349, 455),
|
||||
game.ref(511, 536),
|
||||
);
|
||||
})
|
||||
.on("cycle", (event: any) => {
|
||||
console.log(String(event.target));
|
||||
})
|
||||
.run({ async: true });
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"map": {
|
||||
"height": 1948,
|
||||
"num_land_tiles": 2544294,
|
||||
"width": 4110
|
||||
},
|
||||
"mini_map": {
|
||||
"height": 974,
|
||||
"num_land_tiles": 618860,
|
||||
"width": 2055
|
||||
},
|
||||
"name": "Giant World Map",
|
||||
"nations": []
|
||||
}
|
||||
+241
File diff suppressed because one or more lines are too long
+61
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
+4
-3
@@ -23,21 +23,22 @@ export async function setup(
|
||||
mapName: string,
|
||||
_gameConfig: Partial<GameConfig> = {},
|
||||
humans: PlayerInfo[] = [],
|
||||
currentDir: string = __dirname,
|
||||
): Promise<Game> {
|
||||
// Suppress console.debug for tests.
|
||||
console.debug = () => {};
|
||||
|
||||
// Simple binary file loading using fs.readFileSync()
|
||||
const mapBinPath = path.join(
|
||||
__dirname,
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/map.bin`,
|
||||
);
|
||||
const miniMapBinPath = path.join(
|
||||
__dirname,
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/mini_map.bin`,
|
||||
);
|
||||
const manifestPath = path.join(
|
||||
__dirname,
|
||||
currentDir,
|
||||
`../testdata/maps/${mapName}/manifest.json`,
|
||||
);
|
||||
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["tests/**/*"]
|
||||
"include": ["tests/**/*", "src/**/*"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user