Sites

Menu
These are the docs for 14.4, an old version of SpatialOS. 14.5 is the newest →

Creating and deleting entities

Introduction

Before you begin, note that:

  • all code examples, object and function names used on this page refer to the Worker SDK in C++. Naming might differ slightly in other languages.

  • all code examples on this page 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;
  • in order to create or delete an entity, a worker must have permission to do so. For more information, see the Worker permissions page.

Using the worker::Connection, a worker can request SpatialOS to:

For all of these methods:

  • They return an worker::RequestId request ID. This can be used to match a request with its response. The type parameter C depends on the type of request being sent.
  • The request can fail, so check the StatusCode in the Op in the callback, and retry as necessary. The caller will always get a response callback, but it might be due to a timeout.

Create an entity

There are three ways to create entities:

You create an entity using the Connection method SendCreateEntityRequest, which takes:

  • a worker::Entity representing the initial state of the entity
  • an optional entity ID (either reserved or not reserved)

You’ll receive the response via the Dispatcher callback OnCreateEntityResponse. If the operation succeeds, the response contains the ID of the newly created entity.

Reserve entity IDs

You can reserve entity IDs before creating entities. You might want to start by reserving a large range of entity IDs that you can then use at any time. This guarantees that any future entity IDs you reserve won’t clash with that range.

If you use only the entity reservation system to create entities, this avoids your creation requests failing due to an existing entity already having an ID that you’ve reserved.

However, if you also have code that doesn’t reserve entity IDs before creating entities, then this kind of failure might occur.

Reserve a range of entity IDs using the Connection method SendReserveEntityIdsRequest.

You receive the response via the Dispatcher callback OnReserveEntityIdsResponse. If the operation succeeds, the response contains an entity ID, which is the first in a contiguous range that the reservation system hasn’t returned before, and the corresponding number of entity IDs in the reserved range.

If the operation fails, the entity ID is not set, and the number of entity IDs in the reserved range is 0.

The Op in the callback is ReserveEntityIdsResponseOp.

Assign your own IDs

You can create entities and assign them IDs that you haven’t previously reserved. This is useful if you know what ID you want to use for each entity, and it also means you can reuse IDs.

However, this will fail if you try to assign an ID that’s already in use; therefore, you need to take care if some parts of your code use internal IDs that you assign when you create entities, and other parts of your code use the entity reservation system (either entity ID reservation or creating entities without assigning IDs).

You can create entities with IDs in the range 1 to 2^62 - 1. Numbers outside this range are reserved for system entities. If you try to create an entity with an ID that is outside this range, this will result in an error.

If you create entities without assigning them IDs, the Runtime assigns IDs using the entity ID reservation system.

However, the Runtime does not keep track of which IDs are already assigned to entities, so this will fail if the Runtime tries to assign an ID to an entity and there is already an entity with that ID. Therefore, we recommend you don’t use this method to create entities.

Delete an entity

Delete an entity using the Connection method SendDeleteEntityRequest, which takes an entity ID.

You’ll receive the response via the Dispatcher callback OnDeleteEntityResponse. The response contains no additional data.

Example

Here’s an example of reserving an entity ID, creating an entity with that ID and some initial state, and finally deleting it:

worker::RequestId<worker::CreateEntityRequest> entity_creation_request_id;
worker::RequestId<worker::DeleteEntityRequest> entity_deletion_request_id;

constexpr uint32_t kTimeoutMillis = 500;

void CreateDeleteEntity(worker::Connection& connection, worker::Dispatcher& dispatcher,
                        const improbable::EntityAcl::Data& acl) {
  // Reserve an entity ID.
  worker::RequestId<worker::ReserveEntityIdsRequest> entity_id_reservation_request_id =
      connection.SendReserveEntityIdsRequest(1, kTimeoutMillis);

  // When the reservation succeeds, create an entity with the reserved ID.
  dispatcher.OnReserveEntityIdsResponse([entity_id_reservation_request_id, &connection,
                                         acl](const worker::ReserveEntityIdsResponseOp& op) {
    if (op.RequestId == entity_id_reservation_request_id &&
        op.StatusCode == worker::StatusCode::kSuccess) {
      // ID reservation was successful - create an entity with the reserved ID.
      worker::Entity entity;
      entity.Add<improbable::Position>({{1, 2, 3}});
      entity.Add<improbable::EntityAcl>(acl);
      auto result = connection.SendCreateEntityRequest(entity, op.FirstEntityId, kTimeoutMillis);
      // Check no errors occurred.
      if (result) {
        entity_creation_request_id = *result;
      } else {
        connection.SendLogMessage(worker::LogLevel::kError, "CreateDeleteEntity",
                                  result.GetErrorMessage());
        std::terminate();
      }
    }
  });

  // When the creation succeeds, delete the entity.
  dispatcher.OnCreateEntityResponse([&connection](const worker::CreateEntityResponseOp& op) {
    if (op.RequestId == entity_creation_request_id &&
        op.StatusCode == worker::StatusCode::kSuccess) {
      entity_deletion_request_id = connection.SendDeleteEntityRequest(*op.EntityId, kTimeoutMillis);
    }
  });

  // When the deletion succeeds, we're done.
  dispatcher.OnDeleteEntityResponse([](const worker::DeleteEntityResponseOp& op) {
    if (op.RequestId == entity_deletion_request_id &&
        op.StatusCode == worker::StatusCode::kSuccess) {
      // Test successful!
    }
  });
}

