1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Micro Message Pass: Next Generation (ump-ng) is a library for passing
//! messages between thread/tasks.  It is similar to the `ump` library, but
//! with an added uni-directional message passing primitive.
//!
//! The primary purpose of ump(-ng) is to create simple RPC like designs, but
//! between threads/tasks within a process rather than between processes over
//! networks.
//!
//! # High-level usage overview
//! An application calls [`channel`] to create a linked pair of a [`Server`]
//! and a [`Client`].
//!
//! The server calls [`Server::wait()`]/[`Server::async_wait()`], which
//! blocks and waits for an incoming message from a client.
//!
//! A client, in a separate thread or task, calls [`Client::post()`] to send a
//! unidirectional message to the server, or [`Client::req()`]/
//! [`Client::areq()`] to send a message to the server and wait for a reply.
//!
//! The server's wait call returns either a _post_ message or a _request_
//! message that consist a pair of a message and a [`ReplyContext`] that is
//! used to send a reply back to the client.
//!
//! After processing its application-defined message, the server *must* call
//! the [`ReplyContext::reply()`] on the returned reply context object to
//! return a reply message to the client.
//!
//! Typically the server calls wait again to wait for next message from a
//! client.
//!
//! The client receives the reply from the server and processes it.
//!
//! # Example
//! ```
//! use std::thread;
//!
//! use ump_ng::{channel, MsgType};
//!
//! let (server, client) = channel::<String, String, String, ()>();
//!
//! let server_thread = thread::spawn(move || {
//!   // Wait for data to arrive from a client
//!   loop {
//!     println!("Server waiting for message ..");
//!     match server.wait().unwrap() {
//!       MsgType::Put(data) => {
//!         println!("Server received Put: '{}'", data);
//!       }
//!       MsgType::Request(data, rctx) => {
//!         println!("Server received Request: '{}'", data);
//!
//!         // Process data from client
//!
//!         // Reply to client
//!         let reply = format!("Hello, {}!", data);
//!         println!("Server replying '{}'", reply);
//!         rctx.reply(reply);
//!         break;
//!       }
//!     }
//!   }
//!
//!   println!("Server done");
//! });
//!
//! let msg = String::from("Client");
//! println!("Client putting '{}'", msg);
//! let reply = client.post(msg).unwrap();
//!
//! let msg = String::from("Client");
//! println!("Client requesting '{}'", msg);
//! let reply = client.req(msg).unwrap();
//! println!("Client received reply '{}'", reply);
//!
//! println!("Client done");
//!
//! server_thread.join().unwrap();
//! ```
//! In practice the send/reply types will probably be `enum`s used to
//! indicate command/return type with associated data.  The third type argument
//! to [`channel`] is an error type that can be used to explicitly pass errors
//! back to the sender.
//!
//! # Semantics
//! There are some potentially useful semantic quirks that can be good to know
//! about, but some of them should be used with caution.  This section will
//! describe some semantics that you can rely on, and others that you should be
//! careful about relying on.
//!
//! ## Stable invariants
//!
//! These are behaviors which should not change in future versions.
//!
//! - The reply contexts are independent of the `Server` context.  This has
//!   some useful implications for server threads that spawn separate threads
//!   to process messages and return replies:  *The server can safely terminate
//!   while there are clients waiting for replies* (implied: the server can
//!   safely terminate while there are reply contexts in-flight).
//! - A cloned client is paired with the same server as its origin, but in all
//!   other respects the clone and its origin are independent of each other.
//! - A client can be moved to a new thread.
//! - Any permutation of sync/async server/clients can be combined.  `async`
//!   code must use the async method variants when available.
//!
//! ## Unstable invariants
//!
//! These are invariants you can trust will work in the current version, but
//! they exist merely as a side-effect of the current implementation.  Avoid
//! relying on these if possible.
//!
//! - A single client can be used from two different threads.  If a `Client`
//!   object in placed in an Arc, is cloned and passed to another thread/task
//!   then both the clone and the original can be used simultaneously.  In the
//!   future this may not be allowed. It is recommended that a new clone of the
//!   client be created instead.
//! - Put/Request messages arrive in the same order they were added to the
//!   queue.  In future versions one type may be prioritized over the other.

mod client;
mod err;
mod rctx;
mod server;

pub use err::Error;

pub use crate::{
  client::{Client, WaitReply, WeakClient},
  rctx::ReplyContext,
  server::{MsgType, Server}
};

/// Create a pair of linked [`Server`] and [`Client`] objects.
///
/// The [`Server`] object is used to wait for incoming messages from connected
/// clients.  Once a message arrives it must reply to it using a
/// [`ReplyContext`] that's returned to it in the same call that returned the
/// message.
///
/// The [`Client`] object can be used to send messages to the [`Server`].  The
/// [`Client::req()`] call will not return until the server has replied.
///
/// Clients can be [cloned](Client::clone()); each clone will create a
/// new client object that is connected to the same server object, but is
/// completely independent of the original client.
///
/// The `S` type parameter is the "send" data type that clients will transfer
/// to the server.  The `R` type parameter is the "receive" data type that
/// clients will receive from the server.  The `E` type parameter can be used
/// to return application specific errors from the server to the client.
#[allow(clippy::type_complexity)]
pub fn channel<P, S, R, E>() -> (Server<P, S, R, E>, Client<P, S, R, E>) {
  let (qpusher, qpuller) = sigq::new();

  (Server(qpuller), Client(qpusher))
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :