├── LICENSE ├── README.md ├── tinypipe.c └── tinypipe.h /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Martin Roth (mhroth@gmail.com). 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyPipe 2 | Tiny Pipe is a small C library implementing a lockless queue for passing messages between one producer and one consumer thread. Implementations are included with support for x86 and ARM architectures. 3 | 4 | ## Usage 5 | 6 | ### Initialisation and Freeing 7 | ```c 8 | #include "tinypipe.h" 9 | 10 | Tinypipe pipe; 11 | tpipe_init(&pipe, 10*1024); // 10KB pipe! 12 | 13 | // do stuff 14 | 15 | tpipe_free(&pipe); 16 | ``` 17 | 18 | ### Reading 19 | ```c 20 | while (tpipe_hasData(&pipe)) { 21 | char *buffer = NULL; 22 | int len = 0; 23 | buffer = tpipe_getReadBuffer(&pipe, &len); 24 | assert(buffer != NULL); 25 | assert(len > 0); 26 | 27 | // do stuff with the received buffer (e.g. parse an OSC message) 28 | tosc_message osc; 29 | tosc_parseMessage(&osc, buffer, len); 30 | // ... 31 | 32 | // consume the buffer (i.e. indicate that the data is no longer needed) 33 | // Important! Once the buffer is consumed, the data may not persist. 34 | // Copy the data it is needed later. 35 | tpipe_consume(&pipe); 36 | } 37 | ``` 38 | 39 | ### Writing 40 | ```c 41 | int max_len = 1024; // the maximum required bytes is 1KB 42 | char *buffer = tpipe_getReadBuffer(&pipe, max_len); 43 | assert(buffer != NULL); 44 | 45 | // write stuff to the buffer, which is guaranteed to have enough space to write the maximum requested length 46 | 47 | // indicate that the buffer has been filled, but only with 512 bytes (<= max_len) 48 | int used_len = 512; 49 | tpipe_produce(&pipe, used_len); 50 | ``` 51 | 52 | ### Clearing 53 | It's easy to clear the pipe back to the initialised state. 54 | ```c 55 | tpipe_clear(&pipe); 56 | ``` 57 | 58 | ## License 59 | This code is released under the [ISC License](https://opensource.org/licenses/ISC). It is strongly based on [Enzien Audio](https://enzienaudio.com)'s [HvLightPipe](https://github.com/enzienaudio/heavy/blob/master/src/TinyPipe.h), also released under the ISC license. 60 | -------------------------------------------------------------------------------- /tinypipe.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Martin Roth (mhroth@gmail.com). 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | * PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "tinypipe.h" 22 | 23 | #if __SSE__ 24 | #include 25 | #define hv_sfence() _mm_sfence() 26 | #elif __arm__ 27 | #if __ARM_ACLE 28 | #include 29 | // https://msdn.microsoft.com/en-us/library/hh875058.aspx#BarrierRestrictions 30 | // http://doxygen.reactos.org/d8/d47/armintr_8h_a02be7ec76ca51842bc90d9b466b54752.html 31 | #define hv_sfence() __dmb(0xE) /* _ARM_BARRIER_ST */ 32 | #else 33 | // http://stackoverflow.com/questions/19965076/gcc-memory-barrier-sync-synchronize-vs-asm-volatile-memory 34 | #define hv_sfence() __sync_synchronize() 35 | #endif 36 | #elif _WIN32 || _WIN64 37 | // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684208(v=vs.85).aspx 38 | #define hv_sfence() _WriteBarrier() 39 | #else 40 | #define hv_sfence() __asm__ volatile("" : : : "memory") 41 | #endif 42 | 43 | #define HLP_STOP 0 44 | #define HLP_LOOP -1 45 | #define TPIPE_SET_INT32_AT_BUFFER(a, b) (*((int32_t *) (a)) = (b)) 46 | #define TPIPE_GET_INT32_AT_BUFFER(a) (*((int32_t *) (a))) 47 | 48 | int tpipe_init(TinyPipe *q, int numBytes) { 49 | assert(numBytes > 0); 50 | q->buffer = (char *) malloc(numBytes); 51 | assert(q->buffer != NULL); 52 | q->writeHead = q->buffer; 53 | q->readHead = q->buffer; 54 | q->len = numBytes; 55 | q->remainingBytes = numBytes; 56 | TPIPE_SET_INT32_AT_BUFFER(q->buffer, HLP_STOP); 57 | return numBytes; 58 | } 59 | 60 | void tpipe_free(TinyPipe *q) { 61 | free(q->buffer); 62 | } 63 | 64 | int tpipe_hasData(TinyPipe *q) { 65 | int x = TPIPE_GET_INT32_AT_BUFFER(q->readHead); 66 | if (x == HLP_LOOP) { 67 | q->readHead = q->buffer; 68 | x = TPIPE_GET_INT32_AT_BUFFER(q->readHead); 69 | } 70 | return x; 71 | } 72 | 73 | char *tpipe_getWriteBuffer(TinyPipe *q, int bytesToWrite) { 74 | char *const readHead = q->readHead; 75 | char *const oldWriteHead = q->writeHead; 76 | const int totalByteRequirement = bytesToWrite + 2 * sizeof(int32_t); 77 | 78 | // check if there is enough space to write the data in the remaining 79 | // length of the buffer 80 | if (totalByteRequirement <= q->remainingBytes) { 81 | char *const newWriteHead = oldWriteHead + sizeof(int32_t) + bytesToWrite; 82 | 83 | // check if writing would overwrite existing data in the pipe (return NULL if so) 84 | if ((oldWriteHead < readHead) && (newWriteHead >= readHead)) return NULL; 85 | else return (oldWriteHead + sizeof(int32_t)); 86 | } else { 87 | // there isn't enough space, try looping around to the start 88 | if (totalByteRequirement <= q->len) { 89 | if ((oldWriteHead < readHead) || ((q->buffer + totalByteRequirement) > readHead)) { 90 | return NULL; // overwrite condition 91 | } else { 92 | q->writeHead = q->buffer; 93 | q->remainingBytes = q->len; 94 | TPIPE_SET_INT32_AT_BUFFER(q->buffer, HLP_STOP); 95 | hv_sfence(); 96 | TPIPE_SET_INT32_AT_BUFFER(oldWriteHead, HLP_LOOP); 97 | return q->buffer + sizeof(int32_t); 98 | } 99 | } else { 100 | return NULL; // there isn't enough space to write the data 101 | } 102 | } 103 | } 104 | 105 | void tpipe_produce(TinyPipe *q, int numBytes) { 106 | assert(q->remainingBytes >= (numBytes + (int) (2*sizeof(int32_t)))); 107 | q->remainingBytes -= (sizeof(int32_t) + numBytes); 108 | char *const oldWriteHead = q->writeHead; 109 | q->writeHead += (sizeof(int32_t) + numBytes); 110 | TPIPE_SET_INT32_AT_BUFFER(q->writeHead, HLP_STOP); 111 | 112 | // save everything before this point to memory 113 | hv_sfence(); 114 | 115 | // then save this 116 | TPIPE_SET_INT32_AT_BUFFER(oldWriteHead, numBytes); 117 | } 118 | 119 | char *tpipe_getReadBuffer(TinyPipe *q, int *numBytes) { 120 | *numBytes = TPIPE_GET_INT32_AT_BUFFER(q->readHead); 121 | char *const readBuffer = q->readHead + sizeof(int32_t); 122 | return readBuffer; 123 | } 124 | 125 | void tpipe_consume(TinyPipe *q) { 126 | assert(TPIPE_GET_INT32_AT_BUFFER(q->readHead) != HLP_STOP); 127 | q->readHead += sizeof(int32_t) + TPIPE_GET_INT32_AT_BUFFER(q->readHead); 128 | } 129 | 130 | void tpipe_clear(TinyPipe *q) { 131 | q->writeHead = q->buffer; 132 | q->readHead = q->buffer; 133 | q->remainingBytes = q->len; 134 | memset(q->buffer, 0, q->len); 135 | } 136 | 137 | int tpipe_getTotalData(TinyPipe *q) { 138 | char *p = q->readHead; 139 | int len = 0; 140 | int d = 0; 141 | while ((d = TPIPE_GET_INT32_AT_BUFFER(p)) != HLP_STOP) { 142 | if (d == HLP_LOOP) { 143 | p = q->buffer; 144 | } else { 145 | len += d; 146 | p += (sizeof(int32_t) + d); 147 | } 148 | } 149 | return len; 150 | } 151 | 152 | int tpipe_write(TinyPipe *q, char *data, int numBytes) { 153 | char *buffer = tpipe_getWriteBuffer(q, numBytes); 154 | if (buffer == NULL) return 0; 155 | memcpy(buffer, data, numBytes); 156 | tpipe_produce(q, numBytes); 157 | return 1; 158 | } 159 | -------------------------------------------------------------------------------- /tinypipe.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Martin Roth (mhroth@gmail.com). 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | * PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | 18 | 19 | #ifndef _TINYPIPE_H_ 20 | #define _TINYPIPE_H_ 21 | 22 | #include 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | /* 29 | * This pipe assumes that there is only one producer thread and one consumer 30 | * thread. This data structure does not support any other configuration. 31 | */ 32 | typedef struct TinyPipe { 33 | char *buffer; 34 | char *writeHead; 35 | char *readHead; 36 | int32_t len; 37 | int32_t remainingBytes; // total bytes from write head to end 38 | } TinyPipe; 39 | 40 | /** 41 | * Initialise the pipe with a given length, in bytes. 42 | * 43 | * @param q The pipe. 44 | * 45 | * @return Returns the size of the pipe in bytes. 46 | */ 47 | int tpipe_init(TinyPipe *q, int numBytes); 48 | 49 | /** 50 | * Frees the internal buffer. 51 | * 52 | * @param q The light pipe. 53 | */ 54 | void tpipe_free(TinyPipe *q); 55 | 56 | /** 57 | * Indicates if data is available for reading. 58 | * 59 | * @param q The light pipe. 60 | * @return Returns the number of bytes available for reading. Zero if no bytes are available. 61 | */ 62 | int tpipe_hasData(TinyPipe *q); 63 | 64 | /** 65 | * Returns a pointer to a location in the pipe where numBytes can be written. 66 | * 67 | * @param q The pipe. 68 | * @param numBytes The number of bytes to be written. 69 | * @return A pointer to a location where those bytes can be written. Returns 70 | * NULL if no more space is available. Successive calls to this 71 | * function may eventually return a valid pointer because the readhead 72 | * has been advanced on another thread. 73 | */ 74 | char *tpipe_getWriteBuffer(TinyPipe *q, int numBytes); 75 | 76 | /** 77 | * Indicates to the pipe how many bytes have been written. 78 | * 79 | * @param q The pipe. 80 | * @param numBytes The number of bytes written. In general this should be the 81 | * same value as was passed to the preceeding call to 82 | * tpipe_getWriteBuffer(). 83 | */ 84 | void tpipe_produce(TinyPipe *q, int numBytes); 85 | 86 | /** 87 | * Returns the current read buffer, indicating the number of bytes available 88 | * for reading. 89 | * 90 | * @param q The pipe. 91 | * @param numBytes This value will be filled with the number of bytes available 92 | * for reading. 93 | * 94 | * @return A pointer to the read buffer. 95 | */ 96 | char *tpipe_getReadBuffer(TinyPipe *q, int *numBytes); 97 | 98 | /** 99 | * Indicates that the next set of bytes have been read and are no longer needed. 100 | * 101 | * @param q The pipe. 102 | */ 103 | void tpipe_consume(TinyPipe *q); 104 | 105 | /** 106 | * Resets the queue to the initialised state. This should be done when only one thread is accessing the pipe. 107 | * 108 | * @param q The pipe. 109 | */ 110 | void tpipe_clear(TinyPipe *q); 111 | 112 | /** 113 | * Returns the total amount of data that is currently in the pipe. 114 | * 115 | * @param q The pipe. 116 | * 117 | * @return The number of bytes ready to be read from the pipe. 118 | */ 119 | int tpipe_getTotalData(TinyPipe *q); 120 | 121 | /** 122 | * A convenience function to write a number of bytes to the pipe; 123 | * 124 | * @param q The pipe. 125 | * @param data The data pointer. 126 | * @param numBytes The number of bytes to write. 127 | * 128 | * @return 1 if bytes were successfully written to the pipe. 0 otherwise. 129 | */ 130 | int tpipe_write(TinyPipe *q, char *data, int numBytes); 131 | 132 | #ifdef __cplusplus 133 | } 134 | #endif 135 | 136 | #endif // _TINYPIPE_H_ 137 | --------------------------------------------------------------------------------