How to Improve SCS Streaming Performance when Loading Several Models

We are trying to reduce the model loading time when loading several models using the loadSubtreeFromModel() function to fetch models from the server.

We have tried the following two approaches :

 let node = hwv.model.createNode();
 for(let i =0;i<models.length(); ++i){
     await hwv.model.loadSubtreeFromModel(node,models[i]);
 }

and

let node = hwv.model.createNode();
let pArray = [];
 for(let i =0;i<models.length(); ++i){
     pArray.push(hwv.model.loadSubtreeFromModel(node,models[i]));
 }
return Promise.all(pArray)

To load around 40 Models it takes around 4-5 minutes in the first case. In second case the promises are resolved within 1 - 1.5 minutes but if we measure the time between upto hwv.waitForIdle() when streaming is stopped, it’s almost the same as the first case.

We tried turning off the bounding previews but that did not have any significant impact on the loading times.
Further if we inspect the network activity for the websocket we can see multiple ATTACH_MODEL and COMPUTE_INSTANCE_BOUNDING commands being sent to the sever within small timeframe. Finally the streaming stops when IDLE_MARKER command is sent

Is there any way we can reduce the loading times with the streaming server ?

A similar topic was raised related to scs files Is it possible to load models into webviewer asynchronously?

One approach that might help would be to load the models via an XML file, essentially using the shattered loading approach. You can find some info on this workflow here.

There are two variants of that

  1. Generate a master model server-side. That should have the best performance but requires you to generate this “master model” first on the server (via converter)
  2. Generate the shattered XML client side and then use the loadSubtreeFromXML function for the loading. The below post has more information on how to generate this XML data (it is written for scs loading but also applies to the SC streaming case):
    Model Aggregation via XML

I hope this helps

1 Like

To follow up, here is the slightly modified function from the mentioned post that works with SC models and allows you to specify an array of files for loading:

let xml = generateShatteredXMLFromArray([“microengine”, “arboleda”,“MountainHome”]);
hwv.model.loadSubtreeFromXmlBuffer(hwv.model.getRootNode(),xml);

 function generateShatteredXMLFromArray(names, matrices) {

    let polist = [];
    let instancehash = [];

    let currentid = 2;
    let pochildrentext = "";

    for (let i = 0; i < names.length; i++) {
      let nodename = names[i];

      let instance = instancehash[nodename];
      pochildrentext += currentid + " ";
      if (instance == undefined) {
        let instanceRefId = currentid + 1;
        polist.push({  name:nodename, matrix: matrices ? matrices[i] : null, id: currentid, instanceref: instanceRefId });
        polist.push({  name:nodename,matrix: matrices ? matrices[i] : null, id: instanceRefId });
        instancehash[nodename] = { id: instanceRefId, count: 0 };
        currentid++;
      }
      else {
        polist.push({ name:nodename,matrix: matrices ? matrices[i] : null, id: currentid, instanceref: instance.id });
      }
      currentid++;
    }

    let xml = "";
    xml += '<!--HC 22.0.0-->\n';
    xml += '<Root>\n';
    xml += '<ModelFile>\n';
    xml += '<ProductOccurence Id="0" Name="' + name + '" Behaviour="1" Children="1" IsPart="false" Unit="1000.000000"/>\n';
    xml += '<ProductOccurence Id="1" Name="' + name + ':Master" ExchangeId="" LayerId="65535" Style="65535" Behaviour="1" FilePath="" Children="' + pochildrentext + '" IsPart="false"/>\n';

    for (let i = 0; i < polist.length; i++) {
      let nodename = polist[i].name;
      if (polist[i].instanceref != undefined) {
        let instance = instancehash[nodename];
        xml += '<ProductOccurence Id="' + polist[i].id + '" Name="' + nodename + ":" + (instance.count++) + '" ExchangeId="" LayerId="65535" Style="65535" Behaviour="1" FilePath="" InstanceRef="' + instance.id + '" IsPart="false">\n';

        let matrix = polist[i].matrix;
        if (!matrix) {
          matrix = new Communicator.Matrix();
        }

        xml += '<Transformation RelativeTransfo="' + matrix.m[0] + " " + matrix.m[1] + " " + matrix.m[2] + " " + matrix.m[3] + " " + matrix.m[4] + " " + matrix.m[5] + " " + matrix.m[6] + " " + matrix.m[7] + " " +
          matrix.m[8] + " " + matrix.m[9] + " " + matrix.m[10] + " " + matrix.m[11] + " " + matrix.m[12] + " " + matrix.m[13] + " " + matrix.m[14] + " " + matrix.m[15] + '"/>\n';
        xml += '</ProductOccurence>' + "\n";
      }
      else {
        xml += '<ProductOccurence Id="' + polist[i].id + '" Name="" ExchangeId="" Behaviour="1" IsPart="false">' + "\n";
        xml += '<ExternalModel Name="' + nodename + '" Unit="1000.000000">' + "\n";
        xml += '</ExternalModel>\n';
        xml += '</ProductOccurence>\n';
      }
    }
    xml += '</ModelFile>\n';
    xml += '</Root>\n';
    return xml;
  }

Thanks guido for the fast reply.

1 Like