The SDK provides access to state through Observables. Observers can subscribe to Observables to watch and receive updates of the state.

Receiving State from an Observable

State is provided by subscribing to an Observable. Two types of Observers are supported: functional, and object-oriented. Observables always give an up-to-date view of its state to all of its subscribed Observers. In a sense, Observables and Observers are very similar to Events and Event Handlers.

With our Observable implementation

  • when first subscribing an Observer to an Observable, the Observer will always be called back as soon as the state is available – similar in functionality to a “getData” call
  • all subscribed Observers will be called back for all subsequent updates to the observed state – similar in functionality to an event handler
  • subscribing multiple Observers to a single Observable does not increase the complexity of calculating the state of the Observable
  • the Observable state used in the Observers’ callbacks is cached and shared between all Observers

Using a Functional Observer

Functional Observers are merely callback functions that accept the state as their argument.

For example, to subscribe to the current camera pose:

function poseObserver(pose: MpSdk.Camera.Pose) {
  // use the current pose of the camera
  console.log(pose);
}
mpSdk.Camera.pose.subscribe(poseObserver);

Using an Object-Oriented Observer

Object-Orieneted Observers are objects that conform to the IObserver interface, or implement a member onChanged function.

For example, to subscribe to the current camera pose:

class PoseObserver implements MpSdk.IObserver<MpSdk.Camera.Pose> {
  onChanged(pose: MpSdk.Camera.Pose) {
    // use the current pose of the camera
    console.log(pose);
  }
}

mpSdk.Camera.pose.subscribe(new PoseObserver());

Using Multiple Observers

As mentioned, since Observables always have an up-to-date shared view of state, having multiple Observers is supported and encouraged. To use the camera pose in two different systems within the same application is a matter of adding two Observers.

// Component1.ts
mpSdk.Camera.pose.subscribe(function (pose) { /* use pose */ });


// Component2.ts
mpSdk.Camera.pose.subscribe({
  onChanged(pose) { /* use pose */ },
});

Shutting down an Observer

When updates to state are no longer required, Observers should be removed from the Observable. When subscribing to an Observable, an ISubscription object is returned. The ISubscription object is the object used to remove the subscribed Observer and stop any callback it would have received.

const poseSubscription = mpSdk.Camera.pose.subscribe({
  onChanged(pose) { },
});

// ... some time later
poseSubscription.cancel();
// from here on, the Observer associated with `poseSubscriptoin` will no longer be called back

Cloning an Observer’s View of State

Because state is cached and shared between Observers of the same Observable, any mutations that are done to the state object in the callback can be seen by all Observers and are subject to be blown away by changes to the Observable coming from the application. If a snapshot of the state is needed, saving a reference to the argument in the callback is not enough. In such a case, cloning the state object is needed.

const poseStack: MpSdk.Camera.Pose[] = [];
mpSdk.Camera.pose.subscribe(function (pose) {
  // create a deep clone of the pose and push it into a stack
  poseStack.push({
    ...pose,
    position: { ...pose.position },
    projection: [ ...pose.projection ],
    rotation: { ...pose.rotation },
    sweep: pose.sweep.slice(),
  });
});

Benefits of Observables

Observable state has multiple benefits over our previous implementations of state.

Less Boilerplate

Before Observables, events (on) could be used to watch for some state changes. However, events can be missed. Getting the state of infrequently changing data required polling the data using something like getData to seed the data if the event is missed.

DON’T: Setting up a Camera.MOVE handler and seeding the data using getPose is verbose.

let pose: MpSdk.Camera.Pose;
function updatePose(newPose: MpSdk.Camera.Pose) {
  // update the pose when the camera moves
  pose = newPose;
}

// listen for camera changes
mpSdk.on(mpSdk.Camera.MOVE, updatePose);
// poll the data once in case the camera hasn't been moved yet
pose = await mpSdk.Camera.getPose();

DO: Use the Observable camera pose to reduce the above code to only one call: its subscribe function.

mpSdk.Camera.pose.subscribe({
  onChanged(newPose) {
    // log the current camera pose
    console.log(newPose);
  },
});

Better Efficiency

Another benefit to Observables is that once subscribed, Observers are only called when changes occur. When a “get” call would return the same state as the previous call, it is doing unnecessary work. The excessive “get” calls and extra work done for each call are a potential for decreasing the performance of the application.

DON’T: Polling the current camera pose by using an update loop triggers unnecessary updates

function updatePose() {
  const newPose = await mpSdk.Camera.getPose();
  requestAnimationFrame(updatePose);
}
requestAnimationFrame(updatePose);

DO: Use an Observable to track state and automatically update only as needed

// only called on first subscribe and when pose has changed
mpSdk.Camera.pose.subscribe(function (newPose) { });

Callback Consistency

Previously mentioned, both an event callback and a “get” call were required to successfully fetch and watch data. With an Observable, all of that behavior is completely encapsulated. Any new Observers that subscribe to an Observable will always be called at least once to ensure that they have an accurate view of the state and called again as state changes to ensure that it is kept up-to-date.

DON’T: Subscribing to events will miss prior updates

// may or may not be called depending on if the camera is ever moved after the callback is registered
mpSdk.on(mpSdk.Camera.MOVE, function (newPose) { });

DO: Subscribing to an Observable always has an up-to-date view of state

// always called at a minimum of once with the current state, and with any subsequent updates afterward
mpSdk.Camera.pose.subscribe(function (newPose) { });

Waiting for a Condition of State

A “Condition” can be registered to an Observable as a one-off to wait for a certain condition to be met with a given state. Functional and object-oriented Conditions are supported.

Using a Functional Condition

Similar to Observers, functional Conditions are merely callback functions that receive the state as their argument, but return a boolean to determine if the condition was met.

For example, to wait for the camera pose to be in FLOORPLAN mode:

function floorplanCondition(pose: MpSdk.Camera.Pose): boolean {
  return pose.mode === MpSdk.Mode.Mode.FLOORPLAN;
}

// wait for the camera pose to be in FLOORPLAN MODE ...
await mpSdk.Camera.pose.waitUntil(floorplanCondition);
// ... before continuing

Using an Object-Oriented Condition

Object-Oriented Conditions are objects that conform to the ICondition interface, or implement a member waitUntil function that returns a boolean to determine if the condition was met.

class FloorplanCondition implements MpSdk.ICondition<MpSdk.Camera.Pose> {
  waitUntil(pose: MpSdk.Camera.Pose): boolean {
    return pose.mode === MpSdk.Mode.Mode.FLOORPLAN;
  }
}

// wait for the camera pose to be in FLOORPLAN MODE ...
await mpSdk.Camera.pose.waitUntil(new FloorplanCondition());
// ... before continuing