Worker SDK in C++

Step 2. Address major API breaking changes - Worker SDK in C++

See the Upgrade guide page for step 1, before following this page.

1. Update packages

The Worker SDK in C++ has been a header-only library since SpatialOS 13.0.1. In SpatialOS 14 and onwards, we've removed all the C++ binary packages (beginning with cpp-), and instead there now is a single package called cpp_headers that depends on the Worker SDK in C. Therefore, you should instead download the correct Worker SDK in C native package for your target platform, alongside the cpp_headers package. You can find more information on the Worker SDK in C packages here.

To update the package names in your worker, you'll need to either edit the package names in your worker's spatialos_worker_packages.json (if using the SPL), or in your own build system that uses spatial package get (if using the FPL).

You can find more information on the new C++ package layout here but in summary, you should apply the following changes from SpatialOS 13:

a. For all packages, replace cpp-static with c-static.
b. For Windows packages, replace msvc with vc140.
c. For macOS, iOS and Android packages, replace clang_libcpp with clang.
d. For Linux packages, replace gcc_libstdcpp with gcc510.
e. Obtain the cpp_headers package, as header files are no longer distributed with native packages.

We have also made two other changes to simplify the structure of the binary packages:

f. Static packages (packages beginning with c-static) no longer contain a lib folder. Instead, the libraries are just contained in the root of the package.
g. We've renamed the worker library to improbable_worker to avoid ambiguity. For example, on Windows, we now ship improbable_worker.dll / improbable_worker.lib instead of worker.dll / worker.lib.

2. Upgrade commands and callbacks

In SpatialOS 14, all methods which depend on metaclasses now return an object of type worker::Result<T, None>.

Result<T, E> is an object which may contain either a value of type T or an error code of type E alongside an error message. None is a placeholder type used to represent either a void return value or no error code. The methods which depend on metaclasses are:

Of these, the methods that previously returned void now return an object of type Result<None, None>, that either contains nothing or an error message.

To upgrade your commands and callbacks:

a. We recommend checking that the object returned by these methods does not contain an error.
b. You can access the value contained in Result either via the method GetValue or through the * operator.
To upgrade, in all places you use the value returned by these methods, either dereference the result object or use GetValue.
c. You can access the fields contained in Result via the -> operator.
To upgrade, in all places you access the fields of the value returned by these methods, replace . with ->.

For example, this:

cpp
worker::Entity entity;
entity.Add<example::SomeComponent>({});
auto request_id = connection.SendCreateEntityRequest(entity, {reserved_entity_id}, {});

dispatcher.OnCreateEntityResponse([&](const worker::CreateEntityResponseOp& op) {
    if (op.RequestId == request_id) {
        connection.SendComponentUpdate<example::SomeComponent>(reserved_entity_id, some_update);
    }
});

Becomes this:

worker::Entity entity;
entity.Add<example::SomeComponent>({});
auto request_id_result = connection.SendCreateEntityRequest(entity, {reserved_entity_id}, {});
if (request_id_result.HasError()) { // Alternatively, `if (!request_id_result)`
    // Handle error.
    // The error message can be accessed by calling `request_id_result.GetError()`.
}

dispatcher.OnCreateEntityResponse([&](const worker::CreateEntityResponseOp& op) {
    if (op.RequestId == *request_id_result) { // Alternatively, `request_id_result.GetValue()`
        auto result_update =
            connection.SendComponentUpdate<example::SomeComponent>(reserved_entity_id, some_update);
        if (result_update.HasError()) {
            // Handle error.
        }
    }
});

3. Update usage of the snapshot API

The way snapshot errors are reported has now changed, to allow distinction between different types of errors. In C++, the different snapshot errors are represented by worker::StreamErrorCode. We have changed the signature of the snapshot API methods to reflect this.

3.1. Saving snapshots

All SnapshotOutputStream methods now return a Result object, which may contain an error message and a code of type StreamErrorCode. Previously, a SnapshotOutputStream object could be created through the constructor and be used straight away, with errors being reported by returning an Option<std::string> error message, for example:

std::unordered_map<worker::EntityId, worker::Entity> entities = myEntities();
worker::SnapshotOutputStream output_stream{myComponents(), new_snapshot_filename};
for (auto& snapshot_entity : entities) {
  auto write_error_opt = outputStream.WriteEntity(snapshot_entity.first, snapshot_entity.second);
  if (write_error_opt) {
    // Log error.
    break;
  }
}

To upgrade, adjust your code to instead create the SnapshotOutputStream through Create and instead check after every API call that the returned Result object doesn't contain a StreamErrorCode error. If it does, handle it accordingly. For example:

std::unordered_map<worker::EntityId, worker::Entity> entities;
worker::SnapshotInputStream input_stream{myComponents(), snapshot_filename}; 
while (input_stream.HasNext()) {
    worker::Entity entity;
    worker::EntityId entity_id;
    auto read_error_opt = input_stream.ReadEntity(entity_id, entity);
    if (read_error_opt) {
        // Log error.
        break;
    }
    entities.emplace(entity_id, entity);
}

3.2. Loading snapshots

All SnapshotInputStream methods now return a Result object, which may contain an error message and a code of type StreamErrorCode. Previously, a SnapshotInputStream object could be created through the constructor and be used straight away, with errors being reported by returning an Option<std::string> error message, for example:

cpp
std::unordered_map<worker::EntityId, worker::Entity> entities;
worker::SnapshotInputStream input_stream{myComponents(), snapshot_filename}; 
while (input_stream.HasNext()) {
    worker::Entity entity;
    worker::EntityId entity_id;
    auto read_error_opt = input_stream.ReadEntity(entity_id, entity);
    if (read_error_opt) {
        // Log error.
        break;
    }
    entities.emplace(entity_id, entity);
}

To upgrade, adjust your code to instead create the SnapshotInputStream through Create and instead check after every API call that the returned Result object doesn't contain a StreamErrorCode error. If it does, handle it accordingly.

Note that the method HasNext is deprecated. Instead, when the end of the stream is reached, ReadEntity returns a Result with the StreamErrorCode set to worker::StreamErrorCode::kEof. For example:

std::unordered_map<worker::EntityId, worker::Entity> entities;
auto result_input_stream = worker::SnapshotInputStream::Create(MyComponents(), snapshot_filename);
if (!result_input_stream) {
  // Get error by calling `result_input_stream.GetErrorMessage()`.
  // The snapshot failed to be initialized, and the stream is not usable.
} else {
  while (true) {
    auto result_snapshot_entity = result_input_stream->ReadEntity();
    if (!result_snapshot_entity) {
      auto error_code = result_snapshot_entity.GetErrorCode();
      if (error_code == worker::StreamErrorCode::kInvalidData) {
        // Get error by calling `result_snapshot_entity.GetErrorMessage()`.
        // The last read operation failed, but the snapshot is still usable.
        continue;
      }
      if (error_code == worker::StreamErrorCode::kBadState) {
        // Get error by calling `result_snapshot_entity.GetErrorMessage()`.
        // An internal error occurred when reading and the snapshot is not usable.
        break;
      }
      if (error_code == worker::StreamErrorCode::kEof) {
        // The end of the snapshot was reached.
        break;
      }
      entities.emplace(result_snapshot_entity->first, result_snapshot_entity->second);
    }
  }
}

Updated about a year ago


Next

Now return to the Upgrade guide and follow step 3.

Upgrade guide

Worker SDK in C++


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.