@neurodevs/node-lsl 17.1.0 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/.claude/settings.local.json +10 -0
  2. package/.regressionproof.json +7 -0
  3. package/.vscode/launch.json +3 -2
  4. package/README.md +9 -35
  5. package/build/.spruce/settings.json +5 -1
  6. package/build/__tests__/AbstractPackageTest.d.ts +4 -4
  7. package/build/__tests__/AbstractPackageTest.js +9 -11
  8. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  9. package/build/__tests__/impl/LslBackpressureMonitor.test.d.ts +7 -0
  10. package/build/__tests__/impl/LslBackpressureMonitor.test.js +26 -0
  11. package/build/__tests__/impl/LslBackpressureMonitor.test.js.map +1 -0
  12. package/build/__tests__/impl/LslEventMarkerEmitter.test.d.ts +34 -0
  13. package/build/__tests__/impl/LslEventMarkerEmitter.test.js +243 -0
  14. package/build/__tests__/impl/LslEventMarkerEmitter.test.js.map +1 -0
  15. package/build/__tests__/impl/LslStreamInfo.test.d.ts +0 -3
  16. package/build/__tests__/impl/LslStreamInfo.test.js +2 -27
  17. package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
  18. package/build/__tests__/impl/LslStreamInlet.test.d.ts +22 -8
  19. package/build/__tests__/impl/LslStreamInlet.test.js +240 -66
  20. package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
  21. package/build/__tests__/impl/LslStreamOutlet.test.d.ts +3 -7
  22. package/build/__tests__/impl/LslStreamOutlet.test.js +36 -46
  23. package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
  24. package/build/__tests__/impl/LslWebSocketBridge.test.js +16 -17
  25. package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
  26. package/build/assertions.d.ts +1 -1
  27. package/build/assertions.js.map +1 -1
  28. package/build/impl/LslBackpressureMonitor.d.ts +8 -0
  29. package/build/impl/LslBackpressureMonitor.js +8 -0
  30. package/build/impl/LslBackpressureMonitor.js.map +1 -0
  31. package/build/impl/LslEventMarkerEmitter.d.ts +35 -0
  32. package/build/impl/LslEventMarkerEmitter.js +86 -0
  33. package/build/impl/LslEventMarkerEmitter.js.map +1 -0
  34. package/build/impl/LslStreamInfo.d.ts +4 -8
  35. package/build/impl/LslStreamInfo.js +8 -21
  36. package/build/impl/LslStreamInfo.js.map +1 -1
  37. package/build/impl/LslStreamInlet.d.ts +31 -47
  38. package/build/impl/LslStreamInlet.js +82 -143
  39. package/build/impl/LslStreamInlet.js.map +1 -1
  40. package/build/impl/LslStreamOutlet.d.ts +15 -18
  41. package/build/impl/LslStreamOutlet.js +39 -47
  42. package/build/impl/LslStreamOutlet.js.map +1 -1
  43. package/build/impl/LslWebSocketBridge.d.ts +4 -5
  44. package/build/impl/LslWebSocketBridge.js +8 -8
  45. package/build/impl/LslWebSocketBridge.js.map +1 -1
  46. package/build/impl/workers/inlet/LslStreamInlet.worker.d.ts +22 -0
  47. package/build/impl/workers/inlet/LslStreamInlet.worker.js +42 -0
  48. package/build/impl/workers/inlet/LslStreamInlet.worker.js.map +1 -0
  49. package/build/impl/workers/inlet/StreamInletWorker.d.ts +73 -0
  50. package/build/impl/workers/inlet/StreamInletWorker.js +253 -0
  51. package/build/impl/workers/inlet/StreamInletWorker.js.map +1 -0
  52. package/build/impl/workers/outlet/LslStreamOutlet.worker.d.ts +32 -0
  53. package/build/impl/workers/outlet/LslStreamOutlet.worker.js +82 -0
  54. package/build/impl/workers/outlet/LslStreamOutlet.worker.js.map +1 -0
  55. package/build/index.d.ts +12 -14
  56. package/build/index.js +14 -16
  57. package/build/index.js.map +1 -1
  58. package/build/scripts/finalizeHandles.d.ts +2 -0
  59. package/build/scripts/finalizeHandles.js +48 -0
  60. package/build/scripts/finalizeHandles.js.map +1 -0
  61. package/build/scripts/runLslEventMarkerEmitter.js +11 -0
  62. package/build/scripts/runLslEventMarkerEmitter.js.map +1 -0
  63. package/build/scripts/runOutletToInlet.js +7 -6
  64. package/build/scripts/runOutletToInlet.js.map +1 -1
  65. package/build/scripts/testDestroyOutlet.d.ts +1 -0
  66. package/build/scripts/testDestroyOutlet.js +4 -0
  67. package/build/scripts/testDestroyOutlet.js.map +1 -0
  68. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.d.ts +6 -0
  69. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js +10 -0
  70. package/build/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.js.map +1 -0
  71. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.d.ts +17 -0
  72. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js +30 -0
  73. package/build/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.js.map +1 -0
  74. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.d.ts +10 -0
  75. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js +23 -0
  76. package/build/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.js.map +1 -0
  77. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.d.ts +9 -0
  78. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js +19 -0
  79. package/build/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.js.map +1 -0
  80. package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +3 -3
  81. package/build/testDoubles/StreamInfo/FakeStreamInfo.js +3 -3
  82. package/build/testDoubles/StreamInfo/FakeStreamInfo.js.map +1 -1
  83. package/build/testDoubles/StreamInlet/FakeStreamInlet.d.ts +8 -10
  84. package/build/testDoubles/StreamInlet/FakeStreamInlet.js +6 -7
  85. package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +1 -1
  86. package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +2 -5
  87. package/build/testDoubles/StreamInlet/SpyStreamInlet.js +2 -8
  88. package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +1 -1
  89. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +7 -9
  90. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +5 -8
  91. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
  92. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.d.ts +1 -1
  93. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js +1 -1
  94. package/build/testDoubles/WebSocketBridge/FakeWebSocketBridge.js.map +1 -1
  95. package/build/testDoubles/WorkerThreads/FakeWorker.d.ts +27 -0
  96. package/build/testDoubles/WorkerThreads/FakeWorker.js +67 -0
  97. package/build/testDoubles/WorkerThreads/FakeWorker.js.map +1 -0
  98. package/build/validations.js +0 -2
  99. package/build/validations.js.map +1 -1
  100. package/package.json +24 -8
  101. package/src/.spruce/settings.json +5 -1
  102. package/src/__tests__/AbstractPackageTest.ts +13 -15
  103. package/src/__tests__/impl/LslBackpressureMonitor.test.ts +25 -0
  104. package/src/__tests__/impl/LslEventMarkerEmitter.test.ts +322 -0
  105. package/src/__tests__/impl/LslStreamInfo.test.ts +3 -40
  106. package/src/__tests__/impl/LslStreamInlet.test.ts +366 -95
  107. package/src/__tests__/impl/LslStreamOutlet.test.ts +61 -69
  108. package/src/__tests__/impl/LslWebSocketBridge.test.ts +18 -18
  109. package/src/assertions.ts +2 -1
  110. package/src/impl/LslBackpressureMonitor.ts +13 -0
  111. package/src/impl/LslEventMarkerEmitter.ts +133 -0
  112. package/src/impl/LslStreamInfo.ts +17 -29
  113. package/src/impl/LslStreamInlet.ts +105 -208
  114. package/src/impl/LslStreamOutlet.ts +54 -78
  115. package/src/impl/LslWebSocketBridge.ts +12 -12
  116. package/src/impl/workers/inlet/LslStreamInlet.worker.ts +74 -0
  117. package/src/impl/workers/inlet/StreamInletWorker.ts +348 -0
  118. package/src/impl/workers/outlet/LslStreamOutlet.worker.ts +144 -0
  119. package/src/index.ts +14 -17
  120. package/src/scripts/finalizeHandles.ts +63 -0
  121. package/src/scripts/runLslEventMarkerEmitter.ts +15 -0
  122. package/src/scripts/runOutletToInlet.ts +10 -8
  123. package/src/scripts/testDestroyOutlet.ts +5 -0
  124. package/src/testDoubles/BackpressureMonitor/FakeBackpressureMonitor.ts +13 -0
  125. package/src/testDoubles/EventMarkerEmitter/FakeEventMarkerEmitter.ts +46 -0
  126. package/src/testDoubles/EventMarkerEmitter/SpyEventMarkerEmitter.ts +31 -0
  127. package/src/testDoubles/EventMarkerEmitter/ThrowingEventMarkerEmitter.ts +29 -0
  128. package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +4 -4
  129. package/src/testDoubles/StreamInlet/FakeStreamInlet.ts +11 -17
  130. package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +3 -16
  131. package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +12 -15
  132. package/src/testDoubles/WebSocketBridge/FakeWebSocketBridge.ts +1 -1
  133. package/src/testDoubles/WorkerThreads/FakeWorker.ts +99 -0
  134. package/src/validations.ts +0 -2
  135. package/build/__tests__/impl/LiblslAdapter.test.d.ts +0 -79
  136. package/build/__tests__/impl/LiblslAdapter.test.js +0 -688
  137. package/build/__tests__/impl/LiblslAdapter.test.js.map +0 -1
  138. package/build/__tests__/impl/LslEventMarkerOutlet.test.d.ts +0 -21
  139. package/build/__tests__/impl/LslEventMarkerOutlet.test.js +0 -160
  140. package/build/__tests__/impl/LslEventMarkerOutlet.test.js.map +0 -1
  141. package/build/handleError.d.ts +0 -8
  142. package/build/handleError.js +0 -25
  143. package/build/handleError.js.map +0 -1
  144. package/build/impl/LiblslAdapter.d.ts +0 -159
  145. package/build/impl/LiblslAdapter.js +0 -317
  146. package/build/impl/LiblslAdapter.js.map +0 -1
  147. package/build/impl/LslEventMarkerOutlet.d.ts +0 -28
  148. package/build/impl/LslEventMarkerOutlet.js +0 -63
  149. package/build/impl/LslEventMarkerOutlet.js.map +0 -1
  150. package/build/scripts/runLslEventMarkerOutlet.js +0 -18
  151. package/build/scripts/runLslEventMarkerOutlet.js.map +0 -1
  152. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.d.ts +0 -13
  153. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js +0 -26
  154. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js.map +0 -1
  155. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.d.ts +0 -17
  156. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js +0 -46
  157. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js.map +0 -1
  158. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.d.ts +0 -9
  159. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js +0 -19
  160. package/build/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.js.map +0 -1
  161. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.d.ts +0 -9
  162. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js +0 -19
  163. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js.map +0 -1
  164. package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +0 -50
  165. package/build/testDoubles/Liblsl/FakeLiblsl.js +0 -126
  166. package/build/testDoubles/Liblsl/FakeLiblsl.js.map +0 -1
  167. package/src/__tests__/impl/LiblslAdapter.test.ts +0 -817
  168. package/src/__tests__/impl/LslEventMarkerOutlet.test.ts +0 -195
  169. package/src/handleError.ts +0 -24
  170. package/src/impl/LiblslAdapter.ts +0 -548
  171. package/src/impl/LslEventMarkerOutlet.ts +0 -97
  172. package/src/scripts/runLslEventMarkerOutlet.ts +0 -23
  173. package/src/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.ts +0 -36
  174. package/src/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.ts +0 -75
  175. package/src/testDoubles/EventMarkerOutlet/SpyEventMarkerOutlet.ts +0 -24
  176. package/src/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.ts +0 -28
  177. package/src/testDoubles/Liblsl/FakeLiblsl.ts +0 -182
  178. /package/build/scripts/{runLslEventMarkerOutlet.d.ts → runLslEventMarkerEmitter.d.ts} +0 -0
