Need help using AssetManager with NullEngine/Node

Hi. Sorry, if this has been covered elsewhere. I’m trying to use the AssetManager in a node app (with NullEngine).

My test code is the example code from the NullEngine documentation plus a few lines to import a file with the asset manager.

Runs successfully in node without those lines.
Calling aman.load() causes this error:
E:\dev\bab\sand-box\node_modules\xhr2\lib\xhr2.js:281
throw new NetworkError(Unsupported protocol ${this._url.protocol});
^

Error
at XMLHttpRequest.send (E:\dev\bab\sand-box\node_modules\xhr2\lib\xhr2.js:281:19)
at e.send (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:602616)
at s (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:58518)
at p (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:58526)
at Function.e.LoadFile (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:58754)
at t._loadFile (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:319633)
at t.runTask (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:2149344)
at t.e.run (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:2148097)
at e._runTask (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:2153778)
at e.load (E:\dev\bab\sand-box\node_modules\babylonjs\babylon.js:16:2154548)

Here’s the test code:

//

// Copy-pasted from the NullEngine documentation
// except for the last three lines.
// The line (aman.load()) causes an ‘Unsupported protocol’ error
// when this runs in node.
//
var BABYLON = require(“babylonjs”);
var LOADERS = require(“babylonjs-loaders”);
global.XMLHttpRequest = require(‘xhr2’).XMLHttpRequest;

var engine = new BABYLON.NullEngine();
var scene = new BABYLON.Scene(engine);

var light = new BABYLON.PointLight(“Omni”, new BABYLON.Vector3(20, 20, 100), scene);

var camera = new BABYLON.ArcRotateCamera(“Camera”, 0, 0.8, 100, BABYLON.Vector3.Zero(), scene);

BABYLON.SceneLoader.ImportMesh("", “https://playground.babylonjs.com/scenes/”, “skull.babylon”, scene, function (newMeshes) {
camera.target = newMeshes[0];

console.log("Meshes loaded from babylon file: " + newMeshes.length);
for (var index = 0; index < newMeshes.length; index++) {
    console.log(newMeshes[index].toString());
}

BABYLON.SceneLoader.ImportMesh("", "https://www.babylonjs.com/Assets/DamagedHelmet/glTF/", "DamagedHelmet.gltf", scene, function (meshes) {
    console.log("Meshes loaded from gltf file: " + meshes.length);
    for (var index = 0; index < meshes.length; index++) {
        console.log(meshes[index].toString());
    }
});

console.log("render started")
engine.runRenderLoop(function() {
    scene.render();
})

});

// try asset manager
var aman = new BABYLON.AssetsManager(scene);
var specTask = aman.addTextFileTask(‘load-task’, ‘./models/LoaderSpec/LoaderSpec.json’);
aman.load();

What am I doing wrong?
Thanks for any advice.

OK. After looking more closely at the error, it seems like this is not an AssetManager problem or a Babylon problem. It’s a problem with my xhr2 library: it doesn’t support the ‘file’ protocol. (Indeed the README mentions this specifically.)

I need to find another xhr2 library or setup some work-arounds to use when running in node.

1 Like

So which XHR library did you use? Im having the same issue

Unfortunately, I just went with a work around. A wrapper class around AssetManager that re-formats file paths to http. And my node entry point script starts up a local express http server to serve itself the local files.

Here’s the wrapper class:

    import { AssetsManager, MeshAssetTask, Scene, TextFileAssetTask, BinaryFileAssetTask, Nullable, TextureAssetTask, AbstractAssetTask } from "babylonjs";

//
// Wrapper around an AssetManager
// that reformats file system urls
// to http when in 'nodeMode'. 
// Running in node, the 'xhr2' library doesn't handle
// file protocol.
// Just passes on the url if not in nodeMode.
//
export class MNodeWorkAroundAM
{
    private am : AssetsManager;
    private nodeMode : boolean = false;
    private serverUrl : string | undefined;

    private static get  AssetsRootPath() : string { return "assets"; }

    constructor(
        scene : Scene,
        nodeMode ? : boolean,
        serverUrl ? : string
    ) {
        this.am = new AssetsManager(scene);
        if(nodeMode) { 
            this.nodeMode = nodeMode; 
            this.serverUrl = serverUrl ? serverUrl : "http://localhost:8000";
        }
    }

    set onProgress(onProg : (remaining : number, total : number, task : AbstractAssetTask) => void) {
        this.am.onProgress = onProg;
    }

    set onFinish(onFin : (tasks : AbstractAssetTask[]) => void) {
        this.am.onFinish = onFin;
    }

    addTextFileTask(taskName : string, url : string) : TextFileAssetTask
    {
        return this.am.addTextFileTask(taskName, this.FormatURL(url));
    }

    addBinaryFileTask(taskName : string, url : string) : BinaryFileAssetTask
    {
        return this.am.addBinaryFileTask(taskName, this.FormatURL(url));
    }

    addMeshTask(taskName : string, meshNames : Nullable<string[]>, rootUrl : string, sceneFilename : string) : MeshAssetTask
    {
        return this.am.addMeshTask(taskName, meshNames, this.FormatURL(rootUrl), sceneFilename);
    }

    addTextureTask(taskName : string, url : string, noMinimap ? : boolean, invertY ? : boolean, samplingMode ? : number) : TextureAssetTask
    {
        return this.am.addTextureTask(taskName, this.FormatURL(url), noMinimap, invertY, samplingMode);
    }

    load() : AssetsManager
    {
        return this.am.load();
    }

    static AddAssetRootToPath(path : string) : string {
        return `${MNodeWorkAroundAM.AssetsRootPath}/${path}`;
    }

    private FormatURL(url : string) : string
    {
        // if 'node-mode' add server url to path type urls
        let fullPath = MNodeWorkAroundAM.AddAssetRootToPath(url);
        if(this.nodeMode) {
            if(url.indexOf('http') !== 0) {
                return `${this.serverUrl}/${fullPath}`;
            }
        }
        return fullPath;
    }
}