lance-gg
Version:
A Node.js based real-time multiplayer game server
112 lines (98 loc) • 3.35 kB
text/typescript
import EventEmitter from 'event-emitter';
const SIXTY_PER_SEC = 1000 / 60;
const LOOP_SLOW_THRESH = 0.3;
const LOOP_SLOW_COUNT = 10;
type SchedulerOptions = {
tick: () => void,
period: number,
delay: number
}
/**
* Scheduler class
*
*/
class Scheduler {
private options: SchedulerOptions;
private nextExecTime: number;
private requestedDelay: number;
private delayCounter: number;
public emit: (event: string, arg?: any) => void;
public on: (event: string, handler: any) => void;
public once: (event: string, handler: any) => void;
/**
* schedule a function to be called
*
* @param {Object} options the options
* @param {Function} options.tick the function to be called
* @param {Number} options.period number of milliseconds between each invocation, not including the function's execution time
* @param {Number} options.delay number of milliseconds to add when delaying or hurrying the execution
*/
constructor(options: SchedulerOptions) {
this.options = Object.assign({
tick: null,
period: SIXTY_PER_SEC,
delay: SIXTY_PER_SEC / 3
}, options);
this.nextExecTime = 0;
this.requestedDelay = 0;
this.delayCounter = 0;
// mixin for EventEmitter
let eventEmitter = EventEmitter();
this.on = eventEmitter.on;
this.once = eventEmitter.once;
this.emit = eventEmitter.emit;
}
// in same cases, setTimeout is ignored by the browser,
// this is known to happen during the first 100ms of a touch event
// on android chrome. Double-check the game loop using requestAnimationFrame
nextTickChecker() {
let currentTime = (new Date()).getTime();
if (currentTime > this.nextExecTime) {
this.delayCounter++;
this.callTick();
this.nextExecTime = currentTime + this.options.period;
}
window.requestAnimationFrame(this.nextTickChecker.bind(this));
}
nextTick() {
let stepStartTime = (new Date()).getTime();
if (stepStartTime > this.nextExecTime + this.options.period * LOOP_SLOW_THRESH) {
this.delayCounter++;
} else
this.delayCounter = 0;
this.callTick();
this.nextExecTime = stepStartTime + this.options.period + this.requestedDelay;
this.requestedDelay = 0;
setTimeout(this.nextTick.bind(this), this.nextExecTime - (new Date()).getTime());
}
callTick() {
if (this.delayCounter >= LOOP_SLOW_COUNT) {
this.emit('loopRunningSlow');
this.delayCounter = 0;
}
this.options.tick();
}
/**
* start the schedule
* @return {Scheduler} returns this scheduler instance
*/
start() {
setTimeout(this.nextTick.bind(this));
if (typeof window === 'object' && typeof window.requestAnimationFrame === 'function')
window.requestAnimationFrame(this.nextTickChecker.bind(this));
return this;
}
/**
* delay next execution
*/
delayTick() {
this.requestedDelay += this.options.delay;
}
/**
* hurry the next execution
*/
hurryTick() {
this.requestedDelay -= this.options.delay;
}
}
export { Scheduler, SchedulerOptions }