0

Please help me deal with this garbage I produced:

Program.prototype.init = function()
{
loadText('../res/shaders/blinnPhong-shader.vsh', function (vshErr, vshText) {
    if (vshErr) {
        alert('Fatal error loading vertex shader.');
        console.error(vshErr);
    } else {
        loadText('../res/shaders/blinnPhong-shader.fsh', function (fshErr, fshText) {
            if (fshErr) {
                alert('Fatal error loading fragment shader.');
                console.error(fshErr);
            } else {
                loadJSON('../res/models/dragon.json', function (modelErr, modelObj) {
                    if (modelErr) {
                        alert('Fatal error loading model.');
                        console.error(modelErr);
                    } else {
                        loadImage('../res/textures/susanTexture.png', function (imgErr, img) {
                            if (imgErr) {
                                alert('Fatal error loading texture.');
                                console(imgErr);
                            } else {
                                this.run = true;
                                RunProgram(vshText, fshText, img, modelObj);
                            }
                        });
                    }
                });
            }
        });
    }
});
};

My actual goal is to abstract the resource loading process for a WebGL program. That means in the future there will be arrays of meshes, textures, shaders and I want to be able to connect certain dependencies between resources. For example: I want to create two GameObjects One and Two. One uses shaders and is loaded from a mesh but has no texture, whereas Two uses the same shaders as One but uses its own mesh and also needs a texture. What principles could I use to achieve building these dependencies in JavaScript (with asynchronous loading and so on)?

Edit: So the following is happening with this code: I kept callbacks for now. However this method is part of a Singleton object. I edited the code because in the last else case I am setting a flag of program to true. I keep a global reference of the program object in my main. However due to the callbacks the reference is somehow lost, the global reference keeps its flag to false so the main loop is never reached. It is clearly a problem of the callbacks, since the flag is set when I call "this.run = true" outside the nested callbacks. Any advice on that?

SuperTasche
  • 479
  • 1
  • 7
  • 17
  • First advice is to not update your question and change their scope after you've received answers, questions are as much for you as they are for fellow developers finding them via search engine. Second is to [read this answer](https://stackoverflow.com/a/500459/978057), third is to accept one of the given answers ;) – LJᛃ Oct 26 '17 at 15:56
  • The TLDR is that `this` refers to the scope of the function you're currently in, so you either have to [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) all your callbacks to the original scope, or store a reference of `this` at the top of your Program.Init method and use it deeper down the stack. – LJᛃ Oct 26 '17 at 16:05

2 Answers2

2

You can use promises for this. With the bluebird module, you can convert loadText to a promise function with promise.promiseifyAll(the module loadText is from), or if that is your module, you can make it return a new Promise(function(resolve, reject){})

Using promises, you can make an array of all the promises you want to run and Promise.all([loadText('shader'), loadText("other shader"), ...])

More information on promises

pfg
  • 2,348
  • 1
  • 16
  • 37
2

Using modern APIs like Promises, Fetch and sugar like arrow functions, your code can become:

Program.prototype.init = function () {
    return Promise.all(
        fetch('../res/shaders/blinnPhong-shader.vsh').then(r=>r.text()),
        fetch('../res/shaders/blinnPhong-shader.fsh').then(r=>r.text()),
        fetch('../res/models/dragon.json').then(r=>r.json()),
        new Promise(function (resolve,reject) {
            var i = new Image();
            i.onload = () => resolve(i);
            i.onerror = () => reject('Error loading image '+i.src);
            i.src = '../res/textures/susanTexture.png';
        })
    )
    .then(RunProgram);
}

You could spice things up even further by using related ES2017 features like async functions/await or go all in on compatibility by forgoing arrow functions and using seamless polyfills for promises and fetch. For some simple request caching, wrap fetch:

const fetchCache = Object.create(null);
function fetchCached (url) {
    if (fetchCache[url])
        return Promise.resolve(fetchCache[url]);
    return fetch.apply(null,arguments).then(r=>fetchCache[url]=r);
}

Note that you want your resources to be unique so the above mentioned caching still needs another layer of actual GPU resource caching on top of it, you don't want to create multiple shader programs with the same shader code or array buffers with the same vertex data in them.

Your actual core question as to how you could manage dependencies is a bit too broad / application specific to be answered here on SO. In regards to managing the async nature in such an environment I see two options:

  1. Use placeholder resources and seamlessly replace them once the actual resources are loaded
  2. Wait until everything is loaded before you insert the GameObject into the rendering pipeline

Both approaches have their pros and cons, but usually I'd recommend the first option.

LJᛃ
  • 7,655
  • 2
  • 24
  • 35
  • This works, this use the latest modern API with "sugars", but the code remain even more unreadable than with usual callbacks. However, this is not your fault, this is the way Javascrit is desgned. –  Oct 25 '17 at 09:09
  • @AnHauntingGhostAsPromise How is this "even more unreadable" ? You're saving a lot of both vertical and horizontal space. Also its not all sugar, this code runs the requests in parallel while the original executes them in sequence. On top of that this code enables you to handle all errors in one place, while still allowing you to implement fallback and individual error handling by attaching rejection handlers to individual requests. – LJᛃ Oct 25 '17 at 15:39
  • The readability of a code is not measured by vertical and horizontal space the text takes on screen, but on how it is easy to review, then, to understand the underlying logic of each step, function call, etc: who does what, who call who, in which order, and so on. This has for example its utility in debug context. So yes, you can code something like: `this.method(obj.func(function(foo){return (foo>bar)?true:false;}))` but, this is unreadable. What you write, even if it is valid Javascript, is close to what is called "Spaghetti code", But Javascript structurally, pushs you in such direction. –  Oct 25 '17 at 16:29
  • And with promises, they push ce concept even further. Promises only allow you to compact the code, maybe ordering things sequentialy without extra custom object (promises are objects: cost to buid, memory allocation, etc), but they paradoxaly made things even more hard to follow due to strange syntax mixture: The code appear "imperative" but actually is interpreted "asynchronously". Except in Javascript i never seen so bad tools to handle asynchronous events... But maybe Javascript is too subtle for me. Anyway, now, I will disapear. –  Oct 25 '17 at 16:37
  • ( All this, is mainly due to the Javascript "clusure" concept, and how Javascript is "strangely" parsed and interpreted... In more strict languages, you have no choice, you have to be clear, explicit. The variable scoping is consistent, there is no such exceptions as "closures". You canno do some "black magic" like Javascript standardized it. ) –  Oct 25 '17 at 16:51