Sites

Menu
These are the docs for 12.2, an old version of SpatialOS. The docs for this version are frozen: we do not correct, update or republish them. 14.2 is the newest →

Sending data to SpatialOS

All code examples on this page assume import improbable.worker.*; and import example.*; (the generated code namespace).

A worker can send data such as logging, metrics, and component updates to SpatialOS using the improbable.worker.Connection.

Component updates

Sending component updates

When the worker has authority over a component on an entity, it can send component updates to SpatialOS by using the improbable.worker.Connection method sendComponentUpdate, which takes an EntityId instance and an instance of appropriate update class defined in the schema-generated code. The update classes are always called Update and are defined as a static nested classes of each implementation of ComponentMetaclass. For example, to update a component MyComponent, the appropriate update class is nested in it and can by accessed as MyComponent.Update.

A component update can modify the value of a property or trigger an event. You can modify multiple properties and trigger multiple events in the same component update.

Component updates sent by the worker will appear in the operation list returned by a subsequent call to improbable.worker.Connection.getOpList. This means that Ops.ComponentUpdate callbacks will also be invoked for component updates triggered by the worker itself, but not immediately.

There are a couple of (related) reasons that callbacks for sent component updates should not be invoked immediately:

  • This can lead to extremely unintuitive control flow when components are recursively updated inside a callback.
  • It violates the guarantee that callbacks are only invoked as a result of a call to the process method on the improbable.worker.Dispatcher.

Receiving component updates

To be notified when a worker receives a component update on an entity in the worker’s local view of the simulation, use the improbable.worker.Dispatcher method onComponentUpdate with the same ComponentMetaclass as for sending updates.

Note that the component updates:

  • can be partial, i.e. only update some properties of the component
  • do not necessarily have to contain data that is different from the workers current view of the component
  • could have been sent by SpatialOS rather than a worker for synchronization purposes.

Sending and receiving component events

Sending and receiving events works much in the same way as component updates. For a schema like the following,

package example;

type SwitchToggled {
  int64 time = 1;
}

component Switch {
  id = 1234;
  bool is_enabled = 1;
  event SwitchToggled toggled;
}

To trigger an event:

private static void triggerEvent(Connection connection, EntityId entityId) {
  Switch.Update update = new Switch.Update();
  update.addToggled(new SwitchToggled(1));
  connection.sendComponentUpdate(Switch.COMPONENT, entityId, update);
}

If you are not authoritative on the component, your event will be silently ignored.

Receiving an event works just like receiving a component update, by registering a callback on the dispatcher:

callbackKey = dispatcher.onComponentUpdate(Switch.COMPONENT, op -> {
  Switch.Update update = op.update;
  for (SwitchToggled toggleEvent : update.getToggled()) {
    System.out.println("Switch has been toggled at " + toggleEvent.getTime());
  }
});

Updating component interests

For each entity in its view, a worker receives updates for a set of the entity’s components. This set is the union of the set of components the worker has authority over, and a set of components it is explicitly interested in.

The initial set of explicitly interested components can be configured in the bridge settings. This is the default set of explicit interest for every entity. At runtime, the worker can override this set on a per-entity basis, by using the Connection.sendComponentInterest method.

Whenever the set of interested components for an entity changes, the worker will receive the appropriate onAddComponent and onRemoveComponent callbacks to reflect this change.

For example, you might be interested in one specific switch, but not every switch in the world:

Note that a worker is always considered to be interested in the components of the entities it is authoritative on, in addition to any others specificed in the bridge settings or using the method described above.

Component commands

Sending component commands

To send a command request, use the improbable.worker.Connection method sendCommandRequest. The command is executed by the worker that currently has authority over the component that contains the command on the entity specified in the command request.

The sendCommandRequest function takes:

  • a Class<C> instance
  • an entity ID
  • a request object
  • an optional timeout
  • an optional CommandParameters object

where:

  • The C type needs to be a subclass of CommandMetaclass defined in the schema-generated code.
  • The request object needs to be of the same type as defined in schema for given command C.
  • The CommandParameters object contains a field called AllowShortCircuit, which if set to true will try to “short-circuit” the command and avoid a round trip to SpatialOS in some cases. For more information, see documentation on commands.

Before sending the command, a callback to handle the response should be registered with the improbable.worker.Dispatcher with onCommandResponse(Class<C>). The request ID (of type improbable.worker.RequestId<OutgoingCommandRequest>) returned by sendCommandRequest can be matched up with the one in the improbable.worker.Ops.CommandResponse to identify the request that is being responded to.

Receiving component commands

