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;