Get SpatialOS

Sites

Menu
These are the docs for 13.1, an old version of SpatialOS. The docs for this version are frozen: we do not correct, update or republish them. 13.4 is the newest →

Handling data received from SpatialOS

All code examples in this section assume you have defined a MyComponents function as described in Providing components, and set up the following preamble:

#include <improbable/standard_library.h>
#include <improbable/worker.h>
#include <example.h>

using namespace improbable;

Processing operations

It is up to you to decide how the basic event loop of receiving data from SpatialOS should be structured, depending on the worker’s requirements.

The worker needs to:

  1. Call worker::Connection::GetOpList to get a list of “operations” (for example entity creation, or a component update) that have been sent since the last time the function was called.
  2. Pass the worker::OpList to a worker::Dispatcher, in order to invoke callbacks.

If the connection fails (when Connection.IsConnected() == false), any error messages explaining the cause will be returned by GetOpList(). Make sure to process this list in order to receive all available diagnostic information.

In order to detect that a worker is still responsive, SpatialOS sends periodic heartbeat requests. Responding to these happens automatically, but the buffer for incoming operations can fill up with other messages. If that happens, the heartbeats cannot be processed and your worker may be killed. Therefore, you should never wait multiple seconds between calls to GetOpList().

Example of processing operations

The following snippet shows a (very) simple example implementation of an event loop that processes operations from SpatialOS 60 times per second:

void RunEventLoop(worker::Connection& connection, const worker::Dispatcher& dispatcher) {
  constexpr unsigned kFramesPerSecond = 60;
  constexpr std::chrono::duration<double> kFramePeriodSeconds(
      1. / static_cast<double>(kFramesPerSecond));

  while (true) {
    auto start_time = std::chrono::steady_clock::now();
    auto op_list = connection.GetOpList(0 /* non-blocking */);
    // Invoke user-provided callbacks.
    dispatcher.Process(op_list);

    // Do other work here...

    auto stop_time = std::chrono::steady_clock::now();
    auto wait_for = kFramePeriodSeconds - (stop_time - start_time);
    std::this_thread::sleep_for(wait_for);
  }
}

Dispatcher callbacks

To respond to operations, register callbacks on the worker::Dispatcher. Each method takes an arbitrary callable System.Action<Param>, where the parameter type Param depends on the particular kind of callback being registered.

Timing and threading

You have complete control over callback timing and threading decisions. Dispatcher callbacks are invoked:

Caveats about receiving component updates

Note that the component updates:

  • can be partial, i.e. only update some properties of the component
  • do not necessarily have to contain data that is different from the worker’s current view of the component
  • could have been sent by SpatialOS rather than a worker for synchronization purposes

Registering callbacks

Here’s an example of registering callbacks:

void RegisterLambdaCallbacks(worker::Dispatcher& dispatcher) {
  dispatcher.OnAddEntity([&](const worker::AddEntityOp& op) {
    std::cout << "Entity " << op.EntityId << " added." << std::endl;
  });
  dispatcher.OnComponentUpdate<example::Switch>(
      [&](const worker::ComponentUpdateOp<example::Switch>& op) {
        // `op.Update.toggled()` contains a list of SwitchToggled events.
        for (auto it : op.Update.toggled()) {
          std::cout << "Switch toggled at " << it.time() << std::endl;
        }
      });
}

Of course, you don’t have to use lambdas:

void RegisterCallbacks(worker::Dispatcher& dispatcher) {
  dispatcher.OnComponentUpdate<improbable::Position>(&PositionUpdated);
}

void PositionUpdated(const worker::ComponentUpdateOp<improbable::Position>& op) {
  const auto coords = op.Update.coords().data();
  std::cout << "Updated position of entity " << op.EntityId << " to "
            << " x: " << coords->x() << " y: " << coords->y() << " z: " << coords->z() << std::endl;
}

Unregistering callbacks

To unregister a callback, call Remove() with the Dispatcher::CallbackKey returned from the registration method:

void UnregisterCallbacks(worker::Dispatcher& dispatcher) {
  auto key = dispatcher.OnComponentUpdate<improbable::Position>(
      [&](const worker::ComponentUpdateOp<improbable::Position>& op) {
        std::cout << "Updated position of entity " << op.EntityId << std::endl;
      });

  // ...

  dispatcher.Remove(key);
}

Dispatcher callbacks reference

Some callbacks use a template parameter T which relates to schema-generated code. This is discussed in more detail on the Generated code page.

