1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//!
//! # flo_scene
//!
//! flo_scene is a crate that provides a way to build large pieces of software by combining smaller
//! 'sub-programs' that communicate via messages. This simplifies dependencies over more traditional
//! object-oriented techniques, as sub-programs do not need to directly depend on each other. There
//! are also benefits in terms of testing, code re-use and configurability.
//!
//! flo_scene has some companion crates: `flo_scene_pipe` adds sockets for inter-process communication
//! and a command interface for interacting directly with the scene. `flo_scene_wasm` allows components
//! compiled as wasm to be loaded into a scene. `flo_scene_guest` can be used to write such components.
//!
//! ## Basic usage
//!
//! Scenes are created using `Scene::default()` or `Scene::empty()`. The empty scene contains no
//! subprograms by default but the default scene contains some default ones, in particular a
//! control program that can be used to start other programs or define connections between programs.
//!
//! ```
//! use flo_scene::*;
//!
//! let scene = Scene::default();
//! ```
//!
//! Sub-programs read from a single input stream of messages and can write to any number of output
//! streams. Each output stream can be connected to the input of another program, and these connections
//! can be specified independently of the programs themselves. Messages need to implement the
//! `SceneMessage` trait, and subprograms can be added to a scene using the `add_subprogram()` function.
//!
//! ```
//! # use flo_scene::*;
//! # use serde::*;
//! # use futures::prelude::*;
//! # let scene = Scene::default();
//! #
//! // Simple logger
//! #[derive(Debug, Serialize, Deserialize)]
//! pub enum LogMessage {
//! Info(String),
//! Warning(String),
//! Error(String)
//! }
//!
//! impl SceneMessage for LogMessage { }
//!
//! let log_program = SubProgramId::called("Logger");
//! scene.add_subprogram(log_program,
//! |mut log_messages: InputStream<LogMessage>, _context| async move {
//! while let Some(log_message) = log_messages.next().await {
//! match log_message {
//! LogMessage::Info(msg) => { println!("INFO: {:?}", msg); }
//! LogMessage::Warning(msg) => { println!("WARNING: {:?}", msg); }
//! LogMessage::Error(msg) => { println!("ERROR: {:?}", msg); }
//! }
//! }
//! },
//! 10)
//! ```
//!
//! Connections can be defined by the `connect_programs()` function. Note how this means that something
//! that generates log messages does not need to know their destination and that it's possible to change
//! how something is logging at run-time if needed.
//!
//! ```
//! # use flo_scene::*;
//! # use futures::prelude::*;
//! # use serde::*;
//! # let scene = Scene::default();
//! # let log_program = SubProgramId::new();
//! # #[derive(Serialize, Deserialize)]
//! # pub enum LogMessage { Warning(String) };
//! # impl SceneMessage for LogMessage { }
//! # scene.add_subprogram(log_program, |_: InputStream<LogMessage>, _| async { }, 0);
//! #
//! // Connect any program that writes log messages to our log program
//! // '()' means 'any source' here, it's possible to define connections on a per-subprogram basis if needed.
//! scene.connect_programs((), log_program, StreamId::with_message_type::<LogMessage>()).unwrap();
//! ```
//!
//! Subprograms have a context that can be used to retrieve output streams, or send single messages, so
//! after this connection is set up, anything can send log messages.
//!
//! ```
//! # use flo_scene::*;
//! # use futures::prelude::*;
//! # use serde::*;
//! # let scene = Scene::default();
//! # #[derive(Debug, Serialize, Deserialize)]
//! # pub enum LogMessage { Warning(String) };
//! # impl SceneMessage for LogMessage { }
//! #
//! let test_program = SubProgramId::new();
//! scene.add_subprogram(test_program,
//! |_: InputStream<()>, context| async move {
//! // '()' means send to any target
//! let mut logger = context.send::<LogMessage>(()).unwrap();
//!
//! // Will send to the logger program
//! logger.send(LogMessage::Warning("Hello".to_string())).await.unwrap();
//! },
//! 0);
//! ```
//!
//! Once set up, the scene needs to be run, in the async context of your choice:
//!
//! ```Rust
//! executor::block_on(async {
//! scene.run_scene().await;
//! });
//! ```
//!
//! A single scene can be run in multiple threads if needed and subprograms are naturally able to run
//! asynchronously as they communicate with messages rather than by direct data access.
//!
//! ## A few more advanced things
//!
//! When the scene is created with `Scene::default()`, a control program is present that allows
//! subprograms to start other subprograms or create connections:
//!
//! ```Rust
//! /* ... */
//! context.send_message(SceneControl::start_program(new_program_id, |input, context| /* ... */, 10)).await.unwrap();
//! context.send_message(SceneControl::connect(some_program, some_other_program, StreamId::for_message_type::<MyMessage>())).await.unwrap();
//! ```
//!
//! The empty scene does not get this control program (it's possible to use the `Scene` struct directly though).
//!
//! Filters make it possible to connect two subprograms that take different message types by transforming
//! them. They need to be registered, then they can be used as a stream target:
//!
//! ```Rust
//! // Note that the stream ID identifies the *source* stream: there's only one input stream for any program
//! let mine_to_yours_filter = FilterHandle::for_filter(|my_messages: InputStream<MyMessage>| my_messages.map(|msg| YourMessage::from(msg)));
//! scene.connect(my_program, StreamTarget::Filtered(mine_to_yours_filter, your_program), StreamId::with_message_type::<MyMessage>()).unwrap();
//! ```
//!
//! The message type has some functions that can be overridden to provide some default behaviour which
//! can remove the need to manually configure connections whenever a scene is created:
//!
//! ```Rust
//! impl SceneMessage for MyMessage {
//! fn default_target() -> StreamTarget {
//! StreamTarget::Program(SubProgramId::called("MyMessageHandler"))
//! }
//! }
//! ```
//!
//! A program can 'upgrade' its input stream to annotate the messages with their source if it needs this information:
//!
//! ```Rust
//! scene.add_subprogram(log_program,
//! |log_messages: InputStream<LogMessage>, _context| async move {
//! let mut log_messages = log_messages.messages_with_sources();
//! while let Some((source, log_message)) = log_messages.next().await {
//! match log_message {
//! LogMessage::Info(msg) => { println!("INFO: [{:?}] {:?}", source, msg); }
//! LogMessage::Warning(msg) => { println!("WARNING: [{:?}] {:?}", source, msg); }
//! LogMessage::Error(msg) => { println!("ERROR: [{:?}] {:?}", source, msg); }
//! }
//! }
//! },
//! 10)
//! ```
//!
//! It is possible to request a stream directly to a particular program:
//!
//! ```Rust
//! let mut logger = context.send::<LogMessage>(SubProgramId::called("MoreSpecificLogger"));
//! ```
//!
//! But it's also possible to redirect these with a connection request:
//!
//! ```Rust
//! // The scene is the ultimate arbiter of who can talk to who, so if we don't want our program talking to the MoreSpecificLogger after all we can change that
//! // Take care as this can get confusing!
//! scene.connect(exception_program, standard_logger_program, StreamId::with_message_type::<LogMessage>().for_target(SubProgramId::called("MoreSpecificLogger")));
//! ```
//!
//! You can create and run more than one `Scene` at once if needed.
//!
// I prefer this to be consistent across the struct when initialising
pub use *;
pub use *;
// #[cfg(target_family="wasm")]