新增TimeRuler用于分析游戏平均帧率
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
315
source/src/Utils/Analysis/TimeRuler.ts
Normal file
315
source/src/Utils/Analysis/TimeRuler.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user