Get SpatialOS

Sites

Menu

Query-based interest (beta)

To use query-based interest, you must first upgrade to the new Runtime (available from SpatialOS version 13.4).

Query-based interest is a way of specifying what a worker is interested in. You can use it instead of streaming queries and component delivery settings.

What’s different about query-based interest?

How interest works without query-based interest

Entity interest means that a worker is interested in:

  • chunks that contain entities that it has write access to a component on
  • chunks within a configurable radius of those entities

If you want a worker to be interested in anything other than the chunks determined by entity interest, you have to use streaming queries or specify component delivery settings.

How query-based interest changes this

Entity interest still works in exactly the same way.

But if you want a worker to be interested in anything outside the chunks determined by entity interest, you can specify interest based on the components you’re authoritative over, using queries. You can use these queries to express precisely which components on other entities you are interested in, meaning that the worker only receives updates relevant to what it is simulating.

One key aspect of the per-component nature of query-based interest is that it encourages more granular components, and the linking of functionality to a specific component.

Enabling and specifying query-based interest

You enable query-based interest on individual entities, rather than universally. This means you can selectively enable query-based interest only for the situations in which you currently use streaming queries or component delivery settings. In other words, you should enable query-based interest in any of the following situations:

  • You need a worker to be interested in more than what is specified by entity interest.
  • You need to reduce the amount of data being streamed as a result of entity interest.
  • You want more granular control over what a worker is interested in.

To enable query-based interest for an entity, add the standard schema library component improbable.Interest (see schema definition) to that entity. This lets you specify interest based on the components you’re authoritative over, using queries. It provides a mapping from a component ID to a list of queries, allowing you to define what other components a worker should receive updates for when it becomes authoritative over a component.

For example, if you want to control the interest of a worker that’s simulating the position of a player entity, you could use the Position component (ID 54) as the key to map the queries to, and then use the queries to specify which components this worker should be interested in.

Entities that have the improbable.Interest component will use query-based interest instead of entity interest. This means you need to make sure the query accounts for all updates that were previously covered by entity interest, plus any other updates that were specified by streaming queries or component delivery settings.

Relative constraints

Within the Interest component you can specify the queries that are made possible through the entity query API, plus some new queries, including queries relative to the position of the entity.

For example, you could use a RelativeSphereConstraint to specify a sphere of interest centered around the Position of the entity being simulated. Each QueryConstraint message can only specify one of the available fields, but you can compose multiple constraints using the and_constraint and or_constraint fields.

The available constraints are as follows:

type ComponentInterest {
   type Query {
       QueryConstraint constraint = 1;

       // Either full_snapshot_result or a list of result_component_id 
       // should be provided. Providing both is invalid.
       option<bool> full_snapshot_result = 2;
       list<uint32> result_component_id = 3;
   }

   type QueryConstraint {
       // Only one constraint should be provided. Providing more than one is
       // invalid.

       option<SphereConstraint> sphere_constraint = 1;
       option<CylinderConstraint> cylinder_constraint = 2;
       option<BoxConstraint> box_constraint = 3;
       option<RelativeSphereConstraint> relative_sphere_constraint = 4;
       option<RelativeCylinderConstraint> relative_cylinder_constraint = 5;
       option<RelativeBoxConstraint> relative_box_constraint = 6;
       option<int64> entity_id_constraint = 7;
       option<uint32> component_constraint = 8;
       list<QueryConstraint> and_constraint = 9;
       list<QueryConstraint> or_constraint = 10;
   }
   

   type SphereConstraint {
       Coordinates center = 1;
       double radius = 2;
   }

   type CylinderConstraint {
       Coordinates center = 1;
       double radius = 2;
   }

   type BoxConstraint {
       Coordinates center = 1;
       EdgeLength edge_length = 2;
   }

   type RelativeSphereConstraint {
       double radius = 1;
   }

   type RelativeCylinderConstraint {
       double radius = 1;
   }

   type RelativeBoxConstraint {
       EdgeLength edge_length = 1;
   }

   ...
}

Examples

Minimap

Imagine a simple game that displays a minimap to players. In our simple game, we have three components: PlayerInfo, PlayerControls, and MinimapRepresentation.

  • Our client-worker is authoritative over the PlayerControls component.
  • Our server-worker is authoritative over the PlayerInfo and MinimapRepresentation components.
component PlayerInfo {
   id = 2000;

   int32 player_id = 1;
}

component PlayerControls {
   id = 2001;

   int32 input_value = 1;
}

