diff options
Diffstat (limited to '3rdparty/portmidi/pm_common/pmutil.h')
-rw-r--r-- | 3rdparty/portmidi/pm_common/pmutil.h | 198 |
1 files changed, 125 insertions, 73 deletions
diff --git a/3rdparty/portmidi/pm_common/pmutil.h b/3rdparty/portmidi/pm_common/pmutil.h index ef5ee4bf84a..17f6984a0d7 100644 --- a/3rdparty/portmidi/pm_common/pmutil.h +++ b/3rdparty/portmidi/pm_common/pmutil.h @@ -1,31 +1,48 @@ -/* pmutil.h -- some helpful utilities for building midi - applications that use PortMidi +/** @file pmutil.h lock-free queue for building MIDI + applications with PortMidi. + + PortMidi is not reentrant, and locks can suffer from priority + inversion. To support coordination between system callbacks, a + high-priority thread created with PortTime, and the main + application thread, PortMidi uses a lock-free, non-blocking + queue. The queue implementation is not particular to MIDI and is + available for other uses. */ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +/** @defgroup grp_pmutil Lock-free Queue + @{ +*/ + +/** The queue representation is opaque. Declare a queue as PmQueue * */ typedef void PmQueue; -/* - A single-reader, single-writer queue is created by - Pm_QueueCreate(), which takes the number of messages and - the message size as parameters. The queue only accepts - fixed sized messages. Returns NULL if memory cannot be allocated. +/** create a single-reader, single-writer queue. + + @param num_msgs the number of messages the queue can hold + + @param the fixed message size + + @return the allocated and initialized queue, or NULL if memory + cannot be allocated. Allocation uses #pm_malloc(). + + The queue only accepts fixed sized messages. This queue implementation uses the "light pipe" algorithm which operates correctly even with multi-processors and out-of-order memory writes. (see Alexander Dokumentov, "Lock-free Interprocess - Communication," Dr. Dobbs Portal, http://www.ddj.com/, - articleID=189401457, June 15, 2006. This algorithm requires - that messages be translated to a form where no words contain - zeros. Each word becomes its own "data valid" tag. Because of - this translation, we cannot return a pointer to data still in - the queue when the "peek" method is called. Instead, a buffer - is preallocated so that data can be copied there. Pm_QueuePeek() - dequeues a message into this buffer and returns a pointer to - it. A subsequent Pm_Dequeue() will copy from this buffer. + Communication," Dr. Dobbs Portal, http://www.ddj.com/, + articleID=189401457, June 15, 2006. This algorithm requires that + messages be translated to a form where no words contain + zeros. Each word becomes its own "data valid" tag. Because of this + translation, we cannot return a pointer to data still in the queue + when the "peek" method is called. Instead, a buffer is + preallocated so that data can be copied there. Pm_QueuePeek() + dequeues a message into this buffer and returns a pointer to it. A + subsequent Pm_Dequeue() will copy from this buffer. This implementation does not try to keep reader/writer data in separate cache lines or prevent thrashing on cache lines. @@ -36,92 +53,127 @@ typedef void PmQueue; of the cache line, especially if messages are smaller than cache lines. See the Dokumentov article for explanation. - The algorithm is extended to handle "overflow" reporting. To report - an overflow, the sender writes the current tail position to a field. - The receiver must acknowlege receipt by zeroing the field. The sender - will not send more until the field is zeroed. - - Pm_QueueDestroy() destroys the queue and frees its storage. + The algorithm is extended to handle "overflow" reporting. To + report an overflow, the sender writes the current tail position to + a field. The receiver must acknowlege receipt by zeroing the + field. The sender will not send more until the field is zeroed. */ - PMEXPORT PmQueue *Pm_QueueCreate(long num_msgs, int32_t bytes_per_msg); + +/** destroy a queue and free its storage. + + @param queue a queue created by #Pm_QueueCreate(). + + @return pmNoError or an error code. + + Uses #pm_free(). + + */ PMEXPORT PmError Pm_QueueDestroy(PmQueue *queue); -/* - Pm_Dequeue() removes one item from the queue, copying it into msg. - Returns 1 if successful, and 0 if the queue is empty. - Returns pmBufferOverflow if what would have been the next thing - in the queue was dropped due to overflow. (So when overflow occurs, - the receiver can receive a queue full of messages before getting the - overflow report. This protocol ensures that the reader will be +/** remove one message from the queue, copying it into \p msg. + + @param queue a queue created by #Pm_QueueCreate(). + + @param msg address to which the message, if any, is copied. + + @return 1 if successful, and 0 if the queue is empty. Returns + #pmBufferOverflow if what would have been the next thing in the + queue was dropped due to overflow. (So when overflow occurs, the + receiver can receive a queue full of messages before getting the + overflow report. This protocol ensures that the reader will be notified when data is lost due to overflow. */ PMEXPORT PmError Pm_Dequeue(PmQueue *queue, void *msg); +/** insert one message into the queue, copying it from \p msg. -/* - Pm_Enqueue() inserts one item into the queue, copying it from msg. - Returns pmNoError if successful and pmBufferOverflow if the queue was - already full. If pmBufferOverflow is returned, the overflow flag is set. + @param queue a queue created by #Pm_QueueCreate(). + + @param msg address of the message to be enqueued. + + @return #pmNoError if successful and #pmBufferOverflow if the + queue was already full. If #pmBufferOverflow is returned, the + overflow flag is set. */ PMEXPORT PmError Pm_Enqueue(PmQueue *queue, void *msg); +/** test if the queue is full. -/* - Pm_QueueFull() returns non-zero if the queue is full - Pm_QueueEmpty() returns non-zero if the queue is empty + @param queue a queue created by #Pm_QueueCreate(). - Either condition may change immediately because a parallel - enqueue or dequeue operation could be in progress. Furthermore, - Pm_QueueEmpty() is optimistic: it may say false, when due to - out-of-order writes, the full message has not arrived. Therefore, - Pm_Dequeue() could still return 0 after Pm_QueueEmpty() returns - false. On the other hand, Pm_QueueFull() is pessimistic: if it - returns false, then Pm_Enqueue() is guaranteed to succeed. + @return non-zero iff the queue is empty, and @pmBadPtr if \p queue + is NULL. - Error conditions: Pm_QueueFull() returns pmBadPtr if queue is NULL. - Pm_QueueEmpty() returns FALSE if queue is NULL. + The full condition may change immediately because a parallel + dequeue operation could be in progress. The result is + pessimistic: if it returns false (zero) to the single writer, then + #Pm_Enqueue() is guaranteed to succeed. */ PMEXPORT int Pm_QueueFull(PmQueue *queue); + +/** test if the queue is empty. + + @param queue a queue created by #Pm_QueueCreate(). + + @return zero iff the queue is either empty or NULL. + + The empty condition may change immediately because a parallel + enqueue operation could be in progress. Furthermore, the + result is optimistic: it may say false, when due to + out-of-order writes, the full message has not arrived. Therefore, + #Pm_Dequeue() could still return 0 after #Pm_QueueEmpty() returns + false. +*/ PMEXPORT int Pm_QueueEmpty(PmQueue *queue); +/** get a pointer to the item at the head of the queue. -/* - Pm_QueuePeek() returns a pointer to the item at the head of the queue, - or NULL if the queue is empty. The item is not removed from the queue. - Pm_QueuePeek() will not indicate when an overflow occurs. If you want - to get and check pmBufferOverflow messages, use the return value of - Pm_QueuePeek() *only* as an indication that you should call - Pm_Dequeue(). At the point where a direct call to Pm_Dequeue() would - return pmBufferOverflow, Pm_QueuePeek() will return NULL but internally - clear the pmBufferOverflow flag, enabling Pm_Enqueue() to resume - enqueuing messages. A subsequent call to Pm_QueuePeek() - will return a pointer to the first message *after* the overflow. - Using this as an indication to call Pm_Dequeue(), the first call - to Pm_Dequeue() will return pmBufferOverflow. The second call will - return success, copying the same message pointed to by the previous - Pm_QueuePeek(). - - When to use Pm_QueuePeek(): (1) when you need to look at the message + @param queue a queue created by #Pm_QueueCreate(). + + @result a pointer to the head message or NULL if the queue is empty. + + The message is not removed from the queue. #Pm_QueuePeek() will + not indicate when an overflow occurs. If you want to get and check + #pmBufferOverflow messages, use the return value of + #Pm_QueuePeek() *only* as an indication that you should call + #Pm_Dequeue(). At the point where a direct call to #Pm_Dequeue() + would return #pmBufferOverflow, #Pm_QueuePeek() will return NULL, + but internally clear the #pmBufferOverflow flag, enabling + #Pm_Enqueue() to resume enqueuing messages. A subsequent call to + #Pm_QueuePeek() will return a pointer to the first message *after* + the overflow. Using this as an indication to call #Pm_Dequeue(), + the first call to #Pm_Dequeue() will return #pmBufferOverflow. The + second call will return success, copying the same message pointed + to by the previous #Pm_QueuePeek(). + + When to use #Pm_QueuePeek(): (1) when you need to look at the message data to decide who should be called to receive it. (2) when you need to know a message is ready but cannot accept the message. - Note that Pm_QueuePeek() is not a fast check, so if possible, you - might as well just call Pm_Dequeue() and accept the data if it is there. + Note that #Pm_QueuePeek() is not a fast check, so if possible, you + might as well just call #Pm_Dequeue() and accept the data if it is there. */ PMEXPORT void *Pm_QueuePeek(PmQueue *queue); -/* - Pm_SetOverflow() allows the writer (enqueuer) to signal an overflow - condition to the reader (dequeuer). E.g. when transfering data from - the OS to an application, if the OS indicates a buffer overrun, - Pm_SetOverflow() can be used to insure that the reader receives a - pmBufferOverflow result from Pm_Dequeue(). Returns pmBadPtr if queue - is NULL, returns pmBufferOverflow if buffer is already in an overflow - state, returns pmNoError if successfully set overflow state. +/** allows the writer (enqueuer) to signal an overflow + condition to the reader (dequeuer). + + @param queue a queue created by #Pm_QueueCreate(). + + @return #pmNoError if overflow is set, or #pmBadPtr if queue is + NULL, or #pmBufferOverflow if buffer is already in an overflow + state. + + E.g., when transfering data from the OS to an application, if the + OS indicates a buffer overrun, #Pm_SetOverflow() can be used to + insure that the reader receives a #pmBufferOverflow result from + #Pm_Dequeue(). */ PMEXPORT PmError Pm_SetOverflow(PmQueue *queue); +/** @} */ + #ifdef __cplusplus } #endif /* __cplusplus */ |