Sites

Menu

Creating and deleting entities

This page is deprecated and we plan to remove it in mid-January 2020. See the documentation on the updated behavior.

Until then, if you need to use the functionality described on this page, you must set a flag, enable_new_entity_id_reservation_workflow, to false.

Introduction

Before you begin, note that:

  • all code examples in this section assume using Improbable.Worker.CInterop; and using Example; (the generated code namespace).

  • 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 Improbable.Worker.CInterop.Connection, a worker can request SpatialOS to:

For all of these methods:

  • They return a request ID. This can be used to match a request with its response.
  • The request can fail, so check the StatusCode in the Op processed after calling Connection.GetOpList, and retry as necessary. The caller will always get a response callback, but it might be due to a timeout.

Entity ID Reservations

It is possible to reserve entity IDs before creating entities.

Note: Entity ID reservations can expire at an arbitrary time in the future, without warning. Therefore, it is not guaranteed that the reserved entity ID will be still be valid by the time you create an entity with it. For this reason, we recommend creating entities without reserving IDs, and instead using the automatically assigned entity ID returned in CreateEntityResponseOp.

Reserve a range of entity IDs using the Connection method SendReserveEntityIdsRequest. SendReserveEntityIdsRequest takes an optional timeout.

You’ll receive the response via handling the ReserveEntityIdsResponseOp when processing operations. If the operation succeeds, the response contains an entity ID, which is the first in a contiguous range guaranteed to be unused in the current deployment, 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.

Create an entity

Create an entity using the Connection method SendCreateEntityRequest, which takes:

  • a worker.Entity representing the initial state of the entity
  • an optional entity ID (which, if provided, must have been obtained by a previous call to SendReserveEntityIdsRequest)
  • an optional timeout

You’ll receive the response via handling the CreateEntityResponseOp when processing operations. If the operation succeeds, the response contains the ID of the newly created entity.

Delete an entity

Delete an entity using the Connection method SendDeleteEntityRequest, which takes:

  • an entity ID
  • an optional timeout

You’ll receive the response via handling the DeleteEntityResponseOp when processing operations. 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:

private static System.Int64 entityIdReservationRequestId = 0;
private static System.Int64 entityCreationRequestId = 0;
private static System.Int64 entityDeletionRequestId = 0;

private static void CreateDeleteEntity(Connection connection)
{
  const uint timeoutMillis = 500u;

  // Reserve an entity ID.
  entityIdReservationRequestId = connection.SendReserveEntityIdsRequest(1, timeoutMillis);
}

private static void ProcessCreateDeleteEntity(Connection connection, OpList opList)
{
  const uint timeoutMillis = 500u;

  var opCount = opList.GetOpCount();
  for (var i = 0; i < opCount; ++i)
  {
    OpType opType = opList.GetOpType(i);

    switch (opType)
    {
      case OpType.ReserveEntityIdsResponse:
      {
        var op = opList.GetReserveEntityIdsResponseOp(i);
        if (op.RequestId == entityIdReservationRequestId && op.StatusCode == StatusCode.Success)
        {
          var entity = new Entity();

          // These component IDs all come from standard_library.schema.
          const uint EntityACLComponentID = 50;
          const uint PersistenceComponentID = 55;
          const uint MetadataComponentID = 53;
          const uint PositionComponentID = 54;

          // Empty ACL - should be customised.
          var entityAclSchema = SchemaComponentData.Create();
          var entityAclObject = entityAclSchema.GetFields();
          var requirementSetObject = entityAclObject.AddObject(1);
          entity.Add(new ComponentData(EntityACLComponentID, entityAclSchema));

          // Needed for the entity to be persisted in snapshots.
          var persistenceSchema = SchemaComponentData.Create();
          var persistenceObject = persistenceSchema.GetFields();
          entity.Add(new ComponentData(PersistenceComponentID, persistenceSchema));

          var metadataSchema = SchemaComponentData.Create();
          var metadataObject = metadataSchema.GetFields();
          metadataObject.AddString(1, "Player");
          entity.Add(new ComponentData(MetadataComponentID, metadataSchema));

          var positionSchema = SchemaComponentData.Create();
          var positionObject = positionSchema.GetFields();
          var coordinatesObject = positionObject.AddObject(1);
          coordinatesObject.AddDouble(1, 10.0);
          coordinatesObject.AddDouble(2, 20.0);
          coordinatesObject.AddDouble(3, 30.0);
          entity.Add(new ComponentData(PositionComponentID, positionSchema));

          entityCreationRequestId = connection.SendCreateEntityRequest(entity, op.FirstEntityId, timeoutMillis);
        }
        break;
      }

      case OpType.CreateEntityResponse:
      {
        var op = opList.GetCreateEntityResponseOp(i);
        if (op.RequestId == entityCreationRequestId && op.StatusCode == StatusCode.Success)
        {
          entityDeletionRequestId = connection.SendDeleteEntityRequest(op.EntityId.Value, timeoutMillis);
        }
        break;
      }

      case OpType.DeleteEntityResponse:
      {
        var op = opList.GetCreateEntityResponseOp(i);
        if (op.RequestId == entityDeletionRequestId && op.StatusCode == StatusCode.Success)
        {
          System.Console.WriteLine("Successfully created and deleted entity.");
        }
        break;
      }
    }
  }
}