Entity ACLs

Entity ACLs are exposed to the worker as a component, and can be manipulated as any other component. It’s worth reading Handing over authority and Worker attributes and worker requirements for more information.

ACLs are set at entity creation time, but can be modified dynamically at runtime by the worker that has write access authority over the EntityAcl component.

Example of creating an ACL

This example adds an EntityAcl to an Entity given a CommandRequestOp to make a specific worker (as opposed to, potentially, a set of workers) qualify for write access authority over a component. Specifically:

  • The entity will be visible to workers that have the “client” or “physics” worker attribute (in other words, workers that are in the “client” or “physics” layer).
  • Any worker with “physics” as its attribute can have write access authority over the entity’s Position and EntityAcl components.
  • The worker which issued the CommandRequestOp is the only worker that can have write access authority over the PlayerControls component.

This can be used as part of creating a new entity in response to a command request.

template <typename C>
void AddComponentDelegations(const worker::CommandRequestOp<C>& op, worker::Entity& entity) {
  static const std::string physics = "physics";
  static const std::string client = "client";

  // This requirement set matches only the command caller, i.e. the worker that issued the command,
  // since attribute set includes the caller's unique attribute.
  improbable::WorkerAttributeSet callerWorkerAttributeSet{{"workerId:" + op.CallerWorkerId}};
  improbable::WorkerRequirementSet callerWorkerRequirementSet{
      worker::List<improbable::WorkerAttributeSet>{callerWorkerAttributeSet}};

  worker::List<std::string> physicsWorkerAttributeSet{physics};

  // This requirement set matches any worker with the attribute "physics".
  improbable::WorkerRequirementSet physicsWorkerRequirementSet{
      worker::List<improbable::WorkerAttributeSet>{{physicsWorkerAttributeSet}}};

  // This requirement set matches any worker with the attribute "client" or "physics".
  improbable::WorkerRequirementSet clientOrPhysicsRequirementSet{
      worker::List<improbable::WorkerAttributeSet>{
          improbable::WorkerAttributeSet{worker::List<std::string>{client}},
          physicsWorkerAttributeSet}};

  // Give authority over Position and EntityAcl to any physics worker, and over PlayerControls to
  // the caller worker.
  worker::Map<worker::ComponentId, improbable::WorkerRequirementSet> componentAcl{
      {improbable::Position::ComponentId, physicsWorkerRequirementSet},
      {EntityAcl::ComponentId, physicsWorkerRequirementSet},
      {example::PlayerControls::ComponentId, callerWorkerRequirementSet}};

  entity.Add<EntityAcl>(
      EntityAcl::Data{/* read */ clientOrPhysicsRequirementSet, /* write */ componentAcl});
}

Example of changing write access authority

The worker that has write access authority over the EntityAcl component can decide to give the write access authority over position (or any other component) to a different worker.

In order to do this, you need to modify EntityAcl.Data, mapping Position.ComponentId to callerWorkerRequirementSet (created above). This change can be made using the method below:

// Take a copy of the EntityAclData argument, so that this method doesn't modify the original one.
improbable::EntityAclData
DelegateComponent(improbable::EntityAclData acl, worker::ComponentId componentId,
                  const improbable::WorkerRequirementSet& requirementSet) {
  // Set the write ACL for the specified component to the specified attribute set,
  // assuming the componentAcl option is not empty.
  acl.component_write_acl().insert({{componentId, requirementSet}});
  return acl;
}

For these changes to take effect, the worker that has write access authority over the EntityAcl component needs send the changes through a component update.

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums