Tutorial: Connecting

Connecting

Connecting in Node.js requires an extra step since there is no built in WebSocket implementation.

Dependencies

As we need an external WebSocket we have to install it via npm. We also of course need the client API itself and yargs for command line processing.

$ npm install --save @migenius/realityserver-client websocket yargs

Import modules and initialize

Now we can start the script itself which we'll call index.js. As usual at the top we want to pull in our required modules.

const
  path = require('path'),
  fs = require('fs'),
  { Command, Command_error, Error: Rs_error, Utils, Service } = require('@migenius/realityserver-client'),
  WebSocket = require('websocket').w3cwebsocket;

const service = new Service();

After the built in modules we require the RealityServer® client itself. The individual components required are destructured out for convenience. Note this requires renaming the Error class else it will clash with the builtin Error class, the same would need to be done if extracting the RS.Math API.

We then pull in the websocket module and get the W3C compliant interface. This will be used later to directly create a WebSocket object since window.WebSocket is not available in Node.js.

Yargs

We use the most excellent yargs module to process the command line arguments of this script.

require('yargs')
  .demandCommand(7)
  .usage('$0 [--ssl] <host> <port> <scene_file> <width> <height> <max_samples> <filename>',
    'renders an image in RealityServer',
    yargs=>{
      yargs
        .positional('host', {
          describe: 'hostname to connect to',
          type: 'string'
        })
        .positional('port', {
          describe: 'port to connect to',
          type: 'number'
        })
        .positional('scene_file', {
          describe: 'scene filename to render',
          type: 'string'
        })
        .positional('width', {
          describe: 'image width to render',
          type: 'number'
        })
        .positional('height', {
          describe: 'image height to render',
          type: 'number'
        })
        .positional('max_samples', {
          describe: '# of samples to render',
          type: 'number'
        })
        .positional('filename', {
          describe: 'output filename, extension defines the file format',
          type: 'string'
        });
    }, load_and_render)
  .boolean('ssl')
  .default('ssl', false)
  .describe('ssl', 'if true connect using wss, otherwise ws')
  .help('h')
  .alias('h', 'help')
  .argv;

Describing the above in detail is beyond the scope of this tutorial. Suffice to say it processes the command line shown in the usage line and then calls the load_and_render function.

Connecting

We implement load_and_render initially as below to simply connect to the given RealityServer®

async function load_and_render(argv) {
  const { host, port, ssl, scene_file, width, height, max_samples, filename } = argv;

  const url = `${(ssl ? 'wss://' : 'ws://')}${host}:${port}/service/`;

  console.log(`connecting to: ${url}`);

  try {
    // The default configuration for the WebSocket module has settings unsuitable for use
    // with RealityServer. The RealityServer WebSocket server does not handle fragmented
    // messages well so we need to disable outgoing fragmentation. Additionally, it limits
    // incoming messages to 1MiB by default which can easily be exceeded when rendering
    // large images. Here we up the limit to 10MiB which should cover most situations.
    await service.connect(new WebSocket(url, undefined, undefined, undefined, undefined,
      {
        fragmentOutgoingMessages: false,
        maxReceivedFrameSize: 10485760,
        maxReceivedMessageSize: 10485760
      }
    ));
  } catch (err) {
    console.error(`Web Socket connection failed: ${err.toString()}`);
    return;
  }

  console.log('connected.');

  service.close();
}

First, we destructure out the arguments provided by yargs. From those arguments we generate the URL to connect to. We then connect to the service however instead of supplying the URL we pass an instance of the W3C compatible WebSocket interface. The service will use this object directly for communications rather than creating it's own instance.

Note that you must not process any messages on the WebSocket before passing it to the service otherwise initialization will fail.

From this point on the API can be used in exactly the same way as in the browser. The only exception is the RS.Utils.html_image_display function which requires an Image DOM object to display. However even that can be used if you can provide a custom Image and Object URL implementation.

One thing to note is that in Node.js applications we need to ensure that the service gets closed when we're done. Otherwise the open WebSocket connection will prevent the application from exiting on completion.

WebSocket best practices.

You'll note in the code above that we are providing extra arguments to the WebSocket constructor. This is because the NPM websocket module being used imposes some limitations on message size, see the WebSocket Client documentation. The default values cause any incoming messages larger than 1MiB to be discarded. In most applications this wouldn't be a problem however rendered images can easily become larger than this, especially when using lossless compression formats (EG: a 512x512 Rgba uncompressed image already hits this limit). So we need to increase both the maximum message and maximum fragment size to compensate. Here we are using 10MiB however you should fine tune the value to suit your application.

Additionally, the RealityServer® WebSocket server does not support fragmented messages so we need to disable outgoing message fragmentation otherwise larger command queues may silently disappear from the stream.

Overriding default WebSocket constructor

An alternative to passing in a WebSocket instance to RS.Service#connect is to override the WebSocket constructor used by the service. You can then simply use the service URL when connecting in the same was as in the browser. To do this simply set the RS.Service#websocket static property to the W3C compatible constructor:

const { Command,Command_error,Error: Rs_error,Utils,Service } = require('@migenius/realityserver-client');
Service.websocket = require('websocket').w3cwebsocket;

const service = new Service();

service.connect(url,[ undefined, undefined, undefined, undefined,
  {
    fragmentOutgoingMessages: false,
    maxReceivedFrameSize: 10485760,
    maxReceivedMessageSize: 10485760
  }
]);

This pattern could be more convenient if sharing code between Node.js and the browser or if you are making multiple service connections within an application. Note that in this case the additional arguments for the constructor need to be passed in an array as the second argument to the RS.Service#connect call.

Summary

In this tutorial we have learnt about the differences between initialization in the browser and Node.js and how to connect to RealityServer®.

Source

The complete source for this tutorial can be found below:

Source


<<< Node.js Scene Loading >>>