To handle commands that another worker issues, use the opposite flow:

  1. Register a callback with the improbable.worker.Dispatcher with onCommandRequest(Class<C>).
  2. When the callback is executed:
    • To respond to the command, the worker should call the improbable.worker.Connection method sendCommandResponse, which provides the request ID (of type improbable.worker.RequestId<IncomingCommandRequest>) from the improbable.worker.Ops.CommandRequest and an appropriate response object. The request ID should match the response type defined in the schema.
    • To fail the command, the worker can call sendCommandFailure.

Command failures

Commands can fail. When a command fails, check the improbable.worker.StatusCode field in the improbable.worker.Ops.CommandResponse, and then retry the command as necessary.

The caller always gets a response callback, which can be one of the following failure cases:

  • APPLICATION_ERROR: The command is rejected by the target worker or by SpatialOS.
  • AUTHORITY_LOST: The target worker lost authority, or no worker had authority.
  • NOT_FOUND: The target entity, or target component on the entity, didn’t exist.
  • PERMISSION_DENIED: The sending worker didn’t have permission to send request.
  • TIMEOUT
  • INTERNAL_ERROR: A bug might exist in SpatialOS. Raise a support request or ask on our forums.

Entity queries

Note: In order to send an entity query, a worker must have permission to do so. For more information, see the Worker permissions page.

A worker can run remote entity queries against the simulation by using the improbable.worker.Connection method sendEntityQueryRequest. This takes an improbable.worker.Query.EntityQuery object and an optional timeout.

The query object is made of an improbable.worker.Query.Constraint and an improbable.worker.Query.ResultType. The constraint determines which entities are matched by the query, and the result type determines what data is returned for matched entities. Available constraints and result types are described below.

Constraint Description
EntityIdConstraint Matches a specific entity ID.
ComponentConstraint Matches entities with a particular component.
SphereConstraint Matches entities contained in the given sphere.
AndConstraint Matches entities that match all of the given subconstraints.
OrConstraint Matches entities that match any of the given subconstraints.
NotConstraint Matches entities that do not match the given subconstraint.
Result type Description
CountResultType Returns the number of entities that matched the query.
SnapshotResultType Returns a snapshot of component data for entities that matched the query. To select all components, use new SnapshotResultType(). To select every component whose ID is contained in the given set, use new SnapshotResultType(componentIdSet) (thus, pass an empty set to get no components but entity IDs only).

Important: You should keep entity queries as limited as possible. All queries hit the network and cause a runtime lookup, which is expensive even in the best cases. This means you should:

  • always limit queries to a specific sphere of the world
  • only return the information you need from queries (eg the specific components you care about)
  • if you’re looking for entities that are within your worker’s checkout radius, search internally on the worker instead of using a query

Like other request methods, this returns an improbable.worker.RequestId request ID, which can be used to match a request with its response. The response is received via a callback registered with the improbable.worker.Dispatcher using the onEntityQueryResponse method.

The Ops.EntityQueryResponse contains an int resultCount field (for CountResultType requests) and a Map<EntityId, Entity> (for SnapshotResultType) requests. Again, success or failure of the request is indicated by the statusCode field of the response object, but in the failure case the result may still contain some data: the count or snapshot map might still contain the data for some entities that matched the query, but won’t necessarily contain all matching entities. This is because the worker might still be able to do something useful with a partial result.

Sending and receiving metrics

You can optionally send metrics by calling Connection.sendMetrics. Metrics can be viewed on deployment dashboards and in the Inspector.

Typical use cases for sending metrics are as follows:

  • Reporting your own metrics. There are APIs for both time series and histogram metrics. However, only time series metrics are currently exposed via the Inspector. Unlike the metrics that are listed on Metrics reference, your own metrics are not persistent.
  • Updating a worker’s load. The load of a worker is a floating-pointing value. The reported values direct SpatialOS’s load balancing strategy:

    • A value of 0 indicates an unloaded worker.
    • Values above 1 correspond to an overloaded worker.

Example

The following example demonstrates both use cases:

// A queue of tasks the worker has to complete.
private final static java.util.Queue TaskQueue = new java.util.ArrayDeque<>();
private final static double MAXIMUM_QUEUE_SIZE = 200.0; // An arbitrary maximum value.

// Collect and send user-defined metrics to SpatialOS.
// Note that you should generally call sendMetrics on the same user thread as you call
// other Connection methods.
private static void sendUserMetrics(Connection connection) {
  Metrics metrics = new Metrics();
  // Update the current load of the worker.
  double load = TaskQueue.size() / MAXIMUM_QUEUE_SIZE;
  metrics.load = improbable.collections.Option.of(load);
  // Add custom metrics.
  metrics.gaugeMetrics.put("MyCustomMetric", 1.0);
  connection.sendMetrics(metrics);
}

Workers automatically send several built-in, internal metrics at a period defined by the builtInMetricsReportPeriodMillis field of ConnectionParameters. You can register a callback to receive these metrics inside a Ops.MetricsOp using Dispatcher.OnMetrics.

Search results

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums