Skip to content

Commit 08f55cf

Browse files
stepankuzmingithub-actions[bot]
authored andcommitted
[GLJS-1621] Use native async/await when loading models (internal-9574)
GitOrigin-RevId: f867c813761b80d26955cd23c3e07b890462d088
1 parent f8193fc commit 08f55cf

4 files changed

Lines changed: 79 additions & 97 deletions

File tree

3d-style/source/model_source.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -114,37 +114,44 @@ class ModelSource extends Evented<SourceEvents> implements ISource {
114114
} else {
115115
// Model neither currently loading nor already loaded
116116
this._modelsInfo.set(modelId, {modelSpec, model: null});
117-
this.loadGLTFFromURI(modelSpec.uri).then(gltf => {
118-
if (!gltf) return;
119-
// Check if model is still active
120-
const modelInfo = this._modelsInfo.get(modelId);
121-
if (!modelInfo) return;
122-
123-
const nodes = convertModel(gltf);
124-
const modelSpec = modelInfo.modelSpec;
125-
const model = new Model(modelId, modelSpec.uri, modelSpec.position, modelSpec.orientation, nodes);
126-
ModelSource.applyModelSpecification(model, modelSpec);
127-
model.computeBoundsAndApplyParent();
128-
129-
this.models.push(model);
130-
modelInfo.model = model;
131-
132-
// If all models are loaded, fire data event
133-
if (this.loaded()) {
134-
this.fire(new Event('data', {dataType: 'source', sourceDataType: 'metadata'}));
135-
}
136-
}).catch((err) => {
137-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
138-
this.fire(new ErrorEvent(new Error(`Could not load model ${modelId} from ${modelSpec.uri}: ${err.message}`)));
139-
});
117+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
118+
this.loadModel(modelId, modelSpec);
140119
}
141120
}
142-
// Fire data event if all models are already loaded (i.e model source is empty or there are not more requests pending)
121+
// Fire data event if all models are already loaded (i.e model source is empty or there are no more requests pending)
143122
if (this.loaded()) {
144123
this.fire(new Event('data', {dataType: 'source', sourceDataType: 'metadata'}));
145124
}
146125
}
147126

