Skip to content

Commit 3f6baa2

Browse files
authored
Flesh out documentation more and add a prelude (#22)
Signed-off-by: Michael X. Grey <[email protected]>
1 parent 2b1ca3b commit 3f6baa2

20 files changed

+282
-82
lines changed

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
# :warning: Under Construction :warning:
2-
31
# Reactive Programming for Bevy
42

5-
This library provides sophisticated [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) for the [bevy](https://bevyengine.org/) ECS. In addition to supporting one-shot chains of async operations, it can support reusable workflows with parallel branches, synchronization, races, and cycles. These workflows can be hierarchical, so workflows can be built out of other workflows.
3+
This library provides sophisticated [reactive programming](https://en.wikipedia.org/wiki/Reactive_programming) for the [bevy](https://bevyengine.org/) ECS. In addition to supporting one-shot chains of async operations, it can support reusable workflows with parallel branches, synchronization, races, and cycles. These workflows can be hierarchical, so a workflow can be used as a building block by other workflows.
4+
5+
![sense-think-act workflow](assets/figures/sense-think-act_workflow.svg)
6+
7+
# Why use bevy impulse?
8+
9+
There are several different categories of problems that bevy impulse sets out to solve. If any one of these use-cases is relevant to you, it's worth considering bevy impulse as a solution:
10+
11+
* Coordinating **async activities** (e.g. filesystem i/o, network i/o, or long-running calculations) with regular bevy systems
12+
* Calling **one-shot systems** on an ad hoc basis, where the systems require an input value and produce an output value that you need to use
13+
* Defining a **procedure** to be followed by your application or by an agent or pipeline within your application
14+
* Designing a complex **state machine** that gradually switches between different modes or behaviors while interacting with the world
15+
* Managing many **parallel threads** of activities that need to be synchronized or raced against each other
616

717
# Helpful Links
818

assets/figures/sense-think-act_workflow.svg

Lines changed: 1 addition & 0 deletions
Loading

src/buffer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ where
571571

572572
#[cfg(test)]
573573
mod tests {
574-
use crate::{testing::*, *};
574+
use crate::{prelude::*, testing::*, Gate};
575575
use std::future::Future;
576576

577577
#[test]

src/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ impl CleanupWorkflowConditions {
690690

691691
#[cfg(test)]
692692
mod tests {
693-
use crate::{testing::*, *};
693+
use crate::{prelude::*, testing::*, CancellationCause};
694694
use smallvec::SmallVec;
695695

696696
#[test]

src/callback.rs

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,39 +33,101 @@ use std::{
3333
sync::{Arc, Mutex},
3434
};
3535

36-
/// A Callback is similar to a [`Service`](crate::Service) except it is not
37-
/// associated with an [`Entity`]. Instead it can be passed around and shared as an
38-
/// object. Cloning the Callback will produce a new reference to the same underlying
39-
/// instance. If the Callback has any internal state (e.g. [`Local`](bevy::prelude::Local)
36+
/// A Callback is an object that implements [`Provider`], similar to [`Service`](crate::Service),
37+
/// except it is not associated with an [`Entity`]. Instead it can be passed around and
38+
/// shared as its own object. Cloning a Callback will produce a new reference to the
39+
/// same underlying instance. If the Callback has any internal state (e.g. [`Local`](bevy_ecs::prelude::Local)
4040
/// parameters, change trackers, or mutable captured variables), that internal state will
4141
/// be shared among all its clones.
4242
///
4343
/// There are three ways to instantiate a callback:
4444
///
45-
/// ### [`.as_callback()`](AsCallback)
45+
/// ## [`.as_callback()`](AsCallback)
4646
///
47-
/// If you have a Bevy system with an input parameter of `In<`[`AsyncCallback`]`>`
48-
/// or `In<`[`BlockingCallback`]`>` then you can convert it into a callback
47+
/// If you have a Bevy system with an input parameter of `In<`[`BlockingCallback`]`>`
48+
/// or `In<`[`AsyncCallback`]`>` then you can convert it into a [`Callback`]
4949
/// object by applying `.as_callback()`.
5050
///
51-
/// ### [`.into_async_callback()`](IntoAsyncCallback)
51+
/// ```rust
52+
/// use bevy_impulse::{prelude::*, testing::Integer};
53+
/// use bevy_ecs::prelude::*;
5254
///
53-
/// If you have a Bevy system whose return type implements the [`Future`] trait,
54-
/// it can be converted into an async callback object by applying
55-
/// `.into_async_callback()` to it. The `Response` type of the callback will be
56-
/// `Future::Output` rather than the return type of the system. The return value
57-
/// will be polled in an async compute task pool.
55+
/// fn add_integer(
56+
/// In(input): In<BlockingCallback<i32>>,
57+
/// integer: Res<Integer>,
58+
/// ) -> i32 {
59+
/// input.request + integer.value
60+
/// }
61+
///
62+
/// let callback = add_integer.as_callback();
63+
/// ```
64+
///
65+
/// ```rust
66+
/// use bevy_impulse::{prelude::*, testing::Integer};
67+
/// use bevy_ecs::prelude::*;
68+
/// use std::future::Future;
5869
///
59-
/// ### [`.into_blocking_callback()`](IntoBlockingCallback)
70+
/// fn add_integer_async(
71+
/// In(input): In<AsyncCallback<i32>>,
72+
/// integer: Res<Integer>,
73+
/// ) -> impl Future<Output = i32> {
74+
/// let value = integer.value;
75+
/// async move { input.request + value }
76+
/// }
6077
///
61-
/// Any Bevy system can be converted into a blocking callback by applying
78+
/// let async_callback = add_integer_async.as_callback();
79+
/// ```
80+
///
81+
/// ## [`.into_blocking_callback()`](IntoBlockingCallback)
82+
///
83+
/// Any Bevy system can be converted into a blocking [`Callback`] by applying
6284
/// `.into_blocking_callback()` to it. The `Request` type of the callback will
6385
/// be whatever the input type of the system is (the `T` inside of `In<T>`). The
6486
/// `Response` type of the callback will be whatever the return value of the
6587
/// callback is.
6688
///
67-
/// A blocking callback is always an exclusive system, so it will block all
68-
/// other systems from running until it is finished.
89+
/// A blocking callback is always run as an exclusive system (even if it does not
90+
/// use exclusive system parameters), so it will block all other systems from
91+
/// running until it is finished.
92+
///
93+
/// ```rust
94+
/// use bevy_impulse::{prelude::*, testing::Integer};
95+
/// use bevy_ecs::prelude::*;
96+
///
97+
/// fn add_integer(
98+
/// In(input): In<i32>,
99+
/// integer: Res<Integer>,
100+
/// ) -> i32 {
101+
/// input + integer.value
102+
/// }
103+
///
104+
/// let callback = add_integer.into_blocking_callback();
105+
/// ```
106+
///
107+
/// ## [`.into_async_callback()`](IntoAsyncCallback)
108+
///
109+
/// If you have a Bevy system whose return type implements the [`Future`] trait,
110+
/// it can be converted into an async [`Callback`] object by applying
111+
/// `.into_async_callback()` to it. The `Response` type of the callback will be
112+
/// `<T as Future>::Output` where `T` is the return type of the system. The `Future`
113+
/// returned by the system will be polled in the async compute task pool (unless
114+
/// you activate the `single_threaded_async` feature).
115+
///
116+
/// ```rust
117+
/// use bevy_impulse::{prelude::*, testing::Integer};
118+
/// use bevy_ecs::prelude::*;
119+
/// use std::future::Future;
120+
///
121+
/// fn add_integer(
122+
/// In(input): In<i32>,
123+
/// integer: Res<Integer>,
124+
/// ) -> impl Future<Output = i32> {
125+
/// let value = integer.value;
126+
/// async move { input + value }
127+
/// }
128+
///
129+
/// let callback = add_integer.into_async_callback();
130+
/// ```
69131
pub struct Callback<Request, Response, Streams = ()> {
70132
pub(crate) inner: Arc<Mutex<InnerCallback<Request, Response, Streams>>>,
71133
}

src/chain.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> {
183183
}
184184

185185
/// Apply a map whose output is a Future that will be run in the
186-
/// [`AsyncComputeTaskPool`](bevy::tasks::AsyncComputeTaskPool) (unless
186+
/// [`AsyncComputeTaskPool`](bevy_tasks::AsyncComputeTaskPool) (unless
187187
/// the `single_threaded_async` feature is active). The output of the Future
188188
/// will be the Response of the returned Chain.
189189
pub fn map_async<Task>(
@@ -723,7 +723,7 @@ where
723723
/// that trait, then you can use [`Self::cancel_on_quiet_err`] instead.
724724
///
725725
/// ```
726-
/// use bevy_impulse::{*, testing::*};
726+
/// use bevy_impulse::{prelude::*, testing::*};
727727
///
728728
/// let mut context = TestingContext::minimal_plugins();
729729
///
@@ -959,7 +959,7 @@ where
959959
/// various reasons, this returns a [`Result`]. Follow this with
960960
/// `.dispose_on_err` to filter away errors.
961961
///
962-
/// To access the streams of the service, use [`Chain::then_request_node`].
962+
/// To access the streams of the service, use [`Chain::then_injection_node`].
963963
pub fn then_injection(self) -> Chain<'w, 's, 'a, 'b, Response> {
964964
let source = self.target;
965965
let node = self
@@ -1032,7 +1032,7 @@ impl<'w, 's, 'a, 'b, T: 'static + Send + Sync> Chain<'w, 's, 'a, 'b, T> {
10321032

10331033
#[cfg(test)]
10341034
mod tests {
1035-
use crate::{testing::*, *};
1035+
use crate::{prelude::*, testing::*};
10361036
use smallvec::SmallVec;
10371037

10381038
#[test]

src/channel.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ use crate::{
3131
StreamRequest,
3232
};
3333

34+
/// Provides asynchronous access to the [`World`], allowing you to issue queries
35+
/// or commands and then await the result.
3436
#[derive(Clone)]
3537
pub struct Channel {
3638
inner: Arc<InnerChannel>,
3739
}
3840

3941
impl Channel {
42+
/// Run a query in the world and receive the promise of the query's output.
4043
pub fn query<P>(&self, request: P::Request, provider: P) -> Promise<P::Response>
4144
where
4245
P: Provider,
@@ -49,6 +52,7 @@ impl Channel {
4952
.flatten()
5053
}
5154

55+
/// Get access to a [`Commands`] for the [`World`]
5256
pub fn command<F, U>(&self, f: F) -> Promise<U>
5357
where
5458
F: FnOnce(&mut Commands) -> U + 'static + Send,
@@ -170,7 +174,7 @@ impl<T: Stream> StreamChannel<T> {
170174

171175
#[cfg(test)]
172176
mod tests {
173-
use crate::{testing::*, *};
177+
use crate::{prelude::*, testing::*};
174178
use bevy_ecs::system::EntityCommands;
175179
use std::time::Duration;
176180

src/gate.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ impl Gate {
6363
/// [2]: crate::Chain::then_gate
6464
/// [3]: crate::Builder::create_gate_open
6565
/// [4]: crate::Builder::create_gate_close
66-
/// [5]: crate::chain::then_gate_open
67-
/// [6]: crate::chain::then_gate_close
66+
/// [5]: crate::Chain::then_gate_open
67+
/// [6]: crate::Chain::then_gate_close
6868
pub struct GateRequest<T> {
6969
/// Indicate what action the gate should take
7070
pub action: Gate,
@@ -75,7 +75,7 @@ pub struct GateRequest<T> {
7575

7676
#[cfg(test)]
7777
mod tests {
78-
use crate::{testing::*, *};
78+
use crate::{prelude::*, testing::*};
7979

8080
#[test]
8181
fn test_gate_actions() {

src/impulse.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ where
164164
/// feature is active). The output of the [`Future`] will be the Response of
165165
/// the returned Impulse.
166166
///
167-
/// [1]: bevy::tasks::AsyncComputeTaskPool
167+
/// [1]: bevy_tasks::AsyncComputeTaskPool
168168
#[must_use]
169169
pub fn map_async<Task>(
170170
self,
@@ -335,7 +335,7 @@ impl<T> Default for Collection<T> {
335335

336336
#[cfg(test)]
337337
mod tests {
338-
use crate::{testing::*, *};
338+
use crate::{prelude::*, testing::*, ContinuousQueueView};
339339
use bevy_utils::label::DynEq;
340340
use smallvec::SmallVec;
341341
use std::{

src/lib.rs

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,37 @@
1515
*
1616
*/
1717

18-
//! `bevy_impulse` is an extension to the [Bevy](https://bevyengine.org) game
18+
//! ![sense-think-act workflow](https://raw.githubusercontent.com/open-rmf/bevy_impulse/update_docs/assets/figures/sense-think-act_workflow.svg)
19+
//!
20+
//! Bevy impulse is an extension to the [Bevy](https://bevyengine.org) game
1921
//! engine that allows you to transform [bevy systems](https://bevyengine.org/learn/quick-start/getting-started/ecs/)
2022
//! into services and workflows that can be used for reactive service-oriented
2123
//! programming.
2224
//!
2325
//! ## Services
2426
//!
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+
//! One primitive element of reactive programming is a [service](https://en.wikipedia.org/wiki/Service_(systems_architecture)).
28+
//! In bevy impulse, a [`Service`] is a bevy system that is associated with an
2729
//! entity and can be created using [`Commands::spawn_service`](SpawnServicesExt::spawn_service)
2830
//! or [`App::add_service`](AddServicesExt::add_service).
2931
//!
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.
32+
//! When you spawn a service you will immediately receive a [`Service`] object
33+
//! which references the newly spawned service. If you do not want to hang onto the [`Service`]
34+
//! object, you can find previously spawned services later using the [`ServiceDiscovery`]
35+
//! system parameter.
36+
//!
37+
//! Sometimes [`Service`] is not quite the right fit for your use case, so bevy impulse
38+
//! offers a generalization of services callled [`Provider`] which has some
39+
//! more options for defining a reactive element.
3440
//!
3541
//! ## Workflows
3642
//!
3743
//! 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).
44+
//! You can instead build workflows using [`.spawn_workflow`](SpawnWorkflowExt::spawn_workflow)
45+
//! on [`Commands`](bevy_ecs::prelude::Commands) or [`World`](bevy_ecs::prelude::World).
3946
//! 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.
47+
//! [service](Service) (or more generally a [provider](Provider)) with an input,
48+
//! an output, and possibly streams.
4149
//!
4250
//! There are various operations that can be performed between nodes, such as
4351
//! forking and joining. These operations are built using [`Chain`].
@@ -54,8 +62,8 @@
5462
//! [`Impulse::then`]. Any impulse chain that you create will only run exactly
5563
//! once.
5664
//!
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
65+
//! Once you've finished building your chain, use [`Impulse::detach`] to let it
66+
//! run freely, or use [`Impulse::take`] to get a [`Recipient`] of the final
5967
//! result.
6068
6169
mod async_execution;
@@ -149,7 +157,7 @@ use bevy_ecs::prelude::{Entity, In};
149157
///
150158
/// ```
151159
/// use bevy_ecs::prelude::*;
152-
/// use bevy_impulse::*;
160+
/// use bevy_impulse::prelude::*;
153161
///
154162
/// #[derive(Component, Resource)]
155163
/// struct Precision(i32);
@@ -256,7 +264,7 @@ pub struct AsyncCallback<Request, Streams: StreamPack = ()> {
256264
/// `StreamChannel`s, whichever matches the [`StreamPack`] description.
257265
pub streams: Streams::Channel,
258266
/// The channel that allows querying and syncing with the world while the
259-
/// service runs asynchronously.
267+
/// callback executes asynchronously.
260268
pub channel: Channel,
261269
/// The node in a workflow or impulse chain that asked for the callback
262270
pub source: Entity,
@@ -297,7 +305,7 @@ pub struct AsyncMap<Request, Streams: StreamPack = ()> {
297305
/// `StreamChannel`s, whichever matches the [`StreamPack`] description.
298306
pub streams: Streams::Channel,
299307
/// The channel that allows querying and syncing with the world while the
300-
/// service runs asynchronously.
308+
/// map executes asynchronously.
301309
pub channel: Channel,
302310
/// The node in a workflow or impulse chain that asked for the callback
303311
pub source: Entity,
@@ -319,3 +327,34 @@ impl Plugin for ImpulsePlugin {
319327
app.add_systems(Update, flush_impulses());
320328
}
321329
}
330+
331+
pub mod prelude {
332+
pub use crate::{
333+
buffer::{
334+
Buffer, BufferAccess, BufferAccessMut, BufferKey, BufferSettings, Bufferable, Buffered,
335+
IterBufferable, RetentionPolicy,
336+
},
337+
builder::Builder,
338+
callback::{AsCallback, Callback, IntoAsyncCallback, IntoBlockingCallback},
339+
chain::{Chain, ForkCloneBuilder, UnzipBuilder, Unzippable},
340+
flush::flush_impulses,
341+
impulse::{Impulse, Recipient},
342+
map::{AsMap, IntoAsyncMap, IntoBlockingMap},
343+
map_once::{AsMapOnce, IntoAsyncMapOnce, IntoBlockingMapOnce},
344+
node::{ForkCloneOutput, InputSlot, Node, Output},
345+
promise::{Promise, PromiseState},
346+
provider::{ProvideOnce, Provider},
347+
request::{RequestExt, RunCommandsOnWorldExt},
348+
service::{
349+
traits::*, AddContinuousServicesExt, AddServicesExt, AsDeliveryInstructions,
350+
DeliveryInstructions, DeliveryLabel, DeliveryLabelId, IntoAsyncService,
351+
IntoBlockingService, Service, ServiceDiscovery, SpawnServicesExt,
352+
},
353+
stream::{Stream, StreamFilter, StreamOf, StreamPack},
354+
trim::{TrimBranch, TrimPoint},
355+
workflow::{DeliverySettings, Scope, ScopeSettings, SpawnWorkflowExt, WorkflowSettings},
356+
AsyncCallback, AsyncCallbackInput, AsyncMap, AsyncService, AsyncServiceInput,
357+
BlockingCallback, BlockingCallbackInput, BlockingMap, BlockingService,
358+
BlockingServiceInput, ContinuousQuery, ContinuousService, ContinuousServiceInput,
359+
};
360+
}

0 commit comments

Comments
 (0)