《TypeScript图形渲染实战:2D架构设计与实现》 —3.4.3 触发多个定时任务的操作
3.4.3 触发多个定时任务的操作
定时器处理的关键源码封装在Application类的私有方法_handleTimers中,_handleTimers实现细节已经注释得很详细了。具体代码如下:
// _handleTimers私有方法被Application的update函数调用
// update函数第二个参数是以秒表示的前后帧时间差
// 正符合_handleTimers参数要求
// 计时器依赖于requestAnimationFrame回调
// 如果当前Application没有调用start的话
// 则计时器不会生效
private _handleTimers ( intervalSec : number ) : void {
// 遍历整个timers列表
for ( let i = 0 ; i < this . timers . length ; i ++ ) {
let timer : Timer = this . timers [ i ] ;
// 如果当前timer enabled为false,那么继续循环
// 这句也是重用Timer对象的一个关键实现
if ( timer . enabled === false ) {
continue ;
}
// countdown初始化时 = timeout
// 每次调用本函数,会减少上下帧的时间间隔,也就是update第二个参数传来的值
// 从而形成倒计时的效果
timer . countdown -= intervalSec ;
// 如果countdown 小于 0.0,那么说明时间到了
// 要触发回调了
// 从这里看到,实际上timer并不是很精确的
// 举个例子,假设update每次0.16秒
// timer设置0.3秒回调一次
// 那么实际上是 ( 0.3 - 0.32 ) < 0,触发回调
if ( timer . countdown < 0.0 ) {
// 调用回调函数
timer . callback ( timer . id , timer . callbackData ) ;
// 下面的代码两个分支分别处理触发一次和重复触发的操作
// 如果该计时器需要重复触发
if ( timer . onlyOnce === false ) {
// 重新将countdown设置为timeout
// 由此可见,timeout不会更改,它规定了触发的时间间隔
// 每次更新的是countdown倒计时器
timer . countdown = timer . timeout ; //很精妙的一个技巧
} else { // 如果该计时器只需要触发一次,那么就删除该计时器
this . removeTimer ( timer . id ) ;
}
}
}
}
这段代码实现了设定的3个目标:
* Application类能够同时触发多个计时器。
* 每个计时器可以以不同帧率来重复执行任务。
* 每个计时器可以以倒计时方式执行一次任务。
_handleTimer私有方法可以在Application类的step方法中被调用,也可以在update方法中调用。这里就将其放在step方法中,并且顺便在step方法中添加计算FPS(Frame Per Second)的源码。具体代码如下:
// Application中声明私有成员变量
private _fps : number = 0 ;
// 提供一个只读函数,用于获得当前帧率
public get fps ( ) {
return this . _fps ;
}
// 周而复始地运动
protected step ( timeStamp : number ) : void {
// 第一次调用本函数时,设置start和lastTime为timestamp
if ( this . _startTime === -1 ) this . _startTime = timeStamp ;
if( this . _lastTime === -1 ) this . _lastTime = timeStamp ;
//计算当前时间点与第一次调用step时间点的差
let elapsedMsec = timeStamp - this . _startTime ;
// 下面的代码和前几节的代码有更改:
// 1.增加FPS计算
// 2.增加调用_updateTimer私方法
//计算当前时间点与上一次调用step时间点的差(可以理解为两帧之间的时间差)
// 此时intervalSec实际是毫秒表示
let intervalSec = ( timeStamp - this . _lastTime ) ;
// 第一帧的时候,intervalSec为0,防止0作为分母
if ( intervalSec !== 0 ) {
// 计算fps
this . _fps = 1000.0 / intervalSec ;
}
// update使用的是以秒为单位,因此转换为秒表示
intervalSec /= 1000.0 ;
//记录上一次的时间戳
this ._lastTime = timeStamp ;
this . _handleTimers ( intervalSec ) ;
// console . log (" elapsedTime = " + elapsedMsec + " diffTime = " +
intervalSec);
// 先更新
this . update ( elapsedMsec , intervalSec ) ;
// 后渲染
this . render ( ) ;
// 递归调用,形成周而复始地前进
requestAnimationFrame ( ( elapsedMsec : number ) : void => {
this . step ( elapsedMsec ) ;
} ) ;
// requestAnimationFrame ( this . step . bind ( this ) ) ;
}
完整地了解了整个Timer的实现过程,知道Timer定时触发任务操作都是依赖request AnimationFrame方法的,这意味着如果Application类不调用start方法进入动画循环,是无法触发自定义的Timer定时任务回调的,这一点与Window对象的setTimerout和setInterval有所区别,其他的功能都类似。
- 点赞
- 收藏
- 关注作者
评论(0)