Sending data to SpatialOS
Use the
worker::Connection
to send data such as
component updates, logs, and metrics to SpatialOS.
Component updates
Sending component updates
When the worker has authority over a component for some entity, it can send
component updates for that component to SpatialOS.
Do this using the worker::Connection
method
SendComponentUpdate<T>
which takes a reference to a T::Update
object. The template parameter T
should be a component metaclass from 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 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 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
Process<T>
method on theworker::Dispatcher
.
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:
void TriggerEvent(worker::Connection& connection, worker::EntityId entity_id) {
example::Switch::Update update;
example::SwitchToggled event{1};
update.add_toggled(event);
connection.SendComponentUpdate<example::Switch>(entity_id, 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:
dispatcher.OnComponentUpdate<example::Switch>(
[&](const worker::ComponentUpdateOp<example::Switch>& op) {
// `op.Update.toggled()` contains a list of SwitchToggled events.
for (auto it : op.Update.toggled()) {
std::cout << "Switch toggled at " << it.time() << std::endl;
}
});
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 worker::Dispatcher
method
OnComponentUpdate
with the same
template paramater T
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’s current view of the component
- could have been sent by SpatialOS rather than a worker for synchronization purposes
Updating component interests
For each entity in its view, a worker receives updates for (“has interest in”):
- the set of components the worker has authority over
- the set of components it is explicitly interested in
A worker always has interest in the components of the entities it is authoritative on, in addition to any others specified in the bridge settings or using the method described below.
You can configure the set of explicitly interested components 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, per-component basis, using the
Connection::SendComponentInterest
method.
When the set of interested components for an entity changes, the worker receives 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:
void ComponentInterest(worker::Connection& connection, worker::EntityId entity_id) {
connection.SendComponentInterest(
entity_id,
{{example::Switch::ComponentId, worker::InterestOverride{/* IsInterested */ true}}});
}
Component commands
Sending component commands
To send a command request, use the worker::Connection
method
SendCommandRequest<T>
.
The command is executed by the worker that currently has authority over the component containing the command
on the entity specified in the command request.
SendCommandRequest<T>
takes:
- an entity ID
- an optional timeout
- a
T::Request
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 template parameter
T
should be a command metaclass defined in the schema-generated code.
Before sending the command, register a callback to handle the response with the
worker::Dispatcher
with
OnCommandResponse<T>
.
You can match up the request ID returned by
SendCommandRequest
(of type
worker::RequestId<worker::OutgoingCommandRequest<T>>
)
with the one in the CommandResponseOp
to identify the request that is being responded to.
Command failures
Commands can fail, so you should check the
StatusCode
field in the
CommandResponseOp
, and retry the command as necessary.
The caller will always get a response callback, but it can be one of several failure cases, including:
ApplicationError
(rejected by the target worker or by SpatialOS)AuthorityLost
(target worker lost authority, or no worker had authority)NotFound
(target entity, or target component on the entity, didn’t exist)PermissionDenied
(sending worker didn’t have permission to send request)Timeout
InternalError
(most likely indicates a bug in SpatialOS, should be reported)
For more detail on these status codes, see
the documentation for StatusCode
.
Receiving component commands
To handle commands issued by another worker, use the opposite flow:
- Register a callback with the
worker::Dispatcher
withOnCommandRequest<T>
. When the callback is executed:
To respond to the command, call the
Connection
methodSendCommandResponse<T>
. Supply the request ID (of typeworker::RequestId<worker::IncomingCommandRequest<T>>
) provided by theworker::CommandRequestOp
and an appropriateT::Response
response object.To fail the command, call
SendCommandFailure<T>
.
Note that a RequestId
only uniquely identifies a single command invocation from the perspective of a
single worker; that is, the RequestId
used by the calling worker and the RequestId
used by
the caller worker for handling that same command are unrelated.
Entity queries
A worker can run remote entity queries against the simulation by using the
Connection
method SendEntityQueryRequest
.
This takes a worker::query::EntityQuery
object
and an optional timeout.
Like other request methods, this returns a worker::RequestId
, which can be used to match a request with its
response.
The query object
The query object is made of
an
worker::query::Constraint
, 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
](/reference/13.1/cppsdk/api-reference#worker-query-not_constraint): Matches entities that do not match the given subconstraint.
An
worker::query::ResultType
, 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
worker::query::SnapshotResultType snapshotResultType{{}};
.To select every component whose ID is contained in the given set, use
worker::query::SnapshotResultType 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
worker::Dispatcher
using the
OnEntityQueryResponse
method.
The EntityQueryResponseOp
contains:
- a
std::size_t 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 by going to the Inspector World view and
clicking on a worker.
There are two typical use cases for sending metrics:
- Reporting your own custom metrics. Both time series and histogram metrics are supported.
Updating a worker’s load when the worker is using dynamic load balancing.
The load of a worker is a floating-point value, with 0 indicating an unloaded worker and values above 1 corresponding to an overloaded worker. The reported values direct SpatialOS’s load balancing strategy.
Example
The following example demonstrates both use cases:
// A queue of tasks the worker has to complete.
std::queue<std::function<void()>> taskQueue;
constexpr double kMaximumQueueSize = 200; // An arbitrary maximum value.
void SendUserMetrics(worker::Connection& connection) {
worker::Metrics metrics;
// Update the current load of the worker.
double load = static_cast<double>(taskQueue.size()) / kMaximumQueueSize;
metrics.Load = load;
// Update the custom metrics.
metrics.GaugeMetrics["MyCustomMetric"] = 1.0;
worker::HistogramMetric histogram_metric{};
histogram_metric.RecordObservation(123);
metrics.HistogramMetrics["MyHistogramMetric"] = histogram_metric;
connection.SendMetrics(metrics);
}
Workers automatically send several built-in, internal gauge metrics at a period
defined by the BuiltInMetricsReportPeriodMillis
field of
worker::ConnectionParameters
.
You can register a callback to receive these metrics inside a
worker::MetricsOp
using
Dispatcher::OnMetrics
:
void RegisterMetricsCallback(worker::Dispatcher& dispatcher) {
dispatcher.OnMetrics([&](const worker::MetricsOp& op) {
auto short_circuit_rate =
op.Metrics.GaugeMetrics.find("connection_command_request_short_circuit_rate");
// Do something with the metric, or store it...
std::cout << "Command requests short-circuited per second: " << short_circuit_rate->second
<< std::endl;
});
}
The full list of built-in gauge metrics is as follows. All rate metrics are per-second.
Metric name (std::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_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. |
Sending logs
You can send log messages by calling
Connection::SendLogMessage
.
Log messages can be viewed on deployment dashboards.