新增TimeRuler用于分析游戏平均帧率

This commit is contained in:
yhh
2021-01-20 15:35:59 +08:00
parent 1be5862dc4
commit f6b6a8aa1b
9 changed files with 792 additions and 65 deletions

View File

@@ -18,12 +18,16 @@ module es {
/**
* 简化对内部类的全局内容实例的访问
*/
public static _instance: Core;
private static _instance: Core;
/**
* 用于确定是否应该使用EntitySystems
*/
public static entitySystemsEnabled: boolean;
/**
* 是否正在debug模式
* 仅允许在create时进行更改
*/
public readonly debug: boolean;
public _nextScene: Scene;
/**
* 用于凝聚GraphicsDeviceReset事件
@@ -35,13 +39,8 @@ module es {
public _globalManagers: GlobalManager[] = [];
public _coroutineManager: CoroutineManager = new CoroutineManager();
public _timerManager: TimerManager = new TimerManager();
public width: number;
public height: number;
constructor(width: number, height: number, enableEntitySystems: boolean = true) {
this.width = width;
this.height = height;
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
Core._instance = this;
Core.emitter = new Emitter<CoreEvents>();
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
@@ -50,6 +49,7 @@ module es {
Core.registerGlobalManager(this._timerManager);
Core.entitySystemsEnabled = enableEntitySystems;
this.debug = debug;
this.initialize();
}
@@ -92,6 +92,16 @@ module es {
}
}
/**
* 默认实现创建核心
*/
public static create(debug: boolean = true): Core {
if (this._instance == null) {
this._instance = new es.Core(debug);
}
return this._instance;
}
/**
* 添加一个全局管理器对象,它的更新方法将调用场景前的每一帧。
* @param manager
@@ -142,7 +152,19 @@ module es {
return this._instance._timerManager.schedule(timeInSeconds, repeats, context, onTime);
}
public startDebugUpdate() {
if (!this.debug) return;
TimeRuler.Instance.startFrame();
TimeRuler.Instance.beginMark('update', 0x00ff00);
}
public endDebugUpdate() {
if (!this.debug) return;
TimeRuler.Instance.endMark('update');
}
public startDebugDraw() {
if (!this.debug) return;
this._frameCounter++;
this._frameCounterElapsedTime += Time.deltaTime;
if (this._frameCounterElapsedTime >= 1) {
@@ -167,8 +189,10 @@ module es {
}
protected async update(currentTime?: number) {
if (currentTime != null) Time.update(currentTime);
protected async update(currentTime: number = -1) {
this.startDebugUpdate();
Time.update(currentTime);
if (this._scene != null) {
for (let i = this._globalManagers.length - 1; i >= 0; i--) {
if (this._globalManagers[i].enabled)
@@ -188,6 +212,7 @@ module es {
}
}
this.endDebugUpdate();
this.startDebugDraw();
}
}

View File

@@ -5,11 +5,11 @@ module es {
*/
export class ArcadeRigidbody extends Component implements IUpdatable {
/** 这个刚体的质量。质量为0则是一个不可移动的物体 */
public get mass(){
public get mass() {
return this._mass;
}
public set mass(value: number){
public set mass(value: number) {
this.setMass(value);
}
@@ -20,18 +20,18 @@ module es {
return this._elasticity;
}
public set elasticiy(value: number){
public set elasticiy(value: number) {
this.setElasticity(value);
}
/**
* 0 - 1范围。0表示没有摩擦力1表示物体会停止在原地
*/
public get friction(){
public get friction() {
return this._friction;
}
public set friction(value: number){
public set friction(value: number) {
this.setFriction(value);
}
@@ -42,7 +42,7 @@ module es {
return this._glue;
}
public set glue(value: number){
public set glue(value: number) {
this.setGlue(value);
}
@@ -59,7 +59,7 @@ module es {
/**
* 质量为0的刚体被认为是不可移动的。改变速度和碰撞对它们没有影响
*/
public get isImmovable(){
public get isImmovable() {
return this._mass < 0.0001;
}
@@ -70,7 +70,7 @@ module es {
public _inverseMass: number = 0;
public _collider: Collider;
constructor(){
constructor() {
super();
this._inverseMass = 1 / this._mass;
}
@@ -84,7 +84,7 @@ module es {
if (this._mass > 0.0001)
this._inverseMass = 1 / this._mass;
else
else
this._inverseMass = 0;
return this;
}
@@ -127,12 +127,12 @@ module es {
}
}
public onAddedToEntity(){
public onAddedToEntity() {
this._collider = this.entity.getComponent<es.Collider>(es.Collider);
Debug.warnIf(this._collider == null, "ArcadeRigidbody 没有 Collider。ArcadeRigidbody需要一个Collider!");
}
public update(){
public update() {
if (this.isImmovable || this._collider == null) {
this.velocity = Vector2.zero;
return;
@@ -145,7 +145,7 @@ module es {
let collisionResult = new CollisionResult();
// 捞取我们在新的位置上可能会碰撞到的任何东西
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this._collider, this._collider.collidesWithLayers.value);
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this._collider, this._collider.collidesWithLayers.value);
for (let neighbor of neighbors) {
// 如果邻近的对撞机是同一个实体,则忽略它
if (neighbor.entity.equals(this.entity)) {
@@ -166,7 +166,7 @@ module es {
this.velocity = Vector2.add(this.velocity, relativeVelocity);
}
}
}
}
}
/**
@@ -177,7 +177,7 @@ module es {
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
if (this.isImmovable) {
other.entity.transform.position = Vector2.add(other.entity.transform.position, minimumTranslationVector);
}else if(other.isImmovable) {
} else if (other.isImmovable) {
this.entity.transform.position = Vector2.subtract(this.entity.transform.position, minimumTranslationVector);
} else {
this.entity.transform.position = Vector2.subtract(this.entity.transform.position, Vector2.multiply(minimumTranslationVector, Vector2Ext.halfVector()));
@@ -213,7 +213,7 @@ module es {
* @param minimumTranslationVector
* @param responseVelocity
*/
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity: Vector2 = new Vector2()){
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity: Vector2 = new Vector2()) {
// 首先我们得到反方向的归一化MTV表面法线
let inverseMTV = Vector2.multiply(minimumTranslationVector, new Vector2(-1));
let normal = Vector2.normalize(inverseMTV);

View File

@@ -13,10 +13,15 @@ module es {
public static frameCount = 0;
/** 自场景加载以来的总时间 */
public static timeSinceSceneLoad: number = 0;
private static _lastTime = 0;
private static _lastTime = -1;
public static update(currentTime: number) {
let dt = (currentTime - this._lastTime) / 1000;
if (currentTime == -1)
currentTime = Date.now();
if (this._lastTime == -1)
this._lastTime = currentTime;
let dt = currentTime - this._lastTime;
this.totalTime += dt;
this.deltaTime = dt * this.timeScale;
this.unscaledDeltaTime = dt;

View File

@@ -1,4 +1,4 @@
namespace stopwatch {
namespace es {
/**
* 记录时间的持续时间,一些设计灵感来自物理秒表。
*/
@@ -141,7 +141,7 @@ namespace stopwatch {
*/
private calculatePendingSlice(endStopwatchTime?: number): Slice {
if (this._pendingSliceStartStopwatchTime === undefined) {
return Object.freeze({startTime: 0, endTime: 0, duration: 0});
return Object.freeze({ startTime: 0, endTime: 0, duration: 0 });
}
if (endStopwatchTime === undefined) {

View File

@@ -0,0 +1,315 @@
module es {
export class TimeRuler {
/**
* 最大条数
*/
public static readonly maxBars: number = 8;
/**
* 每条的最大样本数
*/
public static readonly maxSamples: number = 256;
/**
*
*/
public static readonly maxNestCall: number = 32;
/**
* 最大显示帧数
*/
public static readonly maxSampleFrames: number = 4;
/**
* 拍摄快照的时间(以帧数为单位)
*/
public static readonly logSnapDuration: number = 120;
// 每一帧的日志
private logs: FrameLog[];
// 上一帧日志
private prevLog: FrameLog;
//目前的日志
private curLog: FrameLog;
//当前帧数
private frameCount: number = 0;
// 测量时间的秒表
private stopwatch: Stopwatch = new Stopwatch;
// 标记信息阵列
private markers: MarkerInfo[] = [];
// 从标记名称映射到标记ID的词典
private markerNameToIdMap: Map<string, number> = new Map();
public enabled: boolean = true;
private static _instance: TimeRuler;
public static get Instance(): TimeRuler {
if (!this._instance)
this._instance = new TimeRuler();
return this._instance;
}
/**
* 你想在Game.Update方法的开头调用StartFrame。
* 但是当游戏在固定时间步长模式下运行缓慢时Game.Update会被多次调用。
* 在这种情况下我们应该忽略StartFrame的调用为了做到这一点我们只需要跟踪StartFrame的调用次数
*/
private updateCount: number = 0;
constructor() {
this.logs = new Array(2);
for (let i = 0; i < this.logs.length; ++i)
this.logs[i] = new FrameLog();
}
public startFrame() {
if (!Core.Instance.debug) return;
// 当这个方法被多次调用时,我们跳过复位帧
let count = this.updateCount++;
if (this.enabled && (1 < count && count < TimeRuler.maxSampleFrames))
return;
// 更新当前帧记录
this.prevLog = this.logs[this.frameCount++ & 0x1];
this.curLog = this.logs[this.frameCount & 0x1];
let endFrameTime = this.stopwatch.getTime();
// 更新标记并创建日志
for (let barIdx = 0; barIdx < this.prevLog.bars.length; ++barIdx) {
let prevBar = this.prevLog.bars[barIdx];
let nextBar = this.curLog.bars[barIdx];
// 重新打开前一帧中没有被调用的EndMark的标记
for (let nest = 0; nest < prevBar.nestCount; ++nest) {
let markerIdx = prevBar.markerNests[nest];
prevBar.markers[markerIdx].endTime = endFrameTime;
nextBar.markerNests[nest] = nest;
nextBar.markers[nest].markerId = prevBar.markers[markerIdx].markerId;
nextBar.markers[nest].beginTime = 0;
nextBar.markers[nest].endTime = -1;
nextBar.markers[nest].color = prevBar.markers[markerIdx].color;
}
// 更新标记记录
for (let markerIdx = 0; markerIdx < prevBar.markCount; ++markerIdx) {
let duration = prevBar.markers[markerIdx].endTime - prevBar.markers[markerIdx].beginTime;
let markerId = prevBar.markers[markerIdx].markerId;
let m = this.markers[markerId];
m.logs[barIdx].color = prevBar.markers[markerIdx].color;
if (!m.logs[barIdx].initialized) {
// 第一帧流程
m.logs[barIdx].min = duration;
m.logs[barIdx].max = duration;
m.logs[barIdx].avg = duration;
m.logs[barIdx].initialized = true;
} else {
// 第一帧后处理
m.logs[barIdx].min = Math.min(m.logs[barIdx].min, duration);
m.logs[barIdx].max = Math.min(m.logs[barIdx].max, duration);
m.logs[barIdx].avg += duration;
m.logs[barIdx].avg *= 0.5;
if (m.logs[barIdx].samples++ >= TimeRuler.logSnapDuration) {
m.logs[barIdx].snapMin = m.logs[barIdx].min;
m.logs[barIdx].snapMax = m.logs[barIdx].max;
m.logs[barIdx].snapAvg = m.logs[barIdx].avg;
m.logs[barIdx].samples = 0;
}
}
}
nextBar.markCount = prevBar.nestCount;
nextBar.nestCount = prevBar.nestCount;
}
this.stopwatch.reset();
this.stopwatch.start();
}
/**
* 开始测量时间
* @param markerName
* @param color
* @param barIndex
*/
public beginMark(markerName: string, color: number, barIndex: number = 0) {
if (!Core.Instance.debug) return;
if (barIndex < 0 || barIndex >= TimeRuler.maxBars)
throw new Error('barIndex 越位');
let bar = this.curLog.bars[barIndex];
if (bar.markCount >= TimeRuler.maxSamples) {
throw new Error('超出样本数.\n 要么设置更大的数字为TimeRuler.MaxSmpale要么降低样本数');
}
if (bar.nestCount >= TimeRuler.maxNestCall) {
throw new Error('nestCount超出.\n 要么将大的设置为TimeRuler.MaxNestCall要么将小的设置为NestCall');
}
// 获取已注册的标记
let markerId = this.markerNameToIdMap.get(markerName);
if (markerId == null) {
// 如果这个标记没有注册,就注册这个
markerId = this.markers.length;
this.markerNameToIdMap.set(markerName, markerId);
this.markers.push(new MarkerInfo(markerName));
}
// 开始测量
bar.markerNests[bar.nestCount++] = bar.markCount;
// 填充标记参数
bar.markers[bar.markCount].markerId = markerId;
bar.markers[bar.markCount].color = color;
bar.markers[bar.markCount].beginTime = this.stopwatch.getTime();
bar.markers[bar.markCount].endTime = -1;
bar.markCount++;
}
/**
* 停止测量
* @param markerName
* @param barIndex
*/
public endMark(markerName: string, barIndex: number = 0) {
if (!Core.Instance.debug) return;
if (barIndex < 0 || barIndex >= TimeRuler.maxBars)
throw new Error('barIndex 越位');
let bar = this.curLog.bars[barIndex];
if (bar.nestCount <= 0) {
throw new Error('在调用结束标记方法之前调用beginMark方法');
}
let markerId = this.markerNameToIdMap.get(markerName);
if (markerId == null) {
throw new Error(`标记${markerName}没有注册。请确认您指定的名称与BeginMark方法使用的名称相同`);
}
let markerIdx = bar.markerNests[--bar.nestCount];
if (bar.markers[markerIdx].markerId != markerId) {
throw new Error('beginMark/endMark方法的调用顺序不正确. beginMark(A), beginMark(B), endMark(B), endMark(A).但你不能像这样叫它 beginMark(A), beginMark(B), endMark(A), endMark(B)');
}
bar.markers[markerIdx].endTime = this.stopwatch.getTime();
}
/**
* 获取给定条形指数和标记名称的平均时间
* @param barIndex
* @param markerName
*/
public getAverageTime(barIndex: number, markerName: string) {
if (barIndex < 0 || barIndex >= TimeRuler.maxBars)
throw new Error('barIndex 越位');
let result = 0;
let markerId = this.markerNameToIdMap.get(markerName);
if (markerId != null) {
result = this.markers[markerId].logs[barIndex].avg;
}
return result;
}
/**
* 重置标记记录
*/
public resetLog() {
if (!Core.Instance.debug) return;
for (let markerInfo of this.markers) {
for (let i = 0; i < markerInfo.logs.length; ++i) {
markerInfo.logs[i].initialized = false;
markerInfo.logs[i].snapMin = 0;
markerInfo.logs[i].snapMax = 0;
markerInfo.logs[i].snapAvg = 0;
markerInfo.logs[i].min = 0;
markerInfo.logs[i].max = 0;
markerInfo.logs[i].avg = 0;
markerInfo.logs[i].samples = 0;
}
}
}
}
/**
* 标记信息
*/
class MarkerInfo {
// 标记的名称
public name: string;
public logs: MarkerLog[] = new Array(TimeRuler.maxBars);
constructor(name: string) {
this.name = name;
for (let i = 0; i < TimeRuler.maxBars; ++i)
this.logs[i] = new MarkerLog();
}
}
/**
* 标记日志信息
*/
class MarkerLog {
public snapMin: number = 0;
public snapMax: number = 0;
public snapAvg: number = 0;
public min: number = 0;
public max: number = 0;
public avg: number = 0;
public samples: number = 0;
public color: number = 0x000000;
public initialized: boolean = false;
}
/**
* 帧记录信息
*/
class FrameLog {
public bars: MarkerCollection[];
constructor() {
this.bars = new Array(TimeRuler.maxBars);
for (let i = 0; i < TimeRuler.maxBars; ++i)
this.bars[i] = new MarkerCollection();
}
}
/**
* 收集标记
*/
class MarkerCollection {
// 标记收集
public markers: Marker[] = new Array(TimeRuler.maxSamples);
public markCount: number = 0;
public markerNests: number[] = new Array(TimeRuler.maxNestCall);
public nestCount: number = 0;
constructor() {
this.markerNests.fill(0);
for (let i = 0; i < TimeRuler.maxSamples; ++i)
this.markers[i] = new Marker();
}
}
/**
* 标记结构
*/
class Marker {
public markerId: number = 0;
public beginTime: number = 0;
public endTime: number = 0;
public color: number = 0x000000;
}
}