added stats
This commit is contained in:
parent
15dd74f45e
commit
c77a947cc0
|
@ -32,7 +32,7 @@ export default class BoidDemo extends Scene {
|
|||
this.viewport.enableZoom();
|
||||
|
||||
// Create a bunch of boids
|
||||
for(let i = 0; i < 200; i++){
|
||||
for(let i = 0; i < 150; i++){
|
||||
let boid = this.add.graphic(Boid, layer, new Vec2(this.worldSize.x*Math.random(), this.worldSize.y*Math.random()));
|
||||
boid.fb = new FlockBehavior(this, boid, this.boids, 75, 50);
|
||||
boid.setSize(5, 5);
|
||||
|
|
|
@ -3,6 +3,7 @@ import Collection from "./Collection";
|
|||
import AABB from "./AABB"
|
||||
import { Region, Unique } from "./Interfaces/Descriptors";
|
||||
import Map from "./Map";
|
||||
import Stats from "../Debug/Stats";
|
||||
|
||||
/**
|
||||
* Primarily used to organize the scene graph
|
||||
|
@ -144,7 +145,7 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
|
|||
let results = new Array<T>();
|
||||
|
||||
// A map to keep track of the items we've already found
|
||||
let uniqueMap = new Map<T>();
|
||||
let uniqueMap = new Array<boolean>();
|
||||
|
||||
// Query and return
|
||||
this._queryRegion(boundary, results, uniqueMap);
|
||||
|
@ -157,7 +158,7 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
|
|||
* @param results The results matrix
|
||||
* @param uniqueMap A map that stores the unique ids of the results so we know what was already found
|
||||
*/
|
||||
protected _queryRegion(boundary: AABB, results: Array<T>, uniqueMap: Map<T>): void {
|
||||
protected _queryRegion(boundary: AABB, results: Array<T>, uniqueMap: Array<boolean>): void {
|
||||
// Does this quadtree even contain the point?
|
||||
if(!this.boundary.overlaps(boundary)) return;
|
||||
|
||||
|
@ -170,12 +171,22 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
|
|||
} else {
|
||||
// Otherwise, return a set of the items
|
||||
for(let item of this.items){
|
||||
let id = item.getId().toString();
|
||||
// If the item hasn't been found yet and it contains the point
|
||||
if(!uniqueMap.has(id) && item.getBoundary().overlaps(boundary)){
|
||||
// Add it to our found points
|
||||
uniqueMap.add(id, item);
|
||||
results.push(item);
|
||||
// TODO - This is REALLY slow for some reason when we check for unique keys
|
||||
|
||||
// let id = item.getId().toString();
|
||||
// // If the item hasn't been found yet and it contains the point
|
||||
// if(!uniqueMap.has(id) && item.getBoundary().overlaps(boundary)){
|
||||
// // Add it to our found points
|
||||
// uniqueMap.add(id, item);
|
||||
// results.push(item);
|
||||
// }
|
||||
|
||||
// Maybe this is better? Just use a boolean array with no string nonsense?
|
||||
if(item.getId() >= uniqueMap.length || !uniqueMap[item.getId()]){
|
||||
if(item.getBoundary().overlaps(boundary)){
|
||||
results.push(item);
|
||||
uniqueMap[item.getId()] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
243
src/Debug/Stats.ts
Normal file
243
src/Debug/Stats.ts
Normal file
|
@ -0,0 +1,243 @@
|
|||
import Color from "../Utils/Color";
|
||||
|
||||
export default class Stats extends Object {
|
||||
/** The fps of the game. */
|
||||
private static prevfps: Array<number>;
|
||||
private static readonly NUM_POINTS: number = 60;
|
||||
private static ctx: CanvasRenderingContext2D;
|
||||
private static CANVAS_WIDTH: number = 300;
|
||||
private static CANVAS_HEIGHT: number = 300;
|
||||
private static statsDiv: HTMLDivElement;
|
||||
private static graphChoices: HTMLSelectElement;
|
||||
|
||||
// Quadtree stats
|
||||
private static prevClearTimes: Array<number>;
|
||||
private static SGClearTimes: Array<number>;
|
||||
private static avgSGClearTime: number;
|
||||
|
||||
private static prevFillTimes: Array<number>;
|
||||
private static SGFillTimes: Array<number>;
|
||||
private static avgSGFillTime: number;
|
||||
|
||||
private static prevUpdateTimes: Array<number>;
|
||||
private static SGUpdateTimes: Array<number>;
|
||||
private static avgSGUpdateTime: number;
|
||||
|
||||
private static prevQueryTimes: Array<number>;
|
||||
private static SGQueryTimes: Array<number>;
|
||||
private static avgSGQueryTime: number;
|
||||
|
||||
static initStats(): void {
|
||||
let canvas = <HTMLCanvasElement>document.getElementById("stats-canvas");
|
||||
canvas.width = this.CANVAS_WIDTH;
|
||||
canvas.height = this.CANVAS_HEIGHT;
|
||||
this.ctx = canvas.getContext("2d");
|
||||
|
||||
this.statsDiv = <HTMLDivElement>document.getElementById("stats-display");
|
||||
|
||||
this.prevfps = new Array();
|
||||
|
||||
this.prevClearTimes = new Array();
|
||||
this.SGClearTimes = new Array();
|
||||
this.avgSGClearTime = 0;
|
||||
|
||||
this.prevFillTimes = new Array();
|
||||
this.SGFillTimes = new Array();
|
||||
this.avgSGFillTime = 0;
|
||||
|
||||
this.prevUpdateTimes = new Array();
|
||||
this.SGUpdateTimes = new Array();
|
||||
this.avgSGUpdateTime = 0;
|
||||
|
||||
this.prevQueryTimes = new Array();
|
||||
this.SGQueryTimes = new Array();
|
||||
this.avgSGQueryTime = 0;
|
||||
|
||||
let clearTime = document.createElement("span");
|
||||
clearTime.setAttribute("id", "sgclear");
|
||||
let fillTime = document.createElement("span");
|
||||
fillTime.setAttribute("id", "sgfill");
|
||||
let updateTime = document.createElement("span");
|
||||
updateTime.setAttribute("id", "sgupdate");
|
||||
let queryTime = document.createElement("span");
|
||||
queryTime.setAttribute("id", "sgquery");
|
||||
let br1 = document.createElement("br");
|
||||
let br2 = document.createElement("br");
|
||||
let br3 = document.createElement("br");
|
||||
|
||||
this.statsDiv.append(clearTime, br1, fillTime, br2, updateTime, br3, queryTime);
|
||||
|
||||
this.graphChoices = <HTMLSelectElement>document.getElementById("chart-option");
|
||||
let option1 = document.createElement("option");
|
||||
option1.value = "prevfps";
|
||||
option1.label = "FPS";
|
||||
let option2 = document.createElement("option");
|
||||
option2.value = "prevClearTimes";
|
||||
option2.label = "Clear Time";
|
||||
let option3 = document.createElement("option");
|
||||
option3.value = "prevFillTimes";
|
||||
option3.label = "Fill time";
|
||||
let option4 = document.createElement("option");
|
||||
option4.value = "prevUpdateTimes";
|
||||
option4.label = "Update time";
|
||||
let option5 = document.createElement("option");
|
||||
option5.value = "prevQueryTimes";
|
||||
option5.label = "Query Time";
|
||||
let optionAll = document.createElement("option");
|
||||
optionAll.value = "all";
|
||||
optionAll.label = "All";
|
||||
this.graphChoices.append(option1, option2, option3, option4, option5, optionAll);
|
||||
}
|
||||
|
||||
static updateFPS(fps: number): void {
|
||||
this.prevfps.push(fps);
|
||||
if(this.prevfps.length > Stats.NUM_POINTS){
|
||||
this.prevfps.shift();
|
||||
}
|
||||
|
||||
if(this.SGClearTimes.length > 0){
|
||||
this.prevClearTimes.push(this.avgSGClearTime);
|
||||
if(this.prevClearTimes.length > this.NUM_POINTS){
|
||||
this.prevClearTimes.shift();
|
||||
}
|
||||
}
|
||||
if(this.SGFillTimes.length > 0){
|
||||
this.prevFillTimes.push(this.avgSGFillTime);
|
||||
if(this.prevFillTimes.length > this.NUM_POINTS){
|
||||
this.prevFillTimes.shift();
|
||||
}
|
||||
}
|
||||
if(this.SGUpdateTimes.length > 0){
|
||||
this.prevUpdateTimes.push(this.avgSGUpdateTime);
|
||||
if(this.prevUpdateTimes.length > this.NUM_POINTS){
|
||||
this.prevUpdateTimes.shift();
|
||||
}
|
||||
}
|
||||
if(this.SGQueryTimes.length > 0){
|
||||
this.prevQueryTimes.push(this.avgSGQueryTime);
|
||||
if(this.prevQueryTimes.length > this.NUM_POINTS){
|
||||
this.prevQueryTimes.shift();
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSGStats();
|
||||
}
|
||||
|
||||
static log(key: string, data: any): void {
|
||||
if(key === "sgclear"){
|
||||
this.SGClearTimes.push(data);
|
||||
if(this.SGClearTimes.length > 100){
|
||||
this.SGClearTimes.shift();
|
||||
}
|
||||
} else if(key === "sgfill"){
|
||||
this.SGFillTimes.push(data);
|
||||
if(this.SGFillTimes.length > 100){
|
||||
this.SGFillTimes.shift();
|
||||
}
|
||||
} else if(key === "sgupdate"){
|
||||
this.SGUpdateTimes.push(data);
|
||||
if(this.SGUpdateTimes.length > 100){
|
||||
this.SGUpdateTimes.shift();
|
||||
}
|
||||
} else if(key === "sgquery"){
|
||||
this.SGQueryTimes.push(data);
|
||||
if(this.SGQueryTimes.length > 1000){
|
||||
this.SGQueryTimes.shift();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static render(): void {
|
||||
// Display stats
|
||||
this.drawCharts();
|
||||
}
|
||||
|
||||
static drawCharts(){
|
||||
this.ctx.clearRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_HEIGHT);
|
||||
|
||||
let paramString = this.graphChoices.value;
|
||||
|
||||
if(paramString === "prevfps" || paramString === "all"){
|
||||
let param = this.prevfps;
|
||||
let color = Color.BLUE.toString();
|
||||
this.drawChart(param, color);
|
||||
}
|
||||
if(paramString === "prevClearTimes" || paramString === "all"){
|
||||
let param = this.prevClearTimes;
|
||||
let color = Color.RED.toString();
|
||||
this.drawChart(param, color);
|
||||
}
|
||||
if(paramString === "prevFillTimes" || paramString === "all"){
|
||||
let param = this.prevFillTimes;
|
||||
let color = Color.GREEN.toString();
|
||||
this.drawChart(param, color);
|
||||
}
|
||||
if(paramString === "prevUpdateTimes" || paramString === "all"){
|
||||
let param = this.prevUpdateTimes;
|
||||
let color = Color.CYAN.toString();
|
||||
this.drawChart(param, color);
|
||||
}
|
||||
if(paramString === "prevQueryTimes" || paramString === "all"){
|
||||
let param = this.prevQueryTimes;
|
||||
let color = Color.ORANGE.toString();
|
||||
this.drawChart(param, color);
|
||||
}
|
||||
}
|
||||
|
||||
static drawChart(param: Array<number>, color: string){
|
||||
this.ctx.strokeStyle = Color.BLACK.toString();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(10, 10);
|
||||
this.ctx.lineTo(10, this.CANVAS_HEIGHT - 10);
|
||||
this.ctx.closePath();
|
||||
this.ctx.stroke();
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(10, this.CANVAS_HEIGHT - 10);
|
||||
this.ctx.lineTo(this.CANVAS_WIDTH - 10, this.CANVAS_HEIGHT - 10);
|
||||
this.ctx.closePath();
|
||||
this.ctx.stroke();
|
||||
|
||||
let max = Math.max(...param);
|
||||
let prevX = 10;
|
||||
let prevY = this.CANVAS_HEIGHT - 10 - param[0]/max*(this.CANVAS_HEIGHT-20);
|
||||
this.ctx.strokeStyle = color;
|
||||
|
||||
for(let i = 1; i < param.length; i++){
|
||||
let fps = param[i];
|
||||
let x = 10 + i*(this.CANVAS_WIDTH - 20)/this.NUM_POINTS;
|
||||
let y = this.CANVAS_HEIGHT - 10 - fps/max*(this.CANVAS_HEIGHT-20)
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(prevX, prevY);
|
||||
this.ctx.lineTo(x, y);
|
||||
this.ctx.closePath();
|
||||
this.ctx.stroke();
|
||||
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
}
|
||||
|
||||
static updateSGStats(){
|
||||
if(this.SGClearTimes.length > 0){
|
||||
this.avgSGClearTime = this.SGClearTimes.reduce((acc, val) => acc + val)/this.SGClearTimes.length;
|
||||
}
|
||||
|
||||
if(this.SGFillTimes.length > 0){
|
||||
this.avgSGFillTime = this.SGFillTimes.reduce((acc, val) => acc + val)/this.SGFillTimes.length;
|
||||
}
|
||||
|
||||
if(this.SGUpdateTimes.length > 0){
|
||||
this.avgSGUpdateTime = this.SGUpdateTimes.reduce((acc, val) => acc + val)/this.SGUpdateTimes.length;
|
||||
}
|
||||
|
||||
if(this.SGQueryTimes.length > 0){
|
||||
this.avgSGQueryTime = this.SGQueryTimes.reduce((acc, val) => acc + val)/this.SGQueryTimes.length;
|
||||
}
|
||||
|
||||
document.getElementById("sgclear").innerHTML = "Avg SG clear time: " + this.avgSGClearTime;
|
||||
document.getElementById("sgfill").innerHTML = "Avg SG fill time: " + this.avgSGFillTime;
|
||||
document.getElementById("sgupdate").innerHTML = "Avg SG update time: " + this.avgSGUpdateTime;
|
||||
document.getElementById("sgquery").innerHTML = "Avg SG query time: " + this.avgSGQueryTime;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import ResourceManager from "../ResourceManager/ResourceManager";
|
|||
import Viewport from "../SceneGraph/Viewport";
|
||||
import SceneManager from "../Scene/SceneManager";
|
||||
import AudioManager from "../Sound/AudioManager";
|
||||
import Stats from "../Debug/Stats";
|
||||
|
||||
export default class GameLoop {
|
||||
/** The max allowed update fps.*/
|
||||
|
@ -112,6 +113,8 @@ export default class GameLoop {
|
|||
this.resourceManager = ResourceManager.getInstance();
|
||||
this.sceneManager = new SceneManager(this.viewport, this);
|
||||
this.audioManager = AudioManager.getInstance();
|
||||
|
||||
Stats.initStats();
|
||||
}
|
||||
|
||||
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
|
||||
|
@ -153,6 +156,7 @@ export default class GameLoop {
|
|||
this.framesSinceLastFpsUpdate = 0;
|
||||
|
||||
Debug.log("fps", "FPS: " + this.fps.toFixed(1));
|
||||
Stats.updateFPS(this.fps);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,6 +220,7 @@ export default class GameLoop {
|
|||
this.numUpdateSteps++;
|
||||
if(this.numUpdateSteps > 100){
|
||||
this.panic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,6 +277,7 @@ export default class GameLoop {
|
|||
this.ctx.clearRect(0, 0, this.WIDTH, this.HEIGHT);
|
||||
this.sceneManager.render(this.ctx);
|
||||
Debug.render(this.ctx);
|
||||
Stats.render();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import Scene from "../Scene/Scene";
|
|||
import Stack from "../DataTypes/Stack";
|
||||
import Layer from "../Scene/Layer"
|
||||
import AABB from "../DataTypes/AABB";
|
||||
import Stats from "../Debug/Stats";
|
||||
|
||||
export default class SceneGraphArray extends SceneGraph{
|
||||
private nodeList: Array<CanvasNode>;
|
||||
|
@ -45,23 +46,29 @@ export default class SceneGraphArray extends SceneGraph{
|
|||
}
|
||||
|
||||
getNodesInRegion(boundary: AABB): Array<CanvasNode> {
|
||||
let t0 = performance.now();
|
||||
let results = [];
|
||||
|
||||
for(let node of this.nodeList){
|
||||
if(boundary.overlapArea(node.getBoundary())){
|
||||
if(boundary.overlaps(node.getBoundary())){
|
||||
results.push(node);
|
||||
}
|
||||
}
|
||||
let t1 = performance.now();
|
||||
Stats.log("sgquery", (t1-t0));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
let t0 = performance.now();
|
||||
for(let node of this.nodeList){
|
||||
if(!node.getLayer().isPaused()){
|
||||
node.update(deltaT);
|
||||
}
|
||||
}
|
||||
let t1 = performance.now();
|
||||
Stats.log("sgupdate", (t1-t0));
|
||||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {}
|
||||
|
|
|
@ -5,6 +5,7 @@ import Scene from "../Scene/Scene";
|
|||
import RegionQuadTree from "../DataTypes/RegionQuadTree";
|
||||
import Vec2 from "../DataTypes/Vec2";
|
||||
import AABB from "../DataTypes/AABB";
|
||||
import Stats from "../Debug/Stats";
|
||||
|
||||
export default class SceneGraphQuadTree extends SceneGraph {
|
||||
private qt: RegionQuadTree<CanvasNode>;
|
||||
|
@ -34,23 +35,35 @@ export default class SceneGraphQuadTree extends SceneGraph {
|
|||
}
|
||||
|
||||
getNodesInRegion(boundary: AABB): Array<CanvasNode> {
|
||||
return this.qt.queryRegion(boundary);
|
||||
let t0 = performance.now();
|
||||
let res = this.qt.queryRegion(boundary);
|
||||
let t1 = performance.now();
|
||||
|
||||
Stats.log("sgquery", (t1-t0));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
let t0 = performance.now();
|
||||
this.qt.clear();
|
||||
let t1 = performance.now();
|
||||
|
||||
Stats.log("sgclear", (t1-t0));
|
||||
|
||||
t0 = performance.now();
|
||||
for(let node of this.nodes){
|
||||
this.qt.insert(node);
|
||||
}
|
||||
t1 = performance.now();
|
||||
|
||||
Stats.log("sgfill", (t1-t0));
|
||||
|
||||
t0 = performance.now();
|
||||
this.nodes.forEach((node: CanvasNode) => node.update(deltaT));
|
||||
// TODO: forEach is buggy, some nodes are update multiple times
|
||||
// this.qt.forEach((node: CanvasNode) => {
|
||||
// if(!node.getLayer().isPaused()){
|
||||
// node.update(deltaT);
|
||||
// }
|
||||
// });
|
||||
t1 = performance.now();
|
||||
|
||||
Stats.log("sgupdate", (t1-t0));
|
||||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
|
|
|
@ -2,10 +2,18 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Hello World!</title>
|
||||
<title>Game</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game-canvas"></canvas>
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
<canvas id="game-canvas"></canvas>
|
||||
<div>
|
||||
<canvas id="stats-canvas"></canvas>
|
||||
<select name="Display" id="chart-option">
|
||||
</select>
|
||||
<div id="stats-display"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user