Method Invoked when… Parameter type
OnDisconnect The Connection is no longer connected and can no longer be used.
Check the log for errors relating to the disconnection.
worker::DisconnectOp
OnFlagUpdate A worker flag has been created, deleted or when its value has changed. worker::FlagUpdateOp
OnLogMessage The SDK issues a log message for the worker to print. This does not include messages sent using Connection.SendLogMessage. worker::LogMessageOp
OnMetrics The SDK reports built-in internal metrics. worker::MetricsOp
OnCriticalSection A critical section is about to be entered or has just been left. worker::CriticalSectionOp
OnAddEntity An entity is added to the worker’s view of the simulation. worker::AddEntityOp
OnRemoveEntity An entity is removed from the worker’s view of the simulation. worker::RemoveEntityOp
OnReserveEntityIdResponse The worker receives a response for an entity ID reservation it requested. worker::ReserveEntityIdResponseOp
OnReserveEntityIdsResponse The worker receives a response for the reservation of an entity ID range it requested. worker::ReserveEntityIdsResponseOp
OnCreateEntityResponse The worker receives a response for an entity creation it requested. worker::CreateEntityResponseOp
OnDeleteEntityResponse The worker receives a response for an entity deletion it requested. worker::DeleteEntityResponseOp
OnEntityQueryResponse The worker receives a response for an entity query. worker::EntityQueryResponseOp
OnAddComponent<T> A component is added to an entity in the worker’s view of the simulation. worker::AddComponentOp<T>
OnRemoveComponent<T> A component is removed from an entity in the worker’s view of the simulation. worker::RemoveComponentOp
OnAuthorityChange<T> The worker’s authority state over a component changes (to Authoritative, Not Authoritative or Authority Loss Imminent). worker::AuthorityChangeOp
OnComponentUpdate<T> A component on an entity in the worker’s view of the simulation is updated. worker::ComponentUpdateOp<T>
OnCommandRequest<T> The worker receives a command request for a component on an entity over which it has authority. worker::CommandRequestOp<T>
OnCommandResponse<T> The worker receives a command response for a command request. worker::CommandResponseOp<T>

Dispatcher invocation order

When processing a worker::OpList, callbacks for most operations are invoked in the order they were registered with the worker::Dispatcher. However, some operations naturally come in pairs of “start” and “end” operations, and the callbacks for the “end” operation are invoked in the reverse of the order they were registered.

This means that pairs of callbacks registered for the “start” and “end” operations can correctly depend on resources managed by pairs of callbacks registered previously, similar to the usual C++ construction and destruction order.

For example:

  • AddEntityOp and AddComponentOp callbacks are invoked in the order they were registered with the worker::Dispatcher. RemoveEntityOp and RemoveComponentOp callbacks are invoked in reverse order.
  • AuthorityChangeOp callbacks are invoked in the order they were registered when authority is granted, but in reverse order when authority is revoked (or authority loss is imminent).
  • CriticalSectionOps are invoked in usual order when a critical section is entered but reverse order when a critical section is left.
  • FlagUpdateOp callbacks are invoked in the order they were registered when a worker flag is created or its value is changed, but in reverse order when a worker flag is deleted.

In particular, this invocation order has the side-effect that the final state of a component removed in a RemoveComponentOp callback can still be inspected in the worker::View.

Using the View

worker::View is subclass of worker::Dispatcher defined in a separate header file, <improbable/view.h>, which automatically maintains the current state of the worker’s view of the simulated world.

It has a field called Entities, which is a map from entity ID to worker::Entity objects. When a View processes an OpList, it automatically updates the state of this map as appropriate. Any user-defined callbacks are also invoked, as per the Dispatcher contract.

Note that the View can be implemented from scratch using the worker::ForEachComponent functionality, and is intended mostly as an example.

Critical sections

The protocol supports the concept of critical sections. A critical section is a block of operations that should be processed atomically by the worker. For example, a set of entities representing a stack of cubes should be added to the scene in its entirety before any physics simulation is done.

This notion of a critical section does not provide any guarantees.

The CriticalSectionOp callback is invoked with InCriticalSection == true just before the first operation of the critical section is delivered, and is invoked again with InCriticalSection == false just after the last operation of the critical section is delivered.

Worker flags

To access the value of a worker flag, use the GetWorkerFlag method:

void SetWorkSpeedFlag(worker::Connection& connection) {
  auto work_speed_flag = connection.GetWorkerFlag("mycompany_theproject_work_speed");
  if (work_speed_flag) {
    SetWorkSpeed(*work_speed_flag);
  } else {
    SetWorkSpeed("default_work_speed");
  }
}

Worker flags are updated before the registered callbacks are invoked, so it is recommended to store the worker flag values in user code when using callbacks and only use GetWorkerFlag for checking the initial flag values.

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums