Skip to content

Commit ae12395

Browse files
authored
fix: requestAmimatonFrame only needed when observed (#4489)
Ensure only 1 animationFrame is active at a time. Signed-off-by: Gordon Smith <GordonJSmith@gmail.com>
1 parent f62fd18 commit ae12395

File tree

2 files changed

+104
-4
lines changed

2 files changed

+104
-4
lines changed

packages/util/src/dispatch.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import type { IObserverHandle } from "./observer.ts";
22
import { root } from "./platform.ts";
33

4-
export type RquestAnimationFrame = (callback: FrameRequestCallback) => number | undefined;
4+
export type RequestAnimationFrame = (callback: FrameRequestCallback) => number | undefined;
55
export type CancelAnimationFrame = (handle: number) => void | undefined;
66

7-
let requestAnimationFrame: RquestAnimationFrame;
7+
let requestAnimationFrame: RequestAnimationFrame;
88
// let cancelAnimationFrame: CancelAnimationFrame;
99

1010
(function () {
@@ -53,6 +53,7 @@ export class Dispatch<T extends Message = Message> {
5353
private _observerID: number = 0;
5454
private _observers: ObserverAdapter<T>[] = [];
5555
private _messageBuffer: T[] = [];
56+
private _rafHandle: number | undefined = undefined;
5657

5758
constructor() {
5859
}
@@ -72,6 +73,7 @@ export class Dispatch<T extends Message = Message> {
7273
}
7374

7475
private dispatchAll() {
76+
this._rafHandle = undefined;
7577
this.dispatch(this.messages());
7678
this.flush();
7779
}
@@ -95,12 +97,16 @@ export class Dispatch<T extends Message = Message> {
9597
}
9698

9799
send(msg: T) {
100+
if (!this.hasObserver()) return;
98101
this.dispatch([msg]);
99102
}
100103

101104
post(msg: T) {
105+
if (!this.hasObserver()) return;
102106
this._messageBuffer.push(msg);
103-
requestAnimationFrame(() => this.dispatchAll());
107+
if (this._rafHandle === undefined) {
108+
this._rafHandle = requestAnimationFrame(() => this.dispatchAll());
109+
}
104110
}
105111

106112
attach(callback: Callback<T>, type?: MessageConstructor<T>): IObserverHandle {

packages/util/tests/observer.spec.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,101 @@ class ConflatingMessage extends Message {
6565
}
6666
}
6767

68-
describe("observer2", function () {
68+
describe("dispatch", function () {
69+
it("hasObserver - initially false", () => {
70+
const ms = new Dispatch<SimpleMessage>();
71+
expect(ms.hasObserver()).to.be.false;
72+
});
73+
74+
it("hasObserver - true after attach", () => {
75+
const ms = new Dispatch<SimpleMessage>();
76+
const handle = ms.attach(() => { });
77+
expect(ms.hasObserver()).to.be.true;
78+
handle.release();
79+
});
80+
81+
it("hasObserver - false after release", () => {
82+
const ms = new Dispatch<SimpleMessage>();
83+
const handle = ms.attach(() => { });
84+
expect(ms.hasObserver()).to.be.true;
85+
handle.release();
86+
expect(ms.hasObserver()).to.be.false;
87+
});
88+
89+
it("send - immediate synchronous dispatch", () => {
90+
const ms = new Dispatch<SimpleMessage>();
91+
let callCount = 0;
92+
ms.attach((messages) => {
93+
callCount++;
94+
expect(messages.length).to.equal(1);
95+
expect(messages[0].property).to.equal("test");
96+
});
97+
ms.send(new SimpleMessage("test", "value"));
98+
expect(callCount).to.equal(1);
99+
});
100+
101+
it("flush - clears message buffer (prior to dispatch)", () => {
102+
return new Promise<void>((done) => {
103+
const ms = new Dispatch<SimpleMessage>();
104+
const handle = ms.attach(() => {
105+
throw new Error("Should not be called");
106+
});
107+
ms.post(new SimpleMessage("test", "value"));
108+
ms.flush();
109+
handle.release();
110+
// Wait for RAF to ensure no callback
111+
setTimeout(() => done(), 50);
112+
});
113+
});
114+
115+
it("multiple observers receive same messages", () => {
116+
return new Promise<void>((done) => {
117+
const ms = new Dispatch<SimpleMessage>();
118+
let count1 = 0;
119+
let count2 = 0;
120+
const handle1 = ms.attach((messages) => {
121+
count1++;
122+
expect(messages.length).to.equal(2);
123+
});
124+
const handle2 = ms.attach((messages) => {
125+
count2++;
126+
expect(messages.length).to.equal(2);
127+
});
128+
ms.post(new SimpleMessage("prop1", "val1"));
129+
ms.post(new SimpleMessage("prop2", "val2"));
130+
setTimeout(() => {
131+
expect(count1).to.equal(1);
132+
expect(count2).to.equal(1);
133+
handle1.release();
134+
handle2.release();
135+
done();
136+
}, 50);
137+
});
138+
});
139+
140+
it("RAF handle 0 is treated as scheduled", () => {
141+
return new Promise<void>((done) => {
142+
const ms = new Dispatch<SimpleMessage>();
143+
let callCount = 0;
144+
const handle = ms.attach((messages) => {
145+
callCount++;
146+
expect(messages.length).to.equal(3);
147+
});
148+
149+
// Post multiple messages rapidly
150+
ms.post(new SimpleMessage("prop1", "val1"));
151+
ms.post(new SimpleMessage("prop2", "val2"));
152+
ms.post(new SimpleMessage("prop3", "val3"));
153+
154+
setTimeout(() => {
155+
// Should only be called once despite multiple posts
156+
expect(callCount).to.equal(1);
157+
handle.release();
158+
done();
159+
}, 50);
160+
});
161+
});
162+
69163
it("AllMessages", () => {
70164
return new Promise<void>((done) => {
71165
const ms = new Dispatch<ConflatingMessage>();

0 commit comments

Comments
 (0)