silx_core/
lib.rs

1//! This is part of [**Silx**](https://crates.io/crates/silx) project  
2//!
3//! `silx-core` contains core components 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/// Shared structures, traits and macros; reexport
590mod shared;
591pub use self::shared::{ types, servants, utils, channels, id_tools, };
592
593/// silx structures
594pub (crate) mod structs;
595/// silx traits
596pub (crate) mod traits;
597/// silx builder
598pub (crate) mod builder;
599/// silx network
600pub (crate) mod net;
601
602
603const NALLOC: usize = 5;
604
605type QueryIdType = u64;
606type ChannelIdType = u64;
607type ServantIdType = u32; 
608
609
610
611#[doc(hidden)]
612/// Probes for testing features activation
613pub mod probes;