Get SpatialOS

Sites

Menu
You are viewing the docs for 11.0, an old version of SpatialOS. 12.0 is the newest →

The Entity Pipeline API and this guide for building a custom entity pipeline implementation is currently experimental. We're looking for feedback - please get in touch on the forums if you have any thoughts.

Entity pipeline

The entity pipeline is the logic which a worker uses to create and manage their representations of entities.

A SpatialOS deployment communicates with connected workers to instruct them when an entity needs to be added/removed from the worker’s view. For example, when an entity moves into or out of a worker’s checkout area. The entity pipeline handles:

  • choosing how to represent an entity locally - in Unity, we use a prefab to represent an entity
  • dealing with changes in an entity’s components (one being added or removed) and changes in the properties of those components (values being changed, events being triggered)

In most cases, you won’t need to interact with the entity pipeline: the default implementation should suit most people’s needs.

However, if you need to customize any of this process (how entities are created, changed, and removed), you can use the Entity Pipeline APIs and overide the default pipeline with an implementation of your own: either by modifying the default implementation or by writing an implementation from scratch.

What the entity pipeline does

Representing entities

Different workers may represent the same entity in different ways. Unity workers represent entities using GameObjects, based on a specified prefab; that isn’t true for other types of workers.

Entities normally have a Metadata component with an entity_Type string property that, for Unity workers, can be used to specify which prefab to associate with the entity.

Placing entities in the scene

All entities have a Position component that is their canonical position in the world. It is a vector of three double values, and it’s used to govern which entities a worker checks out, load-balancing, and more.

In most cases, entities need to be positioned on a worker in a way that meaningfully represents their Position in the simulated world. However, there are some cases where you’d want to implement custom logic for transforming positions - for example, if the world is very large or very small. Or you might want a secondary representation for entity position.

Usually you’d want to use the Position component, but you can also define additional components to represent an entity’s position if you need to.

When entities change, workers get instructions to make the relevant change: eg AddComponent, RemoveComponent, ComponentUpdate, AuthorityChange. By default, UnitySDK handles these instructions, and you can use the code generated by codegen from your schema to respond to these changes - for example, you can register methods to be called when a particular component changes.

However, you can also build a bespoke process for processing those intructions.

Customizing the entity pipeline

Architecture

In order to understand how you can customize the pipeline, this section explains what parts it has.

The entity pipeline comprises a sequence of blocks that implement the IEntityPipelineBlock interface:

Entity Pipeline diagram

Each pipeline block is added to the pipeline in the order that pipeline instructions (“ops”) should be processed. Every pipeline blocks supports the following instructions:

  • AddEntity: An entity is added to the worker.
  • RemoveEntity: An entity is removed from the worker.
  • AddComponent: A component is added to an entity.
  • UpdateComponent: A component’s properties were updated or component events were triggered.
  • RemoveComponent: A component is removed from an entity.
  • ChangeAuthority: The worker has gained or lost write access to a component of an entity.
  • CriticalSection: The worker has entered or left a critical section. Used for marking atomic collections of instructions.

A worker begins to receive ops once it is connected to SpatialOS. The entity pipeline passes the received ops to the first block of the pipeline, which may perform some action as a result. It may then pass the ops to the next pipeline block (NextEntityBlock) for further processing.

Note: This is optional, so it is possible for the blocks to reorder, filter out or suppress ops.

This means you can create a pipeline implementation that can wait for a certain condition to be satisfied before an entity is spawned. Blocks may process their internal state (possibly emitting ops as a result) inside the body of a ProcessOps() method, which is called periodically (typically once per frame).

The default entity pipeline

If you choose to not set up a custom entity pipeline, the Unity SDK will default to the following logic.

The default entity pipeline consists of the following blocks:

  • CriticalSectionPipelineBlock : Buffer all ops that are part of a critical section and release them to the next block as soon as the critical section ends.
  • ThrottledEntityDispatcher: Limit the number of entities that can be spawned in a single frame and defer entity spawning to the next frame if necessary.
  • LegacyEntityCreator: Spawn and delete entities - Create a GameObject in the current scene according to the entity_type property in the Metadata component of an entity.

    Note: This pipeline block requires that the Position and Metadata components are added to all entities on the worker. The LegacyEntityCreator will not be able to spawn a entity otherwise.

    Make sure that those two components are attached to every entity (see Creating an entity for more details) and that your bridge configuration adds these components to your worker.

    If you can’t include the components, consider replacing this block with a modified version of LegacyEntityCreator.

  • LegacyComponentPipeline: Process component related ops (AddComponent, RemoveComponent, ComponentUpdate, AuthorityChange) and apply changes to code-generated readers/writers.

  • EntityComponentUpdater: Process component related ops (AddComponent, RemoveComponent, ComponentUpdate, AuthorityChange) and apply changes to code-generated monobehaviour components.

Example of a custom entity pipeline

Suppose you want to build a simple entity pipeline that spawns a sphere into the scene when an entity is added.

  1. First, add this very simple pipeline block:

    internal class SimpleEntityPipeline : IEntityPipelineBlock
    {
        public void AddEntity(AddEntityOp addEntity)
        {
            var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            Object.Instantiate(sphere, new Vector3(), Quaternion.identity);
        }
        // ...
    }
    
  2. In Bootstrap.cs, register the block with the pipeline:

    private void Start()
    {
        // ...
    
        EntityPipeline.Instance.AddBlock(new SimpleEntityPipeline());
    
        // ...
    }
    

    You must set up your entity pipeline before a connection with SpatialOS is established. Once the connection is established, you cannot add or remove blocks from the entity pipeline.

    That’s all that’s needed for a minimal implementation. The entity pipeline receives an AddEntityOp for every entity checked out by the worker and relays it to the first block: in this case, the SimpleEntityPipeline which creates the game object.

    This works, but it doesn’t reflect the state of the simulated world: all the entities are spawned at the origin.

    To spawn the entities at their actual location, you need to receive the Position component associated with the entity.

  3. Extend the pipeline block to collect both types of ops:

    internal class SimpleEntityPipeline : IEntityPipelineBlock
    {
        private readonly Queue<AddEntityOp> entitiesToAdd = new Queue<AddEntityOp>();
        private readonly IDictionary<EntityId, PositionComponentData> positions = new Dictionary<EntityId, PositionComponentData>();
    
        public void AddEntity(AddEntityOp addEntity)
        {
            entitiesToAdd.Enqueue(addEntity);
        }
    
        public void AddComponent(AddComponentPipelineOp addComponentOp)
        {
            if (addComponentOp.Component.ComponentId == Position.ComponentId)
            {
                positions[addComponentOp.EntityId] = ((PositionComponent.Impl) addComponentOp.ComponentObject).Data; 
            }
        }
        // ...
    }
    
  4. The object should be instantiated when both the AddEntityOp and AddComponent op for Position have been received. Implement this logic in the ProcessOps() method:

    public override void ProcessOps()
    {
        while (entitiesToAdd.Count > 0)
        {
            var entity = entitiesToAdd.Peek();
            var entityId = entity.EntityId;
                
            PositionData position;
            if (positions.TryGetValue(entity.EntityId, out position))
            {
                var gameObject = Object.Instantiate(sphere, position.coords.ToVector3(), Quaternion.identity);
                positions.Remove(entityId);
                entitiesToAdd.Dequeue();
            }
        }
    }
    

The spheres should now appear in the correct location.

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums