174 lines
5.5 KiB
TypeScript
174 lines
5.5 KiB
TypeScript
|
|
/**
|
|||
|
|
* Stresstest – 50.000 Elemente: SpatialIndex Query-Performance,
|
|||
|
|
* HistoryManager Memory, Undo/Redo Performance.
|
|||
|
|
*/
|
|||
|
|
import { describe, it, expect } from 'vitest';
|
|||
|
|
import { SpatialIndex } from '../src/canvas/SpatialIndex';
|
|||
|
|
import { HistoryManager } from '../src/history/HistoryManager';
|
|||
|
|
import type { CADElement, CADLayer } from '../src/types/cad.types';
|
|||
|
|
|
|||
|
|
const N = 50_000;
|
|||
|
|
|
|||
|
|
function generateElements(count: number): CADElement[] {
|
|||
|
|
const elements: CADElement[] = [];
|
|||
|
|
for (let i = 0; i < count; i++) {
|
|||
|
|
elements.push({
|
|||
|
|
id: `elem-${i}`,
|
|||
|
|
type: 'rect',
|
|||
|
|
layerId: 'layer-1',
|
|||
|
|
x: (i % 1000) * 1.5,
|
|||
|
|
y: Math.floor(i / 1000) * 1.5,
|
|||
|
|
width: 1,
|
|||
|
|
height: 1,
|
|||
|
|
properties: {},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return elements;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function makeLayer(): CADLayer {
|
|||
|
|
return {
|
|||
|
|
id: 'layer-1',
|
|||
|
|
name: 'Layer 1',
|
|||
|
|
visible: true,
|
|||
|
|
locked: false,
|
|||
|
|
color: '#ffffff',
|
|||
|
|
lineType: 'solid',
|
|||
|
|
transparency: 0,
|
|||
|
|
sortOrder: 0,
|
|||
|
|
parentId: null,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
describe('Stresstest: 50.000 Elemente', () => {
|
|||
|
|
it('should bulk-insert 50k elements into SpatialIndex in under 2s', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const index = new SpatialIndex();
|
|||
|
|
const start = performance.now();
|
|||
|
|
index.bulkInsert(elements);
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`SpatialIndex bulkInsert: ${elapsed.toFixed(1)}ms for ${N} elements`);
|
|||
|
|
expect(elapsed).toBeLessThan(2000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should search SpatialIndex with 50k elements in under 10ms', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const index = new SpatialIndex();
|
|||
|
|
index.bulkInsert(elements);
|
|||
|
|
// Search a region in the middle
|
|||
|
|
const start = performance.now();
|
|||
|
|
const results = index.search({ minX: 750, minY: 37.5, maxX: 1500, maxY: 75 });
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`SpatialIndex search: ${elapsed.toFixed(2)}ms, found ${results.length} elements`);
|
|||
|
|
expect(elapsed).toBeLessThan(50);
|
|||
|
|
expect(results.length).toBeGreaterThan(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should search a small point region with 50k elements in under 5ms', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const index = new SpatialIndex();
|
|||
|
|
index.bulkInsert(elements);
|
|||
|
|
const start = performance.now();
|
|||
|
|
const results = index.search({ minX: 750, minY: 37.5, maxX: 751, maxY: 38.5 });
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`SpatialIndex point search: ${elapsed.toFixed(3)}ms, found ${results.length} elements`);
|
|||
|
|
expect(elapsed).toBeLessThan(10);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should clear and re-insert 50k elements in under 2s', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const index = new SpatialIndex();
|
|||
|
|
index.bulkInsert(elements);
|
|||
|
|
// Modify 100 elements
|
|||
|
|
for (let i = 0; i < 100; i++) {
|
|||
|
|
elements[i].x += 5000;
|
|||
|
|
}
|
|||
|
|
const start = performance.now();
|
|||
|
|
index.clear();
|
|||
|
|
index.bulkInsert(elements);
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`SpatialIndex clear+reinsert: ${elapsed.toFixed(1)}ms after 100 modifications`);
|
|||
|
|
expect(elapsed).toBeLessThan(2000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should handle HistoryManager with 50k elements snapshot', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const layers = [makeLayer()];
|
|||
|
|
const history = new HistoryManager({ maxStackSize: 50 });
|
|||
|
|
const start = performance.now();
|
|||
|
|
history.initialize({
|
|||
|
|
elements,
|
|||
|
|
layers,
|
|||
|
|
blocks: [],
|
|||
|
|
groups: [],
|
|||
|
|
bgConfig: null,
|
|||
|
|
});
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`HistoryManager initialize: ${elapsed.toFixed(1)}ms for ${N} elements`);
|
|||
|
|
expect(elapsed).toBeLessThan(1000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should push 10 history snapshots with 50k elements each', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const layers = [makeLayer()];
|
|||
|
|
const history = new HistoryManager({ maxStackSize: 50 });
|
|||
|
|
history.initialize({
|
|||
|
|
elements,
|
|||
|
|
layers,
|
|||
|
|
blocks: [],
|
|||
|
|
groups: [],
|
|||
|
|
bgConfig: null,
|
|||
|
|
});
|
|||
|
|
const start = performance.now();
|
|||
|
|
for (let i = 0; i < 10; i++) {
|
|||
|
|
// Slightly modify elements each snapshot
|
|||
|
|
elements[i * 100].x += 1;
|
|||
|
|
history.pushSnapshot({
|
|||
|
|
elements: [...elements],
|
|||
|
|
layers,
|
|||
|
|
blocks: [],
|
|||
|
|
groups: [],
|
|||
|
|
bgConfig: null,
|
|||
|
|
label: `Step ${i + 1}`,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
const elapsed = performance.now() - start;
|
|||
|
|
console.log(`HistoryManager 10 snapshots: ${elapsed.toFixed(1)}ms for ${N} elements each`);
|
|||
|
|
expect(elapsed).toBeLessThan(5000);
|
|||
|
|
expect(history.getHistory().length).toBe(11); // initial + 10
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should undo/redo with 50k elements in under 100ms', () => {
|
|||
|
|
const elements = generateElements(N);
|
|||
|
|
const layers = [makeLayer()];
|
|||
|
|
const history = new HistoryManager({ maxStackSize: 50 });
|
|||
|
|
history.initialize({
|
|||
|
|
elements,
|
|||
|
|
layers,
|
|||
|
|
blocks: [],
|
|||
|
|
groups: [],
|
|||
|
|
bgConfig: null,
|
|||
|
|
});
|
|||
|
|
history.pushSnapshot({
|
|||
|
|
elements: [...elements],
|
|||
|
|
layers,
|
|||
|
|
blocks: [],
|
|||
|
|
groups: [],
|
|||
|
|
bgConfig: null,
|
|||
|
|
label: 'Step 1',
|
|||
|
|
});
|
|||
|
|
const undoStart = performance.now();
|
|||
|
|
const undoSnap = history.undo();
|
|||
|
|
const undoTime = performance.now() - undoStart;
|
|||
|
|
console.log(`Undo: ${undoTime.toFixed(2)}ms for ${N} elements`);
|
|||
|
|
expect(undoSnap).not.toBeNull();
|
|||
|
|
expect(undoTime).toBeLessThan(100);
|
|||
|
|
const redoStart = performance.now();
|
|||
|
|
const redoSnap = history.redo();
|
|||
|
|
const redoTime = performance.now() - redoStart;
|
|||
|
|
console.log(`Redo: ${redoTime.toFixed(2)}ms for ${N} elements`);
|
|||
|
|
expect(redoSnap).not.toBeNull();
|
|||
|
|
expect(redoTime).toBeLessThan(100);
|
|||
|
|
});
|
|||
|
|
});
|