########################
Syncing Multiple Viewers
########################

Summary
=======

In this chapter, we will create a viewer helper class to keep our viewer states synchronized.


Concepts
========

* Managing multiple viewers
* Traversing the model tree

---------------------------

There are a few ways you could approach synchronizing both viewers, like passing in the second viewer to the operator and making the same calls again on that viewer, but that would create high coupling between separate :doc:`WebViewer </api_ref/viewing/classes/Communicator.WebViewer>` objects. Therefore, you can abstract that same concept into an external helper class that manages any viewers in the application. While we will only have two viewers in this application, the helper class can be extended to an arbitrary number of viewers.

In this section, we will be building the ``SyncHelper`` class located in the file *src/js/SyncHelper.js*. The purpose of this class is to query the model state in the main viewer (the source of truth), and then update all other viewers attached. The constructor will take in an array of :doc:`WebViewer</api_ref/viewing/classes/Communicator.WebViewer>` objects and assign the first element as the ``mainViewer``. All subsequent viewers are "attached viewers" that are updated to reflect the main viewer's state.

.. code-block:: js

	class SyncHelper {
		constructor(viewerList) {
			// Copy the array, so we do not modify the reference values
			let tfViewerList = viewerList.slice(0);
			// Assign the first element to the main viewer
			this._mainViewer = tfViewerList.shift();
			// All remaining viewers are attached
			this._attachedViewers = tfViewerList;
			this._nodeMapping = new Map();
		}
	}

We need a function that executes when a node transformation occurs in the ``mainViewer``. The function will gather all the transforms for the node IDs provided, and make sure the ``attachedViewers`` set their model's respective nodes to the same values.

We will use a ``Map`` to store the ``nodeId`` and its transformation matrix as a key-value pair. Then we use that ``Map`` to set the node matrices for the attached viewers.

Add ``syncNodeTransforms()`` as a member function of ``SyncHelper``:

.. code-block:: js

	syncNodeTransforms(nodeIds = []) {
		let matMap = new Map();
		for (let node of nodeIds) {
			matMap.set(node, this._mainViewer.model.getNodeMatrix(node));
		}

		// Find a matching node in any attached viewer and update its matrix
		for (let [node, matrix] of matMap.entries()) {
			this._attachedViewers.map((viewer, index) => {
				if (this._nodeMapping.has(node)) {
					node = this._nodeMapping.get(node)[index];
				}
				viewer.model.setNodeMatrix(node, matrix);
			});
		}
	}

In the event ``nodeIds`` is empty, we should gather all the nodes of the ``mainViewer`` starting at the root node. First, we will add a helper function to recursively gather all ``nodeIds`` of the ``mainViewer``.


.. code-block:: js

	_gatherAllNodeIds(parent, nodeIds) {
		nodeIds.push(parent);
		let children = this._mainViewer.model.getNodeChildren(parent);
		if (children.length !== 0) {
			for (let child of children) {
				this._gatherAllNodeIds(child, nodeIds);
			}
		}
	}

Now we can prepend ``syncNodeTransforms()`` with the following:

.. code-block:: js

	syncNodeTransforms(nodeIds = []) {
		// Gather all nodes of the mainViewer
		if (nodeIds.length == 0) {
			nodeIds = [];
			this._gatherAllNodeIds(this._mainViewer.model.getAbsoluteRootNode(), nodeIds);
			nodeIds = nodeIds.filter(Boolean);
		}
	 
		let matMap = new Map();
		for (let node of nodeIds) {
			matMap.set(node, this._mainViewer.model.getNodeMatrix(node));
		}
	 
		for (let [node, matrix] of matMap.entries()) {
			this._attachedViewers.map((viewer, index) => {
				if (this._nodeMapping.has(node)) {
					node = this._nodeMapping.get(node)[index];
				}
				viewer.model.setNodeMatrix(node, matrix);
			});
		}
	}

Last, let's write some accessor member functions for later use.

.. code-block:: js

	setNodesMapping(masterNode, mappedNodes) {
		this._nodeMapping.set(masterNode, mappedNodes);
	}
	 
	getMainViewer() {
		return this._mainViewer;
	}
	 
	getAttachedViewers() {
		return this._attachedViewers;
	}

With this in place, let's go back and instantiate this ``SyncHelper`` class in our main application. In our ``main`` constructor, we will instantiate a ``SyncHelper`` object after we have created both :doc:`WebViewer</api_ref/viewing/classes/Communicator.WebViewer>` objects. Add ``this._viewSync = new SyncHelper(this._viewerList);`` to the ``constructor`` in *app.js*:

.. code-block:: js

	// Set class properties
	this._viewerList = [mainViewer, overheadViewer];
	this._viewSync = new SyncHelper(this._viewerList);
	this._modelList = [];
	this._printSurfaces = [];

Recall the :js:attr:`~Communicator.CallbackMap.handleEvent` callback function in *app.js* from the previous section. This callback will be a great place to add our new ``syncNodeTransforms()`` function. Update the ``handleEvent`` callback with the following:

.. code-block:: js

	handleEvent: (eventType, nodeIds, initialMatrices, newMatrices) => {
		this.setMatrixText(mainViewer.model.getNodeNetMatrix(nodeIds[0]));
		this._viewSync.syncNodeTransforms(nodeIds);
	}

You should now be able to select a part in the Main View, add handles with the "Show Handles" button, and watch the respective part in the "Overhead View" move.


.. video:: ../../_static/videos/handleOpSynced.mp4
