Loading textures with TextureLoader.load
is great for as long as someone stays strictly within ThreeJS env (since needsUpdate
does all the work.)
When trying to go "outside" of three, for example "change this icon, when texture is loaded" it becomes a bit .. dated.
Loader allows only for onLoad
callbacks, which are ok only for as long as you care about a single texture.
When you want to "change this icon, when these 3 textures are loaded", there are two options:
onLoad
callbacks (bad)load
in a Promise manually, and .all(...)
them (better)Would be very nice to have this Promise functionality out of the box (this also makes async / await, which improves readability significantly.)
I can see 1 or 2 nice improvements that could help there:
.promise(url)
method to TextureLoader
, that instead of Texture returns a Promise (not ideal, but already better, if you care more about loading.)const textureLoader = new Three.TextureLoader();
Promise.all([
textureLoader.promise(url1), // new Promise instead of new Texture
textureLoader.promise(url2),
textureLoader.promise(url3)
])
.then((textures) => {
// ...
});
Obvious con here is that .promise()
can't nicely return both texture and a promise... unless!
PromisedTexture
wrapper extending Texture
, that would add Promise proto functionality (similarly as CanvasTexture does with canvas.) Promised texture would resolve/reject after initially loading, allowing for handling the async process manually outside of ThreeJS.const textureLoader = new Three.TextureLoader();
const texture1 = textureLoader.promise(url1); // new PromiseTexture
const texture2 = textureLoader.promise(url2);
const texture3 = textureLoader.promise(url3);
Promise.all([ texture1, texture2, texture3 ])
.then((textures) => {
// ...
});
Latest
N/A
N/A
N/A
I could volunteer a PR on this, since I already had to write parts of it for a project, but would like to hear others' opinions, maybe a discussion on the best way of handling it. 馃悾
If we did this we would have to change all the loaders in the lib.
I think it may be easier to make an abstraction in your project.
Could also be done through the Loader
root class?
Loader.prototype.loadPromise = function ( url, onProgress ) {
return new Promise( ( resolve, reject ) => {
this.load( url, resolve, onProgress, reject );
} );
};
Although that, too, could be done in application code. :)
@mjurczyk Yeah you can just wrap the loader in a promise like Don said.
Here is how I do it, if that can help you. I also handle waiting for all of the promises to finish.
https://github.com/marcofugaro/threejs-modern-app/blob/master/src/lib/loadTexture.js
Yes, that's pretty much what I did with my project as well! The main point of my suggestion is to bring ThreeJS's async handling to a modern standard out-of-the-box (like in most things, "you can do it yourself in your project", but it's incredibly handy to have the basics ready made. Instead of spending time learning how Loaders work inside, someone can assume Three works like other APIs and get loading handling done a little quicker.)
@mrdoob if we'd add a promise-casting method for Object3D
and Texture
it should handle most cases I think (Scenes, Meshes and Textures are the only loadable assets?) Moreover we could skip touching loaders at all, by simply adding .promise()
method to these two classes. Then it may work as simply as:
const textureLoader = new TextureLoader();
const texture = textureLoader.load(url, onLoad); // new Texture
const textureAsPromise = textureLoader.load(url).promise(); // new PromisedTexture
textureAsPromise.then(texture => { ... });
(That's pretty much how AWS handles their callback->async, see here: https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/using-promises.html)
If we did this we would have to change all the loaders in the lib.
This is a discussion we need to have at some point. Probably not until we have finished with the rest of the ES6 conversions though.
I think it may be easier to make an abstraction in your project.
Agreed. I use this function to convert loaders (nearly the same as @donmccurdy function above but app level):
const loader = new TextureLoader();
// this utility function allows you to use any three.js
// loader with promises and async/await
function promiseLoader(url) {
return new Promise((resolve, reject) => {
loader.load(url, data=> resolve(data), null, reject);
});
}
Then use it like this:
async function main() {
const texture = await promiseLoader(URL),
}
main().catch(error => {
console.error(error);
});
Works for all three.js loaders (I think).
Could also be done through the Loader root class?
Loader.prototype.loadPromise = function ( url, onProgress ) {
return new Promise( ( resolve, reject ) => {
this.load( url, resolve, onProgress, reject );
} );
};
Thinking about this a bit more - I like this solution. Could we add this method to the library? That would let us support using promises with all loaders in just a couple of lines of code. @mrdoob what do you think?
How about Loader.prototype.loadAsync
?
@looeee one thing to mention in docs is that it drops the original behaviour of load
- so loadAsync
no longer returns a resource and shouldn't be assigned to variables directly.
const tex = (new TextureLoader()).loadAsync(url);
new MeshStandardMaterial({ map: tex }); // <- will throw badly in this case probably
@looeee one thing to mention in docs is that it drops the original behaviour of load
Sure, I'll note that in the TextureLoader
doc. Should be fairly obvious in any case.
Are there any other loaders that work like the TextureLoader
?
The give you a node and then fill it with data? CubeTextureLoader perhaps? HDR mayhaps. But geometries are not treated like this? There鈥檚 no sync empty geometry that returns before the attributes are filled asynchronously.
Most helpful comment
How about
Loader.prototype.loadAsync
?