Skip to content

Commit 0c96a6f

Browse files
committed
Add service discovery and spruce up documentation
Signed-off-by: Michael X. Grey <grey@openrobotics.org>
1 parent f35612d commit 0c96a6f

File tree

11 files changed

+425
-90
lines changed

11 files changed

+425
-90
lines changed

src/chain.rs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,12 @@ pub use fork_clone_builder::*;
3636
pub mod unzip;
3737
pub use unzip::*;
3838

39-
/// After submitting a service request, use [`Chain`] to describe how
40-
/// the response should be handled. At a minimum, for the response to be
41-
/// delivered, you must choose one of:
42-
/// - `.detach()`: Let the service run to completion and then discard the
43-
/// response data.
44-
/// - `.take()`: As long as the [`Promise`] or one of its clones is alive,
45-
/// the service will continue running to completion and you will be able to
46-
/// view the response (or take the response, but only once). If all clones of
47-
/// the [`Promise`] are dropped before the service is delivered, it will
48-
/// be cancelled.
49-
/// - `.detach_and_take()`: As long as the [`Promise`] or one of its clones is
50-
/// alive, you will be able to view the response (or take the response, but
51-
/// only once). The service will run to completion even if every clone of the
52-
/// [`Promise`] is dropped.
39+
/// Chain operations onto the output of a workflow node.
5340
///
54-
/// If you do not select one of the above then the service request will be
55-
/// cancelled without ever attempting to run.
41+
/// Make sure to use [`Self::connect`] when you're done chaining so that the
42+
/// final output of the chain gets connected into another node. If the final
43+
/// output of the chain is meant to be the final output of your workflow then
44+
/// you should connect it to [`Scope::terminate`].
5645
#[must_use]
5746
pub struct Chain<'w, 's, 'a, 'b, T> {
5847
target: Entity,
@@ -65,10 +54,8 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> {
6554
/// use this to resume building this chain later.
6655
///
6756
/// Note that if you do not connect some path of your workflow into the
68-
/// `terminate` slot of your [`Scope`][1] then the workflow will not be able
57+
/// `terminate` slot of your [`Scope`] then the workflow will not be able
6958
/// to run.
70-
///
71-
/// [1]: crate::Scope
7259
#[must_use]
7360
pub fn output(self) -> Output<T> {
7461
Output::new(self.scope(), self.target)
@@ -410,7 +397,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> {
410397
///
411398
/// As the name suggests, a no-op will not actually do anything, but it adds
412399
/// a new link (entity) into the chain.
413-
/// [1]: https://en.wikipedia.org/wiki/NOP_(code)
400+
/// [1]: `<https://en.wikipedia.org/wiki/NOP_(code)>`
414401
#[must_use]
415402
pub fn noop(self) -> Chain<'w, 's, 'a, 'b, T> {
416403
let source = self.target;
@@ -746,7 +733,7 @@ mod tests {
746733
);
747734

748735
context.run_with_conditions(&mut promise, Duration::from_secs(2));
749-
assert!(promise.peek().available().is_some_and(|value| *value == 6.0));
736+
assert!(promise.take().available().is_some_and(|value| value == 6.0));
750737
assert!(context.no_unhandled_errors());
751738
}
752739

@@ -806,7 +793,7 @@ mod tests {
806793
.with_update_count(100),
807794
);
808795

809-
assert_eq!(promise.peek().available().copied(), Some(16.0));
796+
assert_eq!(promise.take().available(), Some(16.0));
810797
assert!(context.no_unhandled_errors());
811798
}
812799

@@ -850,7 +837,7 @@ mod tests {
850837
});
851838

852839
context.run_while_pending(&mut promise);
853-
assert_eq!(promise.peek().available().copied(), Some(15.0));
840+
assert_eq!(promise.take().available(), Some(15.0));
854841
assert!(context.no_unhandled_errors());
855842
}
856843

@@ -943,7 +930,7 @@ mod tests {
943930
});
944931

945932
context.run_with_conditions(&mut promise, Duration::from_secs(2));
946-
assert!(promise.peek().available().is_some_and(|v| *v == 1.0));
933+
assert!(promise.take().available().is_some_and(|v| v == 1.0));
947934
assert!(context.no_unhandled_errors());
948935

