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
//! # talaria
//!
//! `talaria` is a high performance, cyclic message passing library with bounded FIFO semantics.
//!
//! `talaria` is broken up into three main concepts:
//! [Channels](channel::Channel)
//! [Partitions](partition::Partition)
//! [Reservations](partition::Reservation)
//!
//! #### Channels
//! talaria's channels are similar to bounded channels found in the standard library. A couple of
//! differences exist, though.
//!
//! For one, channels need to be provided a fixed collection of objects to manage ahead of time.
//! Secondly, channels are constructed with at least two partitions.
//!
//! #### Partitions
//! Partitions can be thought of as "states" for the data managed by the channel. Why not
//! consider partitions as "owners" of data? Partitions can be configured to either be
//! [Exclusive](partition::Exclusive) or [Concurrent](partition::Concurrent), which describes
//! whether or not multiple instances of the partition can be held simultaneously. So, when all
//! partitions are in "exclusive" mode, it might make sense to consider them as distinct "owners",
//! but concurrent access to a partition changes this somewhat.
//!
//! Partitions are used to reserve one or more objects currently owned by the partition. Once the
//! objects are no longer in use (e.g. go out of scope), they are "transferred" to the next logical
//! partition. So, objects go from partition `0` to partition `1`, from partition `1` to partition
//! `2`, and so on and so forth. Once an object in the final partition is unreserved, it is returned
//! to partition `0`.
//!
//! It's helpful to think of partitions more as "states" in a cycle, rather than "sender" and
//! "receiver" pairs. In fact, this was the original motivation for the library - to use partitions
//! to represent states of an object in its lifecycle, allowing for simple state transitions, and
//! access by multiple owners if desired. This, of course, lends itself to message passing, assuming
//! mutability of the data inside the partitions.
//!
//! #### Reservations
//! Reservations represent exclusive ownership of some data. Reservations are created from a
//! partition. Once available, a [reservation](partition::Reservation) is created which holds
//! exclusive ownership to the objects requested. This means that once you hold a reservation, it is
//! completely safe to read and write to these objects.
//!
//! Once the reservation goes out of scope, or is explicitly dropped, the ownership of the objects
//! are "transferred" to the next logical partition. Notably, for concurrent partitions, multiple
//! reservations can be requested at a time on one partition. Holding a reservation still guarantees
//! exclusive ownership of the objects managed by the reservation, but dropping the reservation can
//! temporarily block execution until the prior reservations have also been passed along. That is to
//! say, reservations respect FIFO ordering - a reservation created first, must be dropped first and
//! later reservations will wait until this is possible.
//!
//! # How to use
//!
//! There are three steps to using `talaria`:
//!
//! 1. Create a channel
//! 2. Get a partition
//! 3. Acquire a reservation
//!
//! Creating a channel can be done via Channel's [builder](channel::Channel::builder) method. We'll
//! need to pass in a fixed collection of objects for it to manage when we call
//! [build](channel::Builder::build).
//!
//! Afterwards, depending on how we configured our partitions, we'll either call
//! [get_exclusive_partition](channel::Channel::get_exclusive_partition)
//! or [get_concurrent_partition](channel::Channel::get_concurrent_partition) to get a partition.
//! This will return a [partition](partition::Partition) which we can use to acquire reservations.
//!
//! Finally, we can call [reserve](partition::Partition::reserve) on the partition to get a
//! reservation. Once we hold a reservation, we can either index directly into the reservation or
//! call [iter](partition::Reservation::iter) (or [iter_mut](partition::Reservation::iter_mut))
//! to get an iterator over the objects managed by the reservation.
//!
//! Once dropped, the items managed by the reservation will be transferred to the next logical
//! partition.
//!
//! ```
//! # use talaria::channel::Channel;
//! #[derive(Clone)]
//! enum Message {
//! Ping,
//! Pong,
//! }
//!
//! fn main() {
//! // id of the partition we'll access on the main thread
//! const MAIN_PARTITION: usize = 0;
//! // id of the partition we'll access on worker thread
//! const THREAD_PARTITION: usize = 1;
//!
//! let channel = Channel::builder()
//! .add_exclusive_partition()
//! .add_exclusive_partition()
//! .build(vec![Message::Ping; 16])
//! .unwrap();
//!
//! let channel_clone = channel.clone();
//!
//! // spin up a worker thread
//! // it will do what the main thread is doing, but in reverse..
//! let thread_handle = std::thread::spawn(move || {
//! let mut partition = channel_clone
//! .get_exclusive_partition(THREAD_PARTITION)
//! .unwrap();
//!
//! while let Ok(mut reservation) = partition.reserve(1) {
//! reservation[0] = match &reservation[0] {
//! Message::Pong => Message::Ping,
//! Message::Ping => panic!("unexpected message!")
//! };
//! }
//! });
//!
//! let mut partition = channel
//! .get_exclusive_partition(MAIN_PARTITION)
//! .unwrap();
//!
//! // reserve an item at a time
//! while let Ok(mut reservation) = partition.reserve(1) {
//! reservation[0] = match &reservation[0] {
//! // if the first (and only) element is a "ping" message,
//! // set it to "pong" and forward it
//! Message::Ping => Message::Pong,
//! // otherwise we got an unexpected message!
//! Message::Pong => panic!("unexpected message!")
//! };
//! }
//!
//! thread_handle.join().unwrap();
//! }
//! ```