/*
* This file is part of AUX.
*
* AUX is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* AUX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General
* Public License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
import { warn, log } from './utils/log.js';
import { sprintf } from './utils/sprintf.js';
function lowAdd(Q, o, prio) {
prio = prio << 1;
let q = Q[prio];
let len = Q[prio + 1];
if (!q) {
Q[prio] = q = [];
len = 0;
}
q[len++] = o;
Q[prio + 1] = len;
}
function lowRemove(Q, o, prio) {
prio = prio << 1;
const q = Q[prio];
let len = Q[prio + 1];
if (!q) return;
for (let i = 0; i < len; i++) {
if (o !== q[i]) continue;
/* NOTE: this looks odd, but it's correct */
q[i] = null;
q[i] = q[--len];
}
Q[prio + 1] = len;
}
function lowHas(Q, o, prio) {
prio = prio << 1;
const q = Q[prio];
const len = Q[prio + 1];
if (!q) return false;
for (let i = 0; i < len; i++) {
if (o === q[i]) return true;
}
return false;
}
function Scheduler() {
this.Q_next = this.Q = [];
this.Q_tmp = [];
this.tmp = [];
this.debug = 0;
this.after_frame_cbs = [];
this.frame_count = 0;
this.current_priority = -1;
this.current_cycle = 0;
}
Scheduler.prototype = {
run: function () {
const Q = this.Q;
this.Q_next = this.Q_tmp;
let empty;
let runs = 0;
const debug = this.debug;
let calls = 0;
let t;
let i;
if (debug) t = performance.now();
while (!empty) {
runs++;
this.current_cycle = runs;
if (runs > 20) throw new Error('something is not right');
empty = true;
for (i = 0; i < Q.length; i += 2) {
const q = Q[i];
const len = Q[i + 1];
if (!q || !len) continue;
empty = false;
this.current_priority = i >> 1;
Q[i] = this.tmp;
Q[i + 1] = 0;
this.tmp = q;
for (let j = 0; j < len; j++) {
try {
q[j]();
} catch (e) {
warn(q[j], 'threw an error', e);
}
q[j] = null;
calls++;
}
}
const after_frame_cbs = this.after_frame_cbs;
if (after_frame_cbs.length) {
this.after_frame_cbs = [];
empty = false;
for (i = 0; i < after_frame_cbs.length; i++) after_frame_cbs[i]();
}
}
this.Q = this.Q_next;
this.Q_tmp = Q;
if (debug) {
t = performance.now() - t;
if (t > debug)
log('DOMScheduler did %d runs and %d calls: %f ms', runs, calls, t);
}
this.running = false;
this.current_priority = -1;
this.frame_count++;
return runs;
},
add: function (o, prio) {
lowAdd(this.Q, o, prio);
},
addNext: function (o, prio) {
lowAdd(this.Q_next, o, prio);
},
remove: function (o, prio) {
lowRemove(this.Q, o, prio);
},
removeNext: function (o, prio) {
lowRemove(this.Q_next, o, prio);
},
has: function (o, prio) {
return lowHas(this.Q, o, prio);
},
hasNext: function (o, prio) {
return lowHas(this.Q_next, o, prio);
},
afterFrame: function (fun) {
this.after_frame_cbs.push(fun);
},
getFrameCount: function () {
return this.frame_count;
},
getCurrentPriority: function () {
return this.current_priority;
},
getCurrentCycle: function () {
return this.current_cycle;
},
describeNow: function () {
if (this.running) {
return sprintf(
'<frame: %d, prio: %d, cycle: %d>',
this.getFrameCount(),
this.getCurrentPriority(),
this.getCurrentCycle()
);
} else {
return '<not scheduled>';
}
},
};
function DOMScheduler() {
Scheduler.call(this);
this.will_render = false;
this.running = false;
this.bound_run = this.run.bind(this);
this.startTime = performance.now();
}
DOMScheduler.prototype = Object.create(Scheduler.prototype);
DOMScheduler.prototype.addNext = function (o, prio) {
Scheduler.prototype.addNext.call(this, o, prio);
if (this.will_render) return;
this.will_render = true;
if (this.running) return;
window.requestAnimationFrame(this.bound_run);
};
DOMScheduler.prototype.add = function (o, prio) {
Scheduler.prototype.add.call(this, o, prio);
if (this.will_render) return;
if (this.running) return;
this.will_render = true;
window.requestAnimationFrame(this.bound_run);
};
DOMScheduler.prototype.run = function () {
this.will_render = false;
this.running = true;
Scheduler.prototype.run.call(this);
this.running = false;
if (this.will_render) window.requestAnimationFrame(this.bound_run);
};
DOMScheduler.prototype.afterFrame = function (fun) {
Scheduler.prototype.afterFrame.call(this, fun);
if (this.will_render) return;
if (this.running) return;
this.will_render = true;
window.requestAnimationFrame(this.bound_run);
};
DOMScheduler.prototype.now = function() {
if (this.running) {
return this.startTime;
} else {
return performance.now();
}
};
export { Scheduler, DOMScheduler };
/**
* Global DOM Scheduler.
*/
export const S = new DOMScheduler();