949936
let mut promise = context.command(|commands| {
@@ -953,7 +940,7 @@ mod tests {
953940
});
954941

955942
context.run_with_conditions(&mut promise, Duration::from_secs(2));
956-
assert!(promise.peek().available().is_some_and(|v| *v == 5.0));
943+
assert!(promise.take().available().is_some_and(|v| v == 5.0));
957944
assert!(context.no_unhandled_errors());
958945

959946
let mut promise = context.command(|commands| {

src/discovery.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/impulse.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,19 @@ where
6666
Response: 'static + Send + Sync,
6767
Streams: StreamPack,
6868
{
69-
/// Keep executing out the impulse chain up to here even if a downstream
69+
/// Keep executing the impulse chain up to here even if a downstream
7070
/// dependent was dropped. If you continue building the chain from this
71-
/// point then the later impulses will not be affected by this use of
72-
/// detached.
71+
/// point, then the later impulses will not be affected by this use of
72+
/// `.detach()` and may be dropped.
7373
///
7474
/// Downstream dependencies get dropped in the following situations:
75-
/// - [`Self::take`] or [`Self::take_response`]: The promise containing the response is dropped.
76-
/// - [`Self::store`], [`Self::push`], or [`Self::insert`]: The target entity of the operation is despawned.
77-
/// - [`Self::send_event`]: This will never be dropped, effectively making it detached automatically.
78-
/// - Not using any of the above: The dependency will immediately be dropped during a flush.
79-
/// If you do not use detach in this scenario, then the chain will be immediately dropped
80-
/// without being run at all. This will also push an error into [`UnhandledErrors`](crate::UnhandledErrors).
75+
///
76+
/// | Operation | Drop condition |
77+
/// |-----------------------------------------------------------|-------------------------------------------------------|
78+
/// | [`Self::take`] <br> [`Self::take_response`] | The promise containing the final response is dropped. |
79+
/// | [`Self::store`] <br> [`Self::push`] <br> [`Self::insert`] | The target entity of the operation is despawned. |
80+
/// | [`Self::detach`] <br> [`Self::send_event`] | This will never be dropped |
81+
/// | Using none of the above | The impulse will immediately be dropped during a flush, so it will never be run at all. <br> This will also push an error into [`UnhandledErrors`](crate::UnhandledErrors). |
8182
pub fn detach(self) -> Impulse<'w, 's, 'a, Response, Streams> {
8283
self.commands.add(Detach { target: self.target });
8384
self
@@ -341,7 +342,7 @@ mod tests {
341342
});
342343

343344
context.run_while_pending(&mut promise);
344-
assert!(promise.peek().available().is_some_and(|v| v == "HELLO"));
345+
assert!(promise.take().available().is_some_and(|v| v == "HELLO"));
345346
assert!(context.no_unhandled_errors());
346347

347348
let mut promise = context.command(|commands| {
@@ -351,7 +352,7 @@ mod tests {
351352
});
352353

353354
context.run_while_pending(&mut promise);
354-
assert!(promise.peek().available().is_some_and(|v| v == "HELLO"));
355+
assert!(promise.take().available().is_some_and(|v| v == "HELLO"));
355356
assert!(context.no_unhandled_errors());
356357

357358
let mut promise = context.command(|commands| {
@@ -362,7 +363,7 @@ mod tests {
362363
});
363364

364365
context.run_while_pending(&mut promise);
365-
assert!(promise.peek().available().is_some_and(|v| v == "HELLO"));
366+
assert!(promise.take().available().is_some_and(|v| v == "HELLO"));
366367
assert!(context.no_unhandled_errors());
367368

368369
let mut promise = context.command(|commands| {
@@ -373,7 +374,7 @@ mod tests {
373374
});
374375

375376
context.run_while_pending(&mut promise);
376-
assert!(promise.peek().available().is_some_and(|v| v == "HELLO"));
377+
assert!(promise.take().available().is_some_and(|v| v == "HELLO"));
377378
assert!(context.no_unhandled_errors());
378379
}
379380

@@ -396,7 +397,7 @@ mod tests {
396397
});
397398

398399
assert!(context.run_with_conditions(&mut promise, conditions.clone()));
399-
assert!(promise.peek().available().is_some_and(|v| v == "hello"));
400+
assert!(promise.take().available().is_some_and(|v| v == "hello"));
400401
assert!(context.no_unhandled_errors());
401402

402403
let mut promise = context.command(|commands| {
@@ -406,7 +407,7 @@ mod tests {
406407
});
407408

408409
assert!(context.run_with_conditions(&mut promise, conditions.clone()));
409-
assert!(promise.peek().available().is_some_and(|v| v == "hello"));
410+
assert!(promise.take().available().is_some_and(|v| v == "hello"));
410411
assert!(context.no_unhandled_errors());
411412

412413
let mut promise = context.command(|commands| {
@@ -417,7 +418,7 @@ mod tests {
417418
});
418419

419420
assert!(context.run_with_conditions(&mut promise, conditions.clone()));
420-
assert!(promise.peek().available().is_some_and(|v| v == "hello"));
421+
assert!(promise.take().available().is_some_and(|v| v == "hello"));
421422
assert!(context.no_unhandled_errors());
422423

423424
let mut promise = context.command(|commands| {
@@ -436,7 +437,7 @@ mod tests {
436437
});
437438

438439
assert!(context.run_with_conditions(&mut promise, conditions.clone()));
439-
assert!(promise.peek().available().is_some_and(|v| v == "hello"));
440+
assert!(promise.take().available().is_some_and(|v| v == "hello"));
440441
assert!(context.no_unhandled_errors());
441442
}
442443
}

src/lib.rs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,49 @@
1515
*
1616
*/
1717

18+
//! `bevy_impulse` is an extension to the [Bevy](https://bevyengine.org) game
19+
//! engine that allows you to transform [bevy systems](https://bevyengine.org/learn/quick-start/getting-started/ecs/)
20+
//! into services and workflows that can be used for reactive service-oriented
21+
//! programming.
22+
//!
23+
//! ## Services
24+
//!
25+
//! One primitive of reactive programming is a [service](https://en.wikipedia.org/wiki/Service_(systems_architecture)).
26+
//! In `bevy_impulse`, a service is a bevy system that is associated with an
27+
//! entity and can be created using [`Commands::spawn_service`](SpawnServicesExt::spawn_service)
28+
//! or [`App::add_service`](AddServicesExt::add_service).
29+
//!
30+
//! When you [spawn](SpawnServicesExt::spawn_service) a service you will
31+
//! immediately receive a [`Service`] object which can be used to refer to it.
32+
//! If you do not want to hang onto the service object, you can find previously
33+
//! spawned services later using the [`ServiceDiscovery`] system parameter.
34+
//!
35+
//! ## Workflows
36+
//!
37+
//! For complex async workflows, a single bevy system may not be sufficient.
38+
//! You can instead build workflows using [`Command::spawn_workflow`](SpawnWorkflow::spawn_workflow).
39+
//! A workflow lets you create a graph of [nodes](Node) where each node is a
40+
//! service with an input, an output, and possibly streams.
41+
//!
42+
//! There are various operations that can be performed between nodes, such as
43+
//! forking and joining. These operations are built using [`Chain`].
44+
//!
45+
//! When you spawn your workflow, you will receive a [`Service`] object that
46+
//! lets you use the workflow as if it's an ordinary service.
47+
//!
48+
//! ## Impulses
49+
//!
50+
//! Services and workflows are reusable building blocks for creating a reactive
51+
//! application. In order to actually run them, call [`Commands::request`](RequestExt::request)
52+
//! which will provide you with an [`Impulse`]. An impulse is a one-time-use
53+
//! reaction to a request which you can chain to subsequent reactions using
54+
//! [`Impulse::then`]. Any impulse chain that you create will only run exactly
55+
//! once.
56+
//!
57+
//! Once you've finished creating your chain, use [`Impulse::detach`] to let it
58+
//! run freely, or use [`Impulse::take`] to receive a [`Promise`] of the final
59+
//! result.
60+
1861
pub mod buffer;
1962
pub use buffer::*;
2063

@@ -33,9 +76,6 @@ pub use chain::*;
3376
pub mod channel;
3477
pub use channel::*;
3578

36-
pub mod discovery;
37-
pub use discovery::*;
38-
3979
pub mod disposal;
4080
pub use disposal::*;
4181

0 commit comments

Comments
 (0)