Skip to content

Instantly share code, notes, and snippets.

@zeraphie
Last active January 25, 2018 14:27
Show Gist options
  • Save zeraphie/6c4f72e0d6d723d1705f3ee614754f8f to your computer and use it in GitHub Desktop.
Save zeraphie/6c4f72e0d6d723d1705f3ee614754f8f to your computer and use it in GitHub Desktop.
JavaScript Engine

Engine

This is a simple JavaScript engine built around request animation frame that features a small api for usage and a queue system

https://codepen.io/zephyr/pen/OzeQJO - Bare bones example

https://codepen.io/zephyr/pen/OOeywo - Animating a snow background

Usage

// Have a variable to track frames outside of the engine
let frame = 0;

// Make a new engine
let engine = new Engine();

// Register a function to the onStart queue (multiple can be made)
engine.onStart.register(() => {
    console.log('Engine started');
});

// Register a function to the onRun queue (multiple can be made)
// This function will be executed every single frame
engine.onRun.register(() => {
    console.log(`New frame: ${frame}`);
    
    // Change the tracking variable every frame
    frame++;
});

// Register a function to the onPause queue (multiple can be made)
engine.onPause.register(() => {
    console.log('Engine is paused');
});

// Register a function to the onUnpause queue (multiple can be made)
engine.onUnpause.register(() => {
    console.log('Engine is unpaused and running');
});

// Register a function to the onStop queue (multiple can be made)
engine.onStop.register(() => {
    console.log('Engine is stopped');
    
    // Reset the tracking variable
    frame = 0;
});

// Small helpers to see the result of the engine in the console
document.addEventListener('keyup', e => {
    if(e.which === 13){ // Enter key
        engine.start();
    }
    
    if(e.which === 32){ // Space key
        engine.pause();
    }
    
    if(e.which === 8 || e.which === 46){ // Backspace/Delete key
        engine.stop();
    }
});
import HookQueue from './HookQueue.js';
export default class Engine {
constructor(){
this.raf = null;
this.frames = 0;
this.started = false;
this.is_running = false;
this.start_time = null;
this.paused_frames = 0;
this.fps = 60;
this.onStart = new HookQueue();
this.onRun = new HookQueue();
this.onPause = new HookQueue();
this.onUnpause = new HookQueue();
this.onStop = new HookQueue();
}
/**
* Get how long the engine has been running for in seconds
*
* @returns {number}
*/
get running_time(){
return this.frames / this.fps;
}
/**
* Get how long the engine has been paused for in seconds
*
* @returns {number}
*/
get paused_time(){
return this.paused_frames / this.fps;
}
/**
* Start the loop
*/
start(){
if(this.started){
return;
}
this.started = true;
this.is_running = true;
if(this.start_time === null){
this.start_time = new Date();
}
this.onStart.call(this);
this.run();
}
/**
* Toggle whether the this is running or paused
*/
pause(){
if(!this.started){
return;
}
if(this.is_running){
this.is_running = false;
this.onPause.call(this);
} else {
this.is_running = true;
this.onUnpause.call(this);
}
}
/**
* End the engine by clearing the interval and frames
*/
stop(){
if(!this.started){
return;
}
this.started = false;
this.is_running = false;
window.cancelAnimationFrame(this.raf);
this.raf = null;
this.onStop.call(this);
this.start_time = null;
this.paused_frames = 0;
this.frames = 0;
}
/**
* Render the scene frame by frame using requestAnimationFrame
* @param fps
*/
run(){
let fpsInterval = 1000 / this.fps;
const runFrame = () => {
// For some reason, this needs to be before the loop
this.raf = window.requestAnimationFrame(runFrame);
let now = new Date();
let elapsed = now - this.start_time;
if (elapsed > fpsInterval) {
this.start_time = now - (elapsed % fpsInterval);
if(this.is_running){
this.onRun.call(this);
this.frames++;
} else {
this.paused_frames++;
}
}
};
runFrame();
}
}
export default class HookQueue {
constructor(){
this.hooks = [];
}
/**
* Register a function on this hook
*
* @param callback
*/
register(callback){
this.hooks.push(callback);
}
/**
* Call the hooks with the arguments provided
*
* @param args
*/
call(...args){
for(let i = 0, len = this.hooks.length; i < len; i++){
if(this.hooks[i](...args, new Date()) !== true){
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment