How to Run a Series
Very often when running a service you may want to feed its output directly into
another service in order to emulate a more complex service or a multi-stage action.
For one-off requests you can chain services together by building a Series.
Tip
If you need to assemble services into a more complex structure than a linear sequence (e.g. parallel threads, conditional branching, loops), you can build a workflow instead.
The advantage of using a
Seriesover a workflow is that you can run a series once and forget about it (cleanup is automatic), whereas when you build a workflow you will spawn aServicethat needs to be managed afterwards.
Every time you run .request(_, _), you immediately receive a Series.
When you use .request(_, _).take_response() you are actually calling
Series::take_response
which terminates the construction of the series after a single service call.
To build a more complex series you can use the chaining methods provided by the
Series struct:
.then(_): Specify a service (or other kind of provider) to pass the last response into, getting back a new response..map_block(_): Specify aFnOnce(T) -> Uto transform the last response into a new value. The function you provide will block the system schedule from running, so it should be short-lived..map_async(_): Specify aFnOnce(T) -> impl Future<Output=U>to transform the last response into a new value. The Future will be evaluated in the async task pool, so put all long-running routines into the async block.
The following example shows a simple series that feeds the output of one service
into another service with a .map_block between them to transform the first
service’s output into a data type that can be consumed by the second service.
let storage = commands.spawn(()).id();
commands
// Ask for three values to be summed
.request(vec![-1.1, 5.0, 3.1], sum_service)
// Convert the resulting value to a string
.map_block(|value| value.to_string())
// Send the string through the parsing service
// which may produce streams of u32, i32, and f32
.then(parsing_service)
// Collect the parsed values in an entity
.collect_streams(storage)
// Detach this series so we can safely drop the tail
.detach();
Dependencies and Detachment
You may notice the .detach() at the end of the previous example series.
Ordinarily a series will automatically stop executing if its result is no longer needed, which we detect based on whether the Outcome of the final response gets dropped.
This allows us to avoid running services needlessly.
However sometimes you want services to run even if you won’t be observing its final result, because you are interested in side-effects from the service rather than the final response of the service.
You can insert .detach() anywhere in a series to ensure that everything before the .detach() gets run even if the part of the series after the .detach() gets dropped.
There are several ways to terminate a series, and each has drop conditions that affect what happens to the series when it gets dropped.
You can find a table of the different terminating operations and their drop conditions in the Series::detach() documentation.