Creating and deleting entities
Using the Improbable.Worker.Connection
,
a worker can request SpatialOS to:
- reserve one entity ID, using
SendReserveEntityIdRequest
- reserve a range of entity IDs, using
SendReserveEntityIdsRequest
- create an entity, using
SendCreateEntityRequest
- delete an entity, using
SendDeleteEntityRequest
For all of these methods:
- They return an
Improbable.Worker.RequestId<C>
request ID. This can be used to match a request with its response. The type parameterC
depends on the type of request being sent. - The request can fail, so check the
StatusCode
in theOp
in the callback, and retry as necessary. The caller will always get a response callback, but it might be due to a timeout.
Reserve an entity ID
Reserve one entity ID using the Connection
method
SendReserveEntityIdRequest
.
SendReserveEntityIdRequest
takes an optional timeout.
You’ll receive the response via the
Dispatcher
callback
OnReserveEntityIdResponse
.
If the operation succeeds, the response contains an entity ID, which is guaranteed to be unused
in the current deployment.
The Op
in the callback is
Worker.ReserveEntityIdResponseOp
.
Reserve a range of entity IDs
Reserve a range of entity IDs using the Connection
method
SendReserveEntityIdsRequest
.
SendReserveEntityIdsRequest
takes an optional timeout.
You’ll 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
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.
The Op
in the callback is
worker.ReserveEntityIdsOp
.
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
SendReserveEntityIdRequest
orSendReserveEntityIdsRequest
) - an optional timeout
You’ll receive the response via the
Dispatcher
callback
OnCreateEntityResponse
.
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 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:
private static void CreateDeleteEntity(Dispatcher dispatcher, Connection connection)
{
const uint timeoutMillis = 500u;
const string entityType = "Player";
// Reserve an entity ID.
var entityIdReservationRequestId = connection.SendReserveEntityIdRequest(timeoutMillis);
// When the reservation succeeds, create an entity with the reserved ID.
var entityCreationRequestId = default(RequestId<CreateEntityRequest>);
dispatcher.OnReserveEntityIdResponse(op =>
{
if (op.RequestId == entityIdReservationRequestId && op.StatusCode == StatusCode.Success)
{
var entity = new Entity();
// Empty ACL - should be customised.
entity.Add(new Improbable.EntityAcl.Data(
new Improbable.WorkerRequirementSet(new Improbable.Collections.List<Improbable.WorkerAttributeSet>()),
new Improbable.Collections.Map<uint, Improbable.WorkerRequirementSet>()));
// Needed for the entity to be persisted in snapshots.
entity.Add(new Improbable.Persistence.Data());
entity.Add(new Improbable.Metadata.Data(entityType));
entity.Add(new Improbable.Position.Data(new Improbable.Coordinates(1, 2, 3)));
entityCreationRequestId = connection.SendCreateEntityRequest(entity, op.EntityId, timeoutMillis);
}
});
// When the creation succeeds, delete the entity.
var entityDeletionRequestId = default(RequestId<DeleteEntityRequest>);
dispatcher.OnCreateEntityResponse(op =>
{
if (op.RequestId == entityCreationRequestId && op.StatusCode == StatusCode.Success)
{
entityDeletionRequestId = connection.SendDeleteEntityRequest(op.EntityId.Value, timeoutMillis);
}
});
// When the deletion succeeds, we're done.
dispatcher.OnDeleteEntityResponse(op =>
{
if (op.RequestId == entityDeletionRequestId && op.StatusCode == StatusCode.Success)
{
System.Console.WriteLine("Successfully created and deleted entity.");
}
});
}
Entity ACLs
Entity ACLs are exposed to the worker as a component, and can be manipulated as any other component. It’s worth reading Understanding 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 authority over the
EntityAcl
component.
Example of creating an ACL
This example adds an EntityAcl
to an Entity
given a CommandRequestOp
,
which is currently the only way to make a specific worker (as opposed to, potentially, a set of workers)
to qualify for authority of a component. Specifically:
- The entity will be visible to workers that have the “client” or “physics” worker attribute.
- Any worker with “physics” as one of its attributes can have authority over the
entity’s
Position
andEntityAcl
components. - The worker, which issued the
CommandRequestOp
, is the only worker that can be authoritative over thePlayerControls
component.
This can be used as part of creating a new entity in response to a command request.
public static void AddComponentDelegations<C>(CommandRequestOp<C> op, Entity entity) where C : ICommandMetaclass
{
// 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 callerWorkerAttributes = new Improbable.Collections.List<Improbable.WorkerAttributeSet>
{
new Improbable.WorkerAttributeSet(op.CallerAttributeSet)
};
var callerWorkerRequirementSet = new Improbable.WorkerRequirementSet(callerWorkerAttributes);
// This requirement set matches any worker with the attribute "physics".
var physicsWorkerRequirementSet = new Improbable.WorkerRequirementSet(
new Improbable.Collections.List<Improbable.WorkerAttributeSet>
{
new Improbable.WorkerAttributeSet(new Improbable.Collections.List<string> {"physics"})
});
// This requirement set matches any worker with the attribute "client" or "physics".
var clientOrPhysicsWorkerRequirementSet = new Improbable.WorkerRequirementSet(
new Improbable.Collections.List<Improbable.WorkerAttributeSet>
{
new Improbable.WorkerAttributeSet(new Improbable.Collections.List<string> {"client"}),
new Improbable.WorkerAttributeSet(new Improbable.Collections.List<string> {"physics"})
});
// Give authority over Position and EntityAcl to any physics worker, and over PlayerControls to the caller worker.
var writeAcl = new Improbable.Collections.Map<uint, Improbable.WorkerRequirementSet>
{
{Improbable.Position.ComponentId, physicsWorkerRequirementSet},
{Improbable.EntityAcl.ComponentId, physicsWorkerRequirementSet},
{PlayerControls.ComponentId, callerWorkerRequirementSet}
};
entity.Add(new Improbable.EntityAcl.Data( /* read */ clientOrPhysicsWorkerRequirementSet, /* write */ writeAcl));
}
Example of changing authority
The worker authoritative over the EntityAcl
component can decide to give the 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:
public static Improbable.EntityAcl.Data DelegateComponent(
Improbable.EntityAcl.Data currentAcl,
uint componentId,
Improbable.WorkerRequirementSet requirementSet)
{
// Take a deep copy, so that this does not modify the current EntityAcl.
var newAcl = currentAcl.DeepCopy();
// Set the write ACL for the specified component to the specified attribute set,
// assuming the componentAcl option is not empty.
newAcl.Value.componentWriteAcl[componentId] = requirementSet;
return newAcl;
}
For these changes to take effect, the worker authoritative over the
EntityAcl
component needs send the
changes through a component update.