127+
private async loadModel(modelId: string, modelSpec: ModelSourceModelSpecification): Promise<void> {
128+
try {
129+
const gltf = await this.loadGLTFFromURI(modelSpec.uri);
130+
if (!gltf) return;
131+
132+
// Check if model is still active
133+
const modelInfo = this._modelsInfo.get(modelId);
134+
if (!modelInfo) return;
135+
136+
const nodes = convertModel(gltf);
137+
const currentModelSpec = modelInfo.modelSpec;
138+
const model = new Model(modelId, currentModelSpec.uri, currentModelSpec.position, currentModelSpec.orientation, nodes);
139+
ModelSource.applyModelSpecification(model, currentModelSpec);
140+
model.computeBoundsAndApplyParent();
141+
142+
this.models.push(model);
143+
modelInfo.model = model;
144+
145+
// If all models are loaded, fire data event
146+
if (this.loaded()) {
147+
this.fire(new Event('data', {dataType: 'source', sourceDataType: 'metadata'}));
148+
}
149+
} catch (err) {
150+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
151+
this.fire(new ErrorEvent(new Error(`Could not load model ${modelId} from ${modelSpec.uri}: ${(err as Error).message}`)));
152+
}
153+
}
154+
148155
private static applyModelSpecification(model: Model, modelSpec: ModelSourceModelSpecification) {
149156
if (modelSpec.nodeOverrides) {
150157
ModelSource.convertNodeOverrides(model, modelSpec.nodeOverrides);

3d-style/util/loaders.ts

Lines changed: 48 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -356,13 +356,11 @@ function resolveUrl(url: string, baseUrl?: string) {
356356
return (new URL(url, baseUrl)).href;
357357
}
358358

359-
function loadBuffer(buffer: {uri: string; byteLength: number}, gltf: GLTF, index: number, baseUrl?: string) {
360-
return fetch(resolveUrl(buffer.uri, baseUrl))
361-
.then(response => response.arrayBuffer())
362-
.then(arrayBuffer => {
363-
assert(arrayBuffer.byteLength >= buffer.byteLength);
364-
gltf.buffers[index] = arrayBuffer;
365-
});
359+
async function loadBuffer(buffer: {uri: string; byteLength: number}, gltf: GLTF, index: number, baseUrl?: string): Promise<void> {
360+
const response = await fetch(resolveUrl(buffer.uri, baseUrl));
361+
const arrayBuffer = await response.arrayBuffer();
362+
assert(arrayBuffer.byteLength >= buffer.byteLength);
363+
gltf.buffers[index] = arrayBuffer;
366364
}
367365

368366
function getGLTFBytes(gltf: GLTF, bufferViewIndex: number): Uint8Array<ArrayBuffer> {
@@ -371,26 +369,22 @@ function getGLTFBytes(gltf: GLTF, bufferViewIndex: number): Uint8Array<ArrayBuff
371369
return new Uint8Array<ArrayBuffer>(buffer, bufferView.byteOffset || 0, bufferView.byteLength);
372370
}
373371

374-
function loadImage(img: {uri?: string; bufferView?: number; mimeType: string}, gltf: GLTF, index: number, baseUrl?: string) {
372+
async function loadImage(img: {uri?: string; bufferView?: number; mimeType: string}, gltf: GLTF, index: number, baseUrl?: string): Promise<void> {
375373
if (img.uri) {
376374
const uri = resolveUrl(img.uri, baseUrl);
377-
return fetch(uri)
378-
.then(response => response.blob())
379-
.then(blob => createImageBitmap(blob))
380-
.then(imageBitmap => {
381-
gltf.images[index] = imageBitmap;
382-
});
375+
const response = await fetch(uri);
376+
const blob = await response.blob();
377+
const imageBitmap = await createImageBitmap(blob);
378+
gltf.images[index] = imageBitmap;
383379
} else if (img.bufferView !== undefined) {
384380
const bytes = getGLTFBytes(gltf, img.bufferView);
385381
const blob = new Blob([bytes], {type: img.mimeType});
386-
return createImageBitmap(blob)
387-
.then(imageBitmap => {
388-
gltf.images[index] = imageBitmap;
389-
});
382+
const imageBitmap = await createImageBitmap(blob);
383+
gltf.images[index] = imageBitmap;
390384
}
391385
}
392386

393-
export function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, baseUrl?: string): Promise<GLTF | void> {
387+
export async function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, baseUrl?: string): Promise<GLTF | void> {
394388
const startTime = PerformanceUtils.now();
395389

396390
const gltf: GLTF = {json: null, images: [], buffers: []};
@@ -420,9 +414,9 @@ export function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, bas
420414
}
421415

422416
const {buffers, images, meshes, extensionsUsed, bufferViews} = gltf.json;
423-
let bufferLoadsPromise: Promise<unknown> = Promise.resolve();
417+
424418
if (buffers) {
425-
const bufferLoads = [];
419+
const bufferLoads: Promise<void>[] = [];
426420
for (let i = 0; i < buffers.length; i++) {
427421
const buffer = buffers[i];
428422
if (buffer.uri) {
@@ -431,58 +425,52 @@ export function decodeGLTF(arrayBuffer: ArrayBuffer, byteOffset: number = 0, bas
431425
gltf.buffers[i] = null;
432426
}
433427
}
434-
bufferLoadsPromise = Promise.all(bufferLoads);
428+
await Promise.all(bufferLoads);
435429
}
436430

437-
return bufferLoadsPromise.then(() => {
438-
const assetLoads = [];
431+
const assetLoads: Promise<unknown>[] = [];
432+
const dracoUsed = extensionsUsed && extensionsUsed.includes(DRACO_EXT);
433+
const meshoptUsed = extensionsUsed && extensionsUsed.includes(MESHOPT_EXT);
439434

440-
const dracoUsed = extensionsUsed && extensionsUsed.includes(DRACO_EXT);
441-
const meshoptUsed = extensionsUsed && extensionsUsed.includes(MESHOPT_EXT);
442-
if (dracoUsed) {
443-
assetLoads.push(waitForDraco());
435+
if (dracoUsed) {
436+
assetLoads.push(waitForDraco());
437+
}
438+
if (meshoptUsed) {
439+
assetLoads.push(waitForMeshopt());
440+
}
441+
if (images) {
442+
for (let i = 0; i < images.length; i++) {
443+
assetLoads.push(loadImage(images[i], gltf, i, baseUrl));
444444
}
445+
}
445446

446-
if (meshoptUsed) {
447-
assetLoads.push(waitForMeshopt());
448-
}
447+
if (assetLoads.length) {
448+
await Promise.all(assetLoads);
449+
}
449450

450-
if (images) {
451-
for (let i = 0; i < images.length; i++) {
452-
assetLoads.push(loadImage(images[i], gltf, i, baseUrl));
451+
if (dracoUsed && meshes) {
452+
for (const {primitives} of meshes) {
453+
for (const primitive of primitives) {
454+
loadDracoMesh(primitive, gltf);
453455
}
454456
}
457+
}
455458

456-
const assetLoadsPromise = assetLoads.length ?
457-
Promise.all(assetLoads) :
458-
Promise.resolve();
459-
460-
return assetLoadsPromise.then(() => {
461-
if (dracoUsed && meshes) {
462-
for (const {primitives} of meshes) {
463-
for (const primitive of primitives) {
464-
loadDracoMesh(primitive, gltf);
465-
}
466-
}
467-
}
468-
469-
if (meshoptUsed && meshes && bufferViews) {
470-
for (const bufferView of bufferViews) {
471-
loadMeshoptBuffer(bufferView, gltf);
472-
}
473-
}
459+
if (meshoptUsed && meshes && bufferViews) {
460+
for (const bufferView of bufferViews) {
461+
loadMeshoptBuffer(bufferView, gltf);
462+
}
463+
}
474464

475-
PerformanceUtils.measureWithDetails(PerformanceUtils.GROUP_COMMON, "decodeGLTF", "Models", startTime);
465+
PerformanceUtils.measureWithDetails(PerformanceUtils.GROUP_COMMON, "decodeGLTF", "Models", startTime);
476466

477-
return gltf;
478-
});
479-
});
467+
return gltf;
480468
}
481469

482-
export function loadGLTF(url: string): Promise<GLTF | void> {
483-
return fetch(url)
484-
.then(response => response.arrayBuffer())
485-
.then(buffer => decodeGLTF(buffer, 0, url));
470+
export async function loadGLTF(url: string): Promise<GLTF | void> {
471+
const response = await fetch(url);
472+
const buffer = await response.arrayBuffer();
473+
return decodeGLTF(buffer, 0, url);
486474
}
487475

488476
export function load3DTile(data: ArrayBuffer): Promise<GLTF | void> {

CLAUDE.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ test/
138138
- JSDoc comments for all public APIs
139139
- Don't use `!.` for non-null assertions (hides potential null issues)
140140
- Don't use `?.` or `??` operators (hides null handling, harder to debug)
141-
- No async/await - use Promises instead (keeps control flow explicit)
142141
- Use `assert` for invariants
143142
- Break complex expressions into named variables, especially WebGL math
144143

eslint.config.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,6 @@ export default tseslint.config(
111111
{
112112
selector: 'ObjectExpression > SpreadElement',
113113
message: 'Spread syntax is not allowed for object assignments. Use Object.assign() or other methods instead.',
114-
}, {
115-
selector: 'AwaitExpression',
116-
message: 'Async/await syntax is not allowed.',
117-
}, {
118-
selector: 'FunctionDeclaration[async=true]',
119-
message: 'Async function declarations are not allowed.',
120-
}, {
121-
selector: 'FunctionExpression[async=true]',
122-
message: 'Async function expressions are not allowed.',
123-
}, {
124-
selector: 'ArrowFunctionExpression[async=true]',
125-
message: 'Async arrow functions are not allowed.',
126114
}, {
127115
selector: 'ClassProperty[value]',
128116
message: 'ClassProperty values are not allowed.',

0 commit comments

Comments
 (0)