Skip to content

Commit 7a58b86

Browse files
stepankuzmingithub-actions[bot]
authored andcommitted
Simplify Actor and Dispatcher APIs
GitOrigin-RevId: 57ca9eb8f983d41ab6fb46db103f631f7e31662a
1 parent ae6ff47 commit 7a58b86

20 files changed

Lines changed: 262 additions & 151 deletions

3d-style/source/indoor_parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function parseActiveFloors(data: VectorTile, indoorTileOptions: IndoorTil
1616
const sourceLayers = calculateIndoorSourceLayers(indoorTileOptions.sourceLayers, new Set(Object.keys(data.layers)));
1717
const indoorState = indoorTileOptions.indoorState;
1818
const indoorData = parseData(data, sourceLayers, indoorState.activeFloors, indoorState.selectedFloorId, tileID);
19-
actor.send('setIndoorData', indoorData, {skipResult: true});
19+
actor.notify('setIndoorData', indoorData);
2020
return activeFloorsVisible ? indoorData.activeFloors : undefined;
2121
}
2222

3d-style/source/tiled_3d_model_source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ class Tiled3DModelSource extends Evented<SourceEvents> implements ISource {
218218
delete tile.request;
219219
}
220220
if (tile.actor) {
221-
tile.actor.send('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
221+
tile.actor.notify('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
222222
}
223223
}
224224

src/source/geojson_source.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ class GeoJSONSource extends Evented<SourceEvents> implements ISource {
422422

423423
// The runtime type is `${this.type}.loadData` so geojson-like source types can reuse
424424
// this path, but every variant returns a LoadGeoJSONResult-shaped reply.
425-
const result = await this.actor.send(`${this.type}.loadData` as 'geojson.loadData', options, {signal: controller.signal});
425+
const result = await this.actor.send(`${this.type}.loadData`, options, {signal: controller.signal});
426426
this._loaded = true;
427427
this._pendingLoad = null;
428428
// although GeoJSON sources contain no metadata, we fire this event at first
@@ -544,7 +544,7 @@ class GeoJSONSource extends Evented<SourceEvents> implements ISource {
544544
}
545545

546546
unloadTile(tile: Tile, _?: Callback<undefined> | null) {
547-
this.actor.send('removeTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
547+
this.actor.notify('removeTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
548548
tile.destroy();
549549
}
550550

src/source/raster_array_tile_source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ class RasterArrayTileSource extends RasterTileSource<'raster-array'> {
167167
}
168168

169169
if (tile.actor) {
170-
tile.actor.send('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
170+
tile.actor.notify('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
171171
}
172172
}
173173

src/source/raster_dem_tile_source.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ class RasterDEMTileSource extends RasterTileSource<'raster-dem'> {
3636
const {request, options} = await parseTileJSONRequest(this._options, this.map._requestManager, controller.signal);
3737
if (controller.signal.aborted) return;
3838

39-
const results = await this.dispatcher.broadcast('loadTileProvider', {
39+
const results = await this.dispatcher.send('loadTileProvider', {
4040
name: tileProvider.name,
4141
url: tileProvider.url,
4242
source: this.id,
4343
scope: this.scope,
4444
type: this.type,
4545
options,
4646
request,
47-
}, {keepResult: true, signal: controller.signal});
47+
}, {signal: controller.signal});
4848

4949
if (controller.signal.aborted) return;
5050

@@ -129,7 +129,7 @@ class RasterDEMTileSource extends RasterTileSource<'raster-dem'> {
129129
delete tile.request;
130130
}
131131
if (tile.actor) {
132-
tile.actor.send('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
132+
tile.actor.notify('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
133133
}
134134
if (callback) callback();
135135
}

src/source/vector_tile_source.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,15 @@ class VectorTileSource extends Evented<SourceEvents> implements ISource<'vector'
181181
const {request, options} = await parseTileJSONRequest(this._options, this.map._requestManager, controller.signal);
182182
if (controller.signal.aborted) return;
183183

184-
const results = await this.dispatcher.broadcast('loadTileProvider', {
184+
const results = await this.dispatcher.send('loadTileProvider', {
185185
name: tileProvider.name,
186186
url: tileProvider.url,
187187
source: this.id,
188188
scope: this.scope,
189189
type: this.type,
190190
options,
191191
request,
192-
}, {keepResult: true, signal: controller.signal});
192+
}, {signal: controller.signal});
193193

194194
if (controller.signal.aborted) return;
195195

@@ -507,13 +507,13 @@ class VectorTileSource extends Evented<SourceEvents> implements ISource<'vector'
507507
delete tile.request;
508508
}
509509
if (tile.actor) {
510-
tile.actor.send('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
510+
tile.actor.notify('abortTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
511511
}
512512
}
513513

514514
unloadTile(tile: Tile, _?: Callback<undefined> | null) {
515515
if (tile.actor) {
516-
tile.actor.send('removeTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope}, {skipResult: true});
516+
tile.actor.notify('removeTile', {uid: tile.uid, type: this.type, source: this.id, scope: this.scope});
517517
}
518518
tile.destroy();
519519
}

src/source/worker_source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export interface WorkerSource {
231231
* `Pick` so the signatures can't drift. Worker sources only send back to the
232232
* main thread, hence `Actor<MainInbox>`.
233233
*/
234-
export type WorkerSourceActor = Pick<Actor<MainInbox>, 'send' | 'sendCancelable' | 'scheduler'>;
234+
export type WorkerSourceActor = Pick<Actor<MainInbox>, 'send' | 'notify' | 'sendCancelable' | 'scheduler'>;
235235

236236
export type WorkerSourceOptions = {
237237
actor: WorkerSourceActor;

src/style/style.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,8 @@ class Style extends Evented<MapEvents> {
564564
pluginStatus: event.pluginStatus,
565565
pluginURL: event.pluginURL
566566
};
567-
self.dispatcher.broadcast('syncRTLPluginState', state, {keepResult: true})
568-
.then((results: boolean[]) => {
567+
self.dispatcher.send('syncRTLPluginState', state)
568+
.then((results) => {
569569
triggerPluginCompletionEvent(null);
570570
const allComplete = results.every((elem) => elem);
571571
if (allComplete) {

src/util/actor.ts

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export type Task = {
1717
error?: Serialized;
1818
targetMapId?: number;
1919
sourceMapId?: number;
20-
skipResult?: boolean;
2120
};
2221

2322
type PendingResponse = {
@@ -82,18 +81,17 @@ class Actor<Outbox extends MessageMap> {
8281
}
8382

8483
/**
85-
* Sends a message from a main-thread map to a Worker or from a Worker back to
86-
* a main-thread map instance.
84+
* Sends a message from a main-thread map to a Worker or from a Worker back to a
85+
* main-thread map instance, and resolves with the worker's result. Registers a pending
86+
* response and is cancelable via `signal`. Use {@link Actor#notify} if you don't need a response.
8787
*
8888
* @param type The name of the target method to invoke or '[source-type].[source-name].name' for a method on a WorkerSource.
8989
* @param data The message payload.
90-
* @param options Optional send options. When `skipResult: true`, the worker skips posting a response and the call returns void.
90+
* @param options Optional send options (`signal` to cancel, `targetMapId`, scheduler `metadata`).
9191
* @private
9292
*/
93-
send<T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options: {skipResult: true; targetMapId?: number}): void;
94-
send<T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {signal?: AbortSignal; targetMapId?: number; metadata?: TaskMetadata}): Promise<Outbox[T]['result']>;
95-
send<T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {signal?: AbortSignal; targetMapId?: number; metadata?: TaskMetadata; skipResult?: boolean}): Promise<Outbox[T]['result']> | void {
96-
const {signal, targetMapId, metadata, skipResult = false} = options || {};
93+
send<T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {signal?: AbortSignal; targetMapId?: number; metadata?: TaskMetadata}): Promise<Outbox[T]['result']> {
94+
const {signal, targetMapId, metadata} = options || {};
9795
// We're using a string ID instead of numbers because they are being used as object keys
9896
// anyway, and thus stringified implicitly. We use random IDs because an actor may receive
9997
// message from multiple other actors which could run in different execution context. A
@@ -109,13 +107,10 @@ class Actor<Outbox extends MessageMap> {
109107
id,
110108
type: type as ActorMessage,
111109
targetMapId,
112-
skipResult,
113110
sourceMapId: this.mapId,
114111
data: serialize(data, buffers)
115112
}, [...buffers]);
116113

117-
if (skipResult) return;
118-
119114
return new Promise((resolve, reject) => {
120115
const entry: PendingResponse = {resolve, reject, metadata: metadata || DEFAULT_METADATA};
121116

@@ -132,6 +127,22 @@ class Actor<Outbox extends MessageMap> {
132127
});
133128
}
134129

130+
/**
131+
* Sends a message without awaiting for response. Use {@link Actor#send} if you need to await a response.
132+
* @private
133+
*/
134+
notify<T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {targetMapId?: number}): void {
135+
const {targetMapId} = options || {};
136+
const buffers: Set<Transferable> = new Set();
137+
138+
this.target.postMessage({
139+
type: type as ActorMessage,
140+
targetMapId,
141+
sourceMapId: this.mapId,
142+
data: serialize(data, buffers)
143+
}, [...buffers]);
144+
}
145+
135146
/**
136147
* Convenience wrapper around {@link send} for the common callback + cancellation pattern:
137148
* fires the message with a fresh AbortController, routes the result to a node-style callback,
@@ -158,12 +169,15 @@ class Actor<Outbox extends MessageMap> {
158169
* must carry the owning map's id as `targetMapId`.
159170
* @private
160171
*/
161-
getWorkerSourceActor(mapId: number): Pick<Actor<Outbox>, 'send' | 'sendCancelable' | 'scheduler'> {
172+
getWorkerSourceActor(mapId: number): Pick<Actor<Outbox>, 'send' | 'notify' | 'sendCancelable' | 'scheduler'> {
162173
return {
163174
scheduler: this.scheduler,
164-
send: <T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {signal?: AbortSignal; metadata?: TaskMetadata; skipResult?: boolean}) => {
175+
send: <T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options?: {signal?: AbortSignal; metadata?: TaskMetadata}) => {
165176
return this.send(type, data, {...options, targetMapId: mapId});
166177
},
178+
notify: <T extends keyof Outbox>(type: T, data: Outbox[T]['params']) => {
179+
this.notify(type, data, {targetMapId: mapId});
180+
},
167181
sendCancelable: <T extends keyof Outbox>(type: T, data: Outbox[T]['params'], options: {metadata?: TaskMetadata}, callback: (err?: Error | null, result?: Outbox[T]['result']) => void) => {
168182
return this.sendCancelable(type, data, {...options, targetMapId: mapId}, callback);
169183
},
@@ -175,8 +189,6 @@ class Actor<Outbox extends MessageMap> {
175189
if (!data) return;
176190

177191
const id = data.id;
178-
if (!id) return;
179-
180192
if (data.targetMapId && this.mapId !== data.targetMapId) {
181193
return;
182194
}
@@ -221,47 +233,51 @@ class Actor<Outbox extends MessageMap> {
221233
entry.reject(err as Error);
222234
}
223235
}
224-
} else {
225-
const buffers: Set<Transferable> = new Set();
226-
const params = deserialize(task.data);
227236

228-
// `task.type` is a message name ('loadTile', etc.) or a runtime
229-
// `sourcetype.method` (WorkerSource types register via Map#addSourceType).
230-
// Both dispatch by runtime string, so the handler lookup is untyped.
231-
try {
232-
let result: unknown;
233-
if (this.parent[task.type]) {
234-
// task.type == 'loadTile', 'removeTile', etc.
235-
result = await this.parent[task.type](task.sourceMapId, params);
236-
} else if (this.parent.getWorkerSource) {
237-
// task.type == sourcetype.method
238-
const keys = task.type.split('.');
239-
const {source, scope} = params as {source: string; scope: string};
240-
const workerSource = this.parent.getWorkerSource(task.sourceMapId, {type: keys[0], source, scope, uid: 0});
241-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
242-
result = await workerSource[keys[1]](params);
243-
} else {
244-
throw new Error(`Could not find function ${task.type}`);
245-
}
246-
if (!task.skipResult) {
247-
this.target.postMessage({
248-
id,
249-
type: '<response>',
250-
sourceMapId: this.mapId,
251-
error: null,
252-
data: serialize(result, buffers)
253-
}, [...buffers]);
254-
}
255-
} catch (err) {
256-
assert(!task.skipResult, 'Tasks that skip results should not throw errors');
257-
this.target.postMessage({
258-
id,
259-
type: '<response>',
260-
sourceMapId: this.mapId,
261-
error: serialize(err),
262-
data: null
263-
}, []);
237+
return;
238+
}
239+
240+
const buffers: Set<Transferable> = new Set();
241+
const params = deserialize(task.data);
242+
243+
// `task.type` is a message name ('loadTile', etc.) or a runtime
244+
// `sourcetype.method` (WorkerSource types register via Map#addSourceType).
245+
// Both dispatch by runtime string, so the handler lookup is untyped.
246+
try {
247+
let result: unknown;
248+
if (this.parent[task.type]) {
249+
// task.type == 'loadTile', 'removeTile', etc.
250+
result = await this.parent[task.type](task.sourceMapId, params);
251+
} else if (this.parent.getWorkerSource) {
252+
// task.type == sourcetype.method
253+
const keys = task.type.split('.');
254+
const {source, scope} = params as {source: string; scope: string};
255+
const workerSource = this.parent.getWorkerSource(task.sourceMapId, {type: keys[0], source, scope, uid: 0});
256+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
257+
result = await workerSource[keys[1]](params);
258+
} else {
259+
throw new Error(`Could not find function ${task.type}`);
264260
}
261+
262+
if (!id) return;
263+
264+
this.target.postMessage({
265+
id,
266+
type: '<response>',
267+
sourceMapId: this.mapId,
268+
error: null,
269+
data: serialize(result, buffers)
270+
}, [...buffers]);
271+
} catch (err) {
272+
if (!id) assert(false, `"${task.type}" threw: ${(err as Error).message}`);
273+
274+
this.target.postMessage({
275+
id,
276+
type: '<response>',
277+
sourceMapId: this.mapId,
278+
error: serialize(err),
279+
data: null
280+
}, []);
265281
}
266282
}
267283

src/util/dispatcher.ts

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,29 +41,30 @@ class Dispatcher {
4141
// track whether all workers are instantiated and ready to receive messages;
4242
// used for optimizations on initial map load
4343
this.ready = false;
44-
this.broadcast('checkIfReady', null, {keepResult: true}).then(() => { this.ready = true; }).catch(() => {});
44+
this.send('checkIfReady', null).then(() => { this.ready = true; }).catch(() => {});
4545
}
4646

4747
/**
48-
* Broadcast a message to all Workers.
49-
*
50-
* By default, broadcasts are fire-and-forget and return `void`. Pass `{keepResult: true}` to
51-
* receive a Promise that resolves with the array of per-worker results (or rejects if any worker rejects).
48+
* Sends a message to every Worker and resolves with the per-worker results in actor order.
49+
* Use {@link Dispatcher#broadcast} when you don't need the results.
5250
*
5351
* @private
5452
*/
55-
broadcast<T extends keyof WorkerInbox>(type: T, data: WorkerInbox[T]['params'], options: {keepResult: true; signal?: AbortSignal}): Promise<Array<WorkerInbox[T]['result']>>;
56-
broadcast<T extends keyof WorkerInbox>(type: T, data?: WorkerInbox[T]['params']): void;
57-
broadcast<T extends keyof WorkerInbox>(type: T, data?: WorkerInbox[T]['params'], options?: {keepResult?: boolean; signal?: AbortSignal}): Promise<Array<WorkerInbox[T]['result']>> | void {
53+
send<T extends keyof WorkerInbox>(type: T, data: WorkerInbox[T]['params'], options?: {signal?: AbortSignal}): Promise<Array<WorkerInbox[T]['result']>> {
5854
assert(this.actors.length);
59-
const {signal, keepResult = false} = options || {};
60-
61-
if (keepResult) {
62-
return Promise.all(this.actors.map(actor => actor.send(type, data, {signal})));
63-
}
55+
return Promise.all(this.actors.map(actor => actor.send(type, data, options)));
56+
}
6457

58+
/**
59+
* Broadcasts a fire-and-forget message to every Worker.
60+
* Use {@link Dispatcher#send} when you need the results.
61+
*
62+
* @private
63+
*/
64+
broadcast<T extends keyof WorkerInbox>(type: T, data?: WorkerInbox[T]['params']): void {
65+
assert(this.actors.length);
6566
for (const actor of this.actors) {
66-
actor.send(type, data, {skipResult: true});
67+
actor.notify(type, data);
6768
}
6869
}
6970

0 commit comments

Comments
 (0)