mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-01 15:53:37 +00:00
Optimize mover rendering and segment plan pipeline
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { GameMapImpl } from "../src/core/game/GameMap";
|
||||
import { densePathToKeypointSegments } from "../src/core/game/MotionPlans";
|
||||
import { MiniMapTransformer } from "../src/core/pathfinding/transformers/MiniMapTransformer";
|
||||
|
||||
function makeMap(width: number, height: number): GameMapImpl {
|
||||
return new GameMapImpl(width, height, new Uint8Array(width * height), 0);
|
||||
@@ -58,3 +59,51 @@ describe("densePathToKeypointSegments", () => {
|
||||
expect(expanded).toEqual(dense.map((t) => t >>> 0));
|
||||
});
|
||||
});
|
||||
|
||||
describe("MiniMapTransformer planSegments compression", () => {
|
||||
it("preserves endpoints and total steps while merging collinear runs", () => {
|
||||
const map = makeMap(10, 10);
|
||||
const miniMap = makeMap(5, 5);
|
||||
|
||||
const miniPath = [
|
||||
miniMap.ref(0, 0),
|
||||
miniMap.ref(1, 0),
|
||||
miniMap.ref(2, 0),
|
||||
miniMap.ref(2, 1),
|
||||
miniMap.ref(2, 2),
|
||||
];
|
||||
|
||||
const inner = {
|
||||
findPath() {
|
||||
return miniPath.slice();
|
||||
},
|
||||
planSegments() {
|
||||
return {
|
||||
points: Uint32Array.from(miniPath),
|
||||
segmentSteps: Uint32Array.from([1, 1, 1, 1]),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const transformer = new MiniMapTransformer(inner as any, map, miniMap);
|
||||
const from = map.ref(0, 0);
|
||||
const to = map.ref(4, 4);
|
||||
|
||||
const dense = transformer.findPath(from, to);
|
||||
expect(dense).not.toBeNull();
|
||||
|
||||
const plan = transformer.planSegments(from, to);
|
||||
expect(plan).not.toBeNull();
|
||||
if (!plan) return;
|
||||
|
||||
expect(Array.from(plan.points)).toEqual([
|
||||
from >>> 0,
|
||||
map.ref(4, 0) >>> 0,
|
||||
to >>> 0,
|
||||
]);
|
||||
expect(Array.from(plan.segmentSteps)).toEqual([4, 4]);
|
||||
|
||||
const totalSteps = Array.from(plan.segmentSteps).reduce((a, b) => a + b, 0);
|
||||
expect(totalSteps).toBe(8);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { PathFinderStepper } from "../src/core/pathfinding/PathFinderStepper";
|
||||
import { PathStatus } from "../src/core/pathfinding/types";
|
||||
|
||||
describe("PathFinderStepper cache priming", () => {
|
||||
it("does not prime next() cache via findPath()", () => {
|
||||
it("primes next() cache via findPath()", () => {
|
||||
let calls = 0;
|
||||
const finder = {
|
||||
findPath(from: number | number[], to: number) {
|
||||
@@ -29,6 +29,6 @@ describe("PathFinderStepper cache priming", () => {
|
||||
if (r1.status === PathStatus.NEXT) {
|
||||
expect(r1.node).toBe(to);
|
||||
}
|
||||
expect(calls).toBe(2);
|
||||
expect(calls).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { pruneInactiveTrails } from "../src/client/graphics/layers/TrailLifecycle";
|
||||
|
||||
describe("UnitLayer trail lifecycle helpers", () => {
|
||||
it("removes transport and nuke trails for inactive units", () => {
|
||||
const nukeTrails = new Map<number, number[]>([
|
||||
[10, [1, 2, 3]],
|
||||
[11, [4, 5]],
|
||||
]);
|
||||
const transportTrails = new Map<number, { xy: number[] }>([
|
||||
[10, { xy: [1, 1, 2, 2] }],
|
||||
[12, { xy: [5, 5, 6, 6] }],
|
||||
]);
|
||||
|
||||
const result = pruneInactiveTrails(
|
||||
nukeTrails,
|
||||
transportTrails,
|
||||
(unitId) => unitId === 11,
|
||||
);
|
||||
|
||||
expect(result).toEqual({ removedNukes: 1, removedTransport: 2 });
|
||||
expect(Array.from(nukeTrails.keys())).toEqual([11]);
|
||||
expect(transportTrails.size).toBe(0);
|
||||
});
|
||||
|
||||
it("keeps all trails when units are active", () => {
|
||||
const nukeTrails = new Map<number, number[]>([[1, [1]]]);
|
||||
const transportTrails = new Map<number, { xy: number[] }>([
|
||||
[2, { xy: [0, 0, 1, 1] }],
|
||||
]);
|
||||
|
||||
const result = pruneInactiveTrails(
|
||||
nukeTrails,
|
||||
transportTrails,
|
||||
() => true,
|
||||
);
|
||||
|
||||
expect(result).toEqual({ removedNukes: 0, removedTransport: 0 });
|
||||
expect(nukeTrails.size).toBe(1);
|
||||
expect(transportTrails.size).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
UnitMotionRenderQueue,
|
||||
UnitMotionRenderQueueEntry,
|
||||
} from "../src/client/graphics/layers/UnitMotionRenderQueue";
|
||||
|
||||
describe("UnitMotionRenderQueue", () => {
|
||||
it("returns highest-priority entry first", () => {
|
||||
const queue = new UnitMotionRenderQueue();
|
||||
queue.enqueue({
|
||||
unitId: 1,
|
||||
version: 1,
|
||||
priority: 10,
|
||||
onScreenHint: false,
|
||||
});
|
||||
queue.enqueue({
|
||||
unitId: 2,
|
||||
version: 1,
|
||||
priority: 20,
|
||||
onScreenHint: true,
|
||||
});
|
||||
|
||||
const first = queue.pollValid(() => true);
|
||||
expect(first?.unitId).toBe(2);
|
||||
});
|
||||
|
||||
it("skips stale entries when validator rejects old versions", () => {
|
||||
const queue = new UnitMotionRenderQueue();
|
||||
const latestVersion = new Map<number, number>([[42, 2]]);
|
||||
|
||||
const stale: UnitMotionRenderQueueEntry = {
|
||||
unitId: 42,
|
||||
version: 1,
|
||||
priority: 100,
|
||||
onScreenHint: true,
|
||||
};
|
||||
const fresh: UnitMotionRenderQueueEntry = {
|
||||
unitId: 42,
|
||||
version: 2,
|
||||
priority: 50,
|
||||
onScreenHint: true,
|
||||
};
|
||||
|
||||
queue.enqueue(stale);
|
||||
queue.enqueue(fresh);
|
||||
|
||||
const picked = queue.pollValid((entry) => {
|
||||
return latestVersion.get(entry.unitId) === entry.version;
|
||||
});
|
||||
|
||||
expect(picked).toEqual(fresh);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user