docs: function layoutWorker(self) {
    var layout; // main thread will send a message to initialize this
    var asyncOptions;
    var completedIterations = 0;
    var stepCalled = false;
    var timeoutId = 0;
    var systemStable = false;
    var graph;
  
    var positions = Object.create(null);
    self.addEventListener('message', handleMessageFromMainThread);
  
    return; // public API is over. Below are private methods only.
  
    function handleMessageFromMainThread(message) {
      var kind = message.data.kind;
      var payload = message.data.payload;
  
      if (kind === messageKind.init) {
        graph = fromjson(payload.graph);
        var options = JSON.parse(payload.options);
  
        init(graph, options);
      } else if (kind === messageKind.step) {
        step();
      } else if (kind === messageKind.pinNode) {
        pinNode(payload.nodeId, payload.isPinned);
      } else if (kind === messageKind.setNodePosition) {
        setNodePosition(payload.nodeId, payload.x, payload.y, payload.z);
      }
      // TODO: listen for graph changes from main thread and update layout here.
    }
  
    function setNodePosition(nodeId, x, y, z) {
      assertInitialized();
  
      layout.setNodePosition.apply(layout, arguments);
      systemStable = false;
      step();
    }
  
    function pinNode(nodeId, isPinned) {
      assertInitialized();
  
      var node = graph.getNode(nodeId);
      if (!node) return; // ignoring right now. should it throw?
  
      layout.pinNode(node, isPinned);
    }
  
    function assertInitialized() {
      if (!graph) throw new Error('Pin node requested without initialied graph');
      if (!layout) throw new Error('Layout was not created. Something is really wrong here');
    }
  
    function init(graph, options) {
      // unfortunately we need to revalidate here, since POSITIVE_INFINITY could
      // be lost during threads transition
      options = validateOptions(options);
      asyncOptions = options.async;
  
      layout = createLayout(graph, options);
      graph.forEachNode(initPosition);
  
      // let main thread know that we can process layout
      self.postMessage({ kind: messageKind.initDone });
    }
  
    function initPosition(node) {
      positions[node.id] = layout.getNodePosition(node.id);
    }
  
    function step() {
      assertInitialized();
  
      stepCalled = true;
  
      if (!timeoutId) {
        runLayoutCycleAsync();
      }
    }
  
    function runLayoutCycleAsync() {
      if (systemStable) {
        timeoutId = 0;
        return;
      }
  
      // we have to unblock this thread to receive messages from the main thread.
      timeoutId = setTimeout(function() {
        runLayoutCycle();
  
        // We either wait until next `step` event from RAF, or run now if asked to
        // not wait for `step`.
        if (stepCalled || !asyncOptions.waitForStep) {
          stepCalled = false;
          runLayoutCycleAsync();
        } else {
          // wait for the next event from the main thread to continue;
          timeoutId = 0;
        }
      }, 0);
    }
  
    function runLayoutCycle() {
      var wasStable = systemStable;
      for (var i = 0; i < asyncOptions.stepsPerCycle; ++i) {
        systemStable = layout.step();
        completedIterations += 1;
      }
  
      if (completedIterations >= asyncOptions.maxIterations) {
        systemStable = true;
      }
  
      self.postMessage({
        kind: messageKind.cycleComplete,
        payload: {
          positions: positions,
          systemStable: systemStable,
          bbox: layout.getGraphRect(),
          iterations: completedIterations
        }
      });
    }
  }