Sites

Menu

Snapshots

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;

The SDK provides two classes to manipulate snapshots stored in files:

This lets you write tools to perform offline transformations of the simulation state, or to programmatically create the starting point of a simulation.

Snapshot manipulation does not require a Connection, making it possible to develop standalone, offline snapshot manipulation tools. However, we recommend using the build infrastructure provided by SpatialOS for workers to build your standalone tools.

These stream classes are the recommended methods of manipulating snapshots as they do not require the entire snapshot to be stored in memory when reading or writing a snapshot.

You can store snapshots either in binary or JSON format depending on your preference. The only difference in usage is how you create the streams. The binary version takes a const ComponentRegistry& which you obtain by calling your MyComponents function (or by using the generated one). The JSON version requires a const SchemaBundle& which you can obtain by calling the generated worker::MakeSchemaBundle() function.

SnapshotInputStream

SnapshotInputStream has three public methods:

/**
 * These two functions create a SnapshotInputStream to read the snapshot file from the given path.
 * They return a Result object which contains an error message and an error code of
 * type StreamErrorCode if an error occurred.
 */
// Read a binary snapshot
static Result<SnapshotInputStream, StreamErrorCode> Create(const ComponentRegistry& registry, const std::string& path);
// Read a JSON snapshot
static Result<SnapshotInputStream, StreamErrorCode> Create(const SchemaBundle& bundle, const std::string& path);

/**
 * Reads the next EntityId and Entity pair from the Snapshot. Returns a Result
 * object which contains either the read pair or an error. The error code can be
 * kBadState, kInvalidData or kEof.
 */
Result<std::pair<EntityId, Entity>, StreamErrorCode> ReadEntity();

SnapshotInputStream::HasNext is deprecated in favour of using StreamErrorCode::kEof to mark the end of a snapshot input stream.

SnapshotOutputStream

SnapshotOutputStream has three public methods:

/**
 * These two functions create a SnapshotOutputStream to write the snapshot file to the given path.
 * They return a Result object which contains either a SnapshotOutputStream object,
 * or an error. The error code is kBadState.
 */
// Write a binary snapshot
static Result<SnapshotOutputStream, StreamErrorCode> Create(const ComponentRegistry& registry, const std::string& path);
// Write a JSON snapshot
static Result<SnapshotOutputStream, StreamErrorCode> Create(const SchemaBundle& bundle, const std::string& path, bool enable_pretty_printing);

/**
 * Writes the EntityId and Entity pair to the output stream. Returns a Result
 * object which contains either None (void) or an error. The error code can be
 * kBadState or kInvalidData.
 */
Result<None, StreamErrorCode> WriteEntity(EntityId entity_id, const Entity& entity);

When a SnapshotOutputStream is destructed, the end of file is written and the stream’s resources are released.

Example

Here is an example of loading a binary snapshot, performing some manipulation on it, and saving it back. It uses the types and components defined in the example from Generated code. This example highlights the different errors which may arise after different snapshot calls, and you can choose how to handle them at your convenience.

bool AddLowHealthEffectToEntities(const std::string& snapshot_filename,
                                  const std::string& new_snapshot_filename) {
  // Create a SnapshotInputStream to read from the snapshot file.
  auto result_input_stream = worker::SnapshotInputStream::Create(MyComponents(), snapshot_filename);
  if (!result_input_stream) {
    // The snapshot failed to be initialized, and the stream is not usable
    // with error code kBadState.
    LogError(result_input_stream.GetErrorMessage());
    return false;
  }
  // Create a SnapshotOutputStream to write to a snapshot file.
  auto result_output_stream =
      worker::SnapshotOutputStream::Create(MyComponents(), new_snapshot_filename);
  if (!result_output_stream) {
    // The snapshot failed to be initialized, and the stream is not usable
    // with error code kBadState.
    LogError(result_input_stream.GetErrorMessage());
    return false;
  }

  // Add the "LowHealth" effect to all entities that have a Status component and
  // less than 10 health points.
  while (true) {
    auto result_snapshot_entity = result_input_stream->ReadEntity();
    if (!result_snapshot_entity) {
      auto read_error_code = result_snapshot_entity.GetErrorCode();
      if (read_error_code == worker::StreamErrorCode::kInvalidData) {
        // The last read operation failed, but the snapshot is still usable.
        // Log or handle the operation failure. Possible errors include
        // unregistered component or failed deserialization.
        LogError(result_snapshot_entity.GetErrorMessage());
        continue;
      }
      if (read_error_code == worker::StreamErrorCode::kBadState) {
        // An internal error occurred when reading and the snapshot is not usable.
        LogError(result_snapshot_entity.GetErrorMessage());
        return false;
      }
      if (read_error_code == worker::StreamErrorCode::kEof) {
        // The eof was reached when reading, replaces HasNext.
        return true;
      }

      auto creature = result_snapshot_entity->second.Get<example::Creature>();
      if (creature && creature->health() < 10) {
        creature->effects().emplace_back("LowHealth", 100);
      }

      // Save entity back to the snapshot file.
      auto result_write = result_output_stream->WriteEntity(result_snapshot_entity->first,
                                                            result_snapshot_entity->second);
      if (!result_write) {
        auto write_error_code = result_write.GetErrorCode();
        if (write_error_code == worker::StreamErrorCode::kInvalidData) {
          // The last write operation failed, but the snapshot is still usable.
          // Log or handle the operation failure. Possible errors include unregistered component,
          // failed serialization, missing Persistence component and entities with the same id.
          LogError(result_write.GetErrorMessage());
          continue;
        }
        if (write_error_code == worker::StreamErrorCode::kBadState) {
          // An internal error occurred when writing and the snapshot is not usable.
          LogError(result_write.GetErrorMessage());
          return false;
        }
      }
    }
  }
}

To make this example work with JSON snapshots, just replace the MyComponents() parameter in the stream Create functions with a call to the generated worker::MakeSchemaBundle() function and choose whether to enable pretty printing in the output stream.

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums