SpectMorph
smnotifybuffer.hh
1 // Licensed GNU LGPL v2.1 or later: http://www.gnu.org/licenses/lgpl-2.1.html
2 
3 #pragma once
4 
5 #include <vector>
6 #include <atomic>
7 #include <cassert>
8 #include <cstring>
9 
10 namespace SpectMorph
11 {
12 
13 /*
14  * NotifyBuffer provides a method to transport notification events from the DSP
15  * thread to the UI thread without locks. It also avoids memory allocations in
16  * the DSP thread.
17  *
18  * To do this, it uses a std::atomic which indicates
19  * - that the DSP thread can write the buffer (STATE_EMPTY)
20  * - that the UI thread can read the buffer (STATE_DATA_VALID)
21  * - that the DSP thread failed to write the data because the available space
22  * was too small, and that the UI thread should allocate more memory (STATE_NEED_RESIZE)
23  *
24  * There is no guarantee that each event is delivered, because writing can fail
25  * - if the UI thread didn't read the old events from the buffer yet
26  * - if the available space is too small to write all events
27  */
29 {
30  enum {
31  STATE_EMPTY,
32  STATE_DATA_VALID,
33  STATE_NEED_RESIZE
34  };
35  std::atomic<int> state { STATE_EMPTY };
36  std::vector<unsigned char> data;
37  size_t rpos = 0;
38  size_t wpos = 0;
39 
40  void
41  write_simple (const void *ptr, size_t size) // DSP thread
42  {
43  size_t new_wpos = wpos + size;
44  if (new_wpos <= data.size())
45  memcpy (&data[wpos], ptr, size);
46 
47  wpos = new_wpos;
48  }
49  void
50  read_simple (void *ptr, size_t size) // UI thread
51  {
52  if (size)
53  {
54  memcpy (ptr, &data[rpos], size);
55  rpos += size;
56  }
57  }
58 public:
59  NotifyBuffer() :
60  data (32)
61  {
62  }
63  bool
64  start_write() // DSP thread
65  {
66  if (state.load() == STATE_EMPTY)
67  {
68  wpos = 0;
69  return true;
70  }
71  return false;
72  }
73  void
74  end_write() // DSP thread
75  {
76  if (wpos <= data.size())
77  {
78  state.store (STATE_DATA_VALID);
79  }
80  else
81  {
82  state.store (STATE_NEED_RESIZE);
83  }
84  }
85  void
86  resize_if_necessary() // UI thread
87  {
88  if (state.load() == STATE_NEED_RESIZE)
89  {
90  data.resize (data.size() * 2);
91  state.store (STATE_EMPTY);
92  }
93  }
94  bool
95  start_read() // UI thread
96  {
97  if (state.load() == STATE_DATA_VALID)
98  {
99  rpos = 0;
100  return true;
101  }
102  return false;
103  }
104  void
105  end_read() // UI thread
106  {
107  state.store (STATE_EMPTY);
108  }
109  void
110  write_int (int i) // DSP thread
111  {
112  write_simple (&i, sizeof (i));
113  }
114  void
115  write_float (float f) // DSP thread
116  {
117  write_simple (&f, sizeof (f));
118  }
119  template<class T> void
120  write_seq (const T* items, size_t length) // DSP thread
121  {
122  write_int (length);
123  write_simple (items, length * sizeof (T));
124  }
125  size_t
126  remaining() // UI thread
127  {
128  return wpos - rpos;
129  }
130  int
131  read_int() // UI thread
132  {
133  int i;
134  read_simple (&i, sizeof (i));
135  return i;
136  }
137  float
138  read_float() // UI thread
139  {
140  float f;
141  read_simple (&f, sizeof (f));
142  return f;
143  }
144  template<class T> std::vector<T>
145  read_seq() // UI thread
146  {
147  int seq_len = read_int();
148  std::vector<T> result (seq_len);
149  read_simple (result.data(), seq_len * sizeof (T));
150  return result;
151  }
152 };
153 
154 }
Definition: smnotifybuffer.hh:29