Entity ACLs

Entity ACLs are exposed to the worker as a component, and can be manipulated as any other component. It’s worth reading 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.

private static void AddAttributeSet(SchemaObject requirementSet, string attribute)
{
  var attributeSet = requirementSet.AddObject(1);
  attributeSet.AddString(1, attribute);
}

public static void AddComponentDelegations(CommandRequestOp op, Entity entity)
{
  const uint EntityACLComponentID = 50;
  const uint PositionComponentID = 54;
  const uint PlazerControlsComponentID = 2220;

  var entityAclSchema = SchemaComponentData.Create();
  var entityAclObject = entityAclSchema.GetFields();

  // This requirement set matches any worker with the attribute "client" or "physics".
  var requirementSetObject = entityAclObject.AddObject(1 /* read_acl ID */);
  AddAttributeSet(requirementSetObject, "client");
  AddAttributeSet(requirementSetObject, "physics");

  // Give authority over Position and EntityAcl to any physics worker, and over PlayerControls to the caller worker.
  var positionWriteAcl = entityAclObject.AddObject(2 /* component_write_acl ID */);
  positionWriteAcl.AddUint32(1, PositionComponentID);
  var entityAclWriteAcl = entityAclObject.AddObject(2);
  entityAclWriteAcl.AddUint32(1, EntityACLComponentID);
  var playerControlsWriteAcl = entityAclObject.AddObject(2);
  playerControlsWriteAcl.AddUint32(1, PlazerControlsComponentID);

  // This requirement set matches only the command caller, i.e. the worker that issued the command,
  // since callerWorkerAttributes includes the caller's unique attribute.
  var callerWorkerRequirementSet = playerControlsWriteAcl.AddObject(2);
  AddAttributeSet(callerWorkerRequirementSet, "workerId:" + op.CallerWorkerId);

  // This requirement set matches any worker with the attribute "physics".
  var positionRequirementSet = positionWriteAcl.AddObject(2);
  AddAttributeSet(positionRequirementSet, "physics");
  var entityAclRequirementSet = entityAclWriteAcl.AddObject(2);
  AddAttributeSet(entityAclRequirementSet, "physics");

  entity.Add(new ComponentData(EntityACLComponentID, entityAclSchema));
}

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 by sending changes to the EntityAcl component through a component update.

Since there is no automated code generation for C# bindings, in order to do this, you need to manually serialize the SchemaObjects as shown in the previous example, this time serializing everything into a SchemaComponentUpdate instead of a SchemaComponentData, and mapping the Position component to callerWorkerRequirementSet when creating the component_write_acl of the EntityAcl component.

public static SchemaComponentUpdate DelegateComponent(string workerId)
{
  const uint PositionComponentID = 54;

  var entityAclSchema = SchemaComponentUpdate.Create();
  var entityAclObject = entityAclSchema.GetFields();

  // add read_acl
  // ...

  var positionWriteAcl = entityAclObject.AddObject(2 /* component_write_acl ID */);
  positionWriteAcl.AddUint32(1, PositionComponentID);
  var positionRequirementSet = positionWriteAcl.AddObject(2);
  AddAttributeSet(positionRequirementSet, "workerId:" + workerId);

  // add other write_acls
  // ...

  return entityAclSchema;
}


————
2019-11-25 Page added without editorial review.

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums