Skip to content

Commit

Permalink
Adds ability to test example performance. Improoves spring-and-damper…
Browse files Browse the repository at this point in the history
… force calculations which speeds up Wheel example. 15 seconds of simulation: BEFORE average was 3.77s (min 3.02s, max 4.16s), AFTER average is 3.12s (min 3.06s, max 3.27s).
  • Loading branch information
robertrypula committed Jan 7, 2020
1 parent 49773b3 commit 3935a87
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<title>Simple Forces</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body onload___________________="runExample('ExampleForceTypeReactionAndFriction')">
<body onload_="runExample('ExampleAdvancedWheel')">
<div id="simple-forces-root">
<h1>Simple Forces</h1>

Expand Down
33 changes: 31 additions & 2 deletions src/lib/core/forces/spring-and-damper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Line } from '@core/constraints-hosts/line';
import { Point } from '@core/constraints-hosts/point';
import { Force, ForceSource } from '@core/force';
import { ForceType } from '@core/models';
import { SimplePoint } from '@core/simple-point';
import { World } from '@core/world';

/*tslint:disable:max-classes-per-file*/
Expand All @@ -16,6 +17,26 @@ export class SpringAndDamperForce extends Force {
}

public calculateForce(point: Point): void {
if (this.isSecondEnd(point)) {
return;
}

const line: Line = this.forceSource.line;
const unitAngle: number = line.getUnitAngle();
const origin: SimplePoint = line.pointA.cloneAsSimplePoint();
const simplePointLineB: SimplePoint = line.pointB.cloneAsSimplePoint().transform(origin, unitAngle);

const springForce: number = -(line.length - simplePointLineB.position.x) * this.forceSource.k;
const dampingForce: number = simplePointLineB.velocity.x * this.forceSource.b;
let finalForce: number = springForce + dampingForce;

this.forceSource.includeMass && (finalForce *= point.mass);

simplePointLineB.force = Complex.create(finalForce, 0);
this.vector = simplePointLineB.transformBack(origin, unitAngle).force;
this.forceSource.pointBForce.vector = simplePointLineB.force.clone().multiplyScalar(-1);

/*
const springMountingPoint: Point =
this.forceSource.line.pointA === point ? this.forceSource.line.pointB : this.forceSource.line.pointA;
const direction: Complex = point.position.clone().subtract(springMountingPoint.position);
Expand All @@ -29,12 +50,16 @@ export class SpringAndDamperForce extends Force {
this.forceSource.includeMass && (finalForce *= point.mass);
this.vector = direction.normalize().multiplyScalar(-finalForce);

*/
// TODO known issue:
// - magnitude of force is calculated twice
// (ends of the spring acts on each other with the same force but opposite direction)
// solution: ignore the second end of the line and calculate everything in the first end
}

protected isSecondEnd(point: Point): boolean {
return point === this.forceSource.line.pointB;
}
}

