-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathsched.mjs
130 lines (104 loc) · 3.26 KB
/
sched.mjs
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
/*
Tools for async batching and scheduling of hierarchical runs,
from ancestors to descendants. See `Sched`.
*/
/* eslint-env browser */
import * as l from './lang.mjs'
import * as o from './obj.mjs'
// Base implementation used by other timers. Nop by itself.
export class BaseTimer extends l.Emp {
constructor(ref) {
super()
this.ref = reqRunner(ref)
this.run = this.run.bind(this)
this.val = undefined
}
// Override in subclass.
timerInit() {}
timerDeinit() {}
run() {try {this.ref.run()} finally {this.unschedule()}}
schedule() {if (!this.val) this.val = this.timerInit(this.run)}
unschedule() {
const {val} = this
if (val) {
this.val = undefined
this.timerDeinit(val)
}
}
deinit() {this.unschedule()}
}
// Default recommended timer.
export class RofTimer extends BaseTimer {
timerInit(run) {return requestAnimationFrame(run)}
timerDeinit(val) {cancelAnimationFrame(val)}
}
// Fallback alternative to `requestAnimationFrame`.
export class TimeoutTimer extends BaseTimer {
timerInit(run) {return setTimeout(run)}
timerDeinit(val) {clearTimeout(val)}
}
// Fake/nop timer that always runs synchronously.
export class SyncTimer extends l.Emp {
constructor(ref) {super().ref = reqRunner(ref)}
schedule() {this.ref.run()}
unschedule() {}
}
/*
Used internally by `Sched`. Updates are scheduled by adding vals to the que, and
flushed together as a batch by calling `.run()`. Reentrant flush is a nop.
TODO consider preventing exceptions from individual runs from interfering with
each other.
*/
export class Que extends Set {
constructor() {super().flushing = false}
add(val) {return super.add(reqRunner(val))}
run() {
if (this.flushing) return this
this.flushing = true
try {
for (const val of this.values()) val.run()
}
finally {
this.flushing = false
this.clear()
}
return this
}
}
/*
Short for "scheduler". Tool for scheduling hierarchical runs, from ancestors to
descendants. Inputs report their "depth", which allows us to determine order.
*/
export class Sched extends o.MixMain(l.Emp) {
constructor() {
super()
this.ques = []
this.timer = new this.Timer(this)
}
// Main API for consumer code.
push(val) {return this.add(val).schedule()}
// Called by timer. Can also be flushed manually.
run() {
this.unschedule()
for (const que of this.ques) if (que) que.run()
return this
}
add(val) {return this.que(val.depth()).add(val), this}
que(depth) {
l.reqNat(depth)
return this.ques[depth] ||= new this.Que()
}
schedule() {return this.timer.schedule(), this}
unschedule() {return this.timer.unschedule(), this}
deinit() {this.unschedule()}
get Timer() {return RofTimer}
get Que() {return Que}
}
export function isDep(val) {return l.hasMeth(val, `depth`)}
export function reqDep(val) {return l.req(val, isDep)}
export function isRunner(val) {return l.hasMeth(val, `run`)}
export function reqRunner(val) {return l.req(val, isRunner)}
export function isDepRunner(val) {return isDep(val) && isRunner(val)}
export function reqDepRunner(val) {return l.req(val, isDepRunner)}
export function isTimer(val) {return l.hasMeth(val, `schedule`) && l.hasMeth(val, `unschedule`)}
export function reqTimer(val) {return l.req(val, isTimer)}