Files
gods-eye-view/src/globe/movers.test.mjs
T
German Khokhlov 39d522c9a9 perf(globe): less churn in movers and lines
Review of Tasks 2–3:
- the mover store's instance checks no longer build arrays or closures
  per set() (orientation and colour checked field by field), and dirty()
  returns one reused range object;
- Cesium models get their matrix and colour through scratch objects
  (Model.modelMatrix and Model.color copy what they are given) instead
  of a new Matrix4 and Color per placement;
- a destroyed MoverSet releases its store (count 0, empty instance
  arrays, no pick ids) so a set object a layer still holds keeps no
  memory, and the Cesium parts drop their billboard, point and model
  lists;
- (with the overlay commit) the Navara line viewport is updated in
  place.

Node micro-measure, 10 000 instances set + packed per frame: 0.27-0.29
ms, about 1.7 KB per frame (0.17 B per instance), as before. Contract:
Cesium 70/70, Navara 76/76.
2026-10-16 19:33:46 +04:00

210 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict';
import test from 'node:test';
import {
MOVER_KINDS,
createMoverSet,
createMoverStore,
normalizeMoverSetOptions,
} from './movers.js';
const atlas = {
canvas: { width: 64, height: 32 },
cells: { jet: [0, 0, 0.5, 1], heli: [0.5, 0, 1, 1] },
};
test('set options are normalised with the contract defaults', () => {
assert.deepEqual(MOVER_KINDS, ['sprite', 'point', 'model']);
const sprite = normalizeMoverSetOptions({
id: 'flights',
kind: 'sprite',
capacity: 10,
sprite: { atlas, sizePx: 24 },
});
assert.equal(sprite.id, 'flights');
assert.equal(sprite.order, 0);
assert.equal(sprite.depthTest, 'normal');
assert.equal(sprite.pickable, false);
assert.deepEqual(sprite.cellKeys, ['jet', 'heli']);
assert.equal(sprite.sprite.scaleByDistance, null);
const point = normalizeMoverSetOptions({
id: 'sats',
kind: 'point',
capacity: 3,
depthTest: { disableWithinM: 5000 },
point: { sizePx: 4 },
});
assert.equal(point.point.outlinePx, 0);
assert.deepEqual(point.depthTest, { disableWithinM: 5000 });
const model = normalizeMoverSetOptions({
id: 'models',
kind: 'model',
capacity: 2,
model: { url: '/models/airplane.glb' },
});
assert.equal(model.model.minimumPixelSize, 0);
assert.equal(model.model.maximumScale, Infinity);
assert.equal(model.model.colorBlend, null);
});
test('bad set options are refused with a TypeError', () => {
for (const options of [
{},
{ id: 'a', kind: 'cube', capacity: 1 },
{ id: 'a', kind: 'sprite', capacity: 0, sprite: { atlas, sizePx: 2 } },
{ id: 'a', kind: 'sprite', capacity: 1.5, sprite: { atlas, sizePx: 2 } },
{ id: 'a', kind: 'sprite', capacity: 1 },
{ id: 'a', kind: 'sprite', capacity: 1, sprite: { atlas, sizePx: -1 } },
{ id: 'a', kind: 'point', capacity: 1, point: {} },
{ id: 'a', kind: 'model', capacity: 1, model: {} },
{
id: 'a',
kind: 'point',
capacity: 1,
point: { sizePx: 2 },
depthTest: 'sometimes',
},
{
id: 'a',
kind: 'sprite',
capacity: 1,
sprite: { atlas, sizePx: 2, scaleByDistance: [1, 2] },
},
])
assert.throws(() => normalizeMoverSetOptions(options), TypeError);
});
test('the store keeps each instance in flat arrays with defaults', () => {
const store = createMoverStore(
normalizeMoverSetOptions({
id: 'f',
kind: 'sprite',
capacity: 3,
sprite: { atlas, sizePx: 16 },
}),
);
assert.equal(store.capacity, 3);
assert.equal(store.count, 0);
store.set(1, {
position: { x: 1, y: 2, z: 3 },
rotation: 0.5,
color: [1, 0, 0, 0.5],
scale: 2,
cell: 'heli',
show: false,
pickId: 'abc',
});
store.set(0, { position: { x: 4, y: 5, z: 6 } });
assert.deepEqual([...store.positions.subarray(0, 6)], [4, 5, 6, 1, 2, 3]);
assert.deepEqual(
[...store.colors.subarray(0, 8)],
[1, 1, 1, 1, 1, 0, 0, 0.5],
);
assert.equal(store.rotations[1], 0.5);
assert.equal(store.scales[0], 1);
assert.equal(store.scales[1], 2);
assert.equal(store.cells[0], 0);
assert.equal(store.cells[1], 1);
assert.equal(store.shows[0], 1);
assert.equal(store.shows[1], 0);
assert.equal(store.pickIds[1], 'abc');
assert.equal(store.pickIds[0], undefined);
// set() without a field keeps the stored value.
store.set(1, { position: { x: 7, y: 8, z: 9 } });
assert.equal(store.scales[1], 2);
assert.equal(store.shows[1], 0);
store.setCount(2);
assert.equal(store.count, 2);
assert.deepEqual([...store.positionsView()], [4, 5, 6, 7, 8, 9]);
assert.deepEqual(store.dirty(), { from: 0, to: 2 });
store.clean();
assert.equal(store.dirty(), null);
store.set(2, { position: { x: 0, y: 0, z: 1 } });
assert.deepEqual(store.dirty(), { from: 2, to: 3 });
});
test('bad instances are refused and leave the store unchanged', () => {
const store = createMoverStore(
normalizeMoverSetOptions({
id: 'f',
kind: 'point',
capacity: 2,
point: { sizePx: 4 },
}),
);
for (const [index, fields] of [
[2, { position: { x: 0, y: 0, z: 0 } }],
[-1, { position: { x: 0, y: 0, z: 0 } }],
[0, { position: { x: Number.NaN, y: 0, z: 0 } }],
[0, { color: [1, 1, 1] }],
[0, { cell: 'jet' }],
[0, { scale: -1 }],
])
assert.throws(() => store.set(index, fields), TypeError);
assert.throws(() => store.setCount(3), RangeError);
assert.equal(store.dirty(), null);
});
test('models take an orientation quaternion', () => {
const store = createMoverStore(
normalizeMoverSetOptions({
id: 'm',
kind: 'model',
capacity: 1,
model: { url: '/m.glb' },
}),
);
assert.deepEqual([...store.orientations], [0, 0, 0, 1]);
store.set(0, { orientation: { x: 0, y: 0, z: 1, w: 0 } });
assert.deepEqual([...store.orientations], [0, 0, 1, 0]);
assert.throws(() => store.set(0, { orientation: { x: 0, y: 0 } }), TypeError);
});
test('the set facade forwards to the store and an engine commit, once destroyed it refuses', () => {
const store = createMoverStore(
normalizeMoverSetOptions({
id: 'f',
kind: 'point',
capacity: 2,
point: { sizePx: 4 },
}),
);
const calls = [];
const set = createMoverSet(store, {
commit: () => calls.push(['commit', { ...store.dirty() }]),
destroy: () => calls.push(['destroy']),
});
set.set(0, { position: { x: 1, y: 2, z: 3 } });
set.setCount(1);
assert.equal(set.count(), 1);
assert.deepEqual([...set.positionsView()], [1, 2, 3]);
set.commit();
assert.deepEqual(calls, [['commit', { from: 0, to: 1 }]]);
assert.equal(store.dirty(), null, 'commit cleans the store');
set.destroy();
set.destroy();
assert.deepEqual(calls.at(-1), ['destroy']);
assert.equal(calls.length, 2);
assert.throws(() => set.set(0, {}), /destroyed/);
assert.throws(() => set.commit(), /destroyed/);
});
test('a destroyed set releases its instance arrays', () => {
const store = createMoverStore(
normalizeMoverSetOptions({
id: 'r',
kind: 'point',
capacity: 1000,
point: { sizePx: 2 },
}),
);
const set = createMoverSet(store, { commit() {}, destroy() {} });
set.set(0, { position: { x: 1, y: 2, z: 3 }, pickId: 'p' });
set.setCount(1);
set.destroy();
assert.equal(store.count, 0);
assert.equal(store.positions.length, 0);
assert.equal(store.pickIds.length, 0);
assert.equal(set.positionsView().length, 0);
assert.equal(store.dirty(), null);
});