Skip to content

Commit a9274aa

Browse files
committed
use Cmd for RAF
1 parent 48107d3 commit a9274aa

3 files changed

Lines changed: 73 additions & 45 deletions

File tree

core/src/TeaCup/Animation.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,8 @@
2424
*/
2525

2626
import { Sub } from './Sub';
27-
28-
let subs: Array<RafSub<any>> = [];
29-
30-
let ticking = false;
31-
32-
function tick() {
33-
if (!ticking) {
34-
ticking = true;
35-
requestAnimationFrame((t: number) => {
36-
subs.forEach((s) => s.trigger(t));
37-
ticking = false;
38-
});
39-
}
40-
}
27+
import { Cmd } from './Cmd';
28+
import { Dispatcher } from './Dispatcher';
4129

4230
class RafSub<M> extends Sub<M> {
4331
readonly mapper: (t: number) => M;
@@ -49,20 +37,34 @@ class RafSub<M> extends Sub<M> {
4937

5038
protected onInit() {
5139
super.onInit();
52-
subs.push(this);
53-
tick();
54-
}
55-
56-
protected onRelease() {
57-
super.onRelease();
58-
subs = subs.filter((s) => s !== this);
40+
setTimeout(() => {
41+
requestAnimationFrame((t) => this.trigger(t));
42+
});
5943
}
6044

6145
trigger(t: number) {
6246
this.dispatch(this.mapper(t));
6347
}
6448
}
6549

50+
/**
51+
* @deprecated Use {@link rafCmd} and manage
52+
* @param mapper
53+
*/
6654
export function onAnimationFrame<M>(mapper: (t: number) => M): Sub<M> {
6755
return new RafSub(mapper);
6856
}
57+
58+
class RafCmd<Msg> extends Cmd<Msg> {
59+
constructor(private readonly mapper: (t: number) => Msg) {
60+
super();
61+
}
62+
63+
execute(dispatch: Dispatcher<Msg>): void {
64+
requestAnimationFrame((t) => dispatch(this.mapper(t)));
65+
}
66+
}
67+
68+
export function rafCmd<Msg>(mapper: (t: number) => Msg): Cmd<Msg> {
69+
return new RafCmd(mapper);
70+
}

samples/src/Samples/Raf.tsx

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,28 @@
2323
*
2424
*/
2525

26-
import { Dispatcher, Cmd, noCmd, Sub, onAnimationFrame } from 'tea-cup-core';
26+
import { Cmd, Dispatcher, noCmd, rafCmd, Sub } from 'tea-cup-core';
2727
import * as React from 'react';
2828

2929
export interface Model {
3030
readonly started: boolean;
3131
readonly t: number;
32+
readonly t2: number;
3233
readonly fps: number;
3334
readonly animText: string;
3435
}
3536

36-
export type Msg = { type: 'raf'; t: number } | { type: 'toggle' } | { type: 'text-changed'; text: string };
37+
export type Msg =
38+
| { type: 'raf'; t: number }
39+
| { type: 'toggle' }
40+
| { type: 'text-changed'; text: string }
41+
| { type: 'raf2'; t: number };
3742

3843
export function init() {
3944
return noCmd<Model, Msg>({
4045
started: false,
4146
t: 0,
47+
t2: 0,
4248
fps: 0,
4349
animText: 'This text gets animated...',
4450
});
@@ -65,6 +71,9 @@ export function view(dispatch: Dispatcher<Msg>, model: Model) {
6571
/>
6672
</div>
6773
<span>Time = {Math.round(model.t)}</span>
74+
<br />
75+
<span>t2 = {Math.round(model.t2)}</span>
76+
<br />
6877
<button onClick={(_) => dispatch({ type: 'toggle' })}>{model.started ? 'Stop' : 'Start'}</button>
6978
{fps}
7079
{anim}
@@ -101,18 +110,43 @@ function viewAnim(text: String, t: number) {
101110
export function update(msg: Msg, model: Model): [Model, Cmd<Msg>] {
102111
switch (msg.type) {
103112
case 'toggle':
104-
return noCmd({ ...model, started: !model.started });
105-
case 'raf':
106-
const delta = msg.t - model.t;
107-
const fps = delta === 0
108-
? model.fps
109-
: 1000 / delta;
110-
return noCmd({
113+
const newModel: Model = {
111114
...model,
112-
t: msg.t,
113-
fps: fps,
114-
});
115+
started: !model.started,
116+
};
117+
return [
118+
newModel,
119+
newModel.started
120+
? Cmd.batch([
121+
rafCmd((t: number) => ({ type: 'raf', t } as Msg)),
122+
rafCmd((t: number) => ({ type: 'raf2', t } as Msg)),
123+
])
124+
: Cmd.none(),
125+
];
126+
case 'raf': {
127+
const delta = msg.t - model.t;
128+
const fps = delta === 0 ? model.fps : 1000 / delta;
129+
const cmd: Cmd<Msg> = model.started ? rafCmd((t: number) => ({ type: 'raf', t } as Msg)) : Cmd.none();
130+
return [
131+
{
132+
...model,
133+
t: msg.t,
134+
fps: fps,
135+
},
136+
cmd,
137+
];
138+
}
115139

140+
case 'raf2': {
141+
const cmd: Cmd<Msg> = model.started ? rafCmd((t: number) => ({ type: 'raf2', t } as Msg)) : Cmd.none();
142+
return [
143+
{
144+
...model,
145+
t2: msg.t,
146+
},
147+
cmd,
148+
];
149+
}
116150
case 'text-changed':
117151
return noCmd({
118152
...model,
@@ -121,12 +155,6 @@ export function update(msg: Msg, model: Model): [Model, Cmd<Msg>] {
121155
}
122156
}
123157

124-
export function subscriptions(model: Model) {
125-
if (model.started) {
126-
return onAnimationFrame((t: number) => {
127-
return { type: 'raf', t: t } as Msg;
128-
});
129-
} else {
130-
return Sub.none<Msg>();
131-
}
158+
export function subscriptions(model: Model): Sub<Msg> {
159+
return Sub.none();
132160
}

tea-cup/src/TeaCup/Program.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,8 @@ export class Program<Model, Msg> extends Component<ProgramProps<Model, Msg>, nev
9191

9292
const d = this.dispatch.bind(this);
9393

94-
setTimeout(() => {
95-
newSub.init(d);
96-
prevSub?.release();
97-
});
94+
newSub.init(d);
95+
prevSub?.release();
9896

9997
// perform commands in a separate timout, to
10098
// make sure that this dispatch is done

0 commit comments

Comments
 (0)