aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_common/pmutil.h
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_common/pmutil.h')
-rwxr-xr-xportmidi/pm_common/pmutil.h184
1 files changed, 184 insertions, 0 deletions
diff --git a/portmidi/pm_common/pmutil.h b/portmidi/pm_common/pmutil.h
new file mode 100755
index 0000000..46c618e
--- /dev/null
+++ b/portmidi/pm_common/pmutil.h
@@ -0,0 +1,184 @@
1/** @file pmutil.h lock-free queue for building MIDI
2 applications with PortMidi.
3
4 PortMidi is not reentrant, and locks can suffer from priority
5 inversion. To support coordination between system callbacks, a
6 high-priority thread created with PortTime, and the main
7 application thread, PortMidi uses a lock-free, non-blocking
8 queue. The queue implementation is not particular to MIDI and is
9 available for other uses.
10 */
11
12#ifndef PORTMIDI_PMUTIL_H
13#define PORTMIDI_PMUTIL_H
14
15#ifdef __cplusplus
16extern "C" {
17#endif /* __cplusplus */
18
19/** @defgroup grp_pmutil Lock-free Queue
20 @{
21*/
22
23/** The queue representation is opaque. Declare a queue as PmQueue * */
24typedef void PmQueue;
25
26/** create a single-reader, single-writer queue.
27
28 @param num_msgs the number of messages the queue can hold
29
30 @param the fixed message size
31
32 @return the allocated and initialized queue, or NULL if memory
33 cannot be allocated. Allocation uses #pm_malloc().
34
35 The queue only accepts fixed sized messages.
36
37 This queue implementation uses the "light pipe" algorithm which
38 operates correctly even with multi-processors and out-of-order
39 memory writes. (see Alexander Dokumentov, "Lock-free Interprocess
40 Communication," Dr. Dobbs Portal, http://www.ddj.com/,
41 articleID=189401457, June 15, 2006. This algorithm requires that
42 messages be translated to a form where no words contain
43 zeros. Each word becomes its own "data valid" tag. Because of this
44 translation, we cannot return a pointer to data still in the queue
45 when the "peek" method is called. Instead, a buffer is
46 preallocated so that data can be copied there. Pm_QueuePeek()
47 dequeues a message into this buffer and returns a pointer to it. A
48 subsequent Pm_Dequeue() will copy from this buffer.
49
50 This implementation does not try to keep reader/writer data in
51 separate cache lines or prevent thrashing on cache lines.
52 However, this algorithm differs by doing inserts/removals in
53 units of messages rather than units of machine words. Some
54 performance improvement might be obtained by not clearing data
55 immediately after a read, but instead by waiting for the end
56 of the cache line, especially if messages are smaller than
57 cache lines. See the Dokumentov article for explanation.
58
59 The algorithm is extended to handle "overflow" reporting. To
60 report an overflow, the sender writes the current tail position to
61 a field. The receiver must acknowlege receipt by zeroing the
62 field. The sender will not send more until the field is zeroed.
63 */
64PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg);
65
66/** destroy a queue and free its storage.
67
68 @param queue a queue created by #Pm_QueueCreate().
69
70 @return pmNoError or an error code.
71
72 Uses #pm_free().
73
74 */
75PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue);
76
77/** remove one message from the queue, copying it into \p msg.
78
79 @param queue a queue created by #Pm_QueueCreate().
80
81 @param msg address to which the message, if any, is copied.
82
83 @return 1 if successful, and 0 if the queue is empty. Returns
84 #pmBufferOverflow if what would have been the next thing in the
85 queue was dropped due to overflow. (So when overflow occurs, the
86 receiver can receive a queue full of messages before getting the
87 overflow report. This protocol ensures that the reader will be
88 notified when data is lost due to overflow.
89 */
90PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg);
91
92/** insert one message into the queue, copying it from \p msg.
93
94 @param queue a queue created by #Pm_QueueCreate().
95
96 @param msg address of the message to be enqueued.
97
98 @return #pmNoError if successful and #pmBufferOverflow if the
99 queue was already full. If #pmBufferOverflow is returned, the
100 overflow flag is set.
101 */
102PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg);
103
104/** test if the queue is full.
105
106 @param queue a queue created by #Pm_QueueCreate().
107
108 @return non-zero iff the queue is empty, and @pmBadPtr if \p queue
109 is NULL.
110
111 The full condition may change immediately because a parallel
112 dequeue operation could be in progress. The result is
113 pessimistic: if it returns false (zero) to the single writer, then
114 #Pm_Enqueue() is guaranteed to succeed.
115 */
116PMEXPORT int Pm_QueueFull(PmQueue *queue);
117
118/** test if the queue is empty.
119
120 @param queue a queue created by #Pm_QueueCreate().
121
122 @return zero iff the queue is either empty or NULL.
123
124 The empty condition may change immediately because a parallel
125 enqueue operation could be in progress. Furthermore, the
126 result is optimistic: it may say false, when due to
127 out-of-order writes, the full message has not arrived. Therefore,
128 #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns
129 false.
130*/
131PMEXPORT int Pm_QueueEmpty(PmQueue *queue);
132
133/** get a pointer to the item at the head of the queue.
134
135 @param queue a queue created by #Pm_QueueCreate().
136
137 @result a pointer to the head message or NULL if the queue is empty.
138
139 The message is not removed from the queue. #Pm_QueuePeek() will
140 not indicate when an overflow occurs. If you want to get and check
141 #pmBufferOverflow messages, use the return value of
142 #Pm_QueuePeek() *only* as an indication that you should call
143 #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue()
144 would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL,
145 but internally clear the #pmBufferOverflow flag, enabling
146 #Pm_Enqueue() to resume enqueuing messages. A subsequent call to
147 #Pm_QueuePeek() will return a pointer to the first message *after*
148 the overflow. Using this as an indication to call #Pm_Dequeue(),
149 the first call to #Pm_Dequeue() will return #pmBufferOverflow. The
150 second call will return success, copying the same message pointed
151 to by the previous #Pm_QueuePeek().
152
153 When to use #Pm_QueuePeek(): (1) when you need to look at the message
154 data to decide who should be called to receive it. (2) when you need
155 to know a message is ready but cannot accept the message.
156
157 Note that #Pm_QueuePeek() is not a fast check, so if possible, you
158 might as well just call #Pm_Dequeue() and accept the data if it is there.
159 */
160PMEXPORT void *Pm_QueuePeek(PmQueue *queue);
161
162/** allows the writer (enqueuer) to signal an overflow
163 condition to the reader (dequeuer).
164
165 @param queue a queue created by #Pm_QueueCreate().
166
167 @return #pmNoError if overflow is set, or #pmBadPtr if queue is
168 NULL, or #pmBufferOverflow if buffer is already in an overflow
169 state.
170
171 E.g., when transfering data from the OS to an application, if the
172 OS indicates a buffer overrun, #Pm_SetOverflow() can be used to
173 insure that the reader receives a #pmBufferOverflow result from
174 #Pm_Dequeue().
175 */
176PMEXPORT PmError Pm_SetOverflow(PmQueue *queue);
177
178/** @} */
179
180#ifdef __cplusplus
181}
182#endif /* __cplusplus */
183
184#endif // PORTMIDI_PMUTIL_H