Skip to content

Commit b1f788c

Browse files
committed
Replace evaluations with proxies and messages
1 parent 80c1987 commit b1f788c

30 files changed

Lines changed: 2490 additions & 1350 deletions

packages/events/src/events.ts

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,51 @@
11
import { IDisposable } from "@coder/disposable";
22

33
export interface Event<T> {
4-
(listener: (e: T) => void): IDisposable;
4+
(listener: (value: T) => void): IDisposable;
5+
(id: number, listener: (value: T) => void): IDisposable;
56
}
67

78
/**
8-
* Emitter typecasts for a single event type.
9+
* Emitter typecasts for a single event type. You can optionally use IDs, but
10+
* using undefined with IDs will not work. If you emit without an ID, *all*
11+
* listeners regardless of their ID (or lack thereof) will receive the event.
12+
* Similarly, if you listen without an ID you will get *all* events for any or
13+
* no ID.
914
*/
1015
export class Emitter<T> {
11-
private listeners = <Array<(e: T) => void>>[];
16+
private listeners = <Array<(value: T) => void>>[];
17+
private readonly idListeners = new Map<number, Array<(value: T) => void>>();
1218

1319
public get event(): Event<T> {
14-
return (cb: (e: T) => void): IDisposable => {
15-
if (this.listeners) {
16-
this.listeners.push(cb);
20+
return (id: number | ((value: T) => void), cb?: (value: T) => void): IDisposable => {
21+
if (typeof id === "number") {
22+
if (this.idListeners.has(id)) {
23+
this.idListeners.get(id)!.push(cb!);
24+
} else {
25+
this.idListeners.set(id, [cb!]);
26+
}
27+
28+
return {
29+
dispose: (): void => {
30+
if (this.idListeners.has(id)) {
31+
const cbs = this.idListeners.get(id)!;
32+
const i = cbs.indexOf(cb!);
33+
if (i !== -1) {
34+
cbs.splice(i, 1);
35+
}
36+
}
37+
},
38+
};
1739
}
1840

41+
cb = id;
42+
this.listeners.push(cb);
43+
1944
return {
2045
dispose: (): void => {
21-
if (this.listeners) {
22-
const i = this.listeners.indexOf(cb);
23-
if (i !== -1) {
24-
this.listeners.splice(i, 1);
25-
}
46+
const i = this.listeners.indexOf(cb!);
47+
if (i !== -1) {
48+
this.listeners.splice(i, 1);
2649
}
2750
},
2851
};
@@ -32,16 +55,31 @@ export class Emitter<T> {
3255
/**
3356
* Emit an event with a value.
3457
*/
35-
public emit(value: T): void {
36-
if (this.listeners) {
37-
this.listeners.forEach((t) => t(value));
58+
public emit(value: T): void;
59+
public emit(id: number, value: T): void;
60+
public emit(id: number | T, value?: T): void {
61+
if (typeof id === "number" && typeof value !== "undefined") {
62+
if (this.idListeners.has(id)) {
63+
this.idListeners.get(id)!.forEach((cb) => cb(value!));
64+
}
65+
this.listeners.forEach((cb) => cb(value!));
66+
} else {
67+
this.idListeners.forEach((cbs) => cbs.forEach((cb) => cb((id as T)!)));
68+
this.listeners.forEach((cb) => cb((id as T)!));
3869
}
3970
}
4071

4172
/**
4273
* Dispose the current events.
4374
*/
44-
public dispose(): void {
45-
this.listeners = [];
75+
public dispose(): void;
76+
public dispose(id: number): void;
77+
public dispose(id?: number): void {
78+
if (typeof id !== "undefined") {
79+
this.idListeners.delete(id);
80+
} else {
81+
this.listeners = [];
82+
this.idListeners.clear();
83+
}
4684
}
4785
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Emitter } from "../src/events";
2+
3+
describe("Event", () => {
4+
const emitter = new Emitter<number>();
5+
6+
it("should listen to global event", (done) => {
7+
const d = emitter.event((value) => {
8+
expect(value).toBe(10);
9+
d.dispose();
10+
done();
11+
});
12+
13+
emitter.emit(10);
14+
});
15+
16+
it("should listen to id event", (done) => {
17+
const d = emitter.event(0, (value) => {
18+
expect(value).toBe(5);
19+
d.dispose();
20+
done();
21+
});
22+
23+
emitter.emit(0, 5);
24+
});
25+
26+
it("should not listen wrong id event", (done) => {
27+
const d = emitter.event(1, (value) => {
28+
expect(value).toBe(6);
29+
d.dispose();
30+
done();
31+
});
32+
33+
emitter.emit(0, 5);
34+
emitter.emit(1, 6);
35+
});
36+
37+
it("should listen to id event globally", (done) => {
38+
const d = emitter.event((value) => {
39+
expect(value).toBe(11);
40+
d.dispose();
41+
done();
42+
});
43+
44+
emitter.emit(1, 11);
45+
});
46+
47+
it("should listen to global event", (done) => {
48+
const d = emitter.event(3, (value) => {
49+
expect(value).toBe(14);
50+
d.dispose();
51+
done();
52+
});
53+
54+
emitter.emit(14);
55+
});
56+
57+
it("should dispose individually", () => {
58+
const fn = jest.fn();
59+
const d = emitter.event(fn);
60+
61+
const fn2 = jest.fn();
62+
const d2 = emitter.event(1, fn2);
63+
64+
d.dispose();
65+
66+
emitter.emit(12);
67+
emitter.emit(1, 12);
68+
69+
expect(fn).not.toBeCalled();
70+
expect(fn2).toBeCalledTimes(2);
71+
72+
d2.dispose();
73+
74+
emitter.emit(12);
75+
emitter.emit(1, 12);
76+
77+
expect(fn).not.toBeCalled();
78+
expect(fn2).toBeCalledTimes(2);
79+
});
80+
81+
it("should dispose by id", () => {
82+
const fn = jest.fn();
83+
emitter.event(fn);
84+
85+
const fn2 = jest.fn();
86+
emitter.event(1, fn2);
87+
88+
emitter.dispose(1);
89+
90+
emitter.emit(12);
91+
emitter.emit(1, 12);
92+
93+
expect(fn).toBeCalledTimes(2);
94+
expect(fn2).not.toBeCalled();
95+
});
96+
97+
it("should dispose all", () => {
98+
const fn = jest.fn();
99+
emitter.event(fn);
100+
emitter.event(1, fn);
101+
102+
emitter.dispose();
103+
104+
emitter.emit(12);
105+
emitter.emit(1, 12);
106+
107+
expect(fn).not.toBeCalled();
108+
});
109+
});

packages/protocol/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
},
1313
"devDependencies": {
1414
"@types/google-protobuf": "^3.2.7",
15+
"@types/rimraf": "^2.0.2",
1516
"@types/text-encoding": "^0.0.35",
17+
"rimraf": "^2.6.3",
1618
"text-encoding": "^0.7.0",
1719
"ts-protoc-gen": "^0.8.0"
1820
}

0 commit comments

Comments
 (0)