component MinimapRepresentation {
   id = 2002;

   uint32 map_icon = 1;
   uint32 faction = 2;
}

When it comes to specifying the interest for player entities, there are two things our client-worker wants to observe:

  • Players within a 20m radius.
  • Minimap objects within a 50m × 50m box.

This translates to two queries:

  • I want to receive Position and PlayerInfo component updates for entities with the PlayerInfo component (ID = 2000) within a 20m radius of my current position in order to draw those players.
  • I want to receive Position and MinimapRepresentation component updates for entities with the MinimapRepresentation component (ID = 2002) within a 50m × 50m box of my current position, in order to draw the minimap.

Because we’re specifying interest for the client-worker, we tie the interest to the component that the client-worker is authoritative over: PlayerControls (ID = 2001).

In C#, the code for specifying this interest would be as follows:

var playerConstraint = new QueryConstraint() {
 andConstraint = new Collections.List<QueryConstraint>() {
   new QueryConstraint() {
     relativeSphereConstraint = new RelativeSphereConstraint(20) },
   new QueryConstraint() {
     componentConstraint = PlayerInfo.ComponentId
   }
 }
};

var minimapConstraint = new QueryConstraint() {
 andConstraint = new Collections.List<QueryConstraint>() {
   new QueryConstraint() {
     relativeBoxConstraint = new RelativeBoxConstraint(
       new EdgeLength(50, double.PositiveInfinity, 50)) },
   new QueryConstraint() {
     componentConstraint = MinimapRepresentation.ComponentId
   }
 }
};

var interestForPlayerControls = new ComponentInterest() {
 queries = new Collections.List<Query>() {
   new Query() {
     constraint = playerConstraint,
     resultComponentId = new Collections.List<uint>() {
       Position.ComponentId, PlayerInfo.ComponentId
     }
   },
   new Query() {
     constraint = minimapConstraint,
     resultComponentId = new Collections.List<uint>() {
       Position.ComponentId, MinimapRepresentation.ComponentId
     }
   }
 }
};

entity.Add<Interest>(new Interest.Data(
 new Collections.Map<uint, ComponentInterest>() {
   { PlayerControls.ComponentId, interestForPlayerControls }
 }));

The worker that’s authoritative over the Interest component on an existing entity can make changes to the interest query at runtime. For example, you can make it so that if a player closes the minimap UI, this removes the minimap query from the interest query.

Teams

This example demonstrates how a player could observe the position of all other players on their team.

Say there is a Red team and a Blue team. Entities representing players have either the RedTeam or BlueTeam component, expressing which team they belong to:

component PlayerControls {
   id = 2001;

   int32 input_value = 1;
}

component RedTeam {
   id = 2004;
}

component BlueTeam {
   id = 2005;
}

In our example we have a server-worker which allocates the RedTeam or BlueTeam component to player entities to split them into teams.

We also have a client-worker that is authoritative over a PlayerControls component on a player entity. We’ll use this component as the key in our Interest map.

Corresponding to this key is a single query for the Position component of all entities in the world with the same team component as the player entity. The component to query for is determined by checking which component, if any, the player entity has. So, if the player entity has the RedTeam component, the query will query for all other entities with the RedTeam component:


var teamComponentId = uint.MaxValue;
if (playerEntity.Get<RedTeam>().HasValue) {
 teamComponentId = RedTeam.ComponentId;
} else if (playerEntity.Get<BlueTeam>().HasValue) {
 teamComponentId = BlueTeam.ComponentId;
}

if (teamComponentId == uint.MaxValue) {
 return;
}

var teamInterest = new ComponentInterest() {
   queries = new Collections.List<Query>() {
     new Query() {
       constraint = new QueryConstraint() {
         componentConstraint = teamComponentId
       },
       resultComponentId = new Collections.List<uint>() {
         Position.ComponentId
       }
     }
   }
 };

playerEntity.Add<Interest>(new Interest.Data(
 new Collections.Map<uint, ComponentInterest>() {
   { PlayerControls.ComponentId, teamInterest }
 }));

Upcoming features

  • Visualising query-based interest in the Inspector

    We’re working on improving the visualisation of query-based interest in the Inspector.

  • Rate limiting

    We’re planning to add the option to specify the rate at which you want to receive updates about particular components. We’re likely to make this available as a new field in the query message, Frequency, which is specified in hertz.

    For example, you might want to receive frequent updates on players’ positions (say at a rate of 60Hz for a first-person shooter game) but less frequent updates for objects displayed on a minimap (say 1Hz).

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums