PIDUINO
threadsafebuffer.h
1/* Copyright © 2014 Chris Desjardins, http://blog.chrisd.info cjd@chrisd.info
2 * Copyright © 2018-2025 Pascal JEAN, https://github.com/epsilonrt
3 * This file is part of the Piduino Library.
4 *
5 * The Piduino Library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * The Piduino Library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with the Piduino Library; if not, see <http://www.gnu.org/licenses/>.
17 *
18 * This file comes from the thread safe queue of Chris Desjardins
19 * (http://blog.chrisd.info), whose code is distributed under the BSD 3-Clause
20 * license, reproduced here as required by this license:
21 *
22 * Copyright (c) 2014, Chris Desjardins
23 * All rights reserved.
24 *
25 * Redistribution and use in source and binary forms, with or without
26 * modification, are permitted provided that the following conditions are met:
27 *
28 * 1. Redistributions of source code must retain the above copyright notice, this
29 * list of conditions and the following disclaimer.
30 * 2. Redistributions in binary form must reproduce the above copyright notice,
31 * this list of conditions and the following disclaimer in the documentation
32 * and/or other materials provided with the distribution.
33 * 3. Neither the name of the copyright holder nor the names of its contributors
34 * may be used to endorse or promote products derived from this software
35 * without specific prior written permission.
36 *
37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
41 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
42 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
43 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
44 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
46 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 */
48#pragma once
49
50#include <list>
51#include <vector>
52#include <mutex>
53#include <condition_variable>
54
55namespace Piduino {
56
63 template <class T> class ThreadSafeBuffer {
64 public:
66 : _queue(),
69 _numEnqueued (0) {
70 }
71
73 }
74
75 virtual void write (const T& data) {
76 {
77 // create a new scope for the mutex
78 std::unique_lock<std::mutex> lock (_queueMutex);
79 writeData (data);
80 _msgNotification.notify_all();
81 }
82 }
83
84 virtual void write (const T * buf, size_t len) {
85 {
86 // create a new scope for the mutex
87 std::unique_lock<std::mutex> lock (_queueMutex);
88 writeData (buf, len);
89 _msgNotification.notify_all();
90 }
91 }
92
93 virtual void write (const std::vector<T>& dataVec) {
94 write (dataVec.data(), dataVec.size());
95 }
96
97 virtual bool read (T& data, long msTimeout = 0) {
98 bool ret = false;
99 {
100 // create a new scope for the mutex
101 std::unique_lock<std::mutex> lock (_queueMutex);
102 waitForData (lock, msTimeout);
103 ret = readData (data);
104 }
105 return ret;
106 }
107
108 // Dequeue everything
109 virtual size_t read (std::vector<T>& dataVec, long msTimeout = 0) {
110 size_t size = 0;
111 {
112 // create a new scope for the mutex
113 std::unique_lock<std::mutex> lock (_queueMutex);
114 waitForData (lock, msTimeout);
115 size = readData (dataVec);
116 }
117 return size;
118 }
119
120 // Dequeue everything
121 virtual size_t read (T * buf, size_t max, long msTimeout = 0) {
122 size_t size = 0;
123 {
124 // create a new scope for the mutex
125 std::unique_lock<std::mutex> lock (_queueMutex);
126 waitForData (lock, msTimeout);
127 size = readData (buf, max);
128 }
129 return size;
130 }
131
132 virtual bool peek (T& data, long msTimeout = 0) {
133 bool ret = false;
134 {
135 // create a new scope for the mutex
136 std::unique_lock<std::mutex> lock (_queueMutex);
137 waitForData (lock, msTimeout);
138 ret = peekData (data);
139 }
140 return ret;
141 }
142
143 // Dequeue everything
144 virtual size_t peek (std::vector<T>& dataVec, long msTimeout = 0) {
145 size_t size = 0;
146 {
147 // create a new scope for the mutex
148 std::unique_lock<std::mutex> lock (_queueMutex);
149 waitForData (lock, msTimeout);
150 size = peekData (dataVec);
151 }
152 return size;
153 }
154
155 // Dequeue everything
156 virtual size_t peek (T * buf, size_t max, long msTimeout = 0) {
157 size_t size = 0;
158 {
159 // create a new scope for the mutex
160 std::unique_lock<std::mutex> lock (_queueMutex);
161 waitForData (lock, msTimeout);
162 size = peekData (buf, max);
163 }
164 return size;
165 }
166
167 size_t size() const {
168 return _numEnqueued;
169 }
170
171 /*
172 ** This function allows you to perform operations on the
173 ** vector in a thread safe way. The functor is a function
174 ** with the following signature:
175 ** int func(std::list<T> &);
176 ** The return value is the number of elements added or removed
177 ** from the list, for example if 5 elements were removed
178 ** and 3 new elements were added then the return value should
179 ** be -2.
180 */
181 template <typename Functor> void iterate (Functor functor) {
182 {
183 // create a new scope for the mutex
184 std::unique_lock<std::mutex> lock (_queueMutex);
185 // the return value of this functor is added to the _numEnqueued
186 // so if you add buffers then return the number of buffers added
187 // or if you remove buffers then return -number of buffers removed
188 int numChanged = functor (_queue);
189 _numEnqueued += numChanged;
190 }
191 }
192
193 protected:
194
195 void waitForData (std::unique_lock<std::mutex>& lock, long msTimeout) {
196 if (msTimeout != 0) {
197 // This function assumes that _queueMutex is locked already!
198 std::chrono::system_clock::time_point timeLimit = std::chrono::system_clock::now() +
199 std::chrono::milliseconds (msTimeout);
200 while (_queue.empty() == true) {
201 // if timeout is specified, then wait until the time is up
202 // otherwise wait forever (forever is msTimeout = -1)
203 if (msTimeout > 0) {
204 _msgNotification.wait_until (lock, timeLimit);
205 if (std::chrono::system_clock::now() >= timeLimit) {
206 break;
207 }
208 }
209 else {
210 _msgNotification.wait (lock);
211 }
212 }
213 }
214 }
215
216 void writeData (const T& data) {
217 // This function assumes that _queueMutex is locked already!
218 _queue.push_back (data);
219 _numEnqueued++;
220 }
221
222 void writeData (const T * buf, size_t len) {
223 // This function assumes that _queueMutex is locked already!
224 while (len--) {
225 writeData (*buf++);
226 }
227 }
228
229 bool readData (T& data) {
230 // This function assumes that _queueMutex is locked already!
231 bool ret = false;
232 if (_queue.empty() == false) {
233 data = _queue.front();
234 _queue.pop_front();
235 _numEnqueued--;
236 ret = true;
237 }
238 return ret;
239 }
240
241 size_t readData (std::vector<T>& dataVec) {
242 // This function assumes that _queueMutex is locked already!
243 size_t size = 0;
244 T data;
245 while (readData (data) == true) {
246 size += sizeOfData (data);
247 dataVec.push_back (data);
248 }
249 return size;
250 }
251
252 size_t readData (T * buf, size_t max) {
253 // This function assumes that _queueMutex is locked already!
254 max = std::min (_numEnqueued, max);
255 size_t size = 0;
256 T data;
257 while ( (readData (data) == true) && (size <= max)) {
258 *buf++ = data;
259 size += sizeOfData (data);
260 }
261 return size;
262 }
263
264 bool peekData (T& data) {
265 // This function assumes that _queueMutex is locked already!
266 bool ret = false;
267 if (_queue.empty() == false) {
268 data = _queue.front();
269 ret = true;
270 }
271 return ret;
272 }
273
274 size_t peekData (std::vector<T>& dataVec) {
275 // This function assumes that _queueMutex is locked already!
276 size_t size = 0;
277 T data;
278 while (peekData (data) == true) {
279 size += sizeOfData (data);
280 dataVec.push_back (data);
281 }
282 return size;
283 }
284
285 size_t peekData (T * buf, size_t max) {
286 // This function assumes that _queueMutex is locked already!
287 max = std::min (_numEnqueued, max);
288 size_t size = 0;
289 T data;
290 while ( (peekData (data) == true) && (size <= max)) {
291 *buf++ = data;
292 size += sizeOfData (data);
293 }
294 return size;
295 }
296
297 virtual size_t sizeOfData (const T&) const {
298 return sizeof (T);
299 }
300
301 std::list<T> _queue;
302 std::mutex _queueMutex;
303 std::condition_variable _msgNotification;
305 };
306}
307/* ========================================================================== */
This provides a thread safe buffer.
virtual bool read(T &data, long msTimeout=0)
virtual size_t read(std::vector< T > &dataVec, long msTimeout=0)
virtual void write(const std::vector< T > &dataVec)
void iterate(Functor functor)
virtual size_t peek(std::vector< T > &dataVec, long msTimeout=0)
void waitForData(std::unique_lock< std::mutex > &lock, long msTimeout)
virtual size_t read(T *buf, size_t max, long msTimeout=0)
void writeData(const T *buf, size_t len)
size_t readData(std::vector< T > &dataVec)
virtual size_t sizeOfData(const T &) const
void writeData(const T &data)
size_t peekData(std::vector< T > &dataVec)
virtual void write(const T &data)
virtual void write(const T *buf, size_t len)
size_t peekData(T *buf, size_t max)
size_t readData(T *buf, size_t max)
std::condition_variable _msgNotification
virtual size_t peek(T *buf, size_t max, long msTimeout=0)
virtual bool peek(T &data, long msTimeout=0)
Global namespace for Piduino.
Definition: board.h:28