LoopController

by Laurens Maneschijn

HTML

<div id="Demo">

	<div id="loopcontrols"></div>

	draw circle:
	<input id="drawcircle" type="checkbox">

	repeat to simulate load:
	<input id="drawcircle_repeat_n" type="range" value="1" min="1" max="50001" step="1000">

	<hr>

	<pre id="log"></pre>

	<canvas id="canvas" style="display: none;"></canvas>

</div>

<div id="Demo2">
	<hr>
	<button onclick="new Demo2()">add Demo2 loop</button>
	<hr>
</div>

CSS

.loopcontrollerinterface button {
	text-align: center;
	vertical-align: middle;
	margin: 2px;
	min-height: 28px;
	min-width: 28px;
/*	aspect-ratio: 1/1;*/
}
.loopcontrollerinterface button.active {
	background-color: #ddd;
	border-width: 2px;
}

input[type="range"] {
	position: relative;
	margin-bottom: 6px;
}
input[type="range"]::after {
	content: attr(title);
	position: absolute;
	z-index: 9;
	top: 12px;
}

.Demo2 {
	border: 1px solid #8888;
	margin: 2px;
	padding: 2px;
}

JavaScript

console.clear();

// TODO: implement private and public things (jsfiddle doesn't support the syntax though)
class LoopController {
	constructor() {
		this.initevents();
		this.loopfunction = function(loop) {};
		this.running = false;
		this.run_once = false;
		this.last_request = null;
		this.setDelay(16); // delay 16 = 62.5 FPS
		this.reset();
	}
	reset() {
		this.event('resetting');
		this.timestamp_start = null;
		this.timestamp_last = null;
		this.framecount = 0;
		this.total_runtime_ms = 0;
		this.frametimes = [];
		this.frametimes_i = 0;
		this.frametimes_max = 10;
		this.total_loopfunction_time = 0;
		this.event('reset');
	}
	toggle() {
		this.event('toggling');
		if (this.running) {
			this.stop();
		} else {
			this.start();
		}
		this.event('toggled');
	}
	stop() {
		if (!this.running) {
			return;
		}
		this.event('stopping');
		this.running = false;
		cancelAnimationFrame(this.last_request);
		this.last_request = null;
		this.timestamp_last = null;
		this.event('stopped');
	}
	start() {
		if (this.running) {
			return;
		}
		this.event('starting');
		this.running = true;
		cancelAnimationFrame(this.last_request);
		this.last_request = requestAnimationFrame(this._step.bind(this));
		this.event('started');
	}
	step() {
		if (this.running) {
			return;
		}
		this.event('stepping');
		this.run_once = true;
		this.start();
	}
	setLoopFunction(loopfunction) {
		if (typeof loopfunction !== 'function') {
			throw Error('LoopController setLoopFunction() no function given.');
		}
		this.loopfunction = loopfunction;
		this.event('loopfunction_changed');
	}
	setDelay(delay) {
		if (typeof delay === 'string') {
			delay = parseFloat(delay);
		}
		if (typeof delay !== 'number' || Number.isNaN(delay)) {
			throw Error('LoopController setDelay() : no number given.');
		}
		var oldvalue = this.delay;
		this.delay = delay;
		if (delay !== oldvalue) {
			this.event('delay_changed');
		}
	}
	setFPS(fps) {
		if (typeof fps === 'string') {
			fps =...