Sending data to SpatialOS
Use the Improbable.Worker.Connection
to send data such as component updates, logs, and metrics to SpatialOS.
Component updates
Sending component updates
When the worker instance has write access authority over a component on an entity, it can send
component updates for that component to SpatialOS.
Do this using the method Connection.SendComponentUpdate<C>
,
which takes an IComponentUpdate<C>
object. The generic type parameter should be a subclass of
IComponentMetaclass
defined in the schema-generated code.
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 instance will appear in the operation list returned by a subsequent call to
Connection.GetOpList
. This means thatOnComponentUpdate
callbacks will also be invoked for component updates triggered by the worker instance 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 theDispatcher
.
SendComponentUpdate
also takes an optional parameter (of type
UpdateParameters
)
which you can use to modify the update sending behaviour. The only option in this parameter is
Loopback
, which defaults to
ShortCircuited
,
meaning that the update will be added to the operation list of the worker from which it was sent. Set
Loopback
to
None
to
disable this behaviour. This can be useful if updates are applied locally before they are sent.
Receiving component updates
To be notified when a worker instance receives a component update on an entity in the worker instance’s local
view of the simulation, use the Dispatcher
method OnComponentUpdate
with the same IComponentMetaclass
generic parameter C
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 worker instance’s current view of the component
- could have been sent by SpatialOS rather than a worker instance for synchronization purposes
Sending and receiving component events
Sending and receiving events works much in the same way as property 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)
{
var update = new Example.Switch.Update();
update.AddToggled(new Example.SwitchToggled(1));
connection.SendComponentUpdate(Example.Switch.Metaclass, entityId, update);
}
If you do not have write access authority over 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:
dispatcher.OnComponentUpdate(Switch.Metaclass, op =>
{
var update = op.Update;
foreach (var toggleEvent in update.toggled)
{
System.Console.WriteLine("Switch has been toggled at {0}.", toggleEvent.time);
}
})
Configuring interest in components
A worker instance gets updates from the entity database in the SpatialOS Runtime about any entity components that it has active read access to. There are three prerequisites for active read access:
- read access permission, which you set up on the
EntityAcl
component - interest, which can be either chunk-based or query-based
- component filters, which can be static or dynamic
When the set of entity components that a worker instance has interest in changes, the worker instance receives an
OnAddComponent
and an OnRemoveComponent
callback to
reflect this change.
For example, you might want to receive updates about one specific switch, but not every switch in the world:
private static void SendComponentInterest(Connection connection, EntityId entityId)
{
var interestOverrides = new System.Collections.Generic.Dictionary<uint, InterestOverride>
{
{ Switch.ComponentId, new InterestOverride { IsInterested = true } }
};
connection.SendComponentInterest(entityId, interestOverrides);
}
Component commands
Sending component commands
To send a command request, use the Connection
method SendCommandRequest<C>
.
The command is executed by the worker instance that currently has write access authority over the component containing the command
on the entity specified in the command request.
SendCommandRequest<C>
takes:
- an entity ID
- an optional timeout
- an
ICommandRequest<C>
object an optional
CommandParameters
objectThis 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. See documentation on commands for more information.The generic type parameter should be a subclass of
ICommandMetaclass
defined in the schema-generated code.
Before sending the command, register a callback to handle the response with the
Dispatcher
with OnCommandResponse<C>
.
You can match up the request ID returned by
SendCommandRequest
(of type
Improbable.Worker.RequestId<OutgoingCommandRequest>
)
with the one in the
CommandResponseOp
to identify the request that is being responded to.
Receiving component commands
To handle commands issued by another worker instance, use the opposite flow:
- Register a callback with the
Dispatcher
withOnCommandRequest<C>
When the callback is executed:
To respond to the command, call the
Connection
methodSendCommandResponse<C>
. Supply the request ID (of typeImprobable.Worker.RequestId<IncomingCommandRequest>
) provided by theCommandRequestOp
and an appropriateImprobable.Worker.ICommandResponse<C>
response object.To fail the command, call
SendCommandFailure<C>
.
Command failures
Commands can fail. When a command fails, check the StatusCode
field in the
CommandResponseOp
, and then retry the command as necessary.
The caller always gets a response callback, which can be one of the following failure cases:
ApplicationError
: The command is rejected by the target worker instance or by SpatialOS.AuthorityLost
: The target worker instance lost write access authority, or no worker instance had write access authority.NotFound
: The target entity, or target component on the entity, didn’t exist.PermissionDenied
: The sending worker instance didn’t have permission to send request.Timeout
InternalError
: A bug might exist in SpatialOS. Raise a support request or ask on our forums.
For more detail on these status codes, see
the documentation for the StatusCode
enum.
Entity queries
A worker instance can run remote entity queries against the world by using the
Connection
method SendEntityQueryRequest
.
This takes an
Improbable.Worker.Query.EntityQuery
object and an optional timeout.
Like other request methods,
SendEntityQueryRequest
returns an Improbable.Worker.RequestId
request ID, which can be used to match a request with its response.
The query object
The query object is made of:
An
Improbable.Worker.Query.IConstraint
, which determines which entities are matched by the query.Available constraints:
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.
Note: An
AndConstraint
with no subconstraints is equivalent to atrue
query and will match all entities. Conversely, anOrConstraint
with no subconstraints is equivalent to afalse
query and will match no entities.
An
Improbable.Worker.Query.IResultType
, which determines what data is returned for matched entities.Available result types:
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).
The query response
The response is received via a callback registered with the
Dispatcher
using the
OnEntityQueryResponse
method.
The EntityQueryResponseOp
contains:
- an
int ResultCount
field (forCountResultType
requests) - a
Map<EntityId, Entity>
(forSnapshotResultType
) requests.
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
.
You can view metrics 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 instance’s load. The load of a worker instance is a floating-point value. The reported values direct SpatialOS’s load balancing strategy:
- A value of 0 indicates an unloaded worker instance.
- Values above 1 correspond to an overloaded worker instance.
Example
The following example demonstrates both use cases:
// A queue of tasks the worker has to complete.
private static readonly System.Collections.Queue TaskQueue = new System.Collections.Queue();
private const double MaximumQueueSize = 200; // An arbitrary maximum value.
private static void SendMetrics(Connection connection)
{
var load = System.Convert.ToDouble(TaskQueue.Count) / MaximumQueueSize;
var metrics = new Metrics { Load = load };
metrics.GaugeMetrics.Add("MyCustomMetric", 1.0);
connection.SendMetrics(metrics);
}
Worker instances 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
MetricsOp
using
Dispatcher.OnMetrics
:
private static void RegisterMetricsCallback(Dispatcher dispatcher)
{
dispatcher.OnMetrics(op => {
var shortCircuitRate = op.Metrics.GaugeMetrics["connection_command_request_short_circuit_rate"];
// Do something with the metric, or store it...
System.Console.WriteLine("Command requests short-circuited per second: " + shortCircuitRate);
});
}
The full list of built-in gauge metrics is as follows. All rate metrics are per-second.
Metric name (string ) |
Metric value (double ) |
---|---|
connection_send_queue_size |
The current size of the send queue (the messages waiting to be sent to SpatialOS). |
connection_send_queue_fill_rate |
The rate at which messages are being added to the send queue. |
connection_receive_queue_size |
The current size of the send queue. |
connection_receive_queue_fill_rate |
The rate at which messages are being received from SpatialOS and added to the receive queue. |
connection_oplist_queue_size |
The current size of the op list. |
connection_oplist_queue_fill_rate |
The rate at which ops are being added to the internal OpList (the queue of processed messages that workers operate on). |
connection_log_message_send_rate |
The rate at which log messages are being sent. |
connection_component_update_send_rate |
The rate at which component updates are being sent. |
connection_add_component_send_rate |
The rate at which add components are being added sent. |
connection_remove_component_send_rate |
The rate at which remove components are being sent. |
connection_command_request_send_rate |
The rate at which command requests are being sent. |
connection_command_response_send_rate |
The rate at which successful command responses are being sent. |
connection_command_failure_send_rate |
The rate at which command failure responses are being sent. |
connection_local_command_timeouts |
The total local commands that timed out when waiting for a response. |
connection_local_command_timeouts_rate |
The rate at which local commands time out when waiting for a response. |
connection_unexpected_command_response_receives |
The total unexpected command responses recieved. |
connection_unexpected_command_response_receives_rate |
The rate at which unexpected command responses are recieved. |
connection_reserve_entity_id_request_send_rate |
The rate at which requests to reserve an entity ID are being sent. |
connection_reserve_entity_ids_request_send_rate |
The rate at which requests to reserve multiple entity IDs are being sent. |
connection_create_entity_request_send_rate |
The rate at which entity creation requests are being sent. |
connection_delete_entity_request_send_rate |
The rate at which entity deletion requests are being sent. |
connection_entity_query_request_send_rate |
The rate at which entity query requests are being sent. |
connection_component_interest_send_rate |
The rate at which component interest updates are being sent. |
connection_authority_loss_imminent_acknowledgement_send_rate |
The rate at which imminent authority loss acknowledgements are being sent. |
connection_command_request_short_circuit_rate |
The rate at which command requests are being short-circuited. |
connection_command_response_short_circuit_rate |
The rate at which successful command responses are being short-circuited. |
connection_command_failure_short_circuit_rate |
The rate at which command failure responses are being sent. |
connection_flag_update_op_receive_rate |
The rate at which FlagUpdate Ops are being received. |
connection_critical_section_op_receive_rate |
The rate at which CriticalSection Ops are being received. |
connection_add_entity_op_receive_rate |
The rate at which AddEntity Ops are being received. |
connection_remove_entity_op_receive_rate |
The rate at which RemoveEntity Ops are being received. |
connection_reserve_entity_id_response_op_receive_rate |
The rate at which ReserveEntityIdResponse Ops are being received. |
connection_reserve_entity_ids_response_op_receive_rate |
The rate at which ReserveEntityIdsResponse Ops are being received. |
connection_create_entity_response_op_receive_rate |
The rate at which CreateEntityResponse Ops are being received. |
connection_delete_entity_response_op_receive_rate |
The rate at which DeleteEntityResponse Ops are being received. |
connection_entity_query_response_op_receive_rate |
The rate at which EntityQueryResponse Ops are being received. |
connection_add_component_op_receive_rate |
The rate at which AddComponent Ops are being received. |
connection_remove_component_op_receive_rate |
The rate at which RemoveComponent Ops are being received. |
connection_authority_change_op_receive_rate |
The rate at which AuthorityChange Ops are being received. |
connection_component_update_op_receive_rate |
The rate at which ComponentUpdate Ops are being received. |
connection_command_request_op_receive_rate |
The rate at which CommandRequest Ops are being received. |
connection_command_response_op_receive_rate |
The rate at which CommandResponse Ops are being received. |
connection_egress_bytes |
The number of bytes that have been sent. This refers to bytes encoded by the application layer - the actual number of bytes transmitted on the transport may be slightly higher. |
connection_egress_bytes_rate |
The rate at which data is being sent, in bytes per second. |
connection_ingress_bytes |
The number of bytes that have been received. This refers to bytes decoded by the application layer - the actual number of bytes received on the transport may be slightly higher. |
connection_ingress_bytes_rate |
The rate at which data is being received, in bytes per second. |
connection_delta_compression_egress_bandwidth_saved_bytes |
The number of network egress bytes saved through delta compressing component updates. |
connection_delta_compression_egress_bandwidth_saved_bytes_rate |
The rate at which network egress bandwidth is saved through delta compressing component updates, in bytes per second. |
connection_delta_compression_egress_total_diffs_sent |
The number of delta compressed component updates sent. |
connection_delta_compression_egress_total_diffs_sent_rate |
The rate at which delta compressed component updates are sent, in updates per second. |
connection_delta_compression_egress_diffs_abandoned |
The number of delta compressed component updates abandoned (due to taking too long to compute or being too large). |
connection_delta_compression_egress_diffs_abandoned_rate |
The rate at which delta compressed component updates are abandoned, in updates per second. |
connection_delta_compression_ingress_bandwidth_saved_bytes |
The number of network ingress bytes saved through delta compressing component updates. |
connection_delta_compression_ingress_bandwidth_saved_bytes_rate |
The rate at which network ingress bandwidth is saved through delta compressing component updates, in bytes per second. |
raknet_receive_buffer_size |
The current size of the RakNet receive buffer. |
raknet_send_buffer_size |
The current size of the RakNet read buffer. |
raknet_send_buffer_size_bytes |
The number of bytes in the RakNet send buffer. |
raknet_resend_buffer_size |
The number of messages waiting in the RakNet resend buffer. |
raknet_resend_buffer_size_bytes |
The number of bytes in the RakNet resend buffer. |
raknet_packet_loss_last_second |
The packet loss over the last second. This number will range from 0.0 to 1.0. |
raknet_packet_loss_lifetime |
The packet loss average over the lifetime of the connection. This number will range from 0.0 to 1.0. |
raknet_last_ping_seconds |
The response time of the last ping emitted by the RakNet client. |
kcp_send_queue_size_packets |
The number of KCP packets currently in the send queue, waiting to be sent. |
kcp_recv_queue_size_packets |
The number of KCP packets currently in the receive queue, waiting to be processed. |
kcp_smoothed_round_trip_time_seconds |
A time-weighted moving average of the round-trip time between the worker and the Runtime. |
kcp_round_trip_time_variation_seconds |
A time-weighted moving average of the difference between each round-trip time sample and the smoothed round-trip time. |
erasure_coding_completed_batches |
The number of erasure codec batches whose packets were all successfully delivered. |
erasure_coding_completed_batches_rate |
The number of erasure codec batches whose packets were all successfully delivered, per second. |
erasure_coding_recovered_batches |
The number of erasure codec batches for which the original data was recovered even though not all packets were delivered. |
erasure_coding_recovered_batches_rate |
The number of erasure codec batches for which the original data was recovered even though not all packets were delivered, per second. |
erasure_coding_unrecoverable_batches |
The number of erasure codec batches for which the original data could not be recovered because too many packets were missing. |
erasure_coding_unrecoverable_batches_rate |
The number of erasure codec batches for which the original data could not be recovered because too many packets were missing, per second. |
The full list of built-in histogram metrics is as follows.
Metric name (string ) |
Metric values (double ) |
---|---|
kcp_packet_send_count |
The number of times each KCP packet had to be sent before its delivery was acknowledged. |
You need to register an OnMetrics
callback with the dispatcher to receive histogram metrics
as they aren’t currently exposed in the Inspector.