Bläddra i källkod

LDrawLoader: parallelize parts library downloads to improve load times (#22253)

* Add "setPartsLibraryPath" function

* Add "preloadMaterials" function

* Use single material if possible

* Fix normal smoothing

* Add smarter subobject cache

* Remove static scope stack

* Make "processObject" async

* fix process order

* Fix construction steps

* comments, variable rename

* arrow functions to classic functions
Garrett Johnson 4 år sedan
förälder
incheckning
aa1580322e
1 ändrade filer med 247 tillägg och 315 borttagningar
  1. 247 315
      examples/jsm/loaders/LDrawLoader.js

+ 247 - 315
examples/jsm/loaders/LDrawLoader.js

@@ -477,6 +477,115 @@ class LineParser {
 
 }
 
+class LDrawFileCache {
+
+	constructor( loader ) {
+
+		this.cache = {};
+		this.loader = loader;
+
+	}
+
+	setData( key, contents ) {
+
+		this.cache[ key.toLowerCase() ] = contents;
+
+	}
+
+	async loadData( fileName ) {
+
+		const key = fileName.toLowerCase();
+		if ( key in this.cache ) {
+
+			return this.cache[ key ];
+
+		}
+
+		this.cache[ fileName ] = new Promise( async ( resolve, reject ) => {
+
+			let triedLowerCase = false;
+			let locationState = FILE_LOCATION_AS_IS;
+			while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
+
+				let subobjectURL = fileName;
+				switch ( locationState ) {
+
+					case FILE_LOCATION_AS_IS:
+						locationState = locationState + 1;
+						break;
+
+					case FILE_LOCATION_TRY_PARTS:
+						subobjectURL = 'parts/' + subobjectURL;
+						locationState = locationState + 1;
+						break;
+
+					case FILE_LOCATION_TRY_P:
+						subobjectURL = 'p/' + subobjectURL;
+						locationState = locationState + 1;
+						break;
+
+					case FILE_LOCATION_TRY_MODELS:
+						subobjectURL = 'models/' + subobjectURL;
+						locationState = locationState + 1;
+						break;
+
+					case FILE_LOCATION_TRY_RELATIVE:
+						subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL;
+						locationState = locationState + 1;
+						break;
+
+					case FILE_LOCATION_TRY_ABSOLUTE:
+
+						if ( triedLowerCase ) {
+
+							// Try absolute path
+							locationState = FILE_LOCATION_NOT_FOUND;
+
+						} else {
+
+							// Next attempt is lower case
+							fileName = fileName.toLowerCase();
+							subobjectURL = fileName;
+							triedLowerCase = true;
+							locationState = FILE_LOCATION_AS_IS;
+
+						}
+
+						break;
+
+				}
+
+				const loader = this.loader;
+				const fileLoader = new FileLoader( loader.manager );
+				fileLoader.setPath( loader.partsLibraryPath );
+				fileLoader.setRequestHeader( loader.requestHeader );
+				fileLoader.setWithCredentials( loader.withCredentials );
+
+				try {
+
+					const text = await fileLoader.loadAsync( subobjectURL );
+					this.setData( fileName, text );
+					resolve( text );
+					return;
+
+				} catch {
+
+					continue;
+
+				}
+
+			}
+
+			reject();
+
+		} );
+
+		return this.cache[ fileName ];
+
+	}
+
+}
+
 function sortByMaterial( a, b ) {
 
 	if ( a.colourCode === b.colourCode ) {
@@ -687,23 +796,19 @@ class LDrawLoader extends Loader {
 
 		super( manager );
 
-		// This is a stack of 'parse scopes' with one level per subobject loaded file.
-		// Each level contains a material lib and also other runtime variables passed between parent and child subobjects
-		// When searching for a material code, the stack is read from top of the stack to bottom
-		// Each material library is an object map keyed by colour codes.
-		this.parseScopesStack = null;
-
 		// Array of THREE.Material
 		this.materials = [];
 
 		// Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
 		// This also allows to handle the embedded text files ("0 FILE" lines)
-		this.subobjectCache = {};
+		this.cache = new LDrawFileCache( this );
 
 		// This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
 		this.fileMap = null;
 
-		// Add default main triangle and line edge materials (used in piecess that can be coloured with a main color)
+		this.rootParseScope = this.newParseScopeLevel();
+
+		// Add default main triangle and line edge materials (used in pieces that can be coloured with a main color)
 		this.setMaterials( [
 			this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ),
 			this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) )
@@ -772,7 +877,12 @@ class LDrawLoader extends Loader {
 		fileLoader.setWithCredentials( this.withCredentials );
 		fileLoader.load( url, function ( text ) {
 
-			scope.processObject( text, onLoad, null, url );
+			scope.processObject( text, null, url, scope.rootParseScope )
+				.then( function ( result ) {
+
+					onLoad( result.groupObject );
+
+				} );
 
 		}, onProgress, onError );
 
@@ -781,20 +891,20 @@ class LDrawLoader extends Loader {
 	parse( text, path, onLoad ) {
 
 		// Async parse.  This function calls onParse with the parsed THREE.Object3D as parameter
+		this.processObject( text, null, path, this.rootParseScope )
+			.then( function ( result ) {
+
+				onLoad( result.groupObject );
 
-		this.processObject( text, onLoad, null, path );
+			} );
 
 	}
 
 	setMaterials( materials ) {
 
 		// Clears parse scopes stack, adds new scope with material library
-
-		this.parseScopesStack = [];
-
-		this.newParseScopeLevel( materials );
-
-		this.getCurrentParseScope().isFromParse = false;
+		this.rootParseScope = this.newParseScopeLevel( materials );
+		this.rootParseScope.isFromParse = false;
 
 		this.materials = materials;
 
@@ -810,7 +920,7 @@ class LDrawLoader extends Loader {
 
 	}
 
-	newParseScopeLevel( materials ) {
+	newParseScopeLevel( materials = null, parentScope = null ) {
 
 		// Adds a new scope level, assign materials to it and returns it
 
@@ -827,9 +937,9 @@ class LDrawLoader extends Loader {
 
 		}
 
-		const topParseScope = this.getCurrentParseScope();
 		const newParseScope = {
 
+			parentScope: parentScope,
 			lib: matLib,
 			url: null,
 
@@ -843,8 +953,8 @@ class LDrawLoader extends Loader {
 
 			// Current subobject
 			currentFileName: null,
-			mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
-			mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
+			mainColourCode: parentScope ? parentScope.mainColourCode : '16',
+			mainEdgeColourCode: parentScope ? parentScope.mainEdgeColourCode : '24',
 			currentMatrix: new Matrix4(),
 			matrix: new Matrix4(),
 
@@ -860,25 +970,15 @@ class LDrawLoader extends Loader {
 			startingConstructionStep: false
 		};
 
-		this.parseScopesStack.push( newParseScope );
-
 		return newParseScope;
 
 	}
 
-	removeScopeLevel() {
-
-		this.parseScopesStack.pop();
-
-		return this;
-
-	}
-
-	addMaterial( material ) {
+	addMaterial( material, parseScope ) {
 
 		// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
 
-		const matLib = this.getCurrentParseScope().lib;
+		const matLib = parseScope.lib;
 
 		if ( ! matLib[ material.userData.code ] ) {
 
@@ -892,7 +992,7 @@ class LDrawLoader extends Loader {
 
 	}
 
-	getMaterial( colourCode ) {
+	getMaterial( colourCode, parseScope = this.rootParseScope ) {
 
 		// Given a colour code search its material in the parse scopes stack
 
@@ -906,50 +1006,30 @@ class LDrawLoader extends Loader {
 
 		}
 
-		for ( let i = this.parseScopesStack.length - 1; i >= 0; i -- ) {
+		while ( parseScope ) {
 
-			const material = this.parseScopesStack[ i ].lib[ colourCode ];
+			const material = parseScope.lib[ colourCode ];
 
 			if ( material ) {
 
 				return material;
 
-			}
-
-		}
-
-		// Material was not found
-		return null;
-
-	}
-
-	getParentParseScope() {
-
-		if ( this.parseScopesStack.length > 1 ) {
-
-			return this.parseScopesStack[ this.parseScopesStack.length - 2 ];
-
-		}
-
-		return null;
-
-	}
-
-	getCurrentParseScope() {
+			} else {
 
-		if ( this.parseScopesStack.length > 0 ) {
+				parseScope = parseScope.parentScope;
 
-			return this.parseScopesStack[ this.parseScopesStack.length - 1 ];
+			}
 
 		}
 
+		// Material was not found
 		return null;
 
 	}
 
 	parseColourMetaDirective( lineParser ) {
 
-		// Parses a colour definition and returns a THREE.Material or null if error
+		// Parses a colour definition and returns a THREE.Material
 
 		let code = null;
 
@@ -1200,16 +1280,16 @@ class LDrawLoader extends Loader {
 
 	//
 
-	objectParse( text ) {
+	objectParse( text, parseScope ) {
 
 		// Retrieve data from the parent parse scope
-		const parentParseScope = this.getParentParseScope();
+		const currentParseScope = parseScope;
+		const parentParseScope = currentParseScope.parentScope;
 
 		// Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
-		const mainColourCode = parentParseScope.mainColourCode;
-		const mainEdgeColourCode = parentParseScope.mainEdgeColourCode;
+		const mainColourCode = currentParseScope.mainColourCode;
+		const mainEdgeColourCode = currentParseScope.mainEdgeColourCode;
 
-		const currentParseScope = this.getCurrentParseScope();
 
 		// Parse result variables
 		let faces;
@@ -1262,7 +1342,7 @@ class LDrawLoader extends Loader {
 
 			}
 
-			const material = scope.getMaterial( colourCode );
+			const material = scope.getMaterial( colourCode, currentParseScope );
 
 			if ( ! material ) {
 
@@ -1300,7 +1380,7 @@ class LDrawLoader extends Loader {
 				if ( line.startsWith( '0 FILE ' ) ) {
 
 					// Save previous embedded file in the cache
-					this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
+					this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
 
 					// New embedded text file
 					currentEmbeddedFileName = line.substring( 7 );
@@ -1390,7 +1470,7 @@ class LDrawLoader extends Loader {
 								material = this.parseColourMetaDirective( lp );
 								if ( material ) {
 
-									this.addMaterial( material );
+									this.addMaterial( material, parseScope );
 
 								}	else {
 
@@ -1539,7 +1619,7 @@ class LDrawLoader extends Loader {
 						// Found the subobject path in the preloaded file path map
 						fileName = scope.fileMap[ fileName ];
 
-					}	else {
+					} else {
 
 						// Standardized subfolders
 						if ( fileName.startsWith( 's/' ) ) {
@@ -1558,10 +1638,6 @@ class LDrawLoader extends Loader {
 						material: material,
 						matrix: matrix,
 						fileName: fileName,
-						originalFileName: fileName,
-						locationState: FILE_LOCATION_AS_IS,
-						url: null,
-						triedLowerCase: false,
 						inverted: bfcInverted !== currentParseScope.inverted,
 						startingConstructionStep: startingConstructionStep
 					} );
@@ -1730,7 +1806,7 @@ class LDrawLoader extends Loader {
 
 		if ( parsingEmbeddedFiles ) {
 
-			this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
+			this.cache.setData( currentEmbeddedFileName.toLowerCase(), currentEmbeddedText );
 
 		}
 
@@ -1768,331 +1844,187 @@ class LDrawLoader extends Loader {
 
 	}
 
-	processObject( text, onProcessed, subobject, url ) {
-
-		const scope = this;
-
-		const parseScope = scope.newParseScopeLevel();
-		parseScope.url = url;
-
-		const parentParseScope = scope.getParentParseScope();
-
-		// Set current matrix
-		if ( subobject ) {
-
-			parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
-			parseScope.matrix.copy( subobject.matrix );
-			parseScope.inverted = subobject.inverted;
-			parseScope.startingConstructionStep = subobject.startingConstructionStep;
-
-		}
+	finalizeObject( subobjectParseScope ) {
 
-		// Add to cache
-		let currentFileName = parentParseScope.currentFileName;
-		if ( currentFileName !== null ) {
-
-			currentFileName = parentParseScope.currentFileName.toLowerCase();
-
-		}
+		const parentParseScope = subobjectParseScope.parentScope;
 
-		if ( scope.subobjectCache[ currentFileName ] === undefined ) {
+		if ( this.smoothNormals && subobjectParseScope.type === 'Part' ) {
 
-			scope.subobjectCache[ currentFileName ] = text;
+			smoothNormals( subobjectParseScope.faces, subobjectParseScope.lineSegments );
 
 		}
 
+		const isRoot = ! parentParseScope.isFromParse;
+		if ( this.separateObjects && ! isPrimitiveType( subobjectParseScope.type ) || isRoot ) {
 
-		// Parse the object (returns a Group)
-		scope.objectParse( text );
-		let finishedCount = 0;
-		onSubobjectFinish();
-
-		function onSubobjectFinish() {
-
-			finishedCount ++;
-
-			if ( finishedCount === parseScope.subobjects.length + 1 ) {
-
-				finalizeObject();
-
-			} else {
+			const objGroup = subobjectParseScope.groupObject;
 
-				// Once the previous subobject has finished we can start processing the next one in the list.
-				// The subobject processing shares scope in processing so it's important that they be loaded serially
-				// to avoid race conditions.
-				// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
-				// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
-				// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
-				const subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
-				Promise.resolve().then( function () {
+			if ( subobjectParseScope.faces.length > 0 ) {
 
-					loadSubobject( subobject );
-
-				} );
-				parseScope.subobjectIndex ++;
+				objGroup.add( createObject( subobjectParseScope.faces, 3, false, subobjectParseScope.totalFaces ) );
 
 			}
 
-		}
-
-		function finalizeObject() {
-
-			if ( scope.smoothNormals && parseScope.type === 'Part' ) {
+			if ( subobjectParseScope.lineSegments.length > 0 ) {
 
-				smoothNormals( parseScope.faces, parseScope.lineSegments );
+				objGroup.add( createObject( subobjectParseScope.lineSegments, 2 ) );
 
 			}
 
-			const isRoot = ! parentParseScope.isFromParse;
-			if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
-
-				const objGroup = parseScope.groupObject;
-
-				if ( parseScope.faces.length > 0 ) {
-
-					objGroup.add( createObject( parseScope.faces, 3, false, parseScope.totalFaces ) );
-
-				}
-
-				if ( parseScope.lineSegments.length > 0 ) {
-
-					objGroup.add( createObject( parseScope.lineSegments, 2 ) );
+			if ( subobjectParseScope.conditionalSegments.length > 0 ) {
 
-				}
-
-				if ( parseScope.conditionalSegments.length > 0 ) {
+				objGroup.add( createObject( subobjectParseScope.conditionalSegments, 2, true ) );
 
-					objGroup.add( createObject( parseScope.conditionalSegments, 2, true ) );
-
-				}
-
-				if ( parentParseScope.groupObject ) {
+			}
 
-					objGroup.name = parseScope.fileName;
-					objGroup.userData.category = parseScope.category;
-					objGroup.userData.keywords = parseScope.keywords;
-					parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+			if ( parentParseScope.groupObject ) {
 
-					parentParseScope.groupObject.add( objGroup );
+				objGroup.name = subobjectParseScope.fileName;
+				objGroup.userData.category = subobjectParseScope.category;
+				objGroup.userData.keywords = subobjectParseScope.keywords;
+				subobjectParseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
 
-				}
+				parentParseScope.groupObject.add( objGroup );
 
-			} else {
-
-				const separateObjects = scope.separateObjects;
-				const parentLineSegments = parentParseScope.lineSegments;
-				const parentConditionalSegments = parentParseScope.conditionalSegments;
-				const parentFaces = parentParseScope.faces;
+			}
 
-				const lineSegments = parseScope.lineSegments;
-				const conditionalSegments = parseScope.conditionalSegments;
-				const faces = parseScope.faces;
+		} else {
 
-				for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
+			const separateObjects = this.separateObjects;
+			const parentLineSegments = parentParseScope.lineSegments;
+			const parentConditionalSegments = parentParseScope.conditionalSegments;
+			const parentFaces = parentParseScope.faces;
 
-					const ls = lineSegments[ i ];
+			const lineSegments = subobjectParseScope.lineSegments;
+			const conditionalSegments = subobjectParseScope.conditionalSegments;
+			const faces = subobjectParseScope.faces;
 
-					if ( separateObjects ) {
+			for ( let i = 0, l = lineSegments.length; i < l; i ++ ) {
 
-						const vertices = ls.vertices;
-						vertices[ 0 ].applyMatrix4( parseScope.matrix );
-						vertices[ 1 ].applyMatrix4( parseScope.matrix );
+				const ls = lineSegments[ i ];
 
-					}
+				if ( separateObjects ) {
 
-					parentLineSegments.push( ls );
+					const vertices = ls.vertices;
+					vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+					vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
 
 				}
 
-				for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+				parentLineSegments.push( ls );
 
-					const os = conditionalSegments[ i ];
+			}
 
-					if ( separateObjects ) {
+			for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) {
 
-						const vertices = os.vertices;
-						const controlPoints = os.controlPoints;
-						vertices[ 0 ].applyMatrix4( parseScope.matrix );
-						vertices[ 1 ].applyMatrix4( parseScope.matrix );
-						controlPoints[ 0 ].applyMatrix4( parseScope.matrix );
-						controlPoints[ 1 ].applyMatrix4( parseScope.matrix );
+				const os = conditionalSegments[ i ];
 
-					}
+				if ( separateObjects ) {
 
-					parentConditionalSegments.push( os );
+					const vertices = os.vertices;
+					const controlPoints = os.controlPoints;
+					vertices[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+					vertices[ 1 ].applyMatrix4( subobjectParseScope.matrix );
+					controlPoints[ 0 ].applyMatrix4( subobjectParseScope.matrix );
+					controlPoints[ 1 ].applyMatrix4( subobjectParseScope.matrix );
 
 				}
 
-				for ( let i = 0, l = faces.length; i < l; i ++ ) {
+				parentConditionalSegments.push( os );
 
-					const tri = faces[ i ];
+			}
 
-					if ( separateObjects ) {
+			for ( let i = 0, l = faces.length; i < l; i ++ ) {
 
-						const vertices = tri.vertices;
-						for ( let i = 0, l = vertices.length; i < l; i ++ ) {
+				const tri = faces[ i ];
 
-							vertices[ i ] = vertices[ i ].clone().applyMatrix4( parseScope.matrix );
+				if ( separateObjects ) {
 
-						}
+					const vertices = tri.vertices;
+					for ( let i = 0, l = vertices.length; i < l; i ++ ) {
 
-						_tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
-						_tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
-						tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
+						vertices[ i ] = vertices[ i ].clone().applyMatrix4( subobjectParseScope.matrix );
 
 					}
 
-					parentFaces.push( tri );
+					_tempVec0.subVectors( vertices[ 1 ], vertices[ 0 ] );
+					_tempVec1.subVectors( vertices[ 2 ], vertices[ 1 ] );
+					tri.faceNormal.crossVectors( _tempVec0, _tempVec1 ).normalize();
 
 				}
 
-				parentParseScope.totalFaces += parseScope.totalFaces;
+				parentFaces.push( tri );
 
 			}
 
-			scope.removeScopeLevel();
+			parentParseScope.totalFaces += subobjectParseScope.totalFaces;
 
-			// If it is root object, compute construction steps
-			if ( ! parentParseScope.isFromParse ) {
-
-				scope.computeConstructionSteps( parseScope.groupObject );
+		}
 
-			}
+	}
 
-			if ( onProcessed ) {
+	async processObject( text, subobject, url, parentScope ) {
 
-				onProcessed( parseScope.groupObject );
+		const scope = this;
 
-			}
+		const parseScope = scope.newParseScopeLevel( null, parentScope );
+		parseScope.url = url;
 
-		}
+		const parentParseScope = parseScope.parentScope;
 
-		function loadSubobject( subobject ) {
+		// Set current matrix
+		if ( subobject ) {
 
+			parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
+			parseScope.matrix.copy( subobject.matrix );
+			parseScope.inverted = subobject.inverted;
+			parseScope.startingConstructionStep = subobject.startingConstructionStep;
 			parseScope.mainColourCode = subobject.material.userData.code;
 			parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
-			parseScope.currentFileName = subobject.originalFileName;
-
-
-			// If subobject was cached previously, use the cached one
-			const cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
-			if ( cached ) {
-
-				scope.processObject( cached, function ( subobjectGroup ) {
-
-					onSubobjectLoaded( subobjectGroup, subobject );
-					onSubobjectFinish();
-
-				}, subobject, url );
-
-				return;
-
-			}
-
-			// Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
-			// Update also subobject.locationState for the next try if this load fails.
-			let subobjectURL = subobject.fileName;
-			let newLocationState = FILE_LOCATION_NOT_FOUND;
-
-			switch ( subobject.locationState ) {
-
-				case FILE_LOCATION_AS_IS:
-					newLocationState = subobject.locationState + 1;
-					break;
-
-				case FILE_LOCATION_TRY_PARTS:
-					subobjectURL = 'parts/' + subobjectURL;
-					newLocationState = subobject.locationState + 1;
-					break;
-
-				case FILE_LOCATION_TRY_P:
-					subobjectURL = 'p/' + subobjectURL;
-					newLocationState = subobject.locationState + 1;
-					break;
-
-				case FILE_LOCATION_TRY_MODELS:
-					subobjectURL = 'models/' + subobjectURL;
-					newLocationState = subobject.locationState + 1;
-					break;
 
-				case FILE_LOCATION_TRY_RELATIVE:
-					subobjectURL = url.substring( 0, url.lastIndexOf( '/' ) + 1 ) + subobjectURL;
-					newLocationState = subobject.locationState + 1;
-					break;
-
-				case FILE_LOCATION_TRY_ABSOLUTE:
-
-					if ( subobject.triedLowerCase ) {
-
-						// Try absolute path
-						newLocationState = FILE_LOCATION_NOT_FOUND;
-
-					} else {
-
-						// Next attempt is lower case
-						subobject.fileName = subobject.fileName.toLowerCase();
-						subobjectURL = subobject.fileName;
-						subobject.triedLowerCase = true;
-						newLocationState = FILE_LOCATION_AS_IS;
-
-					}
-
-					break;
-
-				case FILE_LOCATION_NOT_FOUND:
-
-					// All location possibilities have been tried, give up loading this object
-					console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
-
-					return;
+		}
 
-			}
+		// Parse the object
+		this.objectParse( text, parseScope );
 
-			subobject.locationState = newLocationState;
-			subobject.url = subobjectURL;
+		const subobjects = parseScope.subobjects;
+		const promises = [];
+		for ( let i = 0, l = subobjects.length; i < l; i ++ ) {
 
-			// Load the subobject
-			// Use another file loader here so we can keep track of the subobject information
-			// and use it when processing the next model.
-			const fileLoader = new FileLoader( scope.manager );
-			fileLoader.setPath( scope.partsLibraryPath );
-			fileLoader.setRequestHeader( scope.requestHeader );
-			fileLoader.setWithCredentials( scope.withCredentials );
-			fileLoader.load( subobjectURL, function ( text ) {
+			promises.push( loadSubobject( parseScope.subobjects[ i ] ) );
 
-				scope.processObject( text, function ( subobjectGroup ) {
+		}
 
-					onSubobjectLoaded( subobjectGroup, subobject );
-					onSubobjectFinish();
+		// Kick off of the downloads in parallel but process all the subobjects
+		// in order so all the assembly instructions are correct
+		const subobjectScopes = await Promise.all( promises );
+		for ( let i = 0, l = subobjectScopes.length; i < l; i ++ ) {
 
-				}, subobject, url );
+			this.finalizeObject( subobjectScopes[ i ] );
 
-			}, undefined, function ( err ) {
+		}
 
-				onSubobjectError( err, subobject );
+		// If it is root object then finalize this object and compute construction steps
+		if ( ! parentParseScope.isFromParse ) {
 
-			}, subobject );
+			this.finalizeObject( parseScope );
+			this.computeConstructionSteps( parseScope.groupObject );
 
 		}
 
-		function onSubobjectLoaded( subobjectGroup, subobject ) {
+		return parseScope;
 
-			if ( subobjectGroup === null ) {
-
-				// Try to reload
-				loadSubobject( subobject );
-				return;
+		function loadSubobject( subobject ) {
 
-			}
+			return scope.cache.loadData( subobject.fileName ).then( function ( text ) {
 
-			scope.fileMap[ subobject.originalFileName ] = subobject.url;
+				return scope.processObject( text, subobject, url, parseScope );
 
-		}
+			} ).catch( function () {
 
-		function onSubobjectError( err, subobject ) {
+				console.warn( 'LDrawLoader: Subobject "' + subobject.fileName + '" could not be found.' );
 
-			// Retry download from a different default possible location
-			loadSubobject( subobject );
+			} );
 
 		}