@@ -196,7 +196,7 @@ export default class LslStreamInfoTest extends AbstractPackageTest {
196
196
  assert.isEqualDeep(
197
197
  this.fakeLiblsl.lastAppendChannelsToStreamInfoOptions,
198
198
  {
199
- info: this.instance.boundInfo,
199
+ infoHandle: this.instance.infoHandle,
200
200
  channels: this.channelNames.map((label: string) => ({
201
201
  label,
202
202
  units: this.units,
@@ -212,49 +212,12 @@ export default class LslStreamInfoTest extends AbstractPackageTest {
212
212
  this.instance.destroy()
213
213
 
214
214
  assert.isEqualDeep(
215
- this.fakeLiblsl.lastDestroyStreamInfoOptions?.info,
216
- this.instance.boundInfo,
215
+ this.fakeLiblsl.lastDestroyStreamInfoOptions?.infoHandle,
216
+ this.instance.infoHandle,
217
217
  'Should have called destroyStreamInfo!'
218
218
  )
219
219
  }
220
220
 
221
- @test()
222
- protected static async doesNotCreateNewStreamInfoIfPassedBoundInfo() {
223
- this.LslStreamInfo({
224
- boundInfo: this.instance.boundInfo,
225
- })
226
-
227
- assert.isEqual(
228
- this.fakeLiblsl.createStreamInfoHitCount,
229
- 1,
230
- 'Should not have created new stream info!'
231
- )
232
- }
233
-
234
- @test()
235
- protected static async setsBoundInfoWhenPassed() {
236
- const instance = this.LslStreamInfo({
237
- boundInfo: this.instance.boundInfo,
238
- })
239
-
240
- assert.isEqualDeep(
241
- instance.boundInfo,
242
- this.instance.boundInfo,
243
- 'Should have set bound stream info!'
244
- )
245
- }
246
-
247
- @test()
248
- protected static async cachesInfoAttributesByBoundInfo() {
249
- const cached = LslStreamInfo.From(this.instance.boundInfo)
250
-
251
- assert.isEqualDeep(
252
- cached,
253
- this.instance,
254
- 'Should have set bound stream info!'
255
- )
256
- }
257
-
258
221
  private static readonly defaultOptions = {
259
222
  name: this.name,
260
223
  type: this.type,
@@ -1,17 +1,61 @@
1
- import { test, assert } from '@neurodevs/node-tdd'
1
+ import { Worker } from 'node:worker_threads'
2
2
 
3
- import { StreamInletOptions } from '../../impl/LslStreamInlet.js'
3
+ import { FakeLiblsl } from '@neurodevs/ndx-native'
4
+ import { test, assert } from '@neurodevs/node-tdd'
5
+ import {
6
+ createPointer,
7
+ DataType,
8
+ FieldType,
9
+ JsExternal,
10
+ PointerType,
11
+ unwrapPointer,
12
+ } from 'ffi-rs'
13
+
14
+ import LslStreamInlet, {
15
+ StreamInletOptions,
16
+ } from '../../impl/LslStreamInlet.js'
17
+ import StreamInletWorker from '../../impl/workers/inlet/StreamInletWorker.js'
4
18
  import { SpyStreamInlet } from '../../testDoubles/StreamInlet/SpyStreamInlet.js'
19
+ import FakeWorker from '../../testDoubles/WorkerThreads/FakeWorker.js'
5
20
  import AbstractPackageTest from '../AbstractPackageTest.js'
6
21
 
7
22
  export default class LslStreamInletTest extends AbstractPackageTest {
8
23
  private static instance: SpyStreamInlet
9
24
 
10
25
  private static callsToOnData: {
11
- samples: Float32Array
12
- timestamps: Float64Array
26
+ samples: number[]
27
+ timestamps: number[]
13
28
  }[]
14
29
 
30
+ private static readonly sampleBufferPtr = unwrapPointer(
31
+ createPointer({
32
+ paramsType: [DataType.U8Array],
33
+ paramsValue: [new Float32Array(this.channelCount * this.chunkSize)],
34
+ })
35
+ )[0]
36
+
37
+ private static readonly timestampBufferPtr = unwrapPointer(
38
+ createPointer({
39
+ paramsType: [DataType.U8Array],
40
+ paramsValue: [new Float64Array(this.chunkSize)],
41
+ })
42
+ )[0]
43
+
44
+ private static readonly errorCodePtr = unwrapPointer(
45
+ createPointer({
46
+ paramsType: [DataType.U8Array],
47
+ paramsValue: [new Int32Array(1)],
48
+ })
49
+ )[0]
50
+
51
+ protected static async beforeAll() {
52
+ assert.isEqual(
53
+ LslStreamInlet.waitAfterOpenStreamMs,
54
+ 100,
55
+ 'Default waitAfterOpenStreamMs should be 100!'
56
+ )
57
+ }
58
+
15
59
  protected static async beforeEach() {
16
60
  await super.beforeEach()
17
61
 
@@ -21,6 +65,16 @@ export default class LslStreamInletTest extends AbstractPackageTest {
21
65
 
22
66
  this.callsToOnData = []
23
67
 
68
+ LslStreamInlet.waitAfterOpenStreamMs = 0
69
+
70
+ LslStreamInlet.Worker = FakeWorker as unknown as typeof Worker
71
+ FakeWorker.resetTestDoubles()
72
+
73
+ StreamInletWorker.lsl = this.fakeLiblsl
74
+ StreamInletWorker.freePointer = () => {}
75
+
76
+ FakeWorker.fakeOnData = this.onData
77
+
24
78
  this.instance = await this.LslStreamInlet()
25
79
  }
26
80
 
@@ -31,42 +85,102 @@ export default class LslStreamInletTest extends AbstractPackageTest {
31
85
 
32
86
  @test()
33
87
  protected static async callsBindingsToCreateStreamInlet() {
34
- const fakeInfo = this.instance.getStreamInfo()
88
+ await this.startPulling()
35
89
 
36
- assert.isTruthy(fakeInfo, 'Should have created stream info!')
90
+ assert.isTruthy(this.inletHandle, 'Should have created stream info!')
37
91
 
38
92
  assert.isEqualDeep(
39
93
  this.fakeLiblsl.lastCreateInletOptions,
40
94
  {
41
- info: fakeInfo.boundInfo,
95
+ infoHandle: this.inletHandle,
42
96
  maxBufferedMs: this.maxBufferedMs,
43
97
  },
44
98
  'Should have called createInlet!'
45
99
  )
100
+
101
+ this.stopPulling()
46
102
  }
47
103
 
48
104
  @test()
49
- protected static async flushQueueCallsLslBinding() {
50
- this.instance.flushQueue()
105
+ protected static async flushInletCallsLslBinding() {
106
+ this.instance.flushInlet()
51
107
 
52
108
  assert.isEqualDeep(
53
109
  this.fakeLiblsl.lastFlushInletOptions,
54
- { inlet: this.boundInlet },
110
+ { inletHandle: this.inletHandle },
55
111
  'Should have called flushInlet!'
56
112
  )
57
113
  }
58
114
 
59
115
  @test()
60
116
  protected static async destroyCallsLslBinding() {
61
- this.instance.destroy()
117
+ await this.startPullingThenDestroy()
62
118
 
63
119
  assert.isEqualDeep(
64
120
  this.fakeLiblsl.lastDestroyInletOptions,
65
- { inlet: this.boundInlet },
121
+ { inletHandle: this.inletHandle },
66
122
  'Did not destroy inlet!'
67
123
  )
68
124
  }
69
125
 
126
+ @test()
127
+ protected static async destroyFreesAllPointers() {
128
+ interface FreePointerParams {
129
+ paramsType: FieldType[]
130
+ paramsValue: JsExternal[]
131
+ pointerType: PointerType
132
+ }
133
+
134
+ let calls: FreePointerParams[] = []
135
+
136
+ StreamInletWorker.freePointer = (params: FreePointerParams) => {
137
+ calls.push(params)
138
+ }
139
+
140
+ await this.startPullingThenDestroy()
141
+
142
+ const { paramsType, pointerType } = calls[0]
143
+
144
+ assert.isEqualDeep(
145
+ { paramsType, pointerType },
146
+ {
147
+ paramsType: [
148
+ DataType.U8Array,
149
+ DataType.U8Array,
150
+ DataType.U8Array,
151
+ DataType.U8Array,
152
+ ],
153
+ pointerType: PointerType.CPointer,
154
+ },
155
+
156
+ 'Destroy did not free all pointers!'
157
+ )
158
+ }
159
+
160
+ @test()
161
+ protected static async destroyCallsStopIfIsRunning() {
162
+ await this.startPulling()
163
+ this.destroy()
164
+
165
+ assert.isFalse(
166
+ this.instance.isRunning,
167
+ 'Did not stop inlet before destroying!'
168
+ )
169
+ }
170
+
171
+ @test()
172
+ protected static async destroyDoesNotCallStopIfNotRunning() {
173
+ let wasHit = false
174
+
175
+ this.instance.stopPulling = () => {
176
+ wasHit = true
177
+ }
178
+
179
+ this.instance.destroy()
180
+
181
+ assert.isFalse(wasHit, 'Stop should not have been called!')
182
+ }
183
+
70
184
  @test()
71
185
  protected static async exposesIsRunningFieldThatIsFalseAtFirst() {
72
186
  assert.isFalse(this.isRunning, 'isRunning should be false at first!')
@@ -74,14 +188,135 @@ export default class LslStreamInletTest extends AbstractPackageTest {
74
188
 
75
189
  @test()
76
190
  protected static async startPullingSetsIsRunningToTrue() {
77
- this.startPulling()
78
- await this.wait(10)
191
+ await this.startPulling()
79
192
 
80
193
  assert.isTrue(this.isRunning, 'isRunning should be true!')
81
194
 
82
195
  this.stopPulling()
83
196
  }
84
197
 
198
+ @test()
199
+ protected static async doesNotStartPullingTwiceIfAlreadyRunning() {
200
+ await this.startPulling()
201
+ await this.startPulling()
202
+
203
+ assert.isEqual(
204
+ FakeWorker.callsToPostMessage.filter(
205
+ (msg) => msg.type === 'startPulling'
206
+ ).length,
207
+ 1,
208
+ 'startPulling did not return early!'
209
+ )
210
+
211
+ this.stopPulling()
212
+ }
213
+
214
+ @test()
215
+ protected static async startPullingOpensInletStream() {
216
+ await this.startPulling()
217
+
218
+ const aboutOneYearInMs = 32000000 * 1000
219
+
220
+ assert.isEqualDeep(
221
+ this.fakeLiblsl.lastOpenStreamOptions,
222
+ {
223
+ inletHandle: this.inletHandle,
224
+ timeoutMs: aboutOneYearInMs,
225
+ errorCodePtr: this.errorCodePtr,
226
+ },
227
+ 'Did not open inlet stream!'
228
+ )
229
+
230
+ this.stopPulling()
231
+ }
232
+
233
+ @test()
234
+ protected static async opensInletStreamWithPassedTimeout() {
235
+ const openStreamTimeoutMs = 1000 * Math.random()
236
+
237
+ const instance = await this.LslStreamInlet({
238
+ openStreamTimeoutMs,
239
+ })
240
+
241
+ await this.startPulling(instance)
242
+
243
+ assert.isEqualDeep(
244
+ this.fakeLiblsl.lastOpenStreamOptions,
245
+ {
246
+ inletHandle: this.inletHandle,
247
+ timeoutMs: openStreamTimeoutMs,
248
+ errorCodePtr: this.errorCodePtr,
249
+ },
250
+ 'Did not open inlet stream with passed timeout!'
251
+ )
252
+
253
+ instance.stopPulling()
254
+ }
255
+
256
+ @test()
257
+ protected static async waitsAfterOpeningStreamWithPassedOption() {
258
+ const waitAfterOpenStreamMs = 10
259
+
260
+ const instance = await this.LslStreamInlet({
261
+ waitAfterOpenStreamMs,
262
+ })
263
+
264
+ let t0 = Date.now()
265
+ await this.startPulling(instance)
266
+ let elapsed = Date.now() - t0
267
+
268
+ instance.stopPulling()
269
+
270
+ assert.isAbove(
271
+ elapsed,
272
+ waitAfterOpenStreamMs * 0.8,
273
+ 'Did not wait after opening stream!'
274
+ )
275
+ }
276
+
277
+ @test()
278
+ protected static async stopPullingClosesInletStream() {
279
+ await this.startPulling()
280
+ this.stopPulling()
281
+
282
+ assert.isEqualDeep(
283
+ this.fakeLiblsl.lastCloseStreamOptions,
284
+ {
285
+ inletHandle: this.inletHandle,
286
+ },
287
+ 'Did not close inlet stream!'
288
+ )
289
+ }
290
+
291
+ @test()
292
+ protected static async stopPullingFlushesInletByDefault() {
293
+ await this.startPulling()
294
+ this.stopPulling()
295
+
296
+ assert.isEqualDeep(
297
+ this.fakeLiblsl.lastFlushInletOptions,
298
+ {
299
+ inletHandle: this.inletHandle,
300
+ },
301
+ 'Did not flush inlet!'
302
+ )
303
+ }
304
+
305
+ @test()
306
+ protected static async stopPullingDoesNotFlushInletIfPassedOption() {
307
+ const instance = await this.LslStreamInlet({
308
+ flushInletOnStop: false,
309
+ })
310
+
311
+ await this.startPulling(instance)
312
+ instance.stopPulling()
313
+
314
+ assert.isUndefined(
315
+ this.fakeLiblsl.lastFlushInletOptions,
316
+ 'Should not have flushed inlet!'
317
+ )
318
+ }
319
+
85
320
  @test()
86
321
  protected static async stopPullingSetsIsRunningToFalse() {
87
322
  await this.startThenStop()
@@ -113,16 +348,16 @@ export default class LslStreamInletTest extends AbstractPackageTest {
113
348
 
114
349
  @test()
115
350
  protected static async callsPullSampleIfChunkSizeIsOne() {
116
- const inlet = await this.runChunkSizeOne()
351
+ await this.runChunkSizeOne()
117
352
 
118
353
  assert.isEqualDeep(
119
354
  this.fakeLiblsl.lastPullSampleOptions,
120
355
  {
121
- inlet: this.boundInlet,
122
- dataBufferPtr: inlet['dataBufferPtr'],
123
- dataBufferElements: this.channelCount,
124
- timeout: 0,
125
- errcodePtr: inlet['errorCodeBufferPtr'],
356
+ inletHandle: this.inletHandle,
357
+ sampleBufferPtr: this.sampleBufferPtr,
358
+ sampleBufferElements: this.channelCount,
359
+ timeoutMs: 0,
360
+ errorCodePtr: this.errorCodePtr,
126
361
  },
127
362
  'Should have called pullSample!'
128
363
  )
@@ -135,46 +370,114 @@ export default class LslStreamInletTest extends AbstractPackageTest {
135
370
  assert.isEqualDeep(
136
371
  this.fakeLiblsl.lastPullChunkOptions,
137
372
  {
138
- inlet: this.boundInlet,
139
- dataBufferPtr: this.instance['dataBufferPtr'],
140
- timestampBufferPtr: this.instance['timestampBufferPtr'],
141
- dataBufferElements: this.chunkSize * this.channelCount,
373
+ inletHandle: this.inletHandle,
374
+ sampleBufferPtr: this.sampleBufferPtr,
375
+ timestampBufferPtr: this.timestampBufferPtr,
376
+ sampleBufferElements: this.chunkSize * this.channelCount,
142
377
  timestampBufferElements: this.chunkSize,
143
- timeout: 0,
144
- errcodePtr: this.instance['errorCodeBufferPtr'],
378
+ timeoutMs: 0,
379
+ errorCodePtr: this.errorCodePtr,
145
380
  },
146
381
  'Should have called pullChunk!'
147
382
  )
148
383
  }
149
384
 
385
+ @test()
386
+ protected static async pushSampleHandlesErrorCode() {
387
+ let passedErrorCode: number | undefined
388
+
389
+ StreamInletWorker.handleError = (errorCode: number) => {
390
+ passedErrorCode = errorCode
391
+ }
392
+
393
+ const fakeErrorCode = [-4, -3, -2, -1, 0][Math.floor(Math.random() * 5)]
394
+ const worker = this.getFakeWorker()
395
+
396
+ worker['inletWorker']['pullErrorBuffer'].writeInt32LE(fakeErrorCode)
397
+
398
+ await this.startThenStop()
399
+
400
+ assert.isEqualDeep(
401
+ passedErrorCode,
402
+ FakeLiblsl.fakeErrorCode,
403
+ 'Did not pass the expected error code to handleError!'
404
+ )
405
+ }
406
+
407
+ @test()
408
+ protected static async waitsBetweenPullsIfPassedOption() {
409
+ const waitBetweenPullsMs = 10
410
+
411
+ const instance = await this.LslStreamInlet({
412
+ waitBetweenPullsMs,
413
+ })
414
+
415
+ let pulls = 0
416
+
417
+ const inletWorker = this.getFakeWorker(instance)['inletWorker']
418
+ const original = inletWorker['pullDataOnce'].bind(inletWorker)
419
+
420
+ inletWorker['pullDataOnce'] = async () => {
421
+ pulls++
422
+ await original()
423
+ }
424
+
425
+ const t0 = Date.now()
426
+ const promise = this.startPulling(instance)
427
+
428
+ while (pulls < 2) {
429
+ await this.wait(1)
430
+ }
431
+
432
+ const elapsed = Date.now() - t0
433
+
434
+ assert.isAbove(
435
+ elapsed,
436
+ waitBetweenPullsMs * 0.8,
437
+ 'Did not wait between pulls'
438
+ )
439
+
440
+ instance.stopPulling()
441
+ await promise
442
+ }
443
+
444
+ @test()
445
+ protected static async waitsOneMillisecondBetweenPullsIfNotPassed() {
446
+ assert.isEqual(
447
+ this.instance['waitBetweenPullsMs'],
448
+ 1,
449
+ 'Default waitBetweenPullsMs should be 1!'
450
+ )
451
+ }
452
+
150
453
  @test()
151
454
  protected static async passesTimeoutMsOptionToPullSample() {
152
- const timeoutMs = 1000 * Math.random()
455
+ const pullTimeoutMs = 1000 * Math.random()
153
456
 
154
457
  await this.runInletWithOptions({
155
458
  chunkSize: 1,
156
- timeoutMs,
459
+ pullTimeoutMs,
157
460
  })
158
461
 
159
462
  assert.isEqual(
160
- this.fakeLiblsl.lastPullSampleOptions?.timeout,
161
- timeoutMs / 1000,
162
- 'Did not pass timeoutMs to pullSample!'
463
+ this.fakeLiblsl.lastPullSampleOptions?.timeoutMs,
464
+ pullTimeoutMs,
465
+ 'Did not pass pullTimeoutMs to pullSample!'
163
466
  )
164
467
  }
165
468
 
166
469
  @test()
167
470
  protected static async passesTimeoutMsOptionToPullChunk() {
168
- const timeoutMs = 1000 * Math.random()
471
+ const pullTimeoutMs = 1000 * Math.random()
169
472
 
170
473
  await this.runInletWithOptions({
171
474
  chunkSize: this.chunkSize,
172
- timeoutMs,
475
+ pullTimeoutMs,
173
476
  })
174
477
 
175
478
  assert.isEqual(
176
- this.fakeLiblsl.lastPullChunkOptions?.timeout,
177
- timeoutMs / 1000,
479
+ this.fakeLiblsl.lastPullChunkOptions?.timeoutMs,
480
+ pullTimeoutMs,
178
481
  'Did not pass timeoutMs to pullChunk!'
179
482
  )
180
483
  }
@@ -193,57 +496,9 @@ export default class LslStreamInletTest extends AbstractPackageTest {
193
496
  )
194
497
  }
195
498
 
196
- @test()
197
- protected static async throwsWithUnknownErrorCode() {
198
- await this.assertThrowsWithErrorCode(
199
- -999,
200
- `An unknown liblsl error has occurred!`
201
- )
202
- }
203
-
204
- @test()
205
- protected static async throwsWithErrorCodeNegativeOne() {
206
- await this.assertThrowsWithErrorCode(
207
- -1,
208
- `The liblsl operation failed due to a timeout!`
209
- )
210
- }
211
-
212
- @test()
213
- protected static async throwsWithErrorCodeNegativeTwo() {
214
- await this.assertThrowsWithErrorCode(
215
- -2,
216
- `The liblsl stream has been lost!`
217
- )
218
- }
219
-
220
- @test()
221
- protected static async throwsWithErrorCodeNegativeThree() {
222
- await this.assertThrowsWithErrorCode(
223
- -3,
224
- 'A liblsl argument was incorrectly specified!'
225
- )
226
- }
227
-
228
- @test()
229
- protected static async throwsWithErrorCodeNegativeFour() {
230
- await this.assertThrowsWithErrorCode(
231
- -4,
232
- 'An internal liblsl error has occurred!'
233
- )
234
- }
235
-
236
- private static async assertThrowsWithErrorCode(
237
- errorCode: number,
238
- message: string
239
- ) {
240
- await this.startThenStop()
241
-
242
- this.instance['errorCodeBuffer'].writeInt32LE(errorCode)
243
-
244
- assert.doesThrow(() => {
245
- this.instance['handleErrorCodeIfPresent']()
246
- }, message)
499
+ private static async startPullingThenDestroy() {
500
+ await this.startPulling()
501
+ this.destroy()
247
502
  }
248
503
 
249
504
  private static async runChunkSizeOne() {
@@ -262,31 +517,47 @@ export default class LslStreamInletTest extends AbstractPackageTest {
262
517
 
263
518
  private static async startThenStop(inlet?: SpyStreamInlet) {
264
519
  const instance = inlet || this.instance
265
- instance.startPulling()
520
+ await this.startPulling(instance)
266
521
  await this.wait(10)
267
522
  instance.stopPulling()
268
523
  }
269
524
 
270
- private static startPulling() {
271
- this.instance.startPulling()
525
+ private static async startPulling(instance?: SpyStreamInlet) {
526
+ const inst = instance || this.instance
527
+ const startPullingPromise = inst.startPulling()
528
+
529
+ while (!this.getFakeWorker(inst).createInletPromise) {
530
+ await this.wait(1)
531
+ }
532
+
533
+ await this.getFakeWorker(inst).createInletPromise
534
+ inst['workerReady'] = true
535
+
536
+ await startPullingPromise
272
537
  }
273
538
 
274
539
  private static stopPulling() {
275
540
  this.instance.stopPulling()
276
541
  }
277
542
 
543
+ private static destroy() {
544
+ this.instance.destroy()
545
+ }
546
+
278
547
  private static get isRunning() {
279
548
  return this.instance.isRunning
280
549
  }
281
550
 
282
- private static get boundInlet() {
283
- return this.instance.getBoundInlet()
551
+ private static get inletHandle() {
552
+ return this.getFakeWorker()['inletWorker']['inletHandle']
553
+ }
554
+
555
+ private static getFakeWorker(instance?: SpyStreamInlet) {
556
+ const inst = instance || this.instance
557
+ return inst['worker'] as unknown as FakeWorker
284
558
  }
285
559
 
286
- private static onData = (
287
- samples: Float32Array,
288
- timestamps: Float64Array
289
- ) => {
560
+ private static onData = (samples: number[], timestamps: number[]) => {
290
561
  this.callsToOnData.push({ samples, timestamps })
291
562
  }
292
563