Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | 142x 10x 1540x 1540x 11288915x 11288915x 11288915x 11288915x 11288915x 11288915x 11288915x 11288915x 11594983x 11594983x 11288915x 11288915x 11288915x 11288915x 8614519x 2674396x 15982317x 11286656x 6423093x 4863563x 11286656x 7277408x 11286656x 11286656x 51014721x 51014721x 4670744x 4670744x 4670744x 4670744x 51014721x 22x 13x 188x 371x 218x 186x 186x 15825x 15825x 15825x 15825x 15825x 15825x 15825x 27276167x 4070x 11284845x 11284845x 4697472x 3855x 3176x 3176x 572x 531x 41x | const MaxHeadroom = 0xffffffff;
export class Scheduler {
static get MaxHeadroom() {
return MaxHeadroom;
}
constructor() {
/** @type {ScheduledTask|null} */
this.scheduled = null;
this.epoch = 0;
}
/**
* Schedule a task to run after a delay.
* @param {ScheduledTask} task
* @param {number} delay
*/
schedule(task, delay) {
Iif (task.scheduler !== this) {
throw new Error("Wrong scheduler for task, or non-task");
}
Iif (task.scheduled()) {
throw new Error("Task is already scheduled");
}
const expireEpoch = delay + this.epoch;
task.expireEpoch = expireEpoch;
task._scheduled = true;
let before = this.scheduled;
let prev = null;
while (before && before.expireEpoch <= expireEpoch) {
prev = before;
before = before.next;
}
task.next = before;
task.prev = prev;
if (task.next) task.next.prev = task;
if (task.prev) {
task.prev.next = task;
} else {
this.scheduled = task;
}
}
/**
* Cancel a task.
* @param {ScheduledTask} task
*/
cancel(task) {
if (!task.scheduled()) return;
if (!task.prev) {
// First element, we need to update the head element.
this.scheduled = task.next;
} else {
task.prev.next = task.next;
}
if (task.next) {
task.next.prev = task.prev;
}
task.next = task.prev = null;
task._scheduled = false;
}
/**
* Run all tasks that are due in the next ticks.
* @param {number} ticks number of cycles to run
*/
polltime(ticks) {
const targetEpoch = this.epoch + ticks;
while (this.scheduled && this.scheduled.expireEpoch <= targetEpoch) {
const head = this.scheduled;
this.epoch = head.expireEpoch;
head.cancel(); // cancel first
head.onExpire(); // expiry may reschedule
}
this.epoch = targetEpoch;
}
/**
* The minimum number of cycles that can be run without needing to polltime.
* @returns {number} number of cycles
*/
headroom() {
if (this.scheduled === null) return MaxHeadroom;
return this.scheduled.expireEpoch - this.epoch;
}
/**
* Cancel all scheduled tasks.
* Used during state restore - components will re-register their own tasks.
*/
cancelAll() {
while (this.scheduled) {
this.scheduled.cancel();
}
}
/**
* Capture the scheduler's state for snapshotting.
* @returns {{epoch: number}}
*/
snapshotState() {
return { epoch: this.epoch };
}
/**
* Restore the scheduler's state from a snapshot.
* Cancels all existing tasks - components must re-register theirs after this call.
* @param {{epoch: number}} state
*/
restoreState(state) {
this.cancelAll();
this.epoch = state.epoch;
}
/**
* Create a new task.
* @param {function(): void} onExpire function to call when the task expires
* @returns {ScheduledTask} a handle to the new task
*/
newTask(onExpire) {
return new ScheduledTask(this, onExpire);
}
}
class ScheduledTask {
/**
* @param {Scheduler} scheduler
* @param {function(): void} onExpire
*/
constructor(scheduler, onExpire) {
this.scheduler = scheduler;
this.prev = null;
this.next = null;
this.expireEpoch = 0;
this.onExpire = onExpire;
this._scheduled = false;
}
scheduled() {
return this._scheduled;
}
/**
* @param {number} delay
*/
schedule(delay) {
this.scheduler.schedule(this, delay);
}
/**
* @param {number} delay
*/
reschedule(delay) {
this.scheduler.cancel(this);
this.scheduler.schedule(this, delay);
}
cancel() {
this.scheduler.cancel(this);
}
/** @returns {number|null} cycles until the task expires, or null if it is not scheduled */
offset() {
return this._scheduled ? this.expireEpoch - this.scheduler.epoch : null;
}
/** @param {number|null|undefined} offset from offset(); null or a missing value leaves the task unscheduled */
restoreOffset(offset) {
this.cancel();
if (offset !== null && offset !== undefined) this.schedule(offset);
}
/**
* @param {boolean} state
* @param {number} delay
*/
ensureScheduled(state, delay) {
if (state) {
if (!this.scheduled()) this.schedule(delay);
} else {
this.cancel();
}
}
}
|