Skip to content

Commit 8b96381

Browse files
committed
perf(datasource/graphene): group together segment id requests within the same bounding box (leaves_many)
1 parent 2d7862a commit 8b96381

File tree

1 file changed

+92
-44
lines changed

1 file changed

+92
-44
lines changed

src/datasource/graphene/backend.ts

Lines changed: 92 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,33 +16,33 @@
1616

1717
import { debounce } from "lodash-es";
1818
import {
19-
WithParameters,
20-
withChunkManager,
2119
Chunk,
2220
ChunkSource,
21+
WithParameters,
22+
withChunkManager,
2323
} from "#src/chunk_manager/backend.js";
2424
import { ChunkPriorityTier, ChunkState } from "#src/chunk_manager/base.js";
25+
import type { CredentialsProvider } from "#src/credentials_provider/index.js";
2526
import { WithSharedCredentialsProviderCounterpart } from "#src/credentials_provider/shared_counterpart.js";
2627
import type { ChunkedGraphChunkSpecification } from "#src/datasource/graphene/base.js";
2728
import {
28-
getGrapheneFragmentKey,
29-
GRAPHENE_MESH_NEW_SEGMENT_RPC_ID,
30-
responseIdentity,
31-
ChunkedGraphSourceParameters,
32-
MeshSourceParameters,
3329
CHUNKED_GRAPH_LAYER_RPC_ID,
3430
CHUNKED_GRAPH_RENDER_LAYER_UPDATE_SOURCES_RPC_ID,
31+
ChunkedGraphSourceParameters,
32+
GRAPHENE_MESH_NEW_SEGMENT_RPC_ID,
33+
MeshSourceParameters,
3534
RENDER_RATIO_LIMIT,
35+
getGrapheneFragmentKey,
3636
isBaseSegmentId,
3737
} from "#src/datasource/graphene/base.js";
3838
import { decodeManifestChunk } from "#src/datasource/precomputed/backend.js";
3939
import type { FragmentChunk, ManifestChunk } from "#src/mesh/backend.js";
40-
import { assignMeshFragmentData, MeshSource } from "#src/mesh/backend.js";
40+
import { MeshSource, assignMeshFragmentData } from "#src/mesh/backend.js";
4141
import { decodeDraco } from "#src/mesh/draco/index.js";
4242
import type { DisplayDimensionRenderInfo } from "#src/navigation_state.js";
4343
import type {
44-
RenderedViewBackend,
4544
RenderLayerBackendAttachment,
45+
RenderedViewBackend,
4646
} from "#src/render_layer_backend.js";
4747
import { RenderLayerBackend } from "#src/render_layer_backend.js";
4848
import { withSegmentationLayerBackendState } from "#src/segmentation_display_state/backend.js";
@@ -51,8 +51,8 @@ import type { SharedWatchableValue } from "#src/shared_watchable_value.js";
5151
import type { SliceViewChunkSourceBackend } from "#src/sliceview/backend.js";
5252
import { deserializeTransformedSources } from "#src/sliceview/backend.js";
5353
import type {
54-
TransformedSource,
5554
SliceViewProjectionParameters,
55+
TransformedSource,
5656
} from "#src/sliceview/base.js";
5757
import {
5858
forEachPlaneIntersectingVolumetricChunk,
@@ -61,9 +61,13 @@ import {
6161
import { computeChunkBounds } from "#src/sliceview/volume/backend.js";
6262
import { Uint64Set } from "#src/uint64_set.js";
6363
import { fetchSpecialHttpByteRange } from "#src/util/byte_range_http_requests.js";
64-
import type { CancellationToken } from "#src/util/cancellation.js";
64+
import {
65+
CancellationTokenSource,
66+
type CancellationToken,
67+
} from "#src/util/cancellation.js";
6568
import { vec3, vec3Key } from "#src/util/geom.js";
6669
import { responseArrayBuffer, responseJson } from "#src/util/http_request.js";
70+
import { Signal } from "#src/util/signal.js";
6771
import type {
6872
SpecialProtocolCredentials,
6973
SpecialProtocolCredentialsProvider,
@@ -76,7 +80,7 @@ import {
7680
withSharedVisibility,
7781
} from "#src/visibility_priority/backend.js";
7882
import type { RPC } from "#src/worker_rpc.js";
79-
import { registerSharedObject, registerRPC } from "#src/worker_rpc.js";
83+
import { registerRPC, registerSharedObject } from "#src/worker_rpc.js";
8084

8185
function getVerifiedFragmentPromise(
8286
credentialsProvider: SpecialProtocolCredentialsProvider,
@@ -260,6 +264,73 @@ function decodeChunkedGraphChunk(leaves: string[]) {
260264
return final;
261265
}
262266

267+
class LeavesManyProxy {
268+
pendingRequests = new Map<
269+
string,
270+
[Signal<(response: any) => void>, Uint64Set, CancellationTokenSource]
271+
>();
272+
273+
constructor(
274+
private parameters: ChunkedGraphSourceParameters,
275+
private credentialsProvider?: CredentialsProvider<any>,
276+
) {}
277+
278+
async request(
279+
segment: Uint64,
280+
bounds: string,
281+
cancellationToken: CancellationToken,
282+
): Promise<any> {
283+
const { pendingRequests } = this;
284+
let pendingRequest = pendingRequests.get(bounds);
285+
if (!pendingRequest) {
286+
const { parameters, credentialsProvider } = this;
287+
const signal = new Signal<(request: any) => void>();
288+
const requestCancellationToken = new CancellationTokenSource();
289+
const segments = new Uint64Set();
290+
pendingRequest = [signal, segments, requestCancellationToken];
291+
pendingRequests.set(bounds, pendingRequest);
292+
setTimeout(async () => {
293+
pendingRequests.delete(bounds);
294+
try {
295+
const response = await cancellableFetchSpecialOk(
296+
credentialsProvider,
297+
`${parameters.url}/leaves_many?int64_as_str=1&bounds=${bounds}`,
298+
{
299+
method: "POST",
300+
body: JSON.stringify({
301+
node_ids: [...segments],
302+
}),
303+
},
304+
responseJson,
305+
requestCancellationToken,
306+
);
307+
signal.dispatch(response);
308+
} catch (e) {
309+
signal.dispatch(e);
310+
}
311+
}, 0);
312+
}
313+
const [request, segments, requestCancellationToken] = pendingRequest;
314+
segments.add(segment);
315+
cancellationToken.add(() => {
316+
segments.delete(segment);
317+
if (segments.size === 0) {
318+
requestCancellationToken.cancel();
319+
}
320+
});
321+
return new Promise((f, r) => {
322+
const unregister = request.add((response) => {
323+
unregister();
324+
if (response instanceof Error) {
325+
r(response);
326+
} else {
327+
f(response[segment.toJSON()]);
328+
}
329+
});
330+
});
331+
}
332+
}
333+
263334
@registerSharedObject()
264335
export class GrapheneChunkedGraphChunkSource extends WithParameters(
265336
WithSharedCredentialsProviderCounterpart<SpecialProtocolCredentials>()(
@@ -271,43 +342,37 @@ export class GrapheneChunkedGraphChunkSource extends WithParameters(
271342
chunks: Map<string, ChunkedGraphChunk>;
272343
tempChunkDataSize: Uint32Array;
273344
tempChunkPosition: Float32Array;
345+
leavesManyProxy: LeavesManyProxy;
274346

275347
constructor(rpc: RPC, options: any) {
276348
super(rpc, options);
277349
this.spec = options.spec;
278350
const rank = this.spec.rank;
279351
this.tempChunkDataSize = new Uint32Array(rank);
280352
this.tempChunkPosition = new Float32Array(rank);
353+
this.leavesManyProxy = new LeavesManyProxy(
354+
this.parameters,
355+
this.credentialsProvider,
356+
);
281357
}
282358

283359
async download(
284360
chunk: ChunkedGraphChunk,
285361
cancellationToken: CancellationToken,
286362
): Promise<void> {
287-
const { parameters } = this;
288363
const chunkPosition = this.computeChunkBounds(chunk);
289364
const chunkDataSize = chunk.chunkDataSize!;
290365
const bounds =
291366
`${chunkPosition[0]}-${chunkPosition[0] + chunkDataSize[0]}_` +
292367
`${chunkPosition[1]}-${chunkPosition[1] + chunkDataSize[1]}_` +
293368
`${chunkPosition[2]}-${chunkPosition[2] + chunkDataSize[2]}`;
294369

295-
const request = cancellableFetchSpecialOk(
296-
this.credentialsProvider,
297-
`${parameters.url}/${chunk.segment}/leaves?int64_as_str=1&bounds=${bounds}`,
298-
{},
299-
responseIdentity,
370+
const request = await this.leavesManyProxy.request(
371+
chunk.segment,
372+
bounds,
300373
cancellationToken,
301374
);
302-
await this.withErrorMessage(
303-
request,
304-
`Fetching leaves of segment ${chunk.segment} in region ${bounds}: `,
305-
)
306-
.then((res) => res.json())
307-
.then((res) => {
308-
chunk.leaves = decodeChunkedGraphChunk(res.leaf_ids);
309-
})
310-
.catch((err) => console.error(err));
375+
chunk.leaves = decodeChunkedGraphChunk(request);
311376
}
312377

313378
getChunk(chunkGridPosition: Float32Array, segment: Uint64) {
@@ -325,23 +390,6 @@ export class GrapheneChunkedGraphChunkSource extends WithParameters(
325390
computeChunkBounds(chunk: ChunkedGraphChunk) {
326391
return computeChunkBounds(this, chunk);
327392
}
328-
329-
async withErrorMessage(
330-
promise: Promise<Response>,
331-
errorPrefix: string,
332-
): Promise<Response> {
333-
const response = await promise;
334-
if (response.ok) {
335-
return response;
336-
}
337-
let msg: string;
338-
try {
339-
msg = (await response.json()).message;
340-
} catch {
341-
msg = await response.text();
342-
}
343-
throw new Error(`[${response.status}] ${errorPrefix}${msg}`);
344-
}
345393
}
346394

347395
interface ChunkedGraphRenderLayerAttachmentState {

0 commit comments

Comments
 (0)