docs: function setupMainThread () {
  
      // Send a message to a worker, and optionally get an async response
      // Arguments:
      //   - worker: one or more web worker instances to send the message to (single value or array)
      //   - method: the method with this name, specified with dot-notation, will be invoked in the worker
      //   - message: spread of arguments to call the method with
      // Returns:
      //   - a promise that will be fulfilled if the worker method returns a value (could be immediately, or async)
      //
      WorkerBroker.postMessage = function (worker, method, ...message) {
          // If more than one worker specified, post to multiple
          if (Array.isArray(worker)) {
              return Promise.all(
                  worker.map(w => WorkerBroker.postMessage(w, method, ...message))
              );
          }
  
          // Parse options
          let options = {};
          if (typeof method === 'object') {
              options = method;
              method = method.method;
          }
  
          // Track state of this message
          var promise = new Promise((resolve, reject) => {
              messages[message_id] = { method, message, resolve, reject };
          });
  
  
          let payload, transferables = [];
  
          if (message && message.length === 1 && message[0] instanceof WorkerBroker.withTransferables) {
              transferables = message[0].transferables;
              message = message[0].value;
          }
  
          payload = {
              type: 'main_send',      // mark message as method invocation from main thread
              message_id,             // unique id for this message, for life of program
              method,                 // will dispatch to a function of this name within the worker
              message                 // message payload
          };
  
          if (options.stringify) {
              payload = JSON.stringify(payload);
          }
  
          worker.postMessage(payload, transferables.map(t => t.object));
          freeTransferables(transferables);
          if (transferables.length > 0) {
              log('trace', `'${method}' transferred ${transferables.length} objects to worker thread`);
          }
  
          message_id++;
          return promise;
      };
  
      // Add a worker to communicate with - each worker must be registered from the main thread
      WorkerBroker.addWorker = function (worker) {
          if (!(worker instanceof Worker)) {
              throw Error('Worker broker could not add non-Worker object', worker);
          }
  
          worker.addEventListener('message', function WorkerBrokerMainThreadHandler(event) {
              let data = ((typeof event.data === 'string') ? JSON.parse(event.data) : event.data);
              let id = data.message_id;
  
              // Listen for messages coming back from the worker, and fulfill that message's promise
              if (data.type === 'worker_reply') {
                  // Pass the result to the promise
                  if (messages[id]) {
                      if (data.error) {
                          messages[id].reject(data.error);
                      }
                      else {
                          messages[id].resolve(data.message);
                      }
                      delete messages[id];
                  }
              }
              // Listen for messages initiating a call from the worker, dispatch them,
              // and send any return value back to the worker
              // Unique id for this message & return call to main thread
              else if (data.type === 'worker_send' && id != null) {
                  // Call the requested method and save the return value
                  let result, error, target, method_name, method;
                  try {
                      [method_name, target] = findTarget(data.method);
                      if (!target) {
                          throw Error(`Worker broker could not dispatch message type ${data.method} on target ${data.target} because no object with that name is registered on main thread`);
                      }
  
                      method = (typeof target[method_name] === 'function') && target[method_name];
                      if (!method) {
                          throw Error(`Worker broker could not dispatch message type ${data.method} on target ${data.target} because object has no method with that name`);
                      }
  
                      result = method.apply(target, data.message);
                  }
                  catch(e) {
                      // Thrown errors will be passed back (in string form) to worker
                      error = e;
  
                  }
                  // Send return value to worker
                  let payload, transferables = [];
  
                  // Async result
                  if (result instanceof Promise) {
                      result.then((value) => {
                          if (value instanceof WorkerBroker.withTransferables) {
                              transferables = value.transferables;
                              value = value.value[0];
                          }
  
                          payload = {
                              type: 'main_reply',
                              message_id: id,
                              message: value
                          };
                          worker.postMessage(payload, transferables.map(t => t.object));
                          freeTransferables(transferables);
                          if (transferables.length > 0) {
                              log('trace', `'${method_name}' transferred ${transferables.length} objects to worker thread`);
                          }
  
                      }, (error) => {
                          worker.postMessage({
                              type: 'main_reply',
                              message_id: id,
                              error: (error instanceof Error ? `${error.message}: ${error.stack}` : error)
                          });
                      });
                  }
                  // Immediate result
                  else {
                      if (result instanceof WorkerBroker.withTransferables) {
                          transferables = result.transferables;
                          result = result.value[0];
                      }
  
                      payload = {
                          type: 'main_reply',
                          message_id: id,
                          message: result,
                          error: (error instanceof Error ? `${error.message}: ${error.stack}` : error)
                      };
                      worker.postMessage(payload, transferables.map(t => t.object));
                      freeTransferables(transferables);
                      if (transferables.length > 0) {
                          log('trace', `'${method_name}' transferred ${transferables.length} objects to worker thread`);
                      }
                  }
              }
          });
  
      };
  
      // Expose for debugging
      WorkerBroker.getMessages = function () {
          return messages;
      };
  
      WorkerBroker.getMessageId = function () {
          return message_id;
      };
  
  }