// ----------------------------------------------------------------
Expand All @@ -44,11 +69,15 @@ export class SpringAndDamperForceSource extends ForceSource {
public k: number = DEFAULT_SPRING_AND_DAMPER_K_COEFFICIENT;
public includeMass = true; // TODO move to constants

// NOTE: caching references to force speeds up access to it as each
// point have array of forces and we would need to use slower find()
public pointBForce: SpringAndDamperForce;

public constructor(world: World, public line: Line) {
super(world);

line.pointA.forces.push(new SpringAndDamperForce(this));
line.pointB.forces.push(new SpringAndDamperForce(this));
line.pointB.forces.push((this.pointBForce = new SpringAndDamperForce(this)));

// NOTE: no world's refreshAwareness method is needed - spring & damper force interacts only with two 'self' points
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/core/forces/thrust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export class ThrustForce extends Force {
}

if (this.forceSource.pointAForce !== this) {
throw new Error('Problem to investigate'); // TODO remove it after tests on more complex objects with thrust
// TODO remove it after tests on more complex objects with thrust
throw new Error('Problem to investigate - this should never happen');
}

const lineDirection: Complex = Complex.createPolar(this.forceSource.line.getUnitAngle());
Expand Down
2 changes: 1 addition & 1 deletion src/lib/examples/abstract-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export abstract class AbstractExample {
public log: Array<[string, string]> = [];
public world: World;

protected constructor() {
public constructor() {
this.world = new World();
this.createScene();
this.world.refreshAwareness();
Expand Down
3 changes: 3 additions & 0 deletions src/lib/examples/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Copyright (c) 2018-2020 Robert Rypuła - https://github.com/robertrypula

export const DEFAULT_EXAMPLE_FPS = 60;
34 changes: 34 additions & 0 deletions src/lib/examples/example-advanced-wheel.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2018-2020 Robert Rypuła - https://github.com/robertrypula

import { ExampleAdvancedWheel } from '@';

import { ExampleExecutionDetails } from '@examples/models';
import { getExampleExecutionDetails } from '@examples/tools';

describe('Advanced example - wheel', (): void => {
it('should properly calculate wheel position', (): void => {
const exampleExecutionDetails: ExampleExecutionDetails = getExampleExecutionDetails(
ExampleAdvancedWheel,
(example: ExampleAdvancedWheel): string =>
example.world.physics.time.toFixed(3) + 's: ' + example.wheel.center.position.toStringXY()
);

expect(exampleExecutionDetails.singleRunLog).toEqual([
'0.017s: 2.000 2.999',
'1.017s: 1.269 2.157',
'2.017s: -1.008 1.058',
'3.017s: -4.006 -0.241',
'4.017s: -2.011 -0.511',
'5.017s: -2.035 -0.197',
'6.017s: -4.041 -0.373',
'7.017s: -2.314 -0.459',
'8.017s: -2.052 -0.437',
'9.017s: -2.199 -0.542',
'10.017s: -2.980 -0.529',
'11.017s: -3.751 -0.531',
'12.017s: -3.830 -0.544',
'13.017s: -3.577 -0.531',
'14.017s: -3.328 -0.530'
]);
});
});
15 changes: 15 additions & 0 deletions src/lib/examples/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2018-2020 Robert Rypuła - https://github.com/robertrypula

import { AbstractExample } from '@examples/abstract-example';

export interface ExampleExecutionDetails {
runTime: {
average: number;
max: number;
min: number;
};
runTimes: number[];
singleRunLog: string[];
}

export type ExampleFactory = new () => AbstractExample;
12 changes: 11 additions & 1 deletion src/lib/examples/node/cli.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
// Copyright (c) 2018-2020 Robert Rypuła - https://github.com/robertrypula

import { ExampleAdvancedWheel } from '@examples/example-advanced-wheel';
import { ExampleExecutionDetails } from '@examples/models';
import { getExampleExecutionDetails } from '@examples/tools';

export class CliNodeExample {
public constructor() {
const exampleExecutionDetails: ExampleExecutionDetails = getExampleExecutionDetails(
ExampleAdvancedWheel,
(example: ExampleAdvancedWheel): string =>
example.world.physics.time.toFixed(3) + 's: ' + example.wheel.center.position.toStringXY()
);

/*tslint:disable-next-line:no-console*/
console.log('Hello World!');
console.log(exampleExecutionDetails);
}
}
45 changes: 45 additions & 0 deletions src/lib/examples/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2018-2020 Robert Rypuła - https://github.com/robertrypula

import { AbstractExample } from '@examples/abstract-example';
import { DEFAULT_EXAMPLE_FPS } from '@examples/constants';
import { ExampleExecutionDetails } from '@examples/models';
import { getTime } from '@shared/tools';

export const getExampleExecutionDetails = <T extends AbstractExample>(
exampleFactory: new () => T,
logHandler: (example: T) => string = null,
runs = 1,
seconds = 15,
fps = DEFAULT_EXAMPLE_FPS
): ExampleExecutionDetails => {
const dt: number = 1 / fps;
const timeStart: number = getTime();
const exampleExecutionDetails: ExampleExecutionDetails = {
runTime: {
average: null,
max: null,
min: null
},
runTimes: [],
singleRunLog: []
};

for (let run = 0; run < runs; run++) {
const timeSubStart: number = getTime();
const example: T = new exampleFactory();

for (let second = 0; second < seconds; second++) {
for (let frame = 0; frame < fps; frame++) {
example.animationFrame(dt);

run === 0 && frame === 0 && logHandler && exampleExecutionDetails.singleRunLog.push(logHandler(example));
}
}
exampleExecutionDetails.runTimes.push(getTime() - timeSubStart);
}
exampleExecutionDetails.runTime.average = (getTime() - timeStart) / runs;
exampleExecutionDetails.runTime.max = Math.max(...exampleExecutionDetails.runTimes);
exampleExecutionDetails.runTime.min = Math.min(...exampleExecutionDetails.runTimes);

return exampleExecutionDetails;
};
18 changes: 13 additions & 5 deletions src/lib/examples/web/web-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import { CanvasRenderer, getTime } from '@';

import { AbstractExample } from '@examples/abstract-example';
import { DEFAULT_EXAMPLE_FPS } from '@examples/constants';
import { ExampleFactory } from '@examples/models';
import { getExampleExecutionDetails } from '@examples/tools';
import * as domUtils from '@examples/web/dom-utils';

export class WebRunner {
Expand All @@ -12,7 +15,7 @@ export class WebRunner {
protected previousTime: number = null;
protected canvasRenderer: CanvasRenderer;

public constructor(protected exampleFactory: new () => AbstractExample) {
public constructor(protected exampleFactory: ExampleFactory) {
domUtils.getByTagName('html').classList.add('web-runner');
domUtils.getById('simple-forces-root').innerHTML = require('./web-runner.html');

Expand All @@ -23,14 +26,14 @@ export class WebRunner {
document.addEventListener('keydown', (event: KeyboardEvent): void => this.example.keyboardEvent(event.code, true));
document.addEventListener('keyup', (event: KeyboardEvent): void => this.example.keyboardEvent(event.code, false));

this.animationFrame();
1 ? this.animationFrame() : setTimeout(this.offlineExampleExecution.bind(this), 0); // TODO for development
}

public animationFrame(): void {
const currentTime = getTime();
let dt = this.previousTime === null ? 0 : currentTime - this.previousTime;
const currentTime: number = getTime();
let dt: number = this.previousTime === null ? 0 : currentTime - this.previousTime;

dt = 0.016; // TODO only in development, constant delta between animation frames
dt = 1 / DEFAULT_EXAMPLE_FPS; // TODO check it, probably it would be better to keep constant dt between frames
this.example.animationFrame(dt);
this.canvasRenderer.render();
this.log();
Expand All @@ -39,6 +42,11 @@ export class WebRunner {
window.requestAnimationFrame(this.animationFrame.bind(this));
}

protected offlineExampleExecution(): void {
/*tslint:disable-next-line:no-console*/
console.log(getExampleExecutionDetails(this.exampleFactory, null, 8));
}

protected log(): void {
if (this.logElement) {
this.logElement.innerHTML = this.example.log.map(logItem => logItem.join(': ')).join('\n');
Expand Down

0 comments on commit 3935a87

Please sign in to comment.