Try initializing and move trains in Web Workers

Unfortunately, all of the train data has to be copied from the train web worker
to the main thread on every tick so it knows the new positions of the trains to
render. So every few ticks, the GC causes a little lag as it collects ~90+MB of
obsolete train arrays. There's no way for the two threads to share the same data
thanks to Spectre.
This commit is contained in:
Tyler Hallada 2018-04-22 18:40:20 -04:00
parent ad9167528a
commit 4d1ef84568
11 changed files with 340 additions and 226 deletions

10
package-lock.json generated
View File

@ -12769,6 +12769,16 @@
"errno": "0.1.7" "errno": "0.1.7"
} }
}, },
"worker-loader": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-1.1.1.tgz",
"integrity": "sha512-qJZLVS/jMCBITDzPo/RuweYSIG8VJP5P67mP/71alGyTZRe1LYJFdwLjLalY3T5ifx0bMDRD3OB6P2p1escvlg==",
"dev": true,
"requires": {
"loader-utils": "1.1.0",
"schema-utils": "0.4.5"
}
},
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",

View File

@ -41,7 +41,8 @@
"uglifyjs-webpack-plugin": "^1.2.4", "uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "^4.4.1", "webpack": "^4.4.1",
"webpack-cli": "^2.0.13", "webpack-cli": "^2.0.13",
"webpack-dev-server": "^3.1.1" "webpack-dev-server": "^3.1.1",
"worker-loader": "^1.1.1"
}, },
"dependencies": { "dependencies": {
"@types/pixi.js": "^4.7.2", "@types/pixi.js": "^4.7.2",

View File

@ -5,17 +5,13 @@ import LineConnection from './LineConnection';
import Station from './Station'; import Station from './Station';
import { distance, randomInt, randomPoint } from './utils'; import { distance, randomInt, randomPoint } from './utils';
const CONNECTION_RADIUS = Math.floor(Math.sqrt(
Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2),
) / 8);
export default class Line { export default class Line {
public name: string; public name: string;
public color: tinycolorInstance; public color: ColorFormats.RGBA;
constructor( constructor(
name: string, name: string,
color: tinycolorInstance, color: ColorFormats.RGBA,
) { ) {
this.name = name; this.name = name;
this.color = color; this.color = color;
@ -26,13 +22,14 @@ export default class Line {
stations: Station[], stations: Station[],
visitedStations: Station[], visitedStations: Station[],
connectionLimit: number, connectionLimit: number,
connectionRadius: number,
) { ) {
visitedStations.push(currentStation); visitedStations.push(currentStation);
const otherStations = stations.filter(station => station !== currentStation); const otherStations = stations.filter(station => station !== currentStation);
const closeStations = Station.stationsWithinRadius( const closeStations = Station.stationsWithinRadius(
otherStations, otherStations,
currentStation.location, currentStation.location,
CONNECTION_RADIUS, connectionRadius,
); );
for (let i = 0; i < connectionLimit; i += 1) { for (let i = 0; i < connectionLimit; i += 1) {
if (closeStations.length < 1) { if (closeStations.length < 1) {
@ -51,6 +48,7 @@ export default class Line {
stations, stations,
visitedStations, visitedStations,
connectionLimit, connectionLimit,
connectionRadius,
); );
} }
} }

View File

@ -1,9 +1,11 @@
import Line from './Line'; import Line from './Line';
import Signal from './Signal';
import Station from './Station'; import Station from './Station';
export default class LineConnection { export default class LineConnection {
public station: Station;
public line: Line; public line: Line;
public signals: Signal[];
public station: Station;
constructor( constructor(
station: Station, station: Station,

View File

@ -1,3 +1,4 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2'; import * as tinycolor from 'tinycolor2';
import Direction, { getPointDirection } from './Direction'; import Direction, { getPointDirection } from './Direction';
@ -18,23 +19,23 @@ export default class Station {
return largest; return largest;
} }
public static stationsWithinRadius(stations: Station[], point: PIXI.Point, public static stationsWithinRadius(stations: Station[], point: Point,
radius: number): Station[] { radius: number): Station[] {
return stations.filter(station => distance(point, station.location) <= radius); return stations.filter(station => distance(point, station.location) <= radius);
} }
public static stationsInDirection(stations: Station[], point: PIXI.Point, public static stationsInDirection(stations: Station[], point: Point,
direction: Direction): Station[] { direction: Direction): Station[] {
return stations.filter(station => getPointDirection(point, station.location) === direction); return stations.filter(station => getPointDirection(point, station.location) === direction);
} }
public static closestStation(stations: Station[], point: PIXI.Point): Station { public static closestStation(stations: Station[], point: Point): Station {
return stations.reduce( return stations.reduce(
(prev, curr) => distance(point, prev.location) < distance(point, curr.location) ? prev : curr, (prev, curr) => distance(point, prev.location) < distance(point, curr.location) ? prev : curr,
); );
} }
public static randomCloseLargeStation(stations: Station[], point: PIXI.Point, public static randomCloseLargeStation(stations: Station[], point: Point,
radius: number): Station { radius: number): Station {
const closeStations = Station.stationsWithinRadius(stations, point, const closeStations = Station.stationsWithinRadius(stations, point,
radius); radius);
@ -42,7 +43,7 @@ export default class Station {
return weightedRandom(closeStations, closeStationWeights); return weightedRandom(closeStations, closeStationWeights);
} }
public static isPointDistant(point: PIXI.Point, stations: Station[], public static isPointDistant(point: Point, stations: Station[],
minDistance: number): boolean { minDistance: number): boolean {
for (const station of stations) { for (const station of stations) {
if (distance(point, station.location) < minDistance) { if (distance(point, station.location) < minDistance) {
@ -52,10 +53,15 @@ export default class Station {
return true; return true;
} }
public static randomDistantPoint(stations: Station[], minDistance: number): PIXI.Point | null { public static randomDistantPoint(
stations: Station[],
minDistance: number,
height: number,
width: number,
): Point | null {
let tries = 100; let tries = 100;
while (tries > 0) { while (tries > 0) {
const point = randomPoint(); const point = randomPoint(height, width);
if (Station.isPointDistant(point, stations, minDistance)) { if (Station.isPointDistant(point, stations, minDistance)) {
return point; return point;
} }
@ -64,19 +70,16 @@ export default class Station {
return null; return null;
} }
public location: PIXI.Point; public location: Point;
public population: number; public population: number;
public connections: LineConnection[]; public connections: LineConnection[];
public id: number; public id: number;
public label: PIXI.Text; public color: ColorFormats.RGBA;
public color: tinycolorInstance;
private textStyle: object;
constructor( constructor(
location: PIXI.Point, location: Point,
population: number, population: number,
color: tinycolorInstance, color: ColorFormats.RGBA,
connections?: LineConnection[], connections?: LineConnection[],
) { ) {
this.location = location; this.location = location;
@ -87,15 +90,5 @@ export default class Station {
// for debugging // for debugging
stationCount += 1; stationCount += 1;
this.id = stationCount; this.id = stationCount;
this.textStyle = {
fill: '#FFA500',
fontFamily: 'monospace',
fontSize: '12px',
};
this.renderLabel();
}
public renderLabel() {
this.label = new PIXI.Text(`${this.id}`, this.textStyle);
} }
} }

60
src/Station.worker.ts Normal file
View File

@ -0,0 +1,60 @@
import * as tinycolor from 'tinycolor2';
import Line from './Line';
import Station from './Station';
import { randomInt } from './utils';
const LINE_CONNECTION_LIMIT = 5;
const ctx: Worker = self as any;
const initStations = (numStations: number, height: number, width: number): Station[] => {
const stations: Station[] = [];
for (let i = 0; i < numStations; i += 1) {
stations.push(new Station(
Station.randomDistantPoint(stations, 30, height, width),
randomInt(300, 2000),
tinycolor.random().toRgb()));
}
return stations;
};
const initLines = (numLines: number, stations: Station[], connectionRadius: number): Line[] => {
const lines = [];
for (let i = 0; i < numLines; i += 1) {
let color = tinycolor.random();
while (color.isDark()) {
color = tinycolor.random();
}
const lineColor = color.toRgb();
const stationsWithoutConnections = stations.filter(station =>
station.connections.length === 0,
);
let centralHub: Station;
if (stationsWithoutConnections.length > 0) {
centralHub = Station.largestStation(stationsWithoutConnections);
} else {
centralHub = stations[randomInt(0, stations.length - 1)];
}
const line = new Line(`line-${i}`, lineColor);
const stationsLeft = stations.slice(0);
line.connectStations(centralHub, stationsLeft, [], LINE_CONNECTION_LIMIT, connectionRadius);
lines.push(line);
}
return lines;
};
ctx.addEventListener('message', (event: MessageEvent) => {
if ('initStations' in event.data) {
const { connectionRadius, height, numLines, numStations, width } = event.data.initStations;
let stations: Station[] = [];
let lines: Line[] = [];
let stationsWithConnections: Station[] = [];
while (stationsWithConnections.length === 0) {
// If all stations are too far away to connect, try generating again
stations = initStations(numStations, height, width);
lines = initLines(numLines, stations, connectionRadius);
stationsWithConnections = stations.filter(station => station.connections.length > 0);
}
ctx.postMessage({ stations, lines });
}
});

View File

@ -1,3 +1,4 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2'; import * as tinycolor from 'tinycolor2';
import Station from './Station'; import Station from './Station';
@ -5,25 +6,23 @@ import Station from './Station';
let trainCount = 0; let trainCount = 0;
export default class Train { export default class Train {
public location: PIXI.Point; public location: Point;
public speed: number; public speed: number;
public origin: Station; public origin: Station;
public destination: Station; public destination: Station;
public passengers: number; public passengers: number;
public id: number; public id: number;
public label: PIXI.Text; public color: ColorFormats.RGBA;
public color: tinycolorInstance;
public sprite: PIXI.Sprite;
private textStyle: object; private textStyle: object;
constructor( constructor(
location: PIXI.Point, location: Point,
speed: number, speed: number,
passengers: number, passengers: number,
origin: Station, origin: Station,
destination: Station, destination: Station,
color: tinycolorInstance, color: ColorFormats.RGBA,
) { ) {
this.location = location; this.location = location;
this.speed = speed; this.speed = speed;
@ -32,25 +31,8 @@ export default class Train {
this.passengers = passengers; this.passengers = passengers;
this.color = color; this.color = color;
this.sprite = new PIXI.Sprite(PIXI.loader.resources.nodeImg.texture);
// for debugging // for debugging
trainCount += 1; trainCount += 1;
this.id = trainCount; this.id = trainCount;
this.textStyle = {
fill: '#AEAEAE',
fontFamily: 'monospace',
fontSize: '12px',
};
this.renderLabel();
}
public boardPassengers() {
if (this.location === this.origin.location) { // about to leave a station
}
}
public renderLabel() {
this.label = new PIXI.Text(`${this.id}`, this.textStyle);
} }
} }

116
src/Train.worker.ts Normal file
View File

@ -0,0 +1,116 @@
import Point from 'pixi.js/lib/core/math/Point';
import * as tinycolor from 'tinycolor2';
import Station from './Station';
import Train from './Train';
import { distance, pointsAlmostEqual, randomInt, weightedRandom } from './utils';
// TODO: define these in a common constants file
const MAX_SPEED = 10.0;
const ACCELERATION = 0.025;
const APPROACH_DISTANCE = 3.0;
const TRAIN_CAPACITY = 50;
const ctx: Worker = self as any;
let stations: Station[] = [];
let trains: Train[] = [];
const initTrains = (numTrains: number, stations: Station[]): Train[] => {
const trains = [];
const stationsWithConnections = stations.filter(station => station.connections.length > 0);
for (let i = 0; i < numTrains; i += 1) {
const originStation = stationsWithConnections[
Math.floor(Math.random() * stationsWithConnections.length)
];
trains.push(new Train(
new Point(originStation.location.x, originStation.location.y),
0, 0, originStation, undefined, tinycolor('grey').toRgb()),
);
}
return trains;
};
const moveTrains = (trains: Train[], stations: Station[]) => {
for (const train of trains) {
if (train.origin.connections.length === 0) {
// train is stuck at an orphaned station
continue;
}
// choose a destination randomly with a bias towards larger stations
if (train.destination === undefined) {
const otherStations = train.origin.connections.map(conn => conn.station);
const closeStationWeights = otherStations.map(station => station.population);
train.destination = weightedRandom(otherStations, closeStationWeights);
// board passengers
const boardingPassengers = randomInt(0, Math.min(TRAIN_CAPACITY - train.passengers,
train.origin.population));
// set or mix train color with the color of new passenger origin
if (train.passengers === 0) {
train.color = train.origin.color;
} else {
train.color = tinycolor.mix(
train.color,
train.origin.color,
Math.round((boardingPassengers / train.passengers) * 100),
).toRgb();
}
train.passengers += boardingPassengers;
train.origin.population -= boardingPassengers;
}
// train reached destination, stop moving and let passengers off
if (pointsAlmostEqual(train.location, train.destination.location)) {
train.speed = 0;
// average destination color with passenger color weighted by ratio
// (a simulation of culture mixing)
train.destination.color = tinycolor.mix(
train.destination.color,
train.origin.color,
Math.round((train.passengers / train.destination.population) * 100),
).toRgb();
// transfer passengers to destination
const disembarkingPassengers = randomInt(0, train.passengers);
train.destination.population += disembarkingPassengers;
train.passengers -= disembarkingPassengers;
// prepare for next journey
train.origin = train.destination;
train.destination = undefined;
continue;
}
const journeyLeft = distance(train.location, train.destination.location);
if ((train.speed / ACCELERATION) >= ((journeyLeft / train.speed) - APPROACH_DISTANCE) &&
train.speed !== ACCELERATION) {
// slowing down
train.speed -= ACCELERATION;
} else if (train.speed < MAX_SPEED) {
// speeding up
train.speed += ACCELERATION;
}
// advance train
const progress = train.speed / journeyLeft;
train.location.x += ((train.destination.location.x - train.location.x) * progress);
train.location.y += ((train.destination.location.y - train.location.y) * progress);
}
};
ctx.addEventListener('message', (event: MessageEvent) => {
if ('initTrains' in event.data) {
const { numTrains } = event.data.initTrains;
stations = event.data.initTrains.stations;
trains = initTrains(numTrains, stations);
ctx.postMessage({ initTrains: trains });
} else if ('moveTrains' in event.data) {
// trains = event.data.moveTrains.trains;
// stations = event.data.moveTrains.stations;
moveTrains(trains, stations);
ctx.postMessage({ moveTrains: trains });
}
});

View File

@ -6,18 +6,20 @@ import * as tinycolor from 'tinycolor2';
import Direction from './Direction'; import Direction from './Direction';
import Line from './Line'; import Line from './Line';
import Station from './Station'; import Station from './Station';
import StationWorker from 'worker-loader!./Station.worker';
import Train from './Train'; import Train from './Train';
import { distance, pointsAlmostEqual, pointsEqual, randomInt, randomPoint, import { distance, pointsAlmostEqual, pointsEqual, randomInt, randomPoint,
rangeMap, weightedRandom } from './utils'; rangeMap, weightedRandom } from './utils';
import TrainWorker from 'worker-loader!./Train.worker';
import * as imgNode from './node.png'; import * as imgNode from './node.png';
import './style.css'; import './style.css';
const NODE_RES = 100; const NODE_RES = 100;
const MAX_SPEED = 10.0; const CONNECTION_RADIUS = Math.floor(Math.sqrt(
const ACCELERATION = 0.025; Math.pow(window.innerWidth, 2) + Math.pow(window.innerHeight, 2),
const APPROACH_DISTANCE = 3.0; ) / 8);
const TRAIN_CAPACITY = 50; const TRAIN_CAPACITY = 50;
const LINE_CONNECTION_LIMIT = 5; const LINE_CONNECTION_LIMIT = 5;
const WORLD_WIDTH = 1000; const WORLD_WIDTH = 1000;
@ -29,161 +31,49 @@ const ZOOM_MAX_HEIGHT = 4000;
const trainTexts: PIXI.Text[] = []; const trainTexts: PIXI.Text[] = [];
const initStations = (numStations: number): Station[] => { const drawStations = (stations: Station[], stationLabels: PIXI.Text[], graphics: PIXI.Graphics) => {
const stations: Station[] = []; stations.forEach((station, i) => {
for (let i = 0; i < numStations; i += 1) {
stations.push(new Station(
Station.randomDistantPoint(stations, 30),
randomInt(300, 2000),
tinycolor.random()));
}
return stations;
};
const initTrains = (numTrains: number, stations: Station[]): Train[] => {
const trains = [];
const stationsWithConnections = stations.filter(station => station.connections.length > 0);
for (let i = 0; i < numTrains; i += 1) {
const originStation = stationsWithConnections[
Math.floor(Math.random() * stationsWithConnections.length)
];
trains.push(new Train(
new PIXI.Point(originStation.location.x, originStation.location.y),
0, 0, originStation, undefined, tinycolor('grey')),
);
}
return trains;
};
const initLines = (numLines: number, stations: Station[]): Line[] => {
const lines = [];
for (let i = 0; i < numLines; i += 1) {
let color = tinycolor.random();
while (color.isDark()) {
color = tinycolor.random();
}
const stationsWithoutConnections = stations.filter(station =>
station.connections.length === 0,
);
let centralHub: Station;
if (stationsWithoutConnections.length > 0) {
centralHub = Station.largestStation(stationsWithoutConnections);
} else {
centralHub = stations[randomInt(0, stations.length - 1)];
}
const line = new Line(`line-${i}`, tinycolor.random());
const stationsLeft = stations.slice(0);
line.connectStations(centralHub, stationsLeft, [], LINE_CONNECTION_LIMIT);
lines.push(line);
}
return lines;
};
const moveTrains = (trains: Train[], stations: Station[]) => {
for (const train of trains) {
if (train.origin.connections.length === 0) {
// train is stuck at an orphaned station
continue;
}
// choose a destination randomly with a bias towards larger stations
if (train.destination === undefined) {
const otherStations = train.origin.connections.map(conn => conn.station);
const closeStationWeights = otherStations.map(station => station.population);
train.destination = weightedRandom(otherStations, closeStationWeights);
// board passengers
const boardingPassengers = randomInt(0, Math.min(TRAIN_CAPACITY - train.passengers,
train.origin.population));
// set or mix train color with the color of new passenger origin
if (train.passengers === 0) {
train.color = train.origin.color;
} else {
train.color = tinycolor.mix(
train.color,
train.origin.color,
Math.round((boardingPassengers / train.passengers) * 100),
);
}
train.passengers += boardingPassengers;
train.origin.population -= boardingPassengers;
}
// train reached destination, stop moving and let passengers off
if (pointsAlmostEqual(train.location, train.destination.location)) {
train.speed = 0;
// average destination color with passenger color weighted by ratio
// (a simulation of culture mixing)
train.destination.color = tinycolor.mix(
train.destination.color,
train.origin.color,
Math.round((train.passengers / train.destination.population) * 100),
);
// transfer passengers to destination
const disembarkingPassengers = randomInt(0, train.passengers);
train.destination.population += disembarkingPassengers;
train.passengers -= disembarkingPassengers;
// prepare for next journey
train.origin = train.destination;
train.destination = undefined;
continue;
}
const journeyLeft = distance(train.location, train.destination.location);
if ((train.speed / ACCELERATION) >= ((journeyLeft / train.speed) - APPROACH_DISTANCE) &&
train.speed !== ACCELERATION) {
// slowing down
train.speed -= ACCELERATION;
} else if (train.speed < MAX_SPEED) {
// speeding up
train.speed += ACCELERATION;
}
// advance train
const progress = train.speed / journeyLeft;
train.location.x += ((train.destination.location.x - train.location.x) * progress);
train.location.y += ((train.destination.location.y - train.location.y) * progress);
}
};
const drawStations = (stations: Station[], graphics: PIXI.Graphics) => {
for (const station of stations) {
const radius = station.population / 150; const radius = station.population / 150;
graphics.beginFill(parseInt(station.color.toHex(), 16), 0.5); const color = tinycolor(station.color);
graphics.beginFill(parseInt(color.toHex(), 16), 0.5);
graphics.drawCircle(station.location.x, station.location.y, radius); graphics.drawCircle(station.location.x, station.location.y, radius);
graphics.endFill(); graphics.endFill();
station.label.x = station.location.x + radius + 1; stationLabels[i].x = station.location.x + radius + 1;
station.label.y = station.location.y + radius + 1; stationLabels[i].y = station.location.y + radius + 1;
} });
}; };
const drawTrains = (trains: Train[], graphics: PIXI.Graphics) => { const drawTrains = (
for (const train of trains) { trains: Train[],
trainLabels: PIXI.Text[],
trainSprites: PIXI.Sprite[],
graphics: PIXI.Graphics,
) => {
trains.forEach((train, i) => {
const trainSize = rangeMap(train.passengers, 0, TRAIN_CAPACITY, 1, 5); const trainSize = rangeMap(train.passengers, 0, TRAIN_CAPACITY, 1, 5);
const scale = trainSize / NODE_RES; const scale = trainSize / NODE_RES;
train.sprite.x = train.location.x; const color = tinycolor(train.color);
train.sprite.y = train.location.y; trainSprites[i].x = train.location.x;
train.sprite.scale.x = scale; trainSprites[i].y = train.location.y;
train.sprite.scale.y = scale; trainSprites[i].scale.x = scale;
train.sprite.tint = parseInt(train.color.toHex(), 16); trainSprites[i].scale.y = scale;
train.label.x = train.location.x + scale + 1; trainSprites[i].tint = parseInt(color.toHex(), 16);
train.label.y = train.location.y + scale + 1; trainLabels[i].x = train.location.x + scale + 1;
} trainLabels[i].y = train.location.y + scale + 1;
});
}; };
const drawLines = (stations: Station[], graphics: PIXI.Graphics) => { const drawLines = (stations: Station[], graphics: PIXI.Graphics) => {
for (const station of stations) { for (const station of stations) {
for (const connection of station.connections) { for (const connection of station.connections) {
const color = tinycolor(connection.line.color);
let twoWay = false; let twoWay = false;
for (const conn of connection.station.connections) { for (const conn of connection.station.connections) {
if (conn.station === station) { if (conn.station === station) {
twoWay = true; twoWay = true;
} }
} }
graphics.lineStyle(twoWay ? 2 : 1, parseInt(connection.line.color.toHex(), 16), 1); graphics.lineStyle(twoWay ? 2 : 1, parseInt(color.toHex(), 16), 1);
graphics.moveTo(station.location.x, station.location.y); graphics.moveTo(station.location.x, station.location.y);
graphics.lineTo(connection.station.location.x, connection.station.location.y); graphics.lineTo(connection.station.location.x, connection.station.location.y);
} }
@ -210,28 +100,85 @@ const run = () => {
let stations: Station[] = []; let stations: Station[] = [];
let lines: Line[] = []; let lines: Line[] = [];
let stationsWithConnections: Station[] = []; let trains: Train[] = [];
while (stationsWithConnections.length === 0) { const stationLabels: PIXI.Text[] = [];
// If all stations are too far away to connect, try generating again const trainLabels: PIXI.Text[] = [];
stations = initStations(30); const trainSprites: PIXI.Sprite[] = [];
lines = initLines(4, stations); const stationWorker = new StationWorker();
stationsWithConnections = stations.filter(station => station.connections.length > 0); const trainWorker = new TrainWorker();
}
const trains = initTrains(50, stations); stationWorker.postMessage({ initStations: {
connectionRadius: CONNECTION_RADIUS,
height: window.innerHeight,
numLines: 4,
numStations: 30,
width: window.innerWidth,
}});
stationWorker.onmessage = (event: MessageEvent) => {
stations = event.data.stations;
lines = event.data.lines;
console.log(stations);
console.log(lines);
trainWorker.postMessage({ initTrains: {
stations,
numTrains: 50,
}});
trainWorker.onmessage = (trainEvent: MessageEvent) => {
if ('initTrains' in trainEvent.data) {
trains = trainEvent.data.initTrains;
// add train sprites
for (const train of trains) {
const sprite = new PIXI.Sprite(PIXI.loader.resources.nodeImg.texture);
sprite.visible = false;
trainSprites.push(sprite);
viewport.addChild(sprite);
}
// Add debug labels
for (const train of trains) {
const label = new PIXI.Text(
`${train.id}`, {
fill: '#AEAEAE',
fontFamily: 'monospace',
fontSize: '12px',
},
);
trainLabels.push(label);
viewport.addChild(label);
}
} else if ('moveTrains' in trainEvent.data) {
trains = trainEvent.data.moveTrains;
}
};
for (const station of stations) {
const label = new PIXI.Text(
`${station.id}`, {
fill: '#FFA500',
fontFamily: 'monospace',
fontSize: '12px',
},
);
stationLabels.push(label);
viewport.addChild(label);
}
};
ticker.stop(); ticker.stop();
ticker.add((deltaTime) => { ticker.add((deltaTime) => {
stats.begin(); stats.begin();
moveTrains(trains, stations); trainWorker.postMessage({ moveTrains: {} });
graphics.clear(); graphics.clear();
graphics.lineStyle(1, 0xFFA500, 1); graphics.lineStyle(1, 0xFFA500, 1);
drawStations(stations, graphics); drawStations(stations, stationLabels, graphics);
graphics.lineStyle(1, 0xAEAEAE, 1); graphics.lineStyle(1, 0xAEAEAE, 1);
drawTrains(trains, graphics); drawTrains(trains, trainLabels, trainSprites, graphics);
drawLines(stations, graphics); drawLines(stations, graphics);
@ -240,17 +187,6 @@ const run = () => {
ticker.start(); ticker.start();
viewport.addChild(graphics); viewport.addChild(graphics);
// add train sprites
for (const train of trains) {
viewport.addChild(train.sprite);
}
// Add debug labels
for (const train of trains) {
viewport.addChild(train.label);
}
for (const station of stations) {
viewport.addChild(station.label);
}
document.body.appendChild(app.view); document.body.appendChild(app.view);
app.stage.addChild(viewport); app.stage.addChild(viewport);
viewport.drag().pinch().wheel().clampZoom({ viewport.drag().pinch().wheel().clampZoom({

View File

@ -1,4 +1,4 @@
import * as PIXI from 'pixi.js'; import Point from 'pixi.js/lib/core/math/Point';
const EPSILON = 1.0; const EPSILON = 1.0;
@ -19,20 +19,20 @@ export const weightedRandom = (choices: any[], weights: number[]): any => {
} }
}; };
export const randomPoint = () => ( export const randomPoint = (height: number, width: number): Point => (
new PIXI.Point(randomInt(0, window.innerWidth), randomInt(0, window.innerHeight)) new Point(randomInt(0, width), randomInt(0, height))
); );
export const pointsEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => ( export const pointsEqual = (pointA: Point, pointB: Point): boolean => (
(pointA.x === pointB.x && pointA.y === pointB.y) (pointA.x === pointB.x && pointA.y === pointB.y)
); );
export const pointsAlmostEqual = (pointA: PIXI.Point, pointB: PIXI.Point): boolean => ( export const pointsAlmostEqual = (pointA: Point, pointB: Point): boolean => (
Math.abs(pointA.x - pointB.x) < EPSILON && Math.abs(pointA.x - pointB.x) < EPSILON &&
Math.abs(pointA.y - pointB.y) < EPSILON Math.abs(pointA.y - pointB.y) < EPSILON
); );
export const distance = (pointA: PIXI.Point, pointB: PIXI.Point): number => { export const distance = (pointA: Point, pointB: Point): number => {
const distX = pointA.x - pointB.x; const distX = pointA.x - pointB.x;
const distY = pointA.y - pointB.y; const distY = pointA.y - pointB.y;
return Math.sqrt((distX * distX) + (distY * distY)); return Math.sqrt((distX * distX) + (distY * distY));
@ -43,10 +43,10 @@ export const rangeMap = (num: number, inMin: number, inMax: number,
(num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin (num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
); );
export const angleRadians = (pointA: PIXI.Point, pointB: PIXI.Point): number => ( export const angleRadians = (pointA: Point, pointB: Point): number => (
Math.atan2(-(pointB.x - pointA.x), pointB.y - pointA.y) Math.atan2(-(pointB.x - pointA.x), pointB.y - pointA.y)
); );
export const angleDegrees = (pointA: PIXI.Point, pointB: PIXI.Point): number => ( export const angleDegrees = (pointA: Point, pointB: Point): number => (
180 + angleRadians(pointA, pointB) * (180 / Math.PI) 180 + angleRadians(pointA, pointB) * (180 / Math.PI)
); );

16
typings/custom.d.ts vendored
View File

@ -1 +1,17 @@
declare module '*.png'; declare module '*.png';
declare module 'worker-loader!*' {
class WebpackWorker extends Worker {
constructor();
}
export default WebpackWorker;
}
declare module 'pixi.js/lib/core/math/Point' {
export default PIXI.Point;
}
declare module 'pixi.js/lib/core/text/Text' {
export default PIXI.Text;
}