-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
171 lines (155 loc) · 4.41 KB
/
index.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
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
// Generated by CoffeeScript 2.3.2
// ![PlayFrame](https://avatars3.githubusercontent.com/u/47147479)
// # OverSync
// ###### 0.4 kB Frame Rendering Engine
// ## Installation
// ```sh
// npm install --save @playframe/oversync
// ```
// ## Functionality Overview
// ```js
// import oversync from '@playframe/oversync'
// const sync = oversync(Date.now, requestAnimationFrame)
// ```
// Each method schedules given function
// to be executed in specific time and order
// ```js
// sync.next(fn) // events handling and dom read
// sync.catch(fn) // error handling
// sync.then(fn) // data work is done here
// sync.finally(fn) // finalizing data
// sync.render(fn) // dom manipulation
// // Actual requestAnimationFrame callback
// // No work should be done here
// sync.frame(fn)
// ```
// #### Execution strategy
// Render a frame_0 first,
// then request a new frame_1 and immediately do work.
// After work is done VM is idling for up to 10ms
// until frame callback is fired and frame_1 finally rendered.
// Any event occuring after work is done
// but before frame_1 is rendered will schedule actual work
// to be done onlly after frame_1 is rendered
// ```
// 1ms Request frame_0 and setTimeout(work_for_frame_1)
// 2ms frame_0 is rendered by browser
// 3ms Request frame_1
// 4ms work_for_frame_1: read dom, do work, write dom
// ... idle
// 8ms Click: sync.next(click_handler) for frame_2
// ... idle
// 10ms Fetch: sync.then(fetch_handler) for frame_2
// ...idle
// 15ms Animation callback: setTimeout(work_for_frame_2)
// 16ms frame_1 is rendered
// 17ms Request frame_2
// 18ms work_for_frame_2: read dom, do work, write dom
// ...
// ```
// ## Annotated Source
// Let's define a higher order function
// that would take a `now` timestamp function,
// scheduling `next` function and optionally
// a list `steps` of desired execution order and method names
// and an optional `step` method name.
var delta, pusher, runner, scheduler;
module.exports = (now, next, steps = ['next', 'catch', 'then', 'finally', 'render'], step = 'frame') => {
var delta_runner, push_and_run, schedule, step_ops, steps_ops, sync;
// For each step we would prepare and empty array
step_ops = [];
steps_ops = steps.map(() => {
return [];
});
// For measuring time deltas we would have a fancy runner function
delta_runner = delta(now)(runner);
// `schedule` function for requesting next frame
// in which we would run our `frame` operations and
// schedule work for the rest of the steps
schedule = scheduler(next)(() => {
var run;
run = delta_runner();
run(step_ops);
return setTimeout(() => {
return steps_ops.forEach(run);
});
});
// A pusher function that will `schedule` on every push
push_and_run = pusher(schedule);
// Dynamically creating methods that would push operations
// and schedule execution and returning `sync`
sync = {};
sync[step] = push_and_run(step_ops);
steps.forEach((step, i) => {
return sync[step] = push_and_run(steps_ops[i]);
});
return sync;
};
// #### Abstract functions
// Our `scheduler` is creating a throttled `schedule`
scheduler = (next) => {
return (f) => {
var _scheduled, g;
_scheduled = false;
g = (x) => {
_scheduled = false;
return f(x);
};
return () => {
if (!_scheduled) {
_scheduled = true;
next(g);
}
};
};
};
// This `pusher` is creating a function that will run `task`
// before pushing `op` to `ops`
pusher = (task) => {
return (ops) => {
return (op) => {
task();
ops.push(op);
};
};
};
// Feeding timestamps produced by `now` to a given `f`
// like our `runner`
delta = (now) => {
return (f) => {
var _prev_ts;
_prev_ts = now();
return () => {
var ts;
return f({
delta: (ts = now()) - _prev_ts,
ts: (_prev_ts = ts)
});
};
};
};
// This runner will feed `x` to a list of given `ops`.
// It will recover if any operation fails.
// Clearing `ops` list at the end
runner = (x) => {
return (ops) => {
var e, i, length, recover;
i = 0;
// Rechecking length in outer loop
// could push more ops while running
while (i < (length = ops.length)) {
try {
while (i < length) {
ops[i++](x);
}
} catch (error) {
e = error;
console.error(e);
if (recover = ops[i - 1].r) { // recovering
recover(e);
}
}
}
ops.length = 0; // mutating 👹
};
};