forked from mattgodbolt/jsbeeb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscheduler.js
108 lines (93 loc) · 2.6 KB
/
scheduler.js
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
"use strict";
const MaxHeadroom = 0xffffffff;
export class Scheduler {
constructor() {
this.scheduled = null;
this.epoch = 0;
}
schedule(task, delay) {
if (task.scheduler !== this) {
throw new Error("Wrong scheduler for task, or non-task");
}
if (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(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;
}
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;
}
headroom() {
if (this.scheduled === null) return MaxHeadroom;
return this.scheduled.expireEpoch - this.epoch;
}
newTask(onExpire) {
return new Task(this, onExpire);
}
}
class Task {
constructor(scheduler, onExpire) {
this.scheduler = scheduler;
this.prev = this.next = null;
this.expireEpoch = 0;
this.onExpire = onExpire;
this._scheduled = false;
}
scheduled() {
return this._scheduled;
}
schedule(delay) {
this.scheduler.schedule(this, delay);
}
reschedule(delay) {
this.scheduler.cancel(this);
this.scheduler.schedule(this, delay);
}
cancel() {
this.scheduler.cancel(this);
}
ensureScheduled(state, delay) {
if (state) {
if (!this.scheduled()) this.schedule(delay);
} else {
this.cancel();
}
}
}