Skip to main content

crossflow/stream/
named_stream.rs

1/*
2 * Copyright (C) 2025 Open Source Robotics Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16*/
17
18use bevy_ecs::{
19    prelude::{ChildOf, Commands, Component, Entity, World},
20    system::Command,
21};
22
23use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
24
25use tokio::sync::mpsc::unbounded_channel;
26
27use crate::{
28    AddExecution, AddOperation, Builder, DefaultStreamBufferContainer, DeferredRoster,
29    ExitTargetStorage, InnerChannel, Input, InputBundle, InputSlot, ManageInput, OperationRequest,
30    OperationResult, OperationRoster, OperationSetup, OrBroken, Output, Push, Receiver,
31    RedirectScopeStream, RedirectWorkflowStream, ReportUnhandled, ScopeStorage, SingleInputStorage,
32    StreamEffect, StreamRedirect, StreamRequest, StreamTargetMap, TakenStream, UnusedStreams,
33    UnusedTarget,
34};
35
36pub struct NamedStream<S: StreamEffect>(std::marker::PhantomData<fn(S)>);
37
38impl<S: StreamEffect> NamedStream<S> {
39    pub fn spawn_scope_stream(
40        in_scope: Entity,
41        out_scope: Entity,
42        commands: &mut Commands,
43    ) -> (InputSlot<S::Input>, Output<S::Output>) {
44        let source = commands.spawn(()).id();
45        let target = commands.spawn(UnusedTarget).id();
46        commands.queue(AddOperation::new(
47            Some(in_scope),
48            source,
49            RedirectScopeStream::<S>::new(target),
50        ));
51
52        (
53            InputSlot::new(in_scope, source),
54            Output::new(out_scope, target),
55        )
56    }
57
58    pub fn spawn_workflow_stream(
59        name: impl Into<Cow<'static, str>>,
60        builder: &mut Builder,
61    ) -> InputSlot<S::Input> {
62        let source = builder.commands.spawn(()).id();
63        builder.commands.queue(AddOperation::new(
64            Some(builder.scope()),
65            source,
66            RedirectWorkflowStream::new(NamedStreamRedirect::<S>::static_name(name.into())),
67        ));
68        InputSlot::new(builder.scope(), source)
69    }
70
71    pub fn spawn_node_stream(
72        name: impl Into<Cow<'static, str>>,
73        source: Entity,
74        map: &mut StreamTargetMap,
75        builder: &mut Builder,
76    ) -> Output<S::Output> {
77        let target = builder
78            .commands
79            .spawn((SingleInputStorage::new(source), UnusedTarget))
80            .id();
81
82        map.add_named::<S::Output>(name.into(), target, builder.commands());
83        Output::new(builder.scope(), target)
84    }
85
86    pub fn take_stream(
87        name: impl Into<Cow<'static, str>>,
88        source: Entity,
89        map: &mut StreamTargetMap,
90        commands: &mut Commands,
91    ) -> Receiver<S::Output> {
92        let (sender, receiver) = unbounded_channel::<S::Output>();
93        let target = commands.spawn(()).insert(ChildOf(source)).id();
94
95        map.add_named::<S::Output>(name.into(), target, commands);
96        commands.queue(AddExecution::new(None, target, TakenStream::new(sender)));
97
98        receiver
99    }
100
101    pub fn collect_stream(
102        name: impl Into<Cow<'static, str>>,
103        source: Entity,
104        target: Entity,
105        map: &mut StreamTargetMap,
106        commands: &mut Commands,
107    ) {
108        let name = name.into();
109        let redirect = commands.spawn(()).insert(ChildOf(source)).id();
110        commands.queue(AddExecution::new(
111            None,
112            redirect,
113            Push::<S::Output>::new(target, true).with_name(name.clone()),
114        ));
115        map.add_named::<S::Output>(name, redirect, commands);
116    }
117
118    pub fn make_stream_channel(
119        name: impl Into<Cow<'static, str>>,
120        inner: &Arc<InnerChannel>,
121        world: &World,
122    ) -> NamedStreamChannel<S> {
123        let targets =
124            NamedStreamTargets::new::<S::Output>(world.get::<StreamTargetMap>(inner.source()));
125        NamedStreamChannel::new(name.into(), Arc::new(targets), Arc::clone(&inner))
126    }
127
128    pub fn make_stream_buffer(target_map: Option<&StreamTargetMap>) -> NamedStreamBuffer<S::Input> {
129        let targets = NamedStreamTargets::new::<S::Output>(target_map);
130        NamedStreamBuffer {
131            targets: Arc::new(targets),
132            container: Default::default(),
133        }
134    }
135
136    pub fn process_stream_buffer(
137        name: impl Into<Cow<'static, str>>,
138        buffer: NamedStreamBuffer<S::Input>,
139        source: Entity,
140        session: Entity,
141        unused: &mut UnusedStreams,
142        world: &mut World,
143        roster: &mut OperationRoster,
144    ) -> OperationResult {
145        let name = name.into();
146        let targets = buffer.targets;
147        let mut was_unused = true;
148        for value in Rc::into_inner(buffer.container)
149            .or_broken()?
150            .into_inner()
151            .into_iter()
152        {
153            was_unused = false;
154            let target = targets.get(&name);
155            let mut request = StreamRequest {
156                source,
157                session,
158                target: target.map(NamedTarget::as_entity),
159                world,
160                roster,
161            };
162
163            S::side_effect(value, &mut request)
164                .and_then(|value| {
165                    target
166                        .map(|t| {
167                            t.send_output(
168                                NamedValue {
169                                    name: name.clone(),
170                                    value,
171                                },
172                                request,
173                            )
174                        })
175                        .unwrap_or(Ok(()))
176                })
177                .report_unhandled(source, world);
178        }
179
180        if was_unused {
181            unused.streams.push(std::any::type_name::<Self>());
182        }
183
184        Ok(())
185    }
186
187    pub fn defer_buffer(
188        name: impl Into<Cow<'static, str>>,
189        buffer: NamedStreamBuffer<S::Input>,
190        source: Entity,
191        session: Entity,
192        commands: &mut Commands,
193    ) {
194        let name = name.into();
195        let container: DefaultStreamBufferContainer<_> = buffer
196            .container
197            .take()
198            .into_iter()
199            .map(|value| NamedValue {
200                name: name.clone(),
201                value,
202            })
203            .collect();
204
205        commands.queue(SendNamedStreams::<
206            S,
207            DefaultStreamBufferContainer<NamedValue<S::Input>>,
208        >::new(container, source, session, buffer.targets));
209    }
210}
211
212/// A container that can tie together a name with a value.
213#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
214pub struct NamedValue<T: 'static + Send + Sync> {
215    pub name: Cow<'static, str>,
216    pub value: T,
217}
218
219impl<T: 'static + Send + Sync> NamedValue<T> {
220    pub fn new(name: impl Into<Cow<'static, str>>, value: T) -> Self {
221        Self {
222            name: name.into(),
223            value,
224        }
225    }
226}
227
228#[derive(Default)]
229pub struct NamedStreamTargets {
230    /// Targets that connected to a specific stream name
231    specific: HashMap<Cow<'static, str>, Entity>,
232    /// A target that connected to a general NamedStream
233    general: Option<Entity>,
234    /// A target that accepts the base stream output
235    anonymous: Option<Entity>,
236}
237
238impl NamedStreamTargets {
239    pub fn new<T: 'static + Send + Sync>(targets: Option<&StreamTargetMap>) -> Self {
240        let Some(targets) = targets else {
241            return Self::default();
242        };
243
244        let output_type = std::any::TypeId::of::<T>();
245
246        let mut specific = HashMap::new();
247        for (name, (ty, target)) in &targets.named {
248            if *ty == output_type {
249                specific.insert(name.clone(), *target);
250            }
251        }
252
253        let general = targets.get_anonymous::<NamedValue<T>>();
254        let anonymous = targets.get_anonymous::<T>();
255
256        Self {
257            specific,
258            general,
259            anonymous,
260        }
261    }
262
263    pub fn get(&self, name: &str) -> Option<NamedTarget> {
264        self
265            // First check the specifically named connections
266            .specific
267            .get(name)
268            .copied()
269            .map(NamedTarget::Value)
270            // If there is no specifically named connection, then use a target
271            // that accepts all named streams
272            .or_else(|| self.general.map(NamedTarget::NamedValue))
273            // If there is no target accepting all naemd streams then use one
274            // that accepts the output type without any name
275            .or_else(|| self.anonymous.map(NamedTarget::Value))
276    }
277}
278
279#[derive(Clone, Copy)]
280pub enum NamedTarget {
281    /// Send a named value
282    NamedValue(Entity),
283    /// Send only the value
284    Value(Entity),
285}
286
287impl NamedTarget {
288    pub fn as_entity(self) -> Entity {
289        match self {
290            Self::NamedValue(target) => target,
291            Self::Value(target) => target,
292        }
293    }
294
295    pub fn send_output<T: 'static + Send + Sync>(
296        self,
297        NamedValue { name, value }: NamedValue<T>,
298        request: StreamRequest,
299    ) -> OperationResult {
300        match self {
301            NamedTarget::NamedValue(_) => request.send_output(NamedValue { name, value }),
302            NamedTarget::Value(_) => request.send_output(value),
303        }
304    }
305}
306
307pub struct SendNamedStreams<S, Container> {
308    container: Container,
309    source: Entity,
310    session: Entity,
311    targets: Arc<NamedStreamTargets>,
312    _ignore: std::marker::PhantomData<fn(S)>,
313}
314
315impl<S, Container> SendNamedStreams<S, Container> {
316    pub fn new(
317        container: Container,
318        source: Entity,
319        session: Entity,
320        targets: Arc<NamedStreamTargets>,
321    ) -> Self {
322        Self {
323            container,
324            source,
325            session,
326            targets,
327            _ignore: Default::default(),
328        }
329    }
330}
331
332impl<S, Container> Command for SendNamedStreams<S, Container>
333where
334    S: StreamEffect,
335    Container: 'static + Send + Sync + IntoIterator<Item = NamedValue<S::Input>>,
336{
337    fn apply(self, world: &mut World) {
338        world.get_resource_or_insert_with(DeferredRoster::default);
339        world.resource_scope::<DeferredRoster, _>(|world, mut deferred| {
340            for data in self.container {
341                let NamedValue { name, value } = data;
342                let target = self.targets.get(&name);
343                let mut request = StreamRequest {
344                    source: self.source,
345                    session: self.session,
346                    target: target.map(NamedTarget::as_entity),
347                    world,
348                    roster: &mut deferred,
349                };
350
351                S::side_effect(value, &mut request)
352                    .and_then(move |value| {
353                        target
354                            .map(|t| t.send_output(NamedValue { name, value }, request))
355                            .unwrap_or(Ok(()))
356                    })
357                    .report_unhandled(self.source, world);
358            }
359        });
360    }
361}
362
363pub struct NamedStreamRedirect<S: StreamEffect> {
364    static_name: Option<Cow<'static, str>>,
365    _ignore: std::marker::PhantomData<fn(S)>,
366}
367
368impl<S: StreamEffect> NamedStreamRedirect<S> {
369    pub fn dynamic() -> Self {
370        Self {
371            static_name: None,
372            _ignore: Default::default(),
373        }
374    }
375
376    pub fn static_name(static_name: Cow<'static, str>) -> Self {
377        Self {
378            static_name: Some(static_name),
379            _ignore: Default::default(),
380        }
381    }
382}
383
384#[derive(Component)]
385struct StaticStreamName(Option<Cow<'static, str>>);
386
387impl<S: StreamEffect> StreamRedirect for NamedStreamRedirect<S> {
388    type Input = NamedValue<S::Input>;
389
390    fn setup(self, OperationSetup { source, world }: OperationSetup) -> OperationResult {
391        let mut entity_mut = world.entity_mut(source);
392        if self.static_name.is_some() {
393            // We have a static name for this redirect then we expect to receive
394            // a plain S::Input
395            entity_mut.insert(InputBundle::<S::Input>::new());
396        } else {
397            // If there is no static name then this is meant to receive any
398            // named input.
399            entity_mut.insert(InputBundle::<NamedValue<S::Input>>::new());
400        }
401
402        entity_mut.insert(StaticStreamName(self.static_name));
403
404        Ok(())
405    }
406
407    fn execute(
408        OperationRequest {
409            source,
410            world,
411            roster,
412        }: OperationRequest,
413    ) -> OperationResult {
414        let mut source_mut = world.get_entity_mut(source).or_broken()?;
415
416        let static_name = source_mut.get::<StaticStreamName>().or_broken()?.0.clone();
417        let (scoped_session, name, value) = match static_name {
418            Some(name) => {
419                // If this named stream has a static name then we expect to receive
420                // a plain S::Input and use the stream's static name.
421                let Input {
422                    session: scoped_session,
423                    data: value,
424                } = source_mut.take_input::<S::Input>()?;
425                (scoped_session, name, value)
426            }
427            None => {
428                // f this named stream does not have a static name then it's for
429                // a dynamically named stream, so we expect to receive a named
430                // value.
431                let Input {
432                    session: scoped_session,
433                    data: NamedValue { name, value },
434                } = source_mut.take_input::<NamedValue<S::Input>>()?;
435                (scoped_session, name, value)
436            }
437        };
438
439        let scope = world.get::<ScopeStorage>(source).or_broken()?.get();
440
441        let exit = world
442            .get::<ExitTargetStorage>(scope)
443            .or_broken()?
444            .map
445            .get(&scoped_session)
446            .or_not_ready()?;
447
448        let exit_source = exit.source;
449        let parent_session = exit.parent_session;
450
451        let stream_targets = world.get::<StreamTargetMap>(exit_source).or_broken()?;
452
453        let target = stream_targets.get_named_or_anonymous::<S::Output>(&name);
454        let mut request = StreamRequest {
455            source,
456            session: parent_session,
457            target: target.map(NamedTarget::as_entity),
458            world,
459            roster,
460        };
461
462        S::side_effect(value, &mut request).and_then(|value| {
463            target
464                .map(|t| t.send_output(NamedValue { name, value }, request))
465                .unwrap_or(Ok(()))
466        })?;
467
468        Ok(())
469    }
470}
471
472pub struct NamedStreamChannel<S> {
473    name: Cow<'static, str>,
474    targets: Arc<NamedStreamTargets>,
475    inner: Arc<InnerChannel>,
476    _ignore: std::marker::PhantomData<fn(S)>,
477}
478
479impl<S: StreamEffect> NamedStreamChannel<S> {
480    pub fn send(&self, data: S::Input) {
481        let f = send_named_stream::<S>(
482            self.inner.source,
483            self.inner.session,
484            Arc::clone(&self.targets),
485            self.name.clone(),
486            data,
487        );
488
489        self.inner.sender.send(Box::new(f)).ok();
490    }
491
492    fn new(
493        name: Cow<'static, str>,
494        targets: Arc<NamedStreamTargets>,
495        inner: Arc<InnerChannel>,
496    ) -> Self {
497        Self {
498            name,
499            targets,
500            inner,
501            _ignore: Default::default(),
502        }
503    }
504}
505
506impl<S> Clone for NamedStreamChannel<S> {
507    fn clone(&self) -> Self {
508        Self {
509            name: self.name.clone(),
510            targets: Arc::clone(&self.targets),
511            inner: Arc::clone(&self.inner),
512            _ignore: Default::default(),
513        }
514    }
515}
516
517pub(crate) fn send_named_stream<S: StreamEffect>(
518    source: Entity,
519    session: Entity,
520    targets: Arc<NamedStreamTargets>,
521    name: Cow<'static, str>,
522    value: S::Input,
523) -> impl FnOnce(&mut World, &mut OperationRoster) {
524    move |world: &mut World, roster: &mut OperationRoster| {
525        let target = targets.get(&name);
526        let mut request = StreamRequest {
527            source,
528            session,
529            target: target.map(NamedTarget::as_entity),
530            world,
531            roster,
532        };
533
534        S::side_effect(value, &mut request)
535            .and_then(|value| {
536                target
537                    .map(|t| t.send_output(NamedValue { name, value }, request))
538                    .unwrap_or(Ok(()))
539            })
540            .report_unhandled(source, world);
541    }
542}
543
544pub struct NamedStreamBuffer<T: 'static + Send + Sync> {
545    targets: Arc<NamedStreamTargets>,
546    container: Rc<RefCell<DefaultStreamBufferContainer<T>>>,
547}
548
549impl<T: 'static + Send + Sync> Clone for NamedStreamBuffer<T> {
550    fn clone(&self) -> Self {
551        Self {
552            targets: Arc::clone(&self.targets),
553            container: Rc::clone(&self.container),
554        }
555    }
556}
557
558impl<T: 'static + Send + Sync> NamedStreamBuffer<T> {
559    pub fn send(&self, input: T) {
560        self.container.borrow_mut().push(input);
561    }
562}