@@ -19,7 +19,8 @@ use bevy::prelude::{Entity, Commands};
19
19
20
20
use crate :: {
21
21
Provider , UnusedTarget , StreamPack , Node , InputSlot , Output , StreamTargetMap ,
22
- Buffer , BufferSettings , AddOperation , OperateBuffer ,
22
+ Buffer , BufferSettings , AddOperation , OperateBuffer , Scope , OperateScope ,
23
+ ScopeSettings , BeginCancel ,
23
24
} ;
24
25
25
26
pub ( crate ) mod connect;
@@ -34,7 +35,10 @@ pub(crate) use connect::*;
34
35
/// please open an issue with a minimal reproducible example if you find a way
35
36
/// to make it panic.
36
37
pub struct Builder < ' w , ' s , ' a > {
38
+ /// The scope that this builder is meant to help build
37
39
pub ( crate ) scope : Entity ,
40
+ /// The target for cancellation workflows
41
+ pub ( crate ) finish_scope_cancel : Entity ,
38
42
pub ( crate ) commands : & ' a mut Commands < ' w , ' s > ,
39
43
}
40
44
@@ -97,6 +101,112 @@ impl<'w, 's, 'a> Builder<'w, 's, 'a> {
97
101
Buffer { scope : self . scope , source, _ignore : Default :: default ( ) }
98
102
}
99
103
104
+ /// Create an isolated scope within the workflow. This can be useful for
105
+ /// racing multiple branches, creating an uninterruptible segment within
106
+ /// your workflow, or being able to run the same multiple instances of the
107
+ /// same sub-workflow in parallel without them interfering with each other.
108
+ ///
109
+ /// A value can be sent into the scope by connecting an [`Output`] of a node
110
+ /// in the parent scope to the [`InputSlot`] of the node which gets returned
111
+ /// by this function. Each time a value is sent into the scope, it will run
112
+ /// through the workflow of the scope with a unique session ID. Even if
113
+ /// multiple values are sent in from the same session, they will each be
114
+ /// assigned their own unique session ID while inside of this scope.
115
+ pub fn create_scope < Request , Response , Streams > (
116
+ & mut self ,
117
+ settings : ScopeSettings ,
118
+ build : impl FnOnce ( Scope < Request , Response , Streams > , & mut Builder ) ,
119
+ ) -> Node < Request , Response , Streams >
120
+ where
121
+ Request : ' static + Send + Sync ,
122
+ Response : ' static + Send + Sync ,
123
+ Streams : StreamPack ,
124
+ {
125
+ let scope_id = self . commands . spawn ( ( ) ) . id ( ) ;
126
+ let exit_scope = self . commands . spawn ( UnusedTarget ) . id ( ) ;
127
+ let operation = OperateScope :: < Request , Response , Streams > :: new (
128
+ scope_id, Some ( exit_scope) , settings, self . commands ,
129
+ ) ;
130
+ self . commands . add ( AddOperation :: new ( scope_id, operation) ) ;
131
+
132
+ let ( stream_in, stream_out) = Streams :: spawn_scope_streams (
133
+ scope_id,
134
+ self . scope ,
135
+ self . commands ,
136
+ ) ;
137
+
138
+ let mut builder = Builder {
139
+ scope : scope_id,
140
+ finish_scope_cancel : operation. finish_cancel ( ) ,
141
+ commands : self . commands ,
142
+ } ;
143
+
144
+ let scope = Scope {
145
+ input : Output :: new ( scope_id, operation. enter_scope ( ) ) ,
146
+ terminate : InputSlot :: new ( scope_id, operation. terminal ( ) ) ,
147
+ streams : stream_in,
148
+ } ;
149
+
150
+ build ( scope, & mut builder) ;
151
+
152
+ Node {
153
+ input : InputSlot :: new ( self . scope , scope_id) ,
154
+ output : Output :: new ( self . scope , exit_scope) ,
155
+ streams : stream_out,
156
+ }
157
+ }
158
+
159
+ /// It is possible for a scope to be cancelled before it terminates. Even a
160
+ /// scope which is marked as uninterruptible will still experience a
161
+ /// cancellation if its terminal node becomes unreachable.
162
+ ///
163
+ /// This method allows you to define a workflow that branches off of this
164
+ /// scope that will active if and only if the scope gets cancelled. The
165
+ /// workflow will be activated once for each item in the buffer, and each
166
+ /// activation will have its own session.
167
+ ///
168
+ /// If you only want this cancellation workflow to activate once per
169
+ /// cancelled session, then you should use a buffer that has a limit of one
170
+ /// item.
171
+ ///
172
+ /// The cancelled scope will only finish its cleanup after all cancellation
173
+ /// workflows for the cancelled scope have finished, either by terminating
174
+ /// or by being cancelled themselves.
175
+ //
176
+ // TODO(@mxgrey): Consider offering a setting to choose between whether each
177
+ // buffer item gets its own session or whether they share a session.
178
+ pub fn on_cancel < T : ' static + Send + Sync > (
179
+ & mut self ,
180
+ from_buffer : Buffer < T > ,
181
+ settings : ScopeSettings ,
182
+ build : impl FnOnce ( Scope < T , ( ) , ( ) > , & mut Builder ) ,
183
+ ) {
184
+ let cancelling_scope_id = self . commands . spawn ( ( ) ) . id ( ) ;
185
+ let cancelling_operation = OperateScope :: < T , ( ) , ( ) > :: new (
186
+ cancelling_scope_id, Some ( self . finish_scope_cancel ) , settings, self . commands ,
187
+ ) ;
188
+ self . commands . add ( AddOperation :: new ( cancelling_scope_id, cancelling_operation) ) ;
189
+
190
+ let begin_cancel = self . commands . spawn ( ( ) ) . id ( ) ;
191
+ self . commands . add ( AddOperation :: new (
192
+ begin_cancel,
193
+ BeginCancel :: < T > :: new ( self . scope , from_buffer. source , cancelling_scope_id) ,
194
+ ) ) ;
195
+ let mut builder = Builder {
196
+ scope : cancelling_scope_id,
197
+ finish_scope_cancel : cancelling_operation. finish_cancel ( ) ,
198
+ commands : self . commands ,
199
+ } ;
200
+
201
+ let scope = Scope {
202
+ input : Output :: new ( cancelling_scope_id, cancelling_operation. enter_scope ( ) ) ,
203
+ terminate : InputSlot :: new ( cancelling_scope_id, cancelling_operation. terminal ( ) ) ,
204
+ streams : ( ) ,
205
+ } ;
206
+
207
+ build ( scope, & mut builder) ;
208
+ }
209
+
100
210
/// Get the scope that this builder is building for
101
211
pub fn scope ( & self ) -> Entity {
102
212
self . scope
0 commit comments