silx_types/
lib.rs

1//! This is part of [**Silx**](https://crates.io/crates/silx) project  
2//!
3//! `silx-types` contains type definitions for implementing silx application  
4//!
5//! # Purpose
6//! Silx aims to enable:
7//! * build an application as a network of asynchronous servants on one or more machines
8//! * build these servants without worrying about the detailed implementation of exchange channels between servants 
9//! * connect these servants using simple-to-parameterize exchange channels
10//! * control the coherence of the channel data types thanks to type hash codes
11//! * implement serialization with zero-copy deserialization (rkyv) on the exchange channels
12//! * serialize the application's entire network definition in editable text format, then reload and execute it  
13//!
14//! Silx remains a project under development.   
15//!
16//! To start with, the following example provides a minimalist overview.
17//! Other examples are also available on the project's github.
18//!
19//! # Minimalist example (Hello)
20//! ## Cargo.toml
21//! ```toml
22//! [package]
23//! name = "silx_hello"
24//! version = "0.1.2"
25//! edition = "2021"
26//!
27//! [dependencies]
28//! tokio = "^1.36.0"
29//! serde = "^1.0.197"
30//! typetag = "^0.2.16"
31//!
32//! silx-core = "0.1.2"
33//! silx-types = "0.1.2"
34//! ```
35//! ## main.rs
36//! ```text
37//! use std::{ net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, time::Duration };
38//! use serde::{Deserialize, Serialize};
39//! use tokio::{spawn, time::sleep};
40//!
41//! use silx_core::{ 
42//!     id_tools::IdBuilder, servants::shutdown::ShutdownBuilder, 
43//!     utils::{ 
44//!         produce_emit, produce_future, produce_query, produce_read, produce_reply2, 
45//!         Filable, MsgFromServant, ProcessInstance, ProcessProducer, SendToMaster, 
46//!         ServantBuilder, ServantBuilderParameters, Starter, StarterProducer
47//!     },
48//! };
49//! use silx_types::{ ArchSized, WakeSlx };
50//!
51//! // ///////////////////////////
52//! // Servants implementations
53//!
54//! /// Servant replying greetings by completing queryied full name with Hello
55//! #[derive(Serialize, Deserialize, Clone,)]
56//! struct Hello(String);
57//! #[typetag::serde] impl ServantBuilder for Hello { }
58//! impl ServantBuilderParameters for Hello {
59//!     fn max_cycle_time(&self) -> Duration { Duration::from_millis(100) }
60//!     fn build_process(&self, _task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
61//!         let mut producer = ProcessProducer::new(&send_to_master);         
62//!         let hello = self.0.clone();
63//!         let query_channel = "QueryHello".to_string();
64//!         // build reply process
65//!         produce_reply2!([hello], producer, String => String, query_channel, data, {
66//!             // get full name
67//!             let full_name: &str = data.archive_ref().unwrap();
68//!             // build an return greeting
69//!             let greeting = format!("{hello} {full_name}");
70//!             greeting.arch_sized().unwrap()
71//!         }).unwrap();
72//!         producer.named_process()
73//!     }
74//! }
75//!
76//! /// Servant sending first name 
77//! #[derive(Serialize, Deserialize, Clone,)]
78//! struct FirstName(String);
79//! #[typetag::serde] impl ServantBuilder for FirstName { }
80//! impl ServantBuilderParameters for FirstName {
81//!     fn max_cycle_time(&self) -> Duration { Duration::from_millis(100) }
82//!     fn build_process(&self, _task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
83//!         let mut producer = ProcessProducer::new(&send_to_master);
84//!         let first_name = self.0.clone();
85//!         // build channels
86//!         let emit_channel = "FirstName".to_string();
87//!         let sender = produce_emit!(producer, String, emit_channel, None,).unwrap();
88//!         // build process
89//!         produce_future!(producer, {
90//!             sleep(Duration::from_millis(100)).await; // Wait a little bit for receiver to be ready
91//!             sender.send(first_name.arch_sized().unwrap()).await.unwrap();
92//!         })
93//!     }
94//! }
95//!
96//! /// Servant doing:
97//! /// * receive first name
98//! /// * build full name
99//! /// * query for greeting
100//! /// * print greeting
101//! /// * shutdown
102//! #[derive(Serialize, Deserialize, Clone,)]
103//! struct LastName(String);
104//! #[typetag::serde] impl ServantBuilder for LastName { }
105//! impl ServantBuilderParameters for LastName {
106//!     fn max_cycle_time(&self) -> Duration { Duration::from_millis(100) }
107//!     fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
108//!         let mut producer = ProcessProducer::new(&send_to_master);
109//!         let last_name = self.0.clone();
110//!         // build channels
111//!         let recv_channel = "FirstName".to_string();
112//!         let receiver = produce_read!(producer,String,recv_channel,None,).unwrap();
113//!         let query_channel = "QueryHello".to_string();
114//!         let (query_sender,reply_receiver) = produce_query!(producer,String => String,query_channel, None).unwrap();
115//!         let emit_death = "Shutdown".to_string();
116//!         let death_sender = produce_emit!(producer, WakeSlx, emit_death, None,).unwrap();
117//!         // build process
118//!         produce_future!(producer, {
119//!             // receive first name
120//!             let arc_first_name = receiver.recv().await.unwrap();
121//!             // build full name
122//!             let full_name = format!("{} {last_name}", arc_first_name.archive_ref().unwrap());
123//!             // query for greeting
124//!             let arc_full_name = full_name.arch_sized().unwrap();
125//!             query_sender.send(arc_full_name).await.unwrap();
126//!             let reply = reply_receiver.recv().await.unwrap();
127//!             // print greeting
128//!             println!("{}",reply.archive_ref().unwrap());
129//!             // shutdown            
130//!             death_sender.send(WakeSlx.arch_sized().unwrap()).await.unwrap();
131//!             let tid = task_id.lock().await.generate();
132//!             MsgFromServant::Shutdown(tid).send(&send_to_master).await.unwrap();
133//!         })
134//!     }
135//! }
136//!
137//! // ///////////////////////////
138//! // Network implementation
139//!
140//! /// Given main and slave socket addresses, build main and slave starters
141//! /// * main cluster implements servants `last_name` and `hello`
142//! /// * slave cluster implements servants `first_name` and `shutdown` (which will shutdown the slave)
143//! /// * actions of `last_name`: 
144//! ///   * receive first name from `first_name`
145//! ///   * build full name and query greeting from `hello`
146//! ///   * print greeting
147//! ///   * send shutdown signal to `shutdown` and shutdown main cluster
148//! /// * `main_addr: SocketAddr` : main socket address
149//! /// * `slave_addr: SocketAddr` : slave socket address
150//! /// * `save_dir: &PathBuf` : directory where to save the network
151//! /// * Output: main and slave starters
152//! pub fn build_network (main_addr: SocketAddr, slave_addr: SocketAddr, save_dir: &PathBuf) -> (Starter,Starter) {
153//!     let max_ping = Duration::from_millis(100);
154//!     // set two clusters within the network
155//!     let start_prod = StarterProducer::new(
156//!         main_addr, "starter=main.yaml", "builder=main.yaml", None, 16
157//!     ).add_cluster(
158//!         slave_addr, "starter=slave.yaml", "builder=slave.yaml", None, 16
159//!     ).unwrap().done();
160//!     // add named servants
161//!     let start_prod = start_prod.add_process(
162//!         &main_addr, "last_name".to_string(), "servant=last_name.yaml", LastName("Doe".to_string())
163//!     ).unwrap().add_process(
164//!         &main_addr, "hello".to_string(), "servant=hello.yaml", Hello("Welcome".to_string())
165//!     ).unwrap().add_process(
166//!         &slave_addr, "first_name".to_string(),"servant=first_name.yaml", FirstName("John".to_string())
167//!     ).unwrap().add_process(
168//!         &slave_addr, "shutdown".to_string(),"servant=shutdown.yaml", ShutdownBuilder::new("Shutdown".to_string())
169//!     ).unwrap().done();
170//!     // add channels connecting the servants and produce the starter for each cluster
171//!     let mut starters = start_prod.add_query(
172//!         "channel=QueryHello.yaml", "QueryHello".to_string(), main_addr, ["last_name".to_string()], ["hello".to_string()], max_ping, None
173//!     ).unwrap().add_net_broadcast(
174//!         "channel=FirstName.yaml", "FirstName".to_string(), slave_addr, [format!("first_name"),], main_addr, [format!("last_name"),], max_ping, 16
175//!     ).unwrap().add_net_broadcast(
176//!         "channel=Shutdown.yaml", "Shutdown".to_string(), main_addr, ["last_name".to_string()], slave_addr, ["shutdown".to_string()], max_ping, 16,
177//!     ).unwrap().done();
178//!     // save, get and return starters of the clusters
179//!     let main_starter = starters.remove(&main_addr).unwrap().unload(Some(save_dir)).unwrap();
180//!     let slave_starter = starters.remove(&slave_addr).unwrap().unload(Some(save_dir)).unwrap();
181//!     (main_starter,slave_starter)
182//! }
183//!
184//! // //////////////////////////
185//! // Run the network
186//!
187//! /// Main performs:
188//! /// * build network and save it in files
189//! /// * network execution
190//! /// * network loading from files
191//! /// * execute the loaded network
192//! #[tokio::main]
193//! pub async fn main() {
194//!     // build network and save it in files
195//!     let main_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8180);
196//!     let slave_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8181);
197//!     let save_dir = PathBuf::from("./saved");
198//!     let (main_starter,slave_starter) = build_network(main_addr, slave_addr, &save_dir);
199//!     // network execution
200//!     println!("==== first run -------------\n");
201//!     let handle_slave = spawn(async move { 
202//!         // NOTA: main starter should be launched FIRST
203//!         sleep(Duration::from_millis(100)).await; // So wait a little bit
204//!         slave_starter.run().await.unwrap();
205//!     });
206//!     main_starter.run().await.unwrap();
207//!     handle_slave.await.unwrap();
208//!     sleep(Duration::from_millis(300)).await;
209//!     // network loading from files
210//!     println!("\n==== second run (loadind network) -------------\n");
211//!     let main_starter = Starter::load("starter=main.yaml", &save_dir).unwrap();
212//!     let slave_starter = Starter::load("starter=slave.yaml", &save_dir).unwrap();
213//!     // execute the loaded network
214//!     let handle_slave = spawn(async move { 
215//!         sleep(Duration::from_millis(100)).await;
216//!         slave_starter.run().await.unwrap();
217//!     });
218//!     main_starter.run().await.unwrap();
219//!     handle_slave.await.unwrap();
220//! }
221//! ```
222//! ## Typical output
223//! ```txt
224//! ==== first run -------------
225//!
226//! 127.0.0.1:8181: try to connect 127.0.0.1:8180
227//! 127.0.0.1:8181: Listening connection established
228//! cluster 127.0.0.1:8181 has been built
229//! cluster 127.0.0.1:8180 has been built
230//! Welcome John Doe
231//! cluster 127.0.0.1:8181 is ended
232//! cluster 127.0.0.1:8180 is ended
233//!
234//! ==== second run (loadind network) -------------
235//!
236//! 127.0.0.1:8181: try to connect 127.0.0.1:8180
237//! 127.0.0.1:8181: Listening connection established
238//! cluster 127.0.0.1:8181 has been built
239//! cluster 127.0.0.1:8180 has been built
240//! Welcome John Doe
241//! cluster 127.0.0.1:8180 is ended
242//! cluster 127.0.0.1:8181 is ended
243//! ```
244//! ## Servant definition
245//! Servants are built by implementing the `ServantBuilderParameters` trait and the `ServantBuilder` trait with the macro `#[typetag::serde]`.
246//! The macro `#[typetag::serde]` is required to serialize the `ServantBuilder` implementers, and are therefore necessary to describe the network by means of configuration files (see below).
247//! Below, we take a detailed look at the construction of the `LastName` and `Hello` servants: 
248//! * `LastName` corresponds to the main type of servant, including incoming and outgoing channels and a processing code
249//! * `Hello` is a reply-to-query servant, taking the form of a simple function
250//! Servant construction is the only stage where a Rust implementation is strictly necessary. 
251//! Otherwise, all that is required to build the computing network is the definition of configuration files.
252//! ### Servant `LastName`
253//! All servants must implement the `ServantBuilderParameters` trait and the `ServantBuilder` trait.
254//! The `ServantBuilder` implementation is empty but mandatory.
255//! Implementing `ServantBuilderParameters` requires defining the `max_cycle_time` and `build_process` methods.
256//! The `max_cycle_time` method specifies the maximum time allowed to respond to a request from the cluster master.
257//! After this time, the servant is considered inoperative and is killed, so this feature is of little importance:
258//! ```txt
259//! #[derive(Serialize, Deserialize, Clone,)]
260//! struct LastName(String);
261//! #[typetag::serde] impl ServantBuilder for LastName { }
262//! impl ServantBuilderParameters for LastName {
263//!     fn max_cycle_time(&self) -> Duration { Duration::from_millis(100) }
264//!     fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
265//!         [...]
266//!     }
267//! }
268//! ```
269//! In contrast, implementation of the `build_process` method concerns the essential aspects of the servant's functional behavior
270//! #### Initializing the producer and retrieving servant data
271//! Firstly, a new producer must be initialized with the send channel to the master, and secondly, the servant data can be cloned (this task is not necessary for copyable data).
272//! The producer will be an essential helper in the construction of all servant components:
273//! ```txt
274//! fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
275//!     let mut producer = ProcessProducer::new(&send_to_master);
276//!     let last_name = self.0.clone();
277//!     [...]
278//! }
279//! ```
280//! #### Setting up channels connecting the servant
281//! Channels are built by means of macros `produce_read`, `QueryHello` and `Shutdown`.
282//! These macros are working on the producer:
283//! ```txt
284//! fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
285//!     [...]
286//!     let recv_channel = "FirstName".to_string();
287//!     let receiver = produce_read!(producer,String,recv_channel,None,).unwrap();
288//!     let query_channel = "QueryHello".to_string();
289//!     let (query_sender,reply_receiver) = produce_query!(producer,String => String,query_channel, None).unwrap();
290//!     let emit_death = "Shutdown".to_string();
291//!     let death_sender = produce_emit!(producer, WakeSlx, emit_death, None,).unwrap();
292//!     [...]
293//! }
294//! ```
295//! In this code, we successively define connections to `FirstName`, `QueryHello` and `Shutdown` channels:
296//! * Macro `produce_read` registers the servant as reader of channel `FirstName`.
297//! Receiver `receiver` is generated to access the channel output
298//! * Macro `produce_query` registers the servant as a queryer on channel `QueryHello`.
299//! Sender `query_sender` and receiver `reply_receiver` are generated to send a query and receive a reply
300//! * Macro `produce_emit` registers the servant as emitter on channel `Shutdown`.
301//! Sender `death_sender` is generated to access the channel input
302//! #### Building servant processes
303//! The servant performs the following operations in succession:
304//! * receives the first name and builds the full name
305//! * request for greeting message and print greeting message
306//! * shutdown  
307//! The process is defined by means of macro:
308//! ```txt
309//! produce_future!(producer, { ... })
310//! ```
311//! ##### Receives the first name and builds the full name
312//! The servant awaits a message from `FirstName` channel.
313//! This message is archived and can be accessed as a reference (zero-copy deserialization) using the `archive_ref` method.
314//! The full name is then constructed using the `format` macro:
315//! ```txt
316//! fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
317//!     [...]
318//!     produce_future!(producer, {
319//!         let arc_first_name = receiver.recv().await.unwrap();
320//!         let full_name = format!("{} {last_name}", arc_first_name.archive_ref().unwrap());
321//!         [...]
322//!     })
323//! }
324//! ```
325//! **Note 1:** there are two methods for referencing from an archive, `archive_ref` and `arch_deref`.
326//! The `archive_ref` method references the rkyv archive, while `arch_deref` offers greater flexibility.
327//! However, `arch_deref` is less frequently implemented.  
328//! **Note 2:** `archive_mut` and `arch_deref_mut` are the pinned mutable counterparts of `archive_ref` and `arch_deref`.
329//! ##### Request for greeting message and print greeting message
330//! The servant build an archive from the full name by means of method `arch_sized`, send it as a query, await for a reply, and print this reply:
331//! ```txt
332//! fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
333//!     [...]
334//!     produce_future!(producer, {
335//!         [...]
336//!         let arc_full_name = full_name.arch_sized().unwrap();
337//!         query_sender.send(arc_full_name).await.unwrap();
338//!         let reply = reply_receiver.recv().await.unwrap();
339//!         println!("{}",reply.archive_ref().unwrap());
340//!         [...]
341//!     })
342//! }
343//! ```
344//! ##### Shutdown
345//! The servant shuts down the network by sending a wake-up message to the `shutdown` servant of the other cluster and sending a Shutdown task to its master:
346//! ```txt
347//! fn build_process(&self, task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
348//!     [...]
349//!     produce_future!(producer, {
350//!         [...]
351//!         death_sender.send(WakeSlx.arch_sized().unwrap()).await.unwrap();
352//!         let tid = task_id.lock().await.generate();
353//!         MsgFromServant::Shutdown(tid).send(&send_to_master).await.unwrap();
354//!     })
355//! }
356//! ```
357//! ### Servant `Hello`
358//! This servant is a replier, so the definition of `build_process` is different.
359//! First at all, a new producer is initialized with the send channel to the master, the servant data are cloned and the query channel name is defined:
360//! ```txt
361//! [...]
362//! impl ServantBuilderParameters for Hello {
363//!     [...]
364//!     fn build_process(&self, _task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
365//!         let mut producer = ProcessProducer::new(&send_to_master);         
366//!         let hello = self.0.clone();
367//!         let query_channel = "QueryHello".to_string();
368//!         [...]
369//!     }
370//! }
371//! ```
372//! Then, the replying code is registered to the producer by means of macro:
373//! ```txt
374//! produce_reply2!([hello], producer, String => String, query_channel, data, { ... })
375//! ```
376//! * `[hello]` informs the macro that the non-copyable variable `hello` will be moved to the closure
377//! * `String => String` informs that the query is of type `String` and the reply is of type `String`
378//! * `query_channel` is the name of the query channel
379//! * `data` is the name of the variable containing the query  
380//!
381//! In its process, the servant retrieves the reference to the full name from archive `data`, then prefixes it with the greeting message and finally returns an archive of the result: 
382//! ```txt
383//! [...]
384//! impl ServantBuilderParameters for Hello {
385//!     [...]
386//!     fn build_process(&self, _task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
387//!         [...]
388//!         produce_reply2!([hello], producer, String => String, query_channel, data, {
389//!             let full_name: &str = data.archive_ref().unwrap();
390//!             let greeting = format!("{hello} {full_name}");
391//!             greeting.arch_sized().unwrap()
392//!         }).unwrap();
393//!         [...]
394//!     }
395//! }
396//! ```
397//! At last, the process instance is recovered from `producer` and returned:
398//! ```txt
399//! [...]
400//! impl ServantBuilderParameters for Hello {
401//!     [...]
402//!     fn build_process(&self, _task_id: IdBuilder, send_to_master: SendToMaster,) -> ProcessInstance {
403//!         [...]
404//!         producer.named_process()
405//!     }
406//! }
407//! ```
408//! ## Network definition
409//! The network can be built using the `StarterProducer` and its derivatives.
410//! Another way is to edit configuration files, which are used to build the network's cluster starters by deserialization.
411//! These configuration files can be generated automatically using `StarterProducer` as shown in the `build_network` method in the example.
412//! The example proceeds as follows:
413//!   * initialize a producer with the characteristics of the main and slave clusters. 
414//!   It emerges that the serialization file for each starter and each builder (one per cluster of the two) is supplied.
415//!   The clusters are also identified by their socket addresses:
416//! ```txt
417//! let start_prod = StarterProducer::new(main_addr, "starter=main.yaml", "builder=main.yaml", None, 16)
418//!     .add_cluster(slave_addr, "starter=slave.yaml", "builder=slave.yaml", None, 16).unwrap().done()
419//! ```
420//!   * add servants to the clusters. Here servants `last_name` and `hello` are added to main cluster while servants `first_name`  and `shutdown` are added to slave cluster. The name, serialization file and value of each servant is supplied:
421//! ```txt
422//! let start_prod = start_prod
423//!     .add_process(&main_addr, "last_name".to_string(), "servant=last_name.yaml", LastName("Doe".to_string())).unwrap()
424//!     .add_process(&main_addr, "hello".to_string(), "servant=hello.yaml", Hello("Welcome".to_string())).unwrap()
425//!     .add_process(&slave_addr, "first_name".to_string(),"servant=first_name.yaml", FirstName("John".to_string())).unwrap()
426//!     .add_process(&slave_addr, "shutdown".to_string(),"servant=shutdown.yaml", ShutdownBuilder::new("Shutdown".to_string())).unwrap().done();
427//! ```
428//!   * add the channels to the clusters and retrieve the serializable starters. 
429//!   The serialization file, name and input servants followed by output servants are provided.
430//!   Indeed, the channels may connect several servants to several servants.
431//!   Moreover, the cluster address is provided in case of channels within a cluster, and the input cluster address followed by output cluster address are provided in case of channels betweens two clusters.
432//!   The nature of the channel is determined by the used method, here `add_net_broadcast` and `add_query`:
433//! ```txt
434//! let mut starters = start_prod.add_query(
435//!     "channel=QueryHello.yaml", "QueryHello".to_string(), main_addr, ["last_name".to_string()], ["hello".to_string()], max_ping, None
436//! ).unwrap().add_net_broadcast(
437//!     "channel=FirstName.yaml", "FirstName".to_string(), slave_addr, [format!("first_name"),], main_addr, [format!("last_name"),], max_ping, 16
438//! ).unwrap().add_net_broadcast(
439//!     "channel=Shutdown.yaml", "Shutdown".to_string(), main_addr, ["last_name".to_string()], slave_addr, ["shutdown".to_string()], max_ping, 16,
440//! ).unwrap().done();
441//! ```
442//!   * At this stage, the starters are serializable, but not executable. We can generate serialized files and retrieve the executable starter for a cluster using the `unload` command. 
443//!   At this stage, we have serializable, but not executable, starters. 
444//!   We can generate serialized files and retrieve the executable starter for a cluster using the `unload` command:
445//! ```txt
446//! let main_starter = starters.remove(&main_addr).unwrap().unload(Some(save_dir)).unwrap();
447//! let slave_starter = starters.remove(&slave_addr).unwrap().unload(Some(save_dir)).unwrap();
448//! (main_starter,slave_starter)
449//! ```  
450//! Note that the `unload` command can be used without producing any serialization, by supplying `None` as the serialization directory; you can also use the `unwrap` command, which achieves the same result.
451//! ## Cluster loading and execution
452//! A starter can be loaded using the `load` method, which is a shortcut to a sequence of more elementary commands.
453//! Executing a starter is simply done using the `run` method.
454//! ```txt
455//!     let save_dir = PathBuf::from("./saved");
456//!     [...]
457//!     let main_starter = Starter::load("starter=main.yaml", &save_dir).unwrap();
458//!     main_starter.run().await.unwrap();
459//! ```
460//! ## Saved files from the network serialization
461//! After a run, 11 files are generated from the network serialization in directory `saved` of the project.
462//! ```txt
463//! │   Cargo.toml
464//! │
465//! ├───saved
466//! │   ├───main
467//! │   │       builder=main.yaml
468//! │   │       builder=slave.yaml
469//! │   │       channel=FirstName.yaml
470//! │   │       channel=QueryHello.yaml
471//! │   │       channel=Shutdown.yaml
472//! │   │       servant=first_name.yaml
473//! │   │       servant=hello.yaml
474//! │   │       servant=last_name.yaml
475//! │   │       servant=shutdown.yaml
476//! │   │       starter=main.yaml
477//! │   │
478//! │   └───slave
479//! │           starter=slave.yaml
480//! │
481//! └───src
482//!         main.rs
483//! ```
484//! Directory `saved/main` contains the full definition of the main starter, while directory `saved/slave` contains the full definition of the slave starter.  
485//! **An important point is that all aspects of the network architecture are parameterized by these editable files.**
486//! The only thing that cannot be parameterized and needs to be implemented in **Rust** is the definition of servants, by implementing the traits `ServantBuilder` and `ServantBuilderParameters`.
487//! ### Slave starter saved file
488//! Directory `saved/slave` contains the only file, `starter=slave.yaml`.
489//! #### Slave starter file `starter=slave.yaml`
490//! ```yaml
491//! !Listener
492//! main: 127.0.0.1:8180
493//! this: 127.0.0.1:8181
494//! ```
495//! The file explains it all: 
496//! * slave is a `!Listener`
497//! * its socket address is `this: 127.0.0.1:8181`
498//! * it waits for all its directives and definitions from the main socket address `main: 127.0.0.1:8180`
499//! ### Main starter saved files
500//! Directory `saved/main` contains all the other files, including , `builder=slave.yaml`. 
501//! #### Main starter file `starter=main.yaml`
502//! ```yaml
503//! !Main
504//! builders:
505//!   127.0.0.1:8180: !unloaded
506//!     path: builder=main.yaml
507//!   127.0.0.1:8181: !unloaded
508//!     path: builder=slave.yaml
509//! flow:
510//!   FirstName: !unloaded
511//!     path: channel=FirstName.yaml
512//!   QueryHello: !unloaded
513//!     path: channel=QueryHello.yaml
514//!   Shutdown: !unloaded
515//!     path: channel=Shutdown.yaml
516//! main: 127.0.0.1:8180
517//! ```
518//! The file contains all the structure of the network: 
519//! * main is a `!Main`
520//! * its socket address is `main: 127.0.0.1:8180`
521//! * it controls two clusters, including itself, whose builders are listed after  `builders:`
522//!   * Cluster at address `127.0.0.1:8180` is defined within file `builder=main.yaml`
523//!   * Cluster at address `127.0.0.1:8181` is defined within file `builder=slave.yaml`
524//! * it holds the definition of all channels, which are listed after `flow:`
525//!   * Channels `FirstName`, `QueryHello` and `Shutdown` are defined respectively within `channel=FirstName.yaml`, `channel=QueryHello.yaml` and `channel=Shutdown.yaml`  
526//! ### Builders
527//! #### Builder file `builder=main.yaml`
528//! ```yaml
529//! net_size: null
530//! named_servants:
531//!   hello: !unloaded
532//!     path: servant=hello.yaml
533//!   last_name: !unloaded
534//!     path: servant=last_name.yaml
535//! ctrl_ch_capacity: 16
536//! ```
537//! This file informs that main cluster contains the servants `hello` and `last_name` which are respectively defined within files `servant=hello.yaml` and `servant=last_name.yaml`
538//! #### Builder file `builder=slave.yaml`
539//! ```yaml
540//! net_size: null
541//! named_servants:
542//!   first_name: !unloaded
543//!     path: servant=first_name.yaml
544//!   shutdown: !unloaded
545//!     path: servant=shutdown.yaml
546//! ctrl_ch_capacity: 16
547//! ```
548//! This file informs that slave cluster contains the servants `first_name` and `shutdown` which are respectively defined within files `servant=first_name.yaml` and `servant=shutdown.yaml`
549//! ### Servants and Channels files
550//! The Servant file is inherited from the type definition directly by serialization:
551//! #### Servant file `servant=hello.yaml`
552//! ```yaml
553//! servant: Hello
554//! value: Welcome
555//! ```
556//! Channels are serialized in the same way, but contain channel type, cluster addresses, data type hash codes and input/output servants lists:
557//! #### Channel file `channel=QueryHello.yaml`
558//! ```yaml
559//! !Query
560//! cluster: 127.0.0.1:8180
561//! max_ping:
562//!   secs: 0
563//!   nanos: 100000000
564//! query_type: 31758449-bc37-9d2d-7a6d-5463554081ac
565//! reply_type: 31758449-bc37-9d2d-7a6d-5463554081ac
566//! size: null
567//! input:
568//! - last_name
569//! output:
570//! - hello
571//! ```
572//! #### Channel file `channel=FirstName.yaml`
573//! ```yaml
574//! !NetBroadcast
575//! max_ping:
576//!   secs: 0
577//!   nanos: 100000000
578//! data_type: 31758449-bc37-9d2d-7a6d-5463554081ac
579//! size: 16
580//! input:
581//! - 127.0.0.1:8181
582//! - - first_name
583//! output:
584//! - 127.0.0.1:8180
585//! - - last_name
586//! ```
587
588
589/// definition of silx types and some reexports
590mod types;
591pub use types::{
592    u8slx, u16slx, u32slx, u64slx, u128slx, i8slx, i16slx, i32slx, i64slx, i128slx, f32slx, f64slx, char_slx,
593    Num, Bounded, Float, FloatConst, AsPrimitive, FromPrimitive, NumCast, ToPrimitive, One, Zero, PrimInt,
594    CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, CheckedEuclid, Euclid,
595    Inv, MulAdd, MulAddAssign, Saturating, SaturatingAdd, SaturatingMul, SaturatingSub, WrappingAdd, WrappingMul, WrappingNeg, 
596    WrappingShl, WrappingShr, WrappingSub, Pow, Signed, Unsigned,
597    Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, 
598    Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign, Sum, Product, FpCategory,
599    one, zero, checked_pow, pow, abs, abs_sub, signum, bounds, cast, float, identities, int, ops, real, sign,
600    WakeSlx, ArchToDerefMut, ArchToDeref, IntoSlx, SlxFrom, SlxInto, FromSlx, ArchSized, SlxData, 
601}; 
602
603#[cfg(feature = "use_nalgebra")]
604pub use types::nalgebra;
605
606
607
608#[doc(hidden)]
609/// Probes for testing features activation
